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

High frequency PWM shifts phase when radio is active

image description

When setting up a 1Mhz square wave using NRF_PWM on the NRF52 or GPIOTE on the NRF51 while BLE is advertising, the PWM stops when the radio is active. The problem doesn't happen when the frequency is below 6khz. No PWM interrupt is enabled. Is this an unavoidable limitation with high PWM frequencies?

  • Hi! not sure, but looks like interference with softdevice (SD), which in fact doesn't like when you manage hardware by ports directly. Although, if you run your code within Timelots, looks like something unavoidable, if SD still required for operation.

  • Hi, are you using PWM peripheral with EasyDMA properly? I'm running 8-bit PWM at maximum frequency (16MHz) without any problems even during BLE connection, nrf_drv_pwm driver seems to work well (see nrf_drv_pwm_complex_playback function for how to set up 2-buffer swing). If you will be driving PWM "manually" by SW interrupts (e.g. by combination of timers and GPIO states) then you'll obviously experience glitches due to higher interrupt priority of BLE stack (and that's logical, you've purchased nRF52 because of radio, didn't you;) Cheers Jan

  • Only certain registers are restricted or blocked by the SoftDevice and should not be accessed directly, see here.

  • The PWM should not be affected by the SD, neither the app_pwm library nor the nrf_pwm driver. Can you upload code that will produce the problem? What kind of hardware are you using?

  • These 3 methods show the same artifact:

    Using NRF_PWM:

    NRF_PWM0->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
    NRF_PWM0->PRESCALER = NRF_PWM_CLK_16MHz;
    NRF_PWM0->MODE = NRF_PWM_MODE_UP;
    NRF_PWM0->COUNTERTOP = 20;
    NRF_PWM0->PSEL.OUT[0] = CLK_PIN;
    for(i = 1; i < NRF_PWM_CHANNEL_COUNT; i++)
    {
    	NRF_PWM0->PSEL.OUT[i] = NRF_PWM_PIN_NOT_CONNECTED;
    }
    NRF_PWM0->DECODER = ((uint32_t)NRF_PWM_LOAD_INDIVIDUAL << PWM_DECODER_LOAD_Pos) |
    	((uint32_t)NRF_PWM_STEP_AUTO << PWM_DECODER_MODE_Pos);
    NRF_PWM0->SHORTS = 0;
    NRF_PWM0->INTEN = 0;
    static nrf_pwm_values_individual_t duty_cycles = 
    {
    	.channel_0 = 10,
    	.channel_1 = 10,
    	.channel_2 = 10,
    	.channel_3 = 10,
    };
    
    NRF_PWM0->SEQ[0].PTR = (uint32_t)&duty_cycles;
    NRF_PWM0->SEQ[0].CNT = NRF_PWM_VALUES_LENGTH(duty_cycles);
    NRF_PWM0->SEQ[0].REFRESH = 0;
    NRF_PWM0->SEQ[0].ENDDELAY = 0;
    NRF_PWM0->TASKS_SEQSTART[0] = 1;
    

    Using GPIOTE:

    	NRF_GPIOTE->CONFIG[OSC_GPIOTE] = (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |
    		(CLK_PIN << GPIOTE_CONFIG_PSEL_Pos) |
    		(GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);
    
    // Start the timer.
        OSC_TIMER->MODE      = TIMER_MODE_MODE_Timer;
        OSC_TIMER->BITMODE   = TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos;
        OSC_TIMER->PRESCALER = 0;
    
        // Clears the timer, sets it to 0.
        OSC_TIMER->TASKS_CLEAR = 1;
    
    	OSC_TIMER->CC[0] = 4;
    	OSC_TIMER->CC[1] = 8;
    
    	OSC_TIMER->TASKS_START = 1;
    
    // This PPI channel toggles CLK_PIN on every TIMER2 COMPARE[0] match.
    
        NRF_PPI->CH[PPI_CHANNEL0].EEP = (uint32_t)&OSC_TIMER->EVENTS_COMPARE[0];
        NRF_PPI->CH[PPI_CHANNEL0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[OSC_GPIOTE];
    
    // this PPI channel clears the timer
        NRF_PPI->CH[PPI_CHANNEL1].EEP = (uint32_t)&OSC_TIMER->EVENTS_COMPARE[1];
    
        NRF_PPI->CH[PPI_CHANNEL1].TEP = (uint32_t)nrf_timer_task_address_get(OSC_TIMER, NRF_TIMER_TASK_CLEAR);
    
        // Enable PPI channels
    	NRF_PPI->CHENSET = (1 << PPI_CHANNEL0);
    	NRF_PPI->CHENSET = (1 << PPI_CHANNEL1);
    

    Using a while loop:

    while(1)
    {
    nrf_gpio_pin_toggle(CLK_PIN);
    }
    

    While Nordic has gone through several SDK revisions with different APIs, we haven't had time to port to every new API in search of a solution. They all seem to use the same hardware. There is a workaround by scheduling activity around the downtimes, but ideally the PWM would always work.

Related