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.

  • I had similar feeling but I saw a line in nrf_pwm.c in that repository:

     nrf_gpiote_task_config(pwm_gpiote_channel[i], pwm_io_ch[i], NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH);
    

    when it started GPIOTE task with setting initial pin state high. If that was the case, the PWM waveform shouldn't change in a bistate manner. I don't know what I am missing here...

    1. Does it work properly if you don't have the SD running?

    2. What is the range of duty cycle you wish to produce?

    If you are only using one channel, and you don't need to change duty cycle on the fly, I still believe you'd be better off writing your own PWM functions. If you don't need to use the IRQs, then since it's all hardware(timer compare events, GPIOTE tasks) it should work perfectly fine even with the SD running.

    On the other hand, I don't know what exactly you are trying to achieve.

  • Hi

    We have seen this problem before and there are several possible causes. We think however that the problem is related to the update of the PWM duty cycle when PWM frequency is high, i.e. when nrf_pwm_set_value is called. Are you calling this method, or are you maintaining a fixed duty cycle PWM?

    If the frequency of the PWM is too high, the PWM might flip when the duty cycle is updated. I think the library implements the PWM_MODE_BUZZER_255 mode which is 62.5kHz and that is max frequency in order to do this safely. Is that frequency too low for your scenario? Can you try to lower the frequency in order to see if that is the actual issue.

  • @stefanbirnir Yes, I am using a higher PWM frequency (500kHz) than 62.5kHz. Unfortunately, 62.5 kHz is a little low for my application right now. I actually don't need to change the PWM duty cycle. The reason I constantly turn on (using nrf_pwm_set_value) and off (using nrf_pwm_enabled) PWM is to save power since I only need those PWM signals once every few minutes. And I only use 1 channel for this particular case. Given those conditions, is there a workaround?

  • 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.

Related