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

Random PWM dutycycle inversion in SoftDevice 8.0

I am using the PWM library in this repository for a nRF51822. I write the following code to generate a ~ 30% dutycycle 500 kHz PWM output:

#define V_CLK  5

static void v_clock_start(void)
{   
    // Init PWM
    nrf_pwm_config_t pwm_config = PWM_DEFAULT_CONFIG;
    pwm_config.mode             = PWM_MODE_500kHz; // PRESCALER of 0, pwm_max_value of 31
    pwm_config.num_channels     = 1;
    pwm_config.gpio_num[0]      = V_CLK;
    // Initialize the PWM library
    nrf_pwm_init(&pwm_config);
	
   // Start PWM
   nrf_pwm_set_value(0, 10); 
}

And I use

nrf_pwm_set_enabled(false); 

to stop PWM. The enum type PWM_MODE_500kHz (self-defined) corresponds to PRESCALER of 0 and pwm_max_value of 31, making the PWM frequency 500 kHz. I enable and disable PWM periodically based on a timer, and monitor the output with an oscilloscope. I can see for the majority of time, the PWM has correct dutycycle ~30%. However, from time to time, the dutycycle is inversed (~70%), and I can see the complementary PWM waveform. It happens regularly and randomly. I am just wondering what can cause such a problem.

Parents
  • Hi

    In theory, we have estimated 62.5kHz to be the safe boundary of the PWM frequency. We have not tested with higher frequencies. The library has worked fine in the tests that we have performed so far, which are within the PWM frequency limit mentioned above. I expect that the PWM occasionally flips when you set the duty cycle with after configuring, am I correct?

    I have a suggestion. In your code you call nrf_pwm_init which configures and starts the PWM, and then you set the duty cycle with nrf_pwm_set_value after it has started, which is not recommended with >62.5kHz. Since you intend to use a fixed PWM, you can set the duty cycle before starting the PWM which should be safe. Then you can avoid updating the duty cycle after the PWM is started. For this purpose add the following function in nrf_pwm.c

    void nrf_pwm_set_value_directly(uint32_t pwm_channel, uint32_t pwm_value)
    {
    		pwm_next_value[pwm_channel] = pwm_value;
    		pwm_modified[pwm_channel] = true;
    		PWM_IRQHandler();
    }
    

    Then call this function just before you start the PWM in the nrf_pwm_init function, i.e.

    ...    
    #if(USE_WITH_SOFTDEVICE == 1)
    		sd_radio_session_open(nrf_radio_signal_callback);
    #else
    		NVIC_SetPriority(PWM_IRQn, 0);
    		NVIC_EnableIRQ(PWM_IRQn);
    #endif
    		
    		nrf_pwm_set_value_directly(0, 10);
    		
    		apply_pan73_workaround(PWM_TIMER, true);
    		PWM_TIMER->TASKS_START = 1;
    ...
    

    Then remove your call to your nrf_pwm_set_value function in v_clock_start

    Let me know if that solves the issue

    Update 6.7.2015 A little bit about how the PWM library works.

    Each TIMER peripheral has 4 CC capture/compare registers. One of those register (I think CC[2]) is used to toggle the PWM in one direction (low to high), and one CC register (e.g. CC[0]) is used to toggle in the other direction (high to low). The value of CC[0] is changed in order to change the duty cycle of the PWM channel. If you need another PWM channel, CC[2] is also used for that one to toggle from low to high (the value of CC[2] is fixed), but e.g. CC[1] is used to toggle from high to low. The conclusion is that you can use one TIMER peripheral for 3 PWM channels. If you want more channels, you will have to use another timer peripheral as well.

    The apply_pan73_workaround() is implementation for the anomaly #73 workaround stated in the nRF51822-PAN v3.0 document.

    When updating the PWM duty cycle, it is critical, for safe operation, that it is updated in the correct moment, i.e. not close to the moments when the signal is to be toggled from high to low, or from low to high. If the duty cycle is updated when the signal is about to be toggled, the PWM signal might flip, as you have found out by experience. There are defined safe margins, which is the minimum time from the instant that you are updating the duty cycle until the signal will toggle. If updating the PWM duty cycle inside a safe margin, then the strategy is simply to wait a few us until we are outside the safe margin, and then do the PWM duty cycle update. When the PWM frequency is too high however, there is not enough time outside the safe margins to perform the PWM duty cycle update, and the duty cycle update might result in a PWM signal flip.

    Since TIMER, PPI, GPIOTE is used to produce the PWM signal, no CPU is needed. However, when updating the PWM duty cycle (the value of e.g. CC[0] register), the CPU is needed. But there might also be other peripherals that need the CPU at the same time. Since the PWM duty cycle update is very time critical operation in order to work correctly, we must ensure that the PWM duty cycle update operation is not interrupted once it is started. This is why the PWM library uses timeslots API to do the PWM duty cycle update. The nrf_radio_signal_callback event is received when the radio finishes its scheduled BLE radio activity and grants the CPU to the application for a certain period of time. This ensures that the BLE softdevice will not interrupt the PWM duty cycle update procedure.

  • You are correct, there is no direct call to PWM_IRQHandler() from nrf_pwm_set_value(). The nrf_pwm_set_value() only sets the new duty cycle value into a variable. The actual duty cycle is updated when a timeslot starts, and that is where it is safe to update the duty cycle.

    However, by implementing the new nrf_pwm_set_value_directly() function, we will not have to wait for the timeslot to call the PWM_IRQHandler() and update the PWM duty cycle, we will just call the PWM_IRQHandler() function directly. That should be safe since we did not start the PWM yet.

Reply
  • You are correct, there is no direct call to PWM_IRQHandler() from nrf_pwm_set_value(). The nrf_pwm_set_value() only sets the new duty cycle value into a variable. The actual duty cycle is updated when a timeslot starts, and that is where it is safe to update the duty cycle.

    However, by implementing the new nrf_pwm_set_value_directly() function, we will not have to wait for the timeslot to call the PWM_IRQHandler() and update the PWM duty cycle, we will just call the PWM_IRQHandler() function directly. That should be safe since we did not start the PWM yet.

Children
No Data
Related