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

Long interrupt latency

This is almost certainly a noob (to nRF52840) programming question.

I have modified the peripheral/gpiote example to invoke an ISR on the falling edge of a pin signal. In the ISR I toggle another pin.

On a scope I see about 22 microseconds between the signal fall and the toggle. 

My approach is similar to nrf52-gpiote-interrupt-latency, but there the latency reported is 1.6 us, which would make me happy.

#define GPIO_INPUT_INTR_PIN_NUMBER NRF_GPIO_PIN_MAP(1,12)
#define GPIO_OUTPUT_IRQ_RESPONSE_PIN_NUMBER NRF_GPIO_PIN_MAP(1,13)

void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    nrf_drv_gpiote_out_toggle(GPIO_OUTPUT_IRQ_RESPONSE_PIN_NUMBER);
}

/**
 * @brief Function for application main entry.
 */
int main(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    // set up the pin that is toggled by the IRQ
    nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_SIMPLE(true);
    err_code = nrf_drv_gpiote_out_init(GPIO_OUTPUT_IRQ_RESPONSE_PIN_NUMBER, &out_config);
    APP_ERROR_CHECK(err_code);

    // set up the pin that detects the signal change
    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;
    err_code = nrf_drv_gpiote_in_init(GPIO_INPUT_INTR_PIN_NUMBER, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);

    // enable the interrupt detection
    nrf_drv_gpiote_in_event_enable(GPIO_INPUT_INTR_PIN_NUMBER, true);

    bsp_board_init(BSP_INIT_LEDS);

    while (true)
    {
        bsp_board_led_invert(0);
        nrf_delay_ms(100);
    }
}

  • I noticed no one had responded and it was Friday so good time to do some fun coding.

    I implemented what you specified but did it directly with the registers as I normally don't use most drivers to keep the code efficient. Since I mainly do RF, I didn't have a function generator but I did have some code that implemented two square wave generators on the nRF.  So, basically you connect p0.4 with p1.12 on the DK and presto p1.8 is a duplicate of the signal after passing through an IRQ.

    I should point out that servicing this in an IRQ isn't the best approach but I assume you have your reasons.  If you do it directly in PPI/GPIOTE then there will be only 1 or 2 Pclk latency.

    However even with the IRQ servicing time, the latency was only about 728nsec which corresponds to about 46 opcodes to get there.  Not bad in my opinion.

    Let me know if you have questions. I just shoved the code into the blinky example.  But you need to fix up the C pathways so it finds all the #includes.

    Here is the code:

    #include <stdbool.h>
    #include <stdint.h>
    #include "nrf.h"
    #include "nrf_gpiote.h"
    #include "nrf_gpio.h"
    #include "boards.h"
    //#include "nrf_drv_ppi.h"
    //#include "nrf_drv_timer.h"
    //#include "nrf_drv_gpiote.h"
    #include "app_error.h"
    
    
    
    #define MODE2 0
    #define PSEL 8
    #define PORT 13
    #define POLARITY 16
    #define OUTINIT 20
    #define DIR 0
    #define PULL 2
    #define DRIVE 8
    #define SENSE 16
    
    void GPIOTE_IRQHandler(void)
    {  
    if (NRF_P1->OUT&(0x1<<8))
    {NRF_P1->OUTCLR = (0x1<<8);}
    else
    {NRF_P1->OUTSET = (0x1<<8);}
    
    //  //RESET EVENTS
      NRF_GPIOTE->EVENTS_IN[2] = 0;  
    NVIC_ClearPendingIRQ(GPIOTE_IRQn);
    
    
    }
    
    
    /**
     * @brief Function for application main entry.
     */
    int main(void)
    {
        /* Configure board. */
        NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART    = 1;
    
    /* Wait for the external oscillator to start up */
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
    {
        // Do nothing.
    }
    
            NRF_TIMER1->TASKS_STOP        = 1;                      // Stop timer, if it was running
    NRF_TIMER1->TASKS_CLEAR       = 1;
    NRF_TIMER1->MODE              = TIMER_MODE_MODE_Timer;  // Timer mode (not counter)
    NRF_TIMER1->EVENTS_COMPARE[0] = 0;                      // clean up possible old events
    NRF_TIMER1->EVENTS_COMPARE[1] = 0;
    NRF_TIMER1->EVENTS_COMPARE[2] = 0;
    NRF_TIMER1->EVENTS_COMPARE[3] = 0;
    
            NRF_TIMER2->TASKS_STOP        = 1;                      // Stop timer, if it was running
    NRF_TIMER2->TASKS_CLEAR       = 1;
    NRF_TIMER2->MODE              = TIMER_MODE_MODE_Counter;  // Timer mode (not counter)
    NRF_TIMER2->EVENTS_COMPARE[0] = 0;                      // clean up possible old events
    NRF_TIMER2->EVENTS_COMPARE[1] = 0;
    NRF_TIMER2->EVENTS_COMPARE[2] = 0;
    NRF_TIMER2->EVENTS_COMPARE[3] = 0;
    
    
        NRF_GPIOTE->CONFIG[0] =((GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)
                                |(4                         << GPIOTE_CONFIG_PSEL_Pos)
    														|(0                         << GPIOTE_CONFIG_PORT_Pos)
                                |(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
                                |(GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos));
    
        NRF_GPIOTE->CONFIG[1] =((GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)
                                |(7                         << GPIOTE_CONFIG_PSEL_Pos)
    														|(0                         << GPIOTE_CONFIG_PORT_Pos)
                                |(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
                                |(GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_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->FORK[0].TEP = (uint32_t)&NRF_TIMER2->TASKS_COUNT;
    		
    		NRF_PPI->CH[1].EEP  = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[0];
        NRF_PPI->CH[1].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_OUT[1];
    
    
        NRF_PPI->CHEN       = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos)|(PPI_CHEN_CH1_Enabled << PPI_CHEN_CH1_Pos);
    
        NRF_TIMER1->SHORTS      = (1 << TIMER_SHORTS_COMPARE0_CLEAR_Pos); 
        NRF_TIMER1->PRESCALER   = 0; 
        NRF_TIMER1->CC[0]       = 16;   
    		
    		NRF_TIMER2->SHORTS      = (1 << TIMER_SHORTS_COMPARE0_CLEAR_Pos); 
        NRF_TIMER2->PRESCALER   = 0; 
        NRF_TIMER2->CC[0]       = 400;   
    		
        NRF_TIMER2->TASKS_START = 1;
    		NRF_TIMER1->TASKS_START = 1;
    		
    
    
    
    
    //NRF_P1->PIN_CNF[12]=(0x3000C);
    
    
    		
      NRF_P1->PIN_CNF[12] = (0x00 << DIR) | (0x03 << PULL) | (0x00 << DRIVE) | (0x00 << SENSE);
    //  
    //PPI OUTPUT
    NRF_P1->PIN_CNF[8] = (0x01 << 0x00);
    //
    //  //input config
      NRF_GPIOTE->CONFIG[2] = (0x01 << MODE2) | (0x0C << PSEL) | (0x01 << PORT) | (0x03 << POLARITY);
    //  
    
    
    //  
    //  //RESET EVENTS
      NRF_GPIOTE->EVENTS_IN[2] = 0;  
    	
    NVIC_DisableIRQ(GPIOTE_IRQn);
    NVIC_ClearPendingIRQ(GPIOTE_IRQn);
    
    NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN2_Msk;
    
    NVIC_SetPriority(GPIOTE_IRQn, 3); //optional: set priority of interrupt
    NVIC_EnableIRQ(GPIOTE_IRQn);
    
    
    		
    		while (1)
    {
        // Do nothing.
    }
    
    }
    
    /**
     *@}
     **/

  • Hey, thanks so much for taking time on a Friday to educate me. Not only does this work (of course!), but it's in line with my shared preference to rely on register-level programming. The only real change I had to make was to be sure to make sure the Makefile was not including the gpiote, ppi, and timer sources.

    Many thanks!

    steven

  • Thanks again for the answer a couple of months ago.

    I've had a chance to get back to this and dig into it further. As I suggested in the initial query, lack of experience and knowledge was the source of my problem. I may not be all that much wiser, but have learned that the main problem I was seeing with the `nrf_drv_gpiote` approach (now `nrfx_gpiote`) is that I did not understand that the irq handler was iterating through all possible events. Looking at the source code it's clear that there are a few loops (corresponding to the number of channels) that eat up the time.

    It also did not help that I was running in full debug mode.

    The observed latency dependence is easy to test by changing the value of GPIOTE_NUM_CHAN and observing the delay between the triggering pin transition and the output pin change.

    With a nRF528420-DK and compiling with -O3, the latency is

    ~ 5.5 us with GPIOTE_NUM_CHAN 8

    ~ 3.6 us with GPIOTE_NUM_CHAN 1

    That makes a lot more sense to me. I will want to keep the number of channels at 8 as my HW has several interrupt signals. Only one is used for something timing critical and 5.5 us is well within my margin of comfort.

    Again, thanks for helping me the first time and, in effect, pointing me to  better understanding.

Related