#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 !!

  • Hello World !!

    Here answering my own question....

    I found that working of PWM module depends upon sequence of configuration.

    Here is simple working demo,

    //---------------------------------------------------------------------------------------------------------------------------------------------------------

    #include "nrf_delay.h"

    uint16_t pwm_seq[4];

    void gpio_init(void)
    {


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

      NRF_PWM0->PSEL.OUT[0] = 13 ;     // LED_1
      NRF_PWM0->PSEL.OUT[1] = 14 ;     // LED_2
     
      NRF_PWM0->MODE = 0x00000000;

      NRF_PWM0->PRESCALER = 0x00000004; //1 Mhz

      NRF_PWM0->COUNTERTOP = 1000;  //1 Khz

      NRF_PWM0->DECODER = 0x00000002;
     
      NRF_PWM0->LOOP = 0x00000000;
     
      NRF_PWM0->SEQ[0].REFRESH = 0;
     
      NRF_PWM0->SEQ[0].ENDDELAY = 0;

      NRF_PWM0->SEQ[0].PTR = (uint32_t)(pwm_seq);
     
      NRF_PWM0->SEQ[0].CNT = 4;

      NRF_PWM0->ENABLE = 0x00000001;
     
      NRF_PWM0->TASKS_SEQSTART[0] = 1;


    }


    int main(void)
    {
     
      unsigned char data=0;
      int i;

      gpio_init();

      while(1)
      {
        pwm_seq[0] += 10;
        pwm_seq[1] += 100;
        
        NRF_PWM0->TASKS_SEQSTART[0] = 1;

        if(pwm_seq[0] > 1000)
        {
          pwm_seq[0] = 0;
        }

        if(pwm_seq[1] > 1000)
        {
          pwm_seq[1] = 0;
        }


        nrf_delay_ms(100);
     
      }

      return(0);

    }

  • 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

  • I referred #nrf52840 Datasheet. It is same as your suggestion but  with my style. And it is very simple compare to using combo of PPI & Timer.

    And it works & that's it !!

Related