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!

Parents
  • I'm assuming that sub_period < period.

    As soon as CC[1] triggers, that clears the timer (via PPI) and you have only subperiod ticks until the CC[0] compare is going to trigger the next toggle. I would assume you see this issue mostly when subperiod is small, close to zero, what kind of numbers are you using for subperiod and period?

    What's likely is that indeed TIMER1_IRQ doesn't get run before the timer hits CC[0] again and you get the extra toggle. The softdevice performs regular interrupts and blocks other things happening for up to milliseconds at a time. The more the softdevice is doing, the longer it blocks things for (eg doing nothing does very little, advertising does something, sending data in a connection you can get some very long blocks). Even just having the softdevice loaded and disabled adds 50 cycles to the interrupt start time. I would estimate that in the code above with a softdevice on the chip, the interrupt + the softdevice overhead + the code before you disable the timer is around 80 cycles. That's the best case if the SD isn't servicing its own high priority interrupt when TIMER1 is raised, the worst case is milliseconds.

    You gave TIMER1 an interrupt priority of 3, you could raise that to 1, but if you're talking the kind of microsecond timings you mention, as soon as you start doing things with the softdevice even at priority 1 on the interrupt you're going to have that interrupt delayed, quite possibly by enough to get several toggles of the pin before you turn it off.

    Relying on interrupts for any kind of precise timing when the softdevice is running really doesn't work, not unless you're talking times of the order of 10s of milliseconds or more. Basically assume that TIMER1 interrupt can be delayed by up to 4ms in the worst cases.

    Things you could try:

    Raise the TIMER1 interrupt priority to 1 - but that's not going to work

    Add another PPI channel to stop the timer on CC[1] compare which you only enable in the TIMER1 interrupt one cycle before remainingToggles is 0. So the final toggle also turns off the timer. That will work better, especially if the period value is close to 0xffff, but will still glitch in the worst case. The lower 'period' is, the worse it will glitch.

    Use TIMER2 in count up mode triggered by CC[1] compare using another PPI, clear it, load its compare with the number of toggles you want and set up yet another PPI to stop TIMER1 when you get the compare. Now you have a solution entirely within PPI which should be immune to interrupts, at the cost of 2 timers and 4 PPIs.

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

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

Children
No Data
Related