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

Is it safe to change interrupt handler for GPIOTE while using softdevice ?

Hi,

While trying to measure timings with input captures and pin interrupts, I noticed that the SDK-provided pin changed event is very slow : by the time my handler starts running, the signal level has changed...

I then discovered https://devzone.nordicsemi.com/f/nordic-q-a/54956/copying-vector-table-to-ram-unknown-function-at-0x00000000 and managed to set my own handler for GPIOTE.

Works fine : i typically have a max delay between TIMER0 capture and handler start of ~3us with 1 sensor, and ~70us with 3 sensors.

I then tried to make those measurements while using softdevice (s140) to send out the numbers through BLE.

If measurements are performed within a timeslot, everything seems to work properly, including with the interrupt vector moved to RAM and with my custom GPIOTE handler.

So far so good, but running a couple of examples for a few hours is not enough to uncover all possible problems.

So my question is : do yo see any problems moving the vector table to RAM and tweaking handlers while using softdevice ? or can you confirm this is safe ?

Thanks in advance,

David

Parents
  • Hi Jørgen,

    Thanks for the quick response.

    Which exact code/library/mechanism are you using for this, and what delay are you seeing? There is some interrupt latency running through the MBR/softdevice, but this should be less than 4 us.

    My first attempt was to use the feature in nrfx_gpiote.c, where nrfx_gpiote_irq_handler() gets called to service a pin change interrupt and then ends up calling my own handler.

    The delays i'm referring to run from the signal change to execution at the first line of my "handler".

    To measure this delay i used a scope to see the original signal and a pin toggled on entry of my handler.

    I see typically 21-25us, sometimes up to 50us.

    But now if I setup thinks like so :

    static uint32_t myvectors[256] __attribute__ ((aligned(256)));
    ...
    memcpy(myvectors, (uint32_t*)0x0, sizeof(myvectors));

    SCB->VTOR = (uint32_t) myvectors;

    and then do before starting measuring the sensors :

    myvectors[16 + GPIOTE_IRQn] = my_own_handler;

    I get delays in the order of 1.7us.

    (of course, i put the original handler back in myvectors[16 + GPIOTE_IRQn] when finished with the sensors.

    Plus, I must say that things are getting worse as i try to handle more sensors simultaneously ; using the SDK irq handler leads to varying delays, whereas my own handler gives more stable delays.

    (note : USB CDC was being used in both cases to output numbers)

    So it's not so much the irq handler latency that i'm not happy with, it's all the work done on nrfx_gpiote_irq_handler that i'm trying to work around.

    Are you measuring timing of the inputs, or some timing in the application? GPIOTE can be connected directly to TIMERs through PPI to measure timing/delay in HW, if that could suit your use-case.

    Actually, this is what i did. I'm using PPI and TIMER1 to capture the exact moments where the signal changes.

    To give you more context, i'm using temperature sensors SMT172, which encode temperature in the duty cycle of a quasi-periodic signal. You're supposed to measure how long the signal stays high over a complete low-high-low cycle, calculate a ratio, and repeat over at least 8 consecutive cycles to average the calculated ratios, and then work out the temperature from the ratio. Better measures are obtained if you track multiple 8-cycle blocks.

    So, i have to capture a series of low-high and high-low changes over a few milliseconds, taking great care not to take a low for a high and vice versa.

    The only way to do this was to capture the TIMER value for any change, then use the pin change interrupt to collect the last CC into a buffer, along with the active pin level (calling nrf_gpio_pin_read for this purpose).

    I let the measures run for ~10ms, and then stop everything and process the numbers stored in the buffers afterwards. I use the pin level information to detect the start of valid cycles, and also to spot hickups (i.e. 2 consecutive lows or highs).

    Of course, the more sensors I can manage with one board, the better ! Dealing with 4 sensors simultaneously seems to be okay.

    When powered at 3.3V, the sensors output a signal at 5500-6500Hz, so a complete cycle takes ~150-180us and events could be only 35us apart. Hence my trying hard to cut down on delays.

    Best regards,

    David

  • If the main problem is that the interrupt handler in the GPIOTE driver uses a long time before it calls your event handler, I think it would be better to implement your own simplified driver and remove the the nrfx_gpiote implementation from your project (unless you need this for something else in the application).

    I'm not sure about your configuration of GPIOTE, but you may also speedup the event handler if you use IN-EVENTS (high_accuacy = true), and not PORT-EVENTS. IN events are mapped to a specific pin, while multiple GPIOs can trigger a PORT event. For PORT event, the handler needs to read all enabled PINs in order to detect which pin triggered the interrupt. The GPIOTE driver also have some advanced features, like swapping the polarity to detect toggling of PORT evets, etc., which may slow down the execution event more.

    I did a test with a bare-metal GPIOTE code, and measure interrupt latency between 1.1 - 1.5 us, when the chip is not in System ON idle mode. In System ON, the delay increase to ~15 us. This is tested without any other peripherals or softdevice running, but it should be a lot faster than the GPIOTE driver.

    Code:

    #include <stdbool.h>
    #include <stdint.h>
    #include "nrf.h"
    #include "nrf_gpio.h"
    #include "boards.h"
    
    #include "app_error.h"
    
    
    void GPIOTE_IRQHandler(void)
    {
        if(NRF_GPIOTE->EVENTS_IN[0] == 1)
        {
            nrf_gpio_pin_toggle(LED_1);
            NRF_GPIOTE->EVENTS_IN[0] = 0;
        }
        
        if(NRF_GPIOTE->EVENTS_PORT == 1)
        {
            nrf_gpio_pin_toggle(LED_2);
            NRF_GPIOTE->EVENTS_PORT = 0;
        }
    }
    
    /**
     * @brief Function for application main entry.
     */
    int main(void)
    {
        // Configre LED pins as outputs and turn off
        nrf_gpio_cfg_output(LED_1);
        nrf_gpio_cfg_output(LED_2);
        nrf_gpio_pin_set(LED_1);
        nrf_gpio_pin_set(LED_2);
    
        // Configure BUTTON_1 as input
        nrf_gpio_cfg_input(BUTTON_1, NRF_GPIO_PIN_PULLUP);
    
        // Configure BUTTON_2 as input with LOW sense enabled - will trigger PORT event
        nrf_gpio_cfg_sense_input(BUTTON_2, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
      
        // Configure interrupt on GPIOTE->EVENTS_IN[0] when BUTTON_1 toggles
        NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos) |
                                (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) |
                                (BUTTON_1                      << GPIOTE_CONFIG_PSEL_Pos);
    
        NRF_GPIOTE->INTENSET = (GPIOTE_INTENSET_IN0_Enabled    << GPIOTE_INTENSET_IN0_Pos) |
                               (GPIOTE_INTENSET_PORT_Enabled   << GPIOTE_INTENSET_PORT_Pos);
    
        NVIC_EnableIRQ(GPIOTE_IRQn);
        NVIC_SetPriority(GPIOTE_IRQn, 3); // Highest allowed in application when SD is used
    
    
        while (true)
        {
            //__WFE();
            // Do Nothing - GPIO can be toggled without software intervention.
        }
    }

    Timing (IN-EVENT):

    Timing (PORT-EVENT):

    I would recommend you to try out this approach first. If this does not reduce the interrupt delay, I will check with our softdevice team regarding your initial approach.

    Best regards.
    Jørgen

Reply
  • If the main problem is that the interrupt handler in the GPIOTE driver uses a long time before it calls your event handler, I think it would be better to implement your own simplified driver and remove the the nrfx_gpiote implementation from your project (unless you need this for something else in the application).

    I'm not sure about your configuration of GPIOTE, but you may also speedup the event handler if you use IN-EVENTS (high_accuacy = true), and not PORT-EVENTS. IN events are mapped to a specific pin, while multiple GPIOs can trigger a PORT event. For PORT event, the handler needs to read all enabled PINs in order to detect which pin triggered the interrupt. The GPIOTE driver also have some advanced features, like swapping the polarity to detect toggling of PORT evets, etc., which may slow down the execution event more.

    I did a test with a bare-metal GPIOTE code, and measure interrupt latency between 1.1 - 1.5 us, when the chip is not in System ON idle mode. In System ON, the delay increase to ~15 us. This is tested without any other peripherals or softdevice running, but it should be a lot faster than the GPIOTE driver.

    Code:

    #include <stdbool.h>
    #include <stdint.h>
    #include "nrf.h"
    #include "nrf_gpio.h"
    #include "boards.h"
    
    #include "app_error.h"
    
    
    void GPIOTE_IRQHandler(void)
    {
        if(NRF_GPIOTE->EVENTS_IN[0] == 1)
        {
            nrf_gpio_pin_toggle(LED_1);
            NRF_GPIOTE->EVENTS_IN[0] = 0;
        }
        
        if(NRF_GPIOTE->EVENTS_PORT == 1)
        {
            nrf_gpio_pin_toggle(LED_2);
            NRF_GPIOTE->EVENTS_PORT = 0;
        }
    }
    
    /**
     * @brief Function for application main entry.
     */
    int main(void)
    {
        // Configre LED pins as outputs and turn off
        nrf_gpio_cfg_output(LED_1);
        nrf_gpio_cfg_output(LED_2);
        nrf_gpio_pin_set(LED_1);
        nrf_gpio_pin_set(LED_2);
    
        // Configure BUTTON_1 as input
        nrf_gpio_cfg_input(BUTTON_1, NRF_GPIO_PIN_PULLUP);
    
        // Configure BUTTON_2 as input with LOW sense enabled - will trigger PORT event
        nrf_gpio_cfg_sense_input(BUTTON_2, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
      
        // Configure interrupt on GPIOTE->EVENTS_IN[0] when BUTTON_1 toggles
        NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos) |
                                (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) |
                                (BUTTON_1                      << GPIOTE_CONFIG_PSEL_Pos);
    
        NRF_GPIOTE->INTENSET = (GPIOTE_INTENSET_IN0_Enabled    << GPIOTE_INTENSET_IN0_Pos) |
                               (GPIOTE_INTENSET_PORT_Enabled   << GPIOTE_INTENSET_PORT_Pos);
    
        NVIC_EnableIRQ(GPIOTE_IRQn);
        NVIC_SetPriority(GPIOTE_IRQn, 3); // Highest allowed in application when SD is used
    
    
        while (true)
        {
            //__WFE();
            // Do Nothing - GPIO can be toggled without software intervention.
        }
    }

    Timing (IN-EVENT):

    Timing (PORT-EVENT):

    I would recommend you to try out this approach first. If this does not reduce the interrupt delay, I will check with our softdevice team regarding your initial approach.

    Best regards.
    Jørgen

Children
No Data
Related