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

TIMER1 with PPI to GPIOTE

I'm trying to repeatedly toggle a GPIOTE on every CC[0] compare within the time period set by CC[1]. CC[1] runs multiple times. Every time CC[1] is reached I want to stop the CC[0] compare, check if I've sent a certain number of toggles and if so, stop TIMER1, else I'll re-enable the CC[0] compare and continue running.

I'm having a problem where the GPIOTE occasionally toggles after the final CC[1] compare occurs and I have an extra pulse being sent. It seems like the PPI is not being disabled quickly enough. I also have the S110 soft device enabled, but I don't see how that could affect it.

Here's my code. Any ideas what could be wrong?

Init Code:

NRF_TIMER1->TASKS_CLEAR = 1;
NRF_TIMER1->CC[0] = sub_period;
NRF_TIMER1->CC[1] = period;
NRF_TIMER1->MODE = (TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos);
NRF_TIMER1->PRESCALER = (0 << TIMER_PRESCALER_PRESCALER_Pos);
NRF_TIMER1->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);
NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Msk | TIMER_INTENSET_COMPARE1_Msk;
NRF_TIMER1->SHORTS = (TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos);
NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_TIMER1->EVENTS_COMPARE[0]);
NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[0]);
NRF_PPI->CH[1].EEP = (uint32_t)(&NRF_TIMER1->EVENTS_COMPARE[1]);
NRF_PPI->CH[1].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[0]);
nrf_gpiote_task_config(0, TX_OUT, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW);

NRF_PPI->CHENSET = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos)
                  | (PPI_CHEN_CH1_Enabled << PPI_CHEN_CH1_Pos);

err_code = sd_nvic_SetPriority(TIMER1_IRQn, 3);
APP_ERROR_CHECK(err_code);
err_code = sd_nvic_ClearPendingIRQ(TIMER1_IRQn);
APP_ERROR_CHECK(err_code);
err_code = sd_nvic_EnableIRQ(TIMER1_IRQn);
APP_ERROR_CHECK(err_code);

Interrupt:

void TIMER1_IRQHandler() {
    NRF_TIMER1->EVENTS_COMPARE[0] = 0;
    if (NRF_TIMER1->EVENTS_COMPARE[1]) {
        NRF_TIMER1->EVENTS_COMPARE[1] = 0;
        NRF_TIMER1->INTENCLR = TIMER_INTENCLR_COMPARE0_Msk;
        timer_check();
    }
    NRF_TIMER1->EVENTS_COMPARE[2] = 0;
    NRF_TIMER1->EVENTS_COMPARE[3] = 0;
 }

Check if done toggling:

void timer_check() {
    if (--remainingToggles == 0) {
        NRF_PPI->CHENCLR = (PPI_CHEN_CH0_Disabled << PPI_CHEN_CH0_Pos)
                 | (PPI_CHEN_CH1_Disabled << PPI_CHEN_CH1_Pos);
        nrf_gpiote_unconfig(0);
        NRF_TIMER1->TASKS_SHUTDOWN = 1;
        sd_nvic_DisableIRQ(TIMER1_IRQn);
        sd_nvic_ClearPendingIRQ(TIMER1_IRQn);
    }
    else {
        NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
    }
}

Any advice is greatly appreciated!

  • The period I have to use is fixed at 25 microseconds. The sub_period varies and is equal to period divided by a number from 1 to 5. I'm thinking I might be pushing the limits of the microcontroller since I only have a 16MHz clock.

  • Thank you for the very detailed response! I'm using a period of 25 microseconds, and sub_period is equal to the period divided by a value from 1 to 5. I think I'm pushing the limits with such small timer increments. The softdevice broadcaster is only called after this cycle is complete, but it may still be affecting the timing. Let me discuss your recommendations with my team and see what we can do!

  • 25/5 = 5microseconds = 80 clock cycles. That is probably right on the limit, it's what I guessed above. 50 for the softdevice overhead, 16 for the cortex interrupt. If you absolutely can guarantee there's nothing else will interrupt you then making your IRQ handler more efficient (no debug compile, remove the unecessary sets of the compare events to zero) you may get away with it .. or try the second method where you schedule the extra PPI to turn off the counter on the last cycle, that buys you 20 microseconds.

  • you said

    Every time CC[1] is reached I want to stop the CC[0] compare, check if I've sent a certain number of toggles and if so, stop TIMER1, else I'll re-enable the CC[0] compare and continue running
    

    but you are not stopping CC[0] compare, you are just disabling its interrupt, the event will be generated anyways and through PPI and GPIOTE it will toggle the pin. Instead of just disabling the interrupt at this time, you need to also disable that compare event. you are doing things in few microseconds, yes you are very close to check its limits.

  • Found this in the release notes for S110 version 8.0, "The default behaviour is now that the application can use the CPU while the radio is active. In previous versions of the S110, the CPU execution was blocked by the SoftDevice during radio activity." I was using S110 V7.1, so I'm updating to 8.0.

    I believe your third method is the correct approach. I can't implement it with my project, because TIMER2 is being used to time other events that start before TIMER1 and end after TIMER1 is stopped. So I'm going to implement method 2 and relax the smallest timing constraints to see if that works.

    In case it helps anyone, I received another suggestion to connect an SPI channel to my GPIOTE pin and send the bit toggling pattern as bytes through SPI. This would allow me to set the speed fast enough, and control the on/off time with 1s and 0s. We may experiment with this approach in the future.

Related