Is there a simple example of how to enable two GPIOTE interrupts for fast timing?

I would like to monitor the states of switches sw0 and sw1 on my nrf7002dk board using GPIOTE interrupts.

Initially this is just for example purposes.

I eventually need to assign two other GPIO pins where I need to have the ability to time ~30 microsecond accuracy and I am finding that the standard Zephyr PORT interrupts not fast enough for my timing purposes.

The nrfx example that I looked at assigns just one GPIOTE pin and I am having difficulty assigning another pin. Specifically when it comes time to IRQ_CONNECT and nrfx_gpiote_in_init the second GPIO pin.

Thank you.

Parents
  • Hello,

    I've created an example that uses GPIOTE with 2 input pins. Please see attached. I may be able to expand this sample if you could describe what you are trying to monitor. Is it a signal input you want to measure on each pin as discussed in this thread:  GPIOTE + PPI SDK v2.5.2 issue + zephyr ? If so, you may need to use TIMER+PPI to accurately time signal periods.

    The Zephyr GPIO API also supports use IN events instead of PORT events if you change the triggering flag from *_LEVEL_* to _EDGE_ triggering. 

    Test sample

    hello_world_gpiote.zip

    Best regards,

    Vidar

  • Thank you Vidar.

    That example worked perfectly!

    To expand on this, I did try to use Zephyr GPIO _EDGE_ triggering in combination with the Zephyr timing functions in my pin event handlers (ie. timing_init, timing_start, timing_counter_get, timing_cycles_get), but found the values I was getting sometimes inconsistent as compared to the edge to edge times on my oscilloscope.

    Is this the wrong approach?

    Ultimately I would like to monitor the pulse widths on 4 GPIO pins where the widths can be as lows as 30 microseconds.

    Perhaps I need to move to timing with PPI. Is there an example of using PPI to get accurate GPIO edge to edge timing?

    Thanks again,

    Adam

  • Hi Adam,

    I'm glad to hear that it worked, and thanks for the additional information. With that many inputs, which I assume you will want to sample simultaneously, it might be better to continuously poll the input pins if the inputs are only going to be sampled for a limited amount of time. The appcore has three TIMER instances, so you cannot allocate one TIMER for each pin, which would be necessary if you were going to use PPI for triggering the TIMER start and stop tasks. Maybe you could use pin interrupts to start the polling? The pin states can be read with nrf_gpio_pin_read() from <hal/nrf_gpio.h>. With polling, you will not experience the interrupt latencies.

  • Vidar,

    Actually you raise a good point.

    To further detail... two of the pins (relatively wide pulse widths in the millisecond range) are used to start precision timing of the other two pins (tens of microseconds range), so I can probably get away with just two TIMERs.

    Is there is an example of how to use PPI triggering to use a TIMER for the fast pins?

    Basically I want to be able to accurately capture the low to high and high to low edge to edge time in microseconds. Can this be done using PPI triggering of the TIMER without software having to directly read the TIMER counter value (risk of interrupt latency) and do the math manually?

    Best regards,

    Adam

  • Adam,

    My coworker Torbjørn has created an example of pulse counting with GPIOTE and a TIMER which seems relevant for this: https://github.com/too1/ncs-nrfx-pulse-count-example.

    Adam K. said:
    Basically I want to be able to accurately capture the low to high and high to low edge to edge time in microseconds. Can this be done using PPI triggering of the TIMER without software having to directly read the TIMER counter value (risk of interrupt latency) and do the math manually?

    You can let the timer run freely and trigger a capture task when the pulse begins and another when it ends. However, each PPI endpoint must be connected to a specific capture task, so the CPU must be able to read the timer capture values before the next capture is triggered through PPI.

    Best regards,

    Vidar

  • Vidar,

    I am still having trouble finding a simple timer example that uses TIMER mode rather than COUNTER mode.

    I need to capture the time difference between edges with microsecond accuracy.

    I understand how to detect GPIO edge interrupts and how to start the timer (in this case set to a frequency of 1MHz). Is there a way that I can read the timer value from the interrupt handler every time that I get a GPIO edge?

    I do not think I need the added complexity of using PPI and would like to keep it very simple for now. I am using GPIO rather than GPIOTE to keep things simple.

    Thanks.

    Adam

  • Hi Adam,

    Yes, you can use the nrfx_timer_capture() to read the current counter value for your timer.

    Best regards,

    Vidar

Reply Children
  • Thanks Vidar,

    I keep getting unexpected values when I use nrfx_timer_capture().

    I am not certain that I am setting things up correctly. I would just like to know how to run a 1MHz timer and then be able to read the number of ticks when I get an edge interrupt.

    Without an example, I don't seem to be able to accomplish this.

    Adam K.

  • Hi Adam,

    Here is an updated version of the sample I first uploaded, which enables a timer. It doesn't attempt to capture the signal periods, but it shows how you can capture the counter value from the ISR.

    main.c

    /*
     * Copyright (c) 2012-2014 Wind River Systems, Inc.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <stdio.h>
    #include <zephyr/kernel.h>
    
    #include <zephyr/drivers/clock_control.h>
    #include <zephyr/drivers/clock_control/nrf_clock_control.h>
    
    #include <nrfx_gpiote.h>
    #include <nrfx_timer.h>
    #include <hal/nrf_gpio.h>
    #include <helpers/nrfx_gppi.h>
    #if defined(DPPI_PRESENT)
    #include <nrfx_dppi.h>
    #else
    #include <nrfx_ppi.h>
    #endif
    
    #include <zephyr/logging/log.h>
    #include <zephyr/irq.h>
    #include <zephyr/drivers/gpio.h>
    LOG_MODULE_REGISTER(nrfx_sample, LOG_LEVEL_INF);
    
    #define GPIOTE_INST	NRF_DT_GPIOTE_INST(DT_NODELABEL(signal_input_pins), gpios)
    
    #define INPUT_SIG_1 NRF_DT_GPIOS_TO_PSEL_BY_IDX(DT_NODELABEL(signal_input_pins), gpios, 0)
    
    const nrfx_gpiote_t gpiote = NRFX_GPIOTE_INSTANCE(GPIOTE_INST);
    
    nrfx_timer_t timer_inst = NRFX_TIMER_INSTANCE(0);
    
    static void pin_evt_handler(nrfx_gpiote_pin_t pin,
    			   nrfx_gpiote_trigger_t trigger,
    			   void *context)
    {
    	uint32_t ticks_now;
    
    	ticks_now = nrfx_timer_capture(&timer_inst, NRF_TIMER_CC_CHANNEL0);
    
    	LOG_INF("GPIO input event callback for P%d.%d. Timestamp: %d", 
    		NRF_PIN_NUMBER_TO_PORT(pin), NRF_PIN_NUMBER_TO_PIN(pin), ticks_now);
    	
    }
    
    /* Request HF crystal oscillator for better timer accuracy */
    static void clock_init(void)
    {
    	int err;
    	int res;
    	struct onoff_manager *clk_mgr;
    	struct onoff_client clk_cli;
    
    	clk_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
    	if (!clk_mgr) {
    		LOG_WRN("Unable to get the Clock manager\n");
    		return;
    	}
    
    	sys_notify_init_spinwait(&clk_cli.notify);
    
    	err = onoff_request(clk_mgr, &clk_cli);
    	if (err < 0) {
    		LOG_WRN("Clock request failed: %d\n", err);
    		return;
    	}
    
    	do {
    		err = sys_notify_fetch_result(&clk_cli.notify, &res);
    		if (!err && res) {
    			LOG_WRN("Clock could not be started: %d\n", res);
    			return;
    		}
    	} while (err);
    
    	LOG_INF("Clock has started\n");
    }
    
    static void timer_init(void)
    {
    	nrfx_err_t err;
    	nrfx_timer_config_t config = NRFX_TIMER_DEFAULT_CONFIG(NRFX_MHZ_TO_HZ(1));
    	
    	config.bit_width = NRF_TIMER_BIT_WIDTH_32;
    
    	err = nrfx_timer_init(&timer_inst, &config, NULL);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("nrfx_timer_init error: 0x%08X", err);
    	}
    
    	nrfx_timer_clear(&timer_inst);
    
    	nrfx_timer_enable(&timer_inst);
    }
    
    static void gpiote_init(void) 
    {
    	nrfx_err_t err; 
    	static const nrf_gpio_pin_pull_t pull_config = NRF_GPIO_PIN_PULLUP;
    	uint8_t in_sig1_channel;
    
    	static const nrfx_gpiote_handler_config_t handler_config = {
        	.handler = pin_evt_handler,
    	};
    
    	err = nrfx_gpiote_init(&gpiote, 0);
    	if (err != NRFX_SUCCESS && err != NRFX_ERROR_ALREADY) {
    		LOG_ERR("nrfx_gpiote_init error: 0x%08X", err);
    		return;
    	}
    
    	// Configure input for SIGNAL 1
    	nrfx_gpiote_trigger_config_t trigger_config_sig1 = {
    	    .trigger = NRFX_GPIOTE_TRIGGER_HITOLO,
    	    .p_in_channel = &in_sig1_channel,
    	};
    	nrfx_gpiote_input_pin_config_t input_config = {
    	    .p_pull_config = &pull_config,
    	    .p_trigger_config = &trigger_config_sig1,
    	    .p_handler_config = &handler_config
    	};	
    
    	err = nrfx_gpiote_channel_alloc(&gpiote, &in_sig1_channel);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("Failed to allocate in_channel, error: 0x%08X", err);
    		return;
    	}
    
    	err = nrfx_gpiote_input_configure(&gpiote, INPUT_SIG_1, &input_config);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("nrfx_gpiote_input_configure error: 0x%08X", err);
    		return;
    	}
    	
    	nrfx_gpiote_trigger_enable(&gpiote, INPUT_SIG_1, true);
    }
    
    
    int main(void)
    {
    	LOG_INF("Hello World! %s\n", CONFIG_BOARD);
    	
    	clock_init();
    	timer_init();
    	gpiote_init();
    
    	return 0;
    }
    

    Symbol to be added to prj.conf

    CONFIG_NRFX_TIMER0=y

    Best regards,

    Vidar

  • Thanks Vidar,

    The clock_init function was missing from my code. I was not aware that had to be done. Thank you.

    I am able to produce a 135KHz reference frequency from the device that I am trying to monitor with my particular GPIO pin. That should give me a consistent high edge to low edge time of about 3.7 microseconds. I am capturing the first 100 cycles and printing them out in microseconds when my capturing has stopped.

    017 021 020 016 016 016 016 020 020 016
    016 020 020 016 016 016 021 020 016 016
    016 021 020 016 016 016 021 020 016 016
    016 021 020 016 016 016 021 020 016 016
    016 021 016 016 016 021 020 016 016 016
    021 020 016 016 016 021 020 016 016 016
    021 020 016 016 016 021 020 016 016 016
    021 020 016 016 016 021 020 016 016 016
    021 020 016 016 016 021 020 016 016 016
    021 020 016 016 016 021 020 016 016 016

    With the clock/timer example code that you gave me I am measuring much longer times and they are inconsistent. They are 4 to 5 times longer than I expected.

    I can change the timer config frequency and see the values increase by 2, 4, 8, etc. so I know the timer is working fine.

    For reference, I am NOT using the GPIOTE functions but rather the simpler GPIO setup functions (using GPIO_INT_EDGE_TO_ACTIVE and GPIO_INT_EDGE_TO_INACTIVE) and then capturing the high/low edge values of NRF_TIMER_CC_CHANNEL0 in my callback function and then storing the difference between the low/high edge times (the values I am printing out).

    Is using GPIO setup with callback not fast enough for this application? Is it possible that I am missing edges with this approach and the values I am measuring are actually 4 or 5 edges apart rather than every edge? Is there a way to prioritize the GPIO callback interrupt to get better performance?

    Another thing that makes me think I am missing edges is the number of edges that I am counting when I start and stop the measuring.

    uart:~$ start
    started: 1970/01/01 00:25:22 UTC
    uart:~$ stop
    stopped: 1970/01/01 00:25:24 UTC
    rising_edges: 57884
    falling_edges: 57883

    For a 135KHz signal I would have expected more like 270000 rising and falling edges in 2 seconds. The values are 4 to 5 times less than I would have expected.

    Thanks again.

    Adam

  • Hi Adam,

    The clock_init() function is not required, but it will make the TIMER run off the more accurate HFXO clock source instead of HFINT, which has a worst-case tolerance of +-8%.

    Interrupt latency is likely a factor for the poor measurements results. I recommend connecting the GPIOTE IRQ as a zero latency interrupt to see if that improves the timing:

    https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/kernel/services/interrupts.html#zero-latency-interrupts 

    The other alternative is to disable the GPIOTE interrupt and instead poll the inputs using the gpio HAL (<hal/nrf_gpio.h>)

    Best regards,

    Vidar

  • Thank you Vidar,

    I switched to using GPIOTE and NRFX_GPIOTE_TRIGGER_LOTOHI for that pin and now I am seeing the correct number of low to high edges for 135KHz for roughly 2 seconds of counting.

    uart:~$ start
    started: 1970/01/01 00:00:09 UTC
    uart:~$ stop
    stopped: 1970/01/01 00:00:11 UTC
    rising_edges: 288872
    falling_edges: 0

    Clearly GPIO interrupts have too much latency.

    However, I need to capture LOTOHI and HITOLO edges and then read the timer on every interrupt. Is this also possible?

    If I change the trigger to NRFX_GPIOTE_TRIGGER_TOGGLE how will I know which edge is causing the interrupt? I suspect that calling nrfx_gpiote_in_is_set( pin ) in the pin event handler will not work.

    Best regards,

    Adam

Related