#nrf52840: create 2 PWMs with different pulse width using single instance by direct register access

As per #nrf52840 datasheet

  NRF_P0->DIR |= 0x0001E000;
  NRF_P0->OUT |= 0x0001E000;


  uint16_t pwm_seq[4]= {160 , 3200};

  NRF_PWM0->PSEL.OUT[0] = 0;
  NRF_PWM0->PSEL.OUT[0] = 13 ;

  NRF_PWM0->PSEL.OUT[1] = 0 ;
  NRF_PWM0->PSEL.OUT[1] = 14 ;


  NRF_PWM0->ENABLE = 0x00000001;
 
  NRF_PWM0->MODE = 0x00000000;

  NRF_PWM0->PRESCALER = 0x00000000; //16Mhz

  NRF_PWM0->COUNTERTOP = 16000; // PWM_Freq = 1000 Hz

  //NRF_PWM0->LOOP = 0x00000000;

  //NRF_PWM0->DECODER = 0x00000002;
 
  NRF_PWM0->SEQ[0].PTR = (uint32_t)(pwm_seq);
 
  NRF_PWM0->SEQ[0].CNT = 2;
 
  NRF_PWM0->SEQ[0].REFRESH = 0;
 
  NRF_PWM0->SEQ[0].ENDDELAY = 0;
 
  NRF_PWM0->TASKS_SEQSTART[0] = 1;

I've configured PWM0 registers as mentioned above. But with this setting both LED1 & LED2 are showing same intensity that means both channels set to common pulse width.

How to edit this configuration so that both channels will set to different pulse width ?

If I set NRF_PWM0->DECODER = 0x00000002; I didn't observe anything on two channels.

How to update pulse width dynamically after these initial configuration ?

Thank You !!

Parents
  • Hello,

    I am not quite sure where you found this piece of code, but it can be a bit confusing, since all of our PWM examples are set up in different ways, using different modules.

    The "best" way is to use the timer and PPI, do manage the PWM signals directly. However, it does not use the PWM module. Only a timer and GPIO control. 

    In the Product Specification, on page 559 is an example on one way to use the PWM module, which looks similar to the way that you set it up. 

    Below, I modified this to match your pins.

    int main(void)
    {
        uint16_t pwm_seq[4] =
            {
                1600, 3200};
    
        NRF_PWM0->PSEL.OUT[0] = (13 << PWM_PSEL_OUT_PIN_Pos) | (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    
        NRF_PWM0->PSEL.OUT[1] = (14 << PWM_PSEL_OUT_PIN_Pos) | (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    
        NRF_PWM0->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
    
        NRF_PWM0->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
    
        NRF_PWM0->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
    
        NRF_PWM0->COUNTERTOP = (16000 << PWM_COUNTERTOP_COUNTERTOP_Pos); //1 msec
    
        NRF_PWM0->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
    
        NRF_PWM0->DECODER = (PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
    
        NRF_PWM0->SEQ[0].PTR = ((uint32_t)(pwm_seq) << PWM_SEQ_PTR_PTR_Pos);
    
        NRF_PWM0->SEQ[0].CNT = ((sizeof(pwm_seq) / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos);
    
        NRF_PWM0->SEQ[0].REFRESH = 0;
    
        NRF_PWM0->SEQ[0].ENDDELAY = 0;
    
        NRF_PWM0->TASKS_SEQSTART[0] = 1;
    
        while (true)
        {
            // Do nothing.
            nrf_delay_ms(1000);                 // Wait for one second
            
            if (pwm_seq[0] == 3200)             // Toggle between two duty cycles on channel 0
                pwm_seq[0] = 1600;
            else
                pwm_seq[0] = 3200;
            
            NRF_PWM0->TASKS_SEQSTART[0] = 1;    // Update the PWM module
        }
    }

    Note that the nrf_delay_ms() is a very little power efficient function. I used it because it is easy to set up to show how to update the PWM' duty cycle.

    If you paste this code into e.g. the example found in SDK\examples\peripheral\template_project, you will see that LED1 and LED2 is controlled by the PWM, and the intensity of LED1 is toggling between two values.

    Best Regards,

    Edvin

Reply
  • Hello,

    I am not quite sure where you found this piece of code, but it can be a bit confusing, since all of our PWM examples are set up in different ways, using different modules.

    The "best" way is to use the timer and PPI, do manage the PWM signals directly. However, it does not use the PWM module. Only a timer and GPIO control. 

    In the Product Specification, on page 559 is an example on one way to use the PWM module, which looks similar to the way that you set it up. 

    Below, I modified this to match your pins.

    int main(void)
    {
        uint16_t pwm_seq[4] =
            {
                1600, 3200};
    
        NRF_PWM0->PSEL.OUT[0] = (13 << PWM_PSEL_OUT_PIN_Pos) | (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    
        NRF_PWM0->PSEL.OUT[1] = (14 << PWM_PSEL_OUT_PIN_Pos) | (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    
        NRF_PWM0->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
    
        NRF_PWM0->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
    
        NRF_PWM0->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
    
        NRF_PWM0->COUNTERTOP = (16000 << PWM_COUNTERTOP_COUNTERTOP_Pos); //1 msec
    
        NRF_PWM0->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
    
        NRF_PWM0->DECODER = (PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
    
        NRF_PWM0->SEQ[0].PTR = ((uint32_t)(pwm_seq) << PWM_SEQ_PTR_PTR_Pos);
    
        NRF_PWM0->SEQ[0].CNT = ((sizeof(pwm_seq) / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos);
    
        NRF_PWM0->SEQ[0].REFRESH = 0;
    
        NRF_PWM0->SEQ[0].ENDDELAY = 0;
    
        NRF_PWM0->TASKS_SEQSTART[0] = 1;
    
        while (true)
        {
            // Do nothing.
            nrf_delay_ms(1000);                 // Wait for one second
            
            if (pwm_seq[0] == 3200)             // Toggle between two duty cycles on channel 0
                pwm_seq[0] = 1600;
            else
                pwm_seq[0] = 3200;
            
            NRF_PWM0->TASKS_SEQSTART[0] = 1;    // Update the PWM module
        }
    }

    Note that the nrf_delay_ms() is a very little power efficient function. I used it because it is easy to set up to show how to update the PWM' duty cycle.

    If you paste this code into e.g. the example found in SDK\examples\peripheral\template_project, you will see that LED1 and LED2 is controlled by the PWM, and the intensity of LED1 is toggling between two values.

    Best Regards,

    Edvin

Children
Related