measuring interrupt interval with app_timer

paul_tanner gravatar image

asked 2017-08-11 15:00:35 +0100

I have an interrupt coming in every 50-150 microsecs and I want to measure these intervals as precisely as possible.

I tried this with SDK12.2:

APP_TIMER_INIT(0, 5, false);

then in the (GPIOTE) ISR:

uint32_t this_time=app_timer_cnt_get();

I always get zero back;

Presumably I need to initialise it in a different way?

Thx for any suggestion.

edit retag flag offensive close delete report spam

2 answers

Sort by ยป oldest newest most voted
AmbystomaLabs gravatar image

answered 2017-08-11 15:24:31 +0100

updated 2017-08-14 21:54:37 +0100

This type of thing gets covered fairly regularly here; but the app timer generates software interrupts and a 64MHz processor can't be expected to respond to 50usec interrupts. Depending on whether you are running a softdevice, even millisecond interrupts can be tricky.

The processor has to do quite a bit of work to respond to an interrupt and 3200 opcodes (50 x 64) isn't much to work with.

The right way to measure microsecond timing info from an external source is using PPI and the hardware counters and registers. Then the processor only gets involved to read a result from a register after an event has occurred.

Here is some sample code to measure an input event. The PPI bus and timers use PCLK (16MHz). The prescaler is set to provide a timer interval of 1 usec.

You need to wrap a main around this that runs the inits. Basically the logic is, input low to high starts the timer. Then input high to low stops the timer and captures the value. Since it is done in ppi/gpiote the count will always be accurate and not dependent on interrupt latency.

Once captured an interrupt is generated and you should probably do what you need to do quickly in the handler if you expect events to come in regularly. e.g., you may wish to put the samples in an array and do post processing of large numbers of samples.

The way gpiote is structured, low to high and high to low has to be done on two pins tied together. However, notes on this suggest that as long as you are only working with input events you can tie two gpiote channels to one pin and it will work.

Let me know how/if it works and if you need further assistance.

#define GPIO_PIN_NUM 12
#define GPIO_PIN2_NUM 13

static uint16_t sample = 0;

/**@brief Function for configuring the timer for counting gpio event
void timer_init(void)
    // Use 16MHz from external crystal
    // This could be customized for RC/Xtal, or even to use a 32 kHz crystal

        // Do nothing while waiting for the clock to start

        NRF_TIMER0->TASKS_STOP        = 1;                      // Stop timer, if it was running
    NRF_TIMER0->TASKS_CLEAR       = 1;
    NRF_TIMER0->MODE              = TIMER_MODE_MODE_Timer;  // Timer mode (not counter)
        NRF_TIMER0->BITMODE                     = 0;                                            //Sets number of bits for timer, 0 is 16bits wide
    NRF_TIMER0->EVENTS_COMPARE[0] = 0;                      // clean up possible old events

        NRF_TIMER0->SHORTS      = 0;
        NRF_TIMER0->PRESCALER   = 4;                                     // Input clock is 16MHz, timer clock = 2 ^ prescale -> interval 1us


/**@brief Function for configuring the timer for counting gpio event
void gpiote_init(void)
                NRF_GPIOTE->CONFIG[0] =((GPIOTE_CONFIG_MODE_Event       << GPIOTE_CONFIG_MODE_Pos)
                       |(GPIO_PIN_NUM                         << GPIOTE_CONFIG_PSEL_Pos)
                       |(GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos));

                NRF_GPIOTE->CONFIG[1] =((GPIOTE_CONFIG_MODE_Event       << GPIOTE_CONFIG_MODE_Pos)
                       |(GPIO_PIN2_NUM                         << GPIOTE_CONFIG_PSEL_Pos)
                       |(GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos));

                NRF_PPI->CH[0].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[0]; //TIES PIN1 TO PPI0
                NRF_PPI->CH[0].TEP = (uint32_t)&NRF_TIMER0->TASKS_START; //TIES PIN 1 TO START EVENT

                NRF_PPI->CH[1].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[1]; //TIES PIN2 TO PPI1
                NRF_PPI->CH[1].TEP = (uint32_t)&NRF_TIMER0->TASKS_CAPTURE[0]; //CAPTURE TIMER TO CC[0]
                NRF_PPI->FORK[1].TEP = (uint32_t)&NRF_TIMER0->TASKS_STOP; //STOP TIMER

                NRF_PPI->CHENSET      = (PPI_CHEN_CH0_Msk | PPI_CHEN_CH1_Msk);  //Turns on PPI0,1


                 // Enable interrupt on input 1 event.

void GPIOTE_IRQHandler(void)

//Here is where you pick up the value in the timer register and do something with it.
//You might just stick it in an array and then can manipulate many samples at one time by just doing math against the array.

    sample = NRF_TIMER0->CC[0];
    NRF_TIMER1->TASKS_CLEAR = 1;  //Clears the timer

edit flag offensive delete publish link more


Thx @AmbystomaLabs,

I should have said that I was not expecting to do this with a soft device running.

The GPIOTE interrupts are coming in OK (I'm counting them). If I can just read a fast-running counter I could do determine the intervals. On an Arduino I did this with micros() with about 5usec accuracy. The only issue was handling overflow.

If PPI would allow this, a code example/ fragment would be useful. (In the SDK I see some PPI stuff about linking counters but not about reading them.) Thx.

paul_tanner ( 2017-08-11 15:54:12 +0100 )editconvert to answer

If you mean NVIC overflow, then yes that will be a problem here as well. Without the SD running you could probably execute in code as you suggested but there will always be random latency associated with the processor responding to your interrupt. Best way is using hardware timers.

I will look for some sample code on this and post it if available later today. In the meantime maybe Nordic might respond with it.

AmbystomaLabs ( 2017-08-11 16:40:13 +0100 )editconvert to answer
paul_tanner gravatar image

answered 2017-08-12 17:28:37 +0100

OK. Got this after much googling.

    .frequency          = (nrf_timer_frequency_t) NRF_TIMER_FREQ_1MHz,
    .mode               = (nrf_timer_mode_t) TIMER_MODE_MODE_Timer,
    .bit_width          = (nrf_timer_bit_width_t) TIMER_BITMODE_BITMODE_32Bit,
    .interrupt_priority = TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
    .p_context          = NULL

const nrf_drv_timer_t TIMER_INPUT = NRF_DRV_TIMER_INSTANCE(3);

void timer_event_handler(nrf_timer_event_t event_type, void * p_context) { 
  return;  //  needed even if not used 

and in main()

nrf_drv_timer_config_t in_config_f = NRF_DRV_TIMER_MY_CONFIG;
err_code = nrf_drv_timer_init(&TIMER_INPUT, &in_config_f, timer_event_handler); 

then in ISR

this_time=nrf_drv_timer_capture(&TIMER_INPUT, 0);

(then handle delta microseconds and overflow.)

seems to work well enough. note that putting NULL instead of timer_event_handler causes a an immediate crash.

edit flag offensive delete publish link more


You are on the correct track. The timer driver allows one to set up timers triggered by external events via ppi and then it generates events on completion that allow you to pick up the resulting count at your leisure in code. This way the count is independent of IRQ latency. However, I would recommend just coding it directly using the ppi gpiote registers. The ppi/gpiote hardware is very useful and you will get a better understanding of how it works by avoiding the driver. Assuming time is available I can write something up Monday morning. I think it is time to post a very specific example since this question comes up a lot.

AmbystomaLabs ( 2017-08-13 16:46:12 +0100 )editconvert to answer

See above. I can only answer once so I added the code to my previous answer.

AmbystomaLabs ( 2017-08-14 21:55:18 +0100 )editconvert to answer

Thx for that. I need to study PPI to fully understand this. I think the main point about both answers is that no extra interrupts are introduced. The timer is just available to be read in the pin interrupt's ISR. You example clears the timer which is nicer than handling overflow.

paul_tanner ( 2017-08-15 06:31:22 +0100 )editconvert to answer

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer. Do not ask a new question or reply to an answer here.

[hide preview]

Question Tools

1 follower


Asked: 2017-08-11 15:00:35 +0100

Seen: 128 times

Last updated: aug. 14