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?

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

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

Children
No Data
Related