This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nordic NRF51822 + PWM + PPI + TIMER1

Hello,

I want to make a PWM signal that fires an interrupt at the end of the PWM period. I want to use two CC registers - one for the pulse width and one for the period width. On compare event I want to trigger one task that sets and another that clears a pin. I want to use direct register access (no libraries). I have a softdevice running. My part is NRF51822.

Here is my code:

void TIMER1_IRQHandler(){
    NVIC_ClearPendingIRQ(TIMER1_IRQn);
    NRF_TIMER1->EVENTS_COMPARE[0] = 0;
    if(NRF_TIMER1->EVENTS_COMPARE[1]){
        //One PWM period has elapsed
        NRF_TIMER1->EVENTS_COMPARE[1] = 0;
        NRF_TIMER1->TASKS_CLEAR = 1;
        pwm_triggers++;
    }
}

inline void pwm_init(void){
    NRF_GPIOTE->CONFIG[0] = (0x03 | (25<<8) | (0x01 << 16) | (0x01 << 20)); //Make GPIO a task, use pin 25, task sets pin high, init as pin high, 0x111903
    NRF_GPIOTE->CONFIG[1] = (0x03 | (25<<8) | (0x02 << 16) | (0x00<< 20)); //Make GPIO a task, use pin 25, task sets pin low, init as pin low, 0x21903
   
    NRF_PPI->CHG[0] |= 0x03;
    NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];
    NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
    NRF_PPI->CH[1].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[1];
    NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[1];
    NRF_PPI->CHEN |= 0x03;

    NRF_TIMER1->POWER = 0x01;
    NRF_TIMER1->TASKS_CLEAR = 1;
    NRF_TIMER1->MODE = 0x00; //Timer mode
    NRF_TIMER1->BITMODE = 0x01; //8-bit
    NRF_TIMER1->PRESCALER = 0x00; //Prescaler = /1
    NRF_TIMER1->CC[0] = 1;
    NRF_TIMER1->CC[1] = 20;

    while(NRF_TIMER1->CC[0] != 1){ }
    while(NRF_TIMER1->CC[1] != 20){ }

    NRF_TIMER1->INTENSET = 0x30000; //Enable interrupt on compare register 0 & 1
    NVIC_SetPriority(TIMER1_IRQn, APP_IRQ_PRIORITY_HIGHEST);
    NVIC_ClearPendingIRQ(TIMER1_IRQn);
    NVIC_EnableIRQ(TIMER1_IRQn);
    NRF_TIMER1->TASKS_START = 1;
}

This code doesn't work, but the separate modules work - the timer is counting and interrupting, if I use toggle task [NRF_GPIOTE->CONFIG[1] = (0x03 | (25<<8) | (0x03 << 16) | (0x01 << 20))] for the GPIO I see PWM output but I cannot control the duty this way.

With the debugger I see that the interrupt never exits because the COMPARE_EVENT[1] is set whenever COMPARE_EVENT[0] is set, which is not expected.

I believe I'm doing something fundamentally wrong, maybe I'm missing something important ...

Any help is appreciated!

Regards,

L. B.

Parents
  • Hello,

    It's not possible to assign the same pin to two GPIOTE channels, so that's probably why it's not working. But it should be possible to use the toggle task instead with one channel. Here's a slightly modified version of your code:

    void TIMER1_IRQHandler(){
        //NVIC_ClearPendingIRQ(TIMER1_IRQn);
       // NRF_TIMER1->EVENTS_COMPARE[0] = 0;
        //if(NRF_TIMER1->EVENTS_COMPARE[1]){ // No other compare events enabled
            //One PWM period has elapsed
            NRF_TIMER1->EVENTS_COMPARE[1] = 0;
            //NRF_TIMER1->TASKS_CLEAR = 1;
            pwm_triggers++;
        //}
    }
    
    void pwm_init(void){
        NRF_GPIOTE->CONFIG[0] = (0x03 | (25 << 8) | (0x3 /*toggle*/ << 16) | (0x01 << 20)); //Make GPIO a task, use pin 25, task sets pin high, init as pin high, 0x111903
    //    NRF_GPIOTE->CONFIG[1] = (0x03 | (27 <<8)  | (0x02 << 16) | (0x01  << 20)); //Make GPIO a task, use pin 25, task sets pin low, init as pin low, 0x21903
        
        //NRF_PPI->CHG[0] |= 0x03; // Grouping was not used
        NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];
        NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
        NRF_PPI->CH[1].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[1];
        NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
        NRF_PPI->CHEN |= 0x03;
    
        //NRF_TIMER1->POWER = 0x01;
        NRF_TIMER1->TASKS_CLEAR = 1;
        NRF_TIMER1->MODE = 0; //Timer mode
        NRF_TIMER1->BITMODE = 0x01; //8-bit
        NRF_TIMER1->PRESCALER = 0; //Prescaler = /1
        NRF_TIMER1->SHORTS = 2; //	Enable COMPARE1_CLEAR to clear timer on CC[1] match 
        NRF_TIMER1->CC[0] = 1; 
        NRF_TIMER1->CC[1] = 20;
    
        while(NRF_TIMER1->CC[0] != 1){ }
        while(NRF_TIMER1->CC[1] != 20){ }
    
        NRF_TIMER1->INTENSET = 1 << 17; //Enable interrupt on compare register  1. Event 0 appears to be ignored anyway
        NVIC_SetPriority(TIMER1_IRQn, 3);
        NVIC_ClearPendingIRQ(TIMER1_IRQn);
        NVIC_EnableIRQ(TIMER1_IRQn);
        NRF_TIMER1->TASKS_START = 1;
    }
    

    Can you try and see if works?

    With the debugger I see that the interrupt never exits because the COMPARE_EVENT[1] is set whenever COMPARE_EVENT[0] is set, which is not expected.

    The debugger only halts the CPU while the timers are kept running. So it is impossible to read out compare event 0 before compare event 1 manually.

    Just a warning, it starts to become complicated as soon as you want to update the duty cycle on the fly. If that's something you intend to do, you should consider modifying the existing PWM library instead.    

    Regards,

    Vidar

Reply
  • Hello,

    It's not possible to assign the same pin to two GPIOTE channels, so that's probably why it's not working. But it should be possible to use the toggle task instead with one channel. Here's a slightly modified version of your code:

    void TIMER1_IRQHandler(){
        //NVIC_ClearPendingIRQ(TIMER1_IRQn);
       // NRF_TIMER1->EVENTS_COMPARE[0] = 0;
        //if(NRF_TIMER1->EVENTS_COMPARE[1]){ // No other compare events enabled
            //One PWM period has elapsed
            NRF_TIMER1->EVENTS_COMPARE[1] = 0;
            //NRF_TIMER1->TASKS_CLEAR = 1;
            pwm_triggers++;
        //}
    }
    
    void pwm_init(void){
        NRF_GPIOTE->CONFIG[0] = (0x03 | (25 << 8) | (0x3 /*toggle*/ << 16) | (0x01 << 20)); //Make GPIO a task, use pin 25, task sets pin high, init as pin high, 0x111903
    //    NRF_GPIOTE->CONFIG[1] = (0x03 | (27 <<8)  | (0x02 << 16) | (0x01  << 20)); //Make GPIO a task, use pin 25, task sets pin low, init as pin low, 0x21903
        
        //NRF_PPI->CHG[0] |= 0x03; // Grouping was not used
        NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];
        NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
        NRF_PPI->CH[1].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[1];
        NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
        NRF_PPI->CHEN |= 0x03;
    
        //NRF_TIMER1->POWER = 0x01;
        NRF_TIMER1->TASKS_CLEAR = 1;
        NRF_TIMER1->MODE = 0; //Timer mode
        NRF_TIMER1->BITMODE = 0x01; //8-bit
        NRF_TIMER1->PRESCALER = 0; //Prescaler = /1
        NRF_TIMER1->SHORTS = 2; //	Enable COMPARE1_CLEAR to clear timer on CC[1] match 
        NRF_TIMER1->CC[0] = 1; 
        NRF_TIMER1->CC[1] = 20;
    
        while(NRF_TIMER1->CC[0] != 1){ }
        while(NRF_TIMER1->CC[1] != 20){ }
    
        NRF_TIMER1->INTENSET = 1 << 17; //Enable interrupt on compare register  1. Event 0 appears to be ignored anyway
        NVIC_SetPriority(TIMER1_IRQn, 3);
        NVIC_ClearPendingIRQ(TIMER1_IRQn);
        NVIC_EnableIRQ(TIMER1_IRQn);
        NRF_TIMER1->TASKS_START = 1;
    }
    

    Can you try and see if works?

    With the debugger I see that the interrupt never exits because the COMPARE_EVENT[1] is set whenever COMPARE_EVENT[0] is set, which is not expected.

    The debugger only halts the CPU while the timers are kept running. So it is impossible to read out compare event 0 before compare event 1 manually.

    Just a warning, it starts to become complicated as soon as you want to update the duty cycle on the fly. If that's something you intend to do, you should consider modifying the existing PWM library instead.    

    Regards,

    Vidar

Children
Related