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!

  • What's the reasoning behind having hardware toggle the pin, instead of software? You could insert code into the else clause of your if() statement in timer_check() which toggles the pin instead.

  • My timer period is in microseconds and the time between toggles could be in nanoseconds. So ideally I'd like to avoid the overhead of toggling the pin in software, to get the most precise timing possible. If it's not possible to prevent the extra toggle I'm seeing, I may do it through software to guarantee the proper sequence.

  • But you've already incurred that overhead just by having TIMER1_IRQHandler() exist. To set a bit in NRF_GPIO->OUTSET is probably only a few instructions--an LDR to contain the new bit pattern, another LDR to contain the base address of NRF_GPIO, and an STR to store the new bit pattern at the offset in NRF_GPIO where the OUTSET field is.

  • What are the values of your period and sub_period?

    in your timer_check(), the first thing I would do inside the if statement would be to disable the timer, I think shutdown takes longer than disable. after this i would disable PPI and GPIOTE.

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

Related