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

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

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

  • 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

Related