mbed library sources

Fork of mbed-src by mbed official

targets/hal/TARGET_NORDIC/TARGET_NRF51822/pwmout_api.c

Committer:
mbed_official
Date:
2014-07-08
Revision:
250:a49055e7a707
Parent:
229:9bd26d142f33
Child:
251:de9a1e4ffd79

File content as of revision 250:a49055e7a707:

/* mbed Microcontroller Library
 * Copyright (c) 2013 Nordic Semiconductor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "mbed_assert.h"
#include "pwmout_api.h"
#include "cmsis.h"
#include "pinmap.h"
#include "mbed_error.h"

#define NO_PWMS         3
#define TIMER_PRECISION 4 //4us ticks   
#define TIMER_PRESCALER 6 //4us ticks  =   16Mhz/(2**6)
static const PinMap PinMap_PWM[] = {
    {p0,  PWM_1, 1},
    {p1,  PWM_1, 1},
    {p2,  PWM_1, 1},
    {p3,  PWM_1, 1},
    {p4,  PWM_1, 1},
    {p5,  PWM_1, 1},
    {p6,  PWM_1, 1},
    {p7,  PWM_1, 1},
    {p8,  PWM_1, 1},
    {p9,  PWM_1, 1},
    {p10,  PWM_1, 1},
    {p11,  PWM_1, 1},
    {p12,  PWM_1, 1},
    {p13,  PWM_1, 1},
    {p14,  PWM_1, 1},
    {p15,  PWM_1, 1},
    {p16,  PWM_1, 1},
    {p17,  PWM_1, 1},
    {p18,  PWM_1, 1},
    {p19,  PWM_1, 1},
    {p20,  PWM_1, 1},
    {p21,  PWM_1, 1},
    {p22,  PWM_1, 1},
    {p23,  PWM_1, 1},
    {p24,  PWM_1, 1},
    {p25,  PWM_1, 1},
    {p28,  PWM_1, 1},
    {p29,  PWM_1, 1},
    {p30,  PWM_1, 1},
    {NC, NC, 0}
};

static NRF_TIMER_Type *Timers[1] = {
    NRF_TIMER2
};

uint16_t PERIOD                = 20000/TIMER_PRECISION;//20ms
uint8_t PWM_taken[NO_PWMS]     = {0,0,0};
uint16_t PULSE_WIDTH[NO_PWMS]  = {1,1,1};//set to 1 instead of 0
uint16_t ACTUAL_PULSE[NO_PWMS] = {0,0,0};


/** @brief Function for handling timer 2 peripheral interrupts.
 */
 #ifdef __cplusplus
extern "C" {
#endif 
void TIMER2_IRQHandler(void)
{
    NRF_TIMER2->EVENTS_COMPARE[3] = 0;
    NRF_TIMER2->CC[3]   =  PERIOD;
    
    if(PWM_taken[0]){
        NRF_TIMER2->CC[0]  = PULSE_WIDTH[0];
    }
    if(PWM_taken[1]){
        NRF_TIMER2->CC[1]  = PULSE_WIDTH[1];
    }
    if(PWM_taken[2]){
        NRF_TIMER2->CC[2]  = PULSE_WIDTH[2];
    }
    
    NRF_TIMER2->TASKS_START = 1;

}
#ifdef __cplusplus
}
#endif 
/** @brief Function for initializing the Timer peripherals.
 */
void timer_init(uint8_t pwmChoice)
{
    NRF_TIMER_Type *timer = Timers[0];
    timer->TASKS_STOP = 0;
    
    if(pwmChoice == 0){
        timer->POWER     = 0;
        timer->POWER     = 1;    
        timer->MODE      = TIMER_MODE_MODE_Timer;    
        timer->BITMODE   = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
        timer->PRESCALER = TIMER_PRESCALER; 
        timer->CC[3]     = PERIOD;
    }
    
    timer->CC[pwmChoice] = PULSE_WIDTH[pwmChoice];
    
    //high priority application interrupt
    NVIC_SetPriority(TIMER2_IRQn, 1);
    NVIC_EnableIRQ(TIMER2_IRQn);
    
    timer->TASKS_START = 0x01;
}
/** @brief Function for initializing the GPIO Tasks/Events peripheral.
 */
void gpiote_init(PinName pin,uint8_t channel_number)
{
    // Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output.
    NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
                            | (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos)
                            | (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos)
                            | (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos)
                            | (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
    NRF_GPIO->OUTCLR = (1UL << pin);
    // Configure GPIOTE channel 0 to toggle the PWM pin state
    // @note Only one GPIOTE task can be connected to an output pin.
    /* Configure channel to Pin31, not connected to the pin, and configure as a tasks that will set it to proper level */
    NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)     |
                                         (31UL                          << GPIOTE_CONFIG_PSEL_Pos)     |
                                         (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos);
     /* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
    __NOP();
    __NOP();
    __NOP(); 
    /* Launch the task to take the GPIOTE channel output to the desired level */
    NRF_GPIOTE->TASKS_OUT[channel_number] = 1;
    
    /* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly. 
       If it does not, the channel output inheritance sets the proper level. */
    NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos)     |
                                         ((uint32_t)pin    << GPIOTE_CONFIG_PSEL_Pos)     |
                                         ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle      << GPIOTE_CONFIG_POLARITY_Pos) |
                                        ((uint32_t)GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos);// ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);//

    /* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
    __NOP();
    __NOP();
    __NOP(); 
}
/** @brief Function for initializing the Programmable Peripheral Interconnect peripheral.
 */
static void ppi_init(uint8_t pwm)
{
//using ppi channels 0-7 (only 0-7 are available)
    uint8_t channel_number = 2*pwm;
    NRF_TIMER_Type *timer = Timers[0];
    
    // Configure PPI channel 0 to toggle ADVERTISING_LED_PIN_NO on every TIMER1 COMPARE[0] match
    NRF_PPI->CH[channel_number].TEP     = (uint32_t)&NRF_GPIOTE->TASKS_OUT[pwm];
    NRF_PPI->CH[channel_number+1].TEP   = (uint32_t)&NRF_GPIOTE->TASKS_OUT[pwm];
    NRF_PPI->CH[channel_number].EEP     = (uint32_t)&timer->EVENTS_COMPARE[pwm];    
    NRF_PPI->CH[channel_number+1].EEP   = (uint32_t)&timer->EVENTS_COMPARE[3];    
    
    // Enable PPI channels.
    NRF_PPI->CHEN |= (1 << channel_number)
                  |  (1 << (channel_number+1));
}

void setModulation(pwmout_t* obj,uint8_t toggle,uint8_t high)
{
    if(high){
        NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);
        if(toggle){
            NRF_GPIOTE->CONFIG[obj->pwm] |= (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos)   |
                                            ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle      << GPIOTE_CONFIG_POLARITY_Pos);
        }
        else{
            NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle      << GPIOTE_CONFIG_POLARITY_Pos);
            NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_LoToHi      << GPIOTE_CONFIG_POLARITY_Pos);
        }
    }
    else{
        NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);
        
        if(toggle){
            NRF_GPIOTE->CONFIG[obj->pwm] |= (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos)   |
                                            ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle      << GPIOTE_CONFIG_POLARITY_Pos);
        }
        else{
            NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle      << GPIOTE_CONFIG_POLARITY_Pos);
            NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_HiToLo      << GPIOTE_CONFIG_POLARITY_Pos);
        }
    }
}
void pwmout_init(pwmout_t* obj, PinName pin) {
    // determine the channel
    uint8_t pwmOutSuccess = 0;
    PWMName pwm = (PWMName)pinmap_peripheral(pin, PinMap_PWM);
    
    MBED_ASSERT(pwm != (PWMName)NC);
        
    if(PWM_taken[(uint8_t)pwm]){
        for(uint8_t i = 1; !pwmOutSuccess && (i<NO_PWMS) ;i++){
            if(!PWM_taken[i]){
                pwm           = (PWMName)i;
                PWM_taken[i]  = 1;
                pwmOutSuccess = 1;
            }
        }
    }
    else{
        pwmOutSuccess           = 1;
        PWM_taken[(uint8_t)pwm] = 1;
    }
    
    if(!pwmOutSuccess){
        error("PwmOut pin mapping failed. All available PWM channels are in use.");
    }
    
    obj->pwm = pwm;
    obj->pin = pin;
    
    gpiote_init(pin,(uint8_t)pwm);
    ppi_init((uint8_t)pwm);
    
    if(pwm == 0){
        NRF_POWER->TASKS_CONSTLAT = 1;
    }
    
    timer_init((uint8_t)pwm);
    
    //default to 20ms: standard for servos, and fine for e.g. brightness control
    pwmout_period_ms(obj, 20);
    pwmout_write    (obj, 0);
    
}

void pwmout_free(pwmout_t* obj) {
    // [TODO]
}

void pwmout_write(pwmout_t* obj, float value) {
    uint16_t oldPulseWidth;
    
    NRF_TIMER2->EVENTS_COMPARE[3] = 0;
    NRF_TIMER2->TASKS_STOP = 1;
    
    if (value < 0.0f) {
        value = 0.0;
    } else if (value > 1.0f) {
        value = 1.0;
    }        
    
    oldPulseWidth           = ACTUAL_PULSE[obj->pwm];
    ACTUAL_PULSE[obj->pwm]  = PULSE_WIDTH[obj->pwm]  = value* PERIOD;
    
    if(PULSE_WIDTH[obj->pwm] == 0){
        PULSE_WIDTH[obj->pwm] = 1;
        setModulation(obj,0,0);    
    }
    else if(PULSE_WIDTH[obj->pwm] == PERIOD){
        PULSE_WIDTH[obj->pwm] = PERIOD-1;
        setModulation(obj,0,1);
    }
    else if( (oldPulseWidth == 0) || (oldPulseWidth == PERIOD) ){
        setModulation(obj,1,oldPulseWidth == PERIOD);
    }    
        
    NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
    NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk; 
    NRF_TIMER2->TASKS_START = 1;
}

float pwmout_read(pwmout_t* obj) {
    return ((float)PULSE_WIDTH[obj->pwm]/(float)PERIOD);
}

void pwmout_period(pwmout_t* obj, float seconds) {
    pwmout_period_us(obj, seconds * 1000000.0f);
}

void pwmout_period_ms(pwmout_t* obj, int ms) {
    pwmout_period_us(obj, ms * 1000);
}

// Set the PWM period, keeping the duty cycle the same.
void pwmout_period_us(pwmout_t* obj, int us) {
    uint32_t periodInTicks = us/TIMER_PRECISION;
    
    NRF_TIMER2->EVENTS_COMPARE[3] = 0;
    NRF_TIMER2->TASKS_STOP = 1;
    
    if(periodInTicks>((1<<16) -1))
    {
        PERIOD = (1<<16 )-1;//131ms
    }
    else if(periodInTicks<5){
        PERIOD = 5;
    }
    else{
        PERIOD =periodInTicks;
    }
    NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
    NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk; 
    NRF_TIMER2->TASKS_START = 1;    
}

void pwmout_pulsewidth(pwmout_t* obj, float seconds) {
    pwmout_pulsewidth_us(obj, seconds * 1000000.0f);
}

void pwmout_pulsewidth_ms(pwmout_t* obj, int ms) {
    pwmout_pulsewidth_us(obj, ms * 1000);
}

void pwmout_pulsewidth_us(pwmout_t* obj, int us) {
    uint32_t pulseInTicks  = us/TIMER_PRECISION; 
    uint16_t oldPulseWidth = ACTUAL_PULSE[obj->pwm];
    
    NRF_TIMER2->EVENTS_COMPARE[3] = 0;
    NRF_TIMER2->TASKS_STOP = 1;
    
    ACTUAL_PULSE[obj->pwm] = PULSE_WIDTH[obj->pwm]  = pulseInTicks;
    
    if(PULSE_WIDTH[obj->pwm] == 0){
        PULSE_WIDTH[obj->pwm] = 1;
        setModulation(obj,0,0);    
    }
    else if(PULSE_WIDTH[obj->pwm] == PERIOD){
        PULSE_WIDTH[obj->pwm] = PERIOD-1;
        setModulation(obj,0,1);
    }
    else if( (oldPulseWidth == 0) || (oldPulseWidth == PERIOD) ){
        setModulation(obj,1,oldPulseWidth == PERIOD);
    } 
    NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
    NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk; 
    NRF_TIMER2->TASKS_START = 1;      
}