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

measuring interrupt interval with app_timer

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.

  • 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
        NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
        NRF_CLOCK->TASKS_HFCLKSTART    = 1;
    
        while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
        {
            // Do nothing while waiting for the clock to start
        }
    
    		//Timer 
    		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->EVENTS_COMPARE[1] = 0;
        NRF_TIMER0->EVENTS_COMPARE[2] = 0;
        NRF_TIMER0->EVENTS_COMPARE[3] = 0;
    		
    		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_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_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_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_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
    				
    				NVIC_EnableIRQ(GPIOTE_IRQn);
    				
    				 // Enable interrupt on input 1 event.
    				NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk;
    }
    
    
    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
    	
    }
    
  • 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.

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

  • OK. Got this after much googling.

    #define NRF_DRV_TIMER_MY_CONFIG {
        .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); 
    APP_ERROR_CHECK(err_code);
    nrf_drv_timer_enable(&TIMER_INPUT);
    

    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.

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

Related