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

GPIOTE/PPI/TIMER errors

Hello,

nRF52832 BLE project using SDK 15.3.0 and SoftDevice 112 v6.1.1. Circuit has the output of a comparator connected to two GPIO pins (COMPARATOR_PIN1, COMPARATOR_PIN2). From time to time, a sensor will cause the comparator output to go LoToHi and a short time later (typically 100-300 milliseconds) HiToLo. I need to accurately measure the duration the pin is hi in milliseconds.

I use two timers along with GPIOTE and PPI. Timer2 is in timer mode with prescaler 9 (NRF_TIMER_FREQ_31250Hz). Timer1 is in counter mode and counts the number of times Timer2 overflows (compare value 0). Code to initialize timers:

void timers_init() {

	// Configure TIMER2 as timer
	nrfx_timer_config_t timer2_cfg = NRFX_TIMER_DEFAULT_CONFIG;
	timer2_cfg.mode = NRF_TIMER_MODE_TIMER;
	timer2_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16;
	timer2_cfg.frequency = NRF_TIMER_FREQ_31250Hz;
	nrfx_timer_init(&m_timer2, &timer2_cfg, timer_event_handler);
	nrfx_timer_enable(&m_timer2);
	nrfx_timer_pause(&m_timer2);
	nrfx_timer_compare(&m_timer2,NRF_TIMER_CC_CHANNEL0,0,false);
	nrfx_timer_clear(&m_timer2);


	// Configure TIMER1 as counter
	nrfx_timer_config_t timer1_cfg = NRFX_TIMER_DEFAULT_CONFIG;
	timer1_cfg.mode = NRF_TIMER_MODE_COUNTER;
	timer1_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16;
	nrfx_timer_init(&m_timer1, &timer1_cfg, timer_event_handler);
	nrfx_timer_clear(&m_timer1);
	nrfx_timer_enable(&m_timer1);

}

GPIOTE and PPI are used to:

- start Timer2 when GPIO pin COMPARATOR_PIN1 goes LoToHi
- increment Timer1 when Timer2 overflows (compare value 0)
- capture Timer2 value when GPIO pin COMPARATOR_PIN2 goes HiToLo
- capture Timer1 value when GPIO pin COMPARATOR_PIN2 goes HiToLo

GPIOTE interrupt is enabled only for the HiToLo transition. Here's the code to set up GPIOTE and PPI:

void gpiote_ppi_init(void) {

	nrfx_err_t err_code = NRF_SUCCESS;

	// Configure GPIOTE channel 0 as event that occurs when pin COMPARATOR_PIN1 changes from digital low to hi.
	NRF_GPIOTE->CONFIG[0] =  (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos)
		| (COMPARATOR_PIN1 << GPIOTE_CONFIG_PSEL_Pos) // using GPIO photoTransistorDigitalPin as input
		| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);

	// Configure GPIOTE channel 1 as event that occurs when pin COMPARATOR_PIN2 changes from digital hi to low.
	NRF_GPIOTE->CONFIG[1] =  (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)
		| (COMPARATOR_PIN2 << GPIOTE_CONFIG_PSEL_Pos) // using GPIO photoTransistorDigitalPin as input
		| (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
  
	// Interrupt only on HiToLo transition.
	NRF_GPIOTE->INTENCLR = GPIOTE_INTENCLR_IN0_Msk | GPIOTE_INTENCLR_IN2_Msk | GPIOTE_INTENCLR_IN3_Msk;
	NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk;

	// Clear all events.
	NRF_GPIOTE->EVENTS_IN[0] = 0;
	NRF_GPIOTE->EVENTS_IN[1] = 0;
	NRF_GPIOTE->EVENTS_IN[2] = 0;
	NRF_GPIOTE->EVENTS_IN[3] = 0;

	// PPI channels
	uint8_t chan_0 = 255;
	uint8_t chan_1 = 255;
	uint8_t chan_2 = 255;
	uint8_t chan_3 = 255;

	err_code = nrfx_ppi_channel_alloc(&chan_0);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_alloc(&chan_1);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_alloc(&chan_2);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_alloc(&chan_3);
	APP_ERROR_CHECK(err_code);

	// Configure PPI channel 0 to increment TIMER1 when TIMER2 overflows.
	err_code = nrfx_ppi_channel_assign(chan_0, nrfx_timer_event_address_get(&m_timer2,NRF_TIMER_EVENT_COMPARE0), nrfx_timer_task_address_get(&m_timer1,NRF_TIMER_TASK_COUNT));
	APP_ERROR_CHECK(err_code);

	// Configure PPI channel 1 to start TIMER2 when GPIOTE channel 0 event occurs (pin goes LoToHi).
	err_code = nrfx_ppi_channel_assign(chan_1, (uint32_t)&NRF_GPIOTE->EVENTS_IN[0],nrfx_timer_task_address_get(&m_timer2,NRF_TIMER_TASK_START));
	APP_ERROR_CHECK(err_code);

	// Configure PPI channel 2 to capture TIMER2 when GPIOTE channel 1 event occurs (pin goes HiToLo).
	err_code = nrfx_ppi_channel_assign(chan_2, (uint32_t)&NRF_GPIOTE->EVENTS_IN[1],nrfx_timer_task_address_get(&m_timer2,NRF_TIMER_TASK_CAPTURE0));
	APP_ERROR_CHECK(err_code);

	// Configure PPI channel 3 to capture TIMER1 when GPIOTE channel 1 event occurs (pin goes HiToLo).
	err_code = nrfx_ppi_channel_assign(chan_3, (uint32_t)&NRF_GPIOTE->EVENTS_IN[1],nrfx_timer_task_address_get(&m_timer1,NRF_TIMER_TASK_CAPTURE0));
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_enable(chan_0);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_enable(chan_1);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_enable(chan_2);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_ppi_channel_enable(chan_3);
	APP_ERROR_CHECK(err_code);
	
	// enable GPIOTE interrupts
    sd_nvic_SetPriority(GPIOTE_IRQn,APP_IRQ_PRIORITY_HIGH);
    sd_nvic_ClearPendingIRQ(GPIOTE_IRQn);
	sd_nvic_EnableIRQ(GPIOTE_IRQn);

}

Finally, when the GPIOTE interrupt handler is called, it calculates the duration the comparator output was hi, then resets for next event.

void GPIOTE_IRQHandler(void) {

	// clear events
	NRF_GPIOTE->EVENTS_IN[0] = 0;
	NRF_GPIOTE->EVENTS_IN[1] = 0;

	// disable GPIOTE interrupts until finished processing event
	sd_nvic_DisableIRQ(GPIOTE_IRQn);

	// stop timer2
	nrfx_timer_pause(&m_timer2);

	// timer1 and timer2 values have been captured into capture registers 0
	uint32_t numberOfOverflows = nrfx_timer_capture_get(&m_timer1,0);
	uint32_t timerValue = nrfx_timer_capture_get(&m_timer2,0);

	// factor in number of times TIMER2 overflowed.
	uint32_t durationInTicks = timerValue + (numberOfOverflows * 65535);

	// calculate duration in milliseconds * 100 to preserve accuracy
	double durationMillisecondsTimes100Double = durationInTicks * 3.2; // based on prescaler of 9

	// calculate milliseconds
	double durationMillisecondsDouble = durationMillisecondsTimes100Double / 100;

	// round to milliseconds and store in global that will be sent to central via BLE
	m_durationInMilliseconds = durationMillisecondsDouble; // round to milliseconds

	nrfx_timer_clear(&m_timer1);
	nrfx_timer_clear(&m_timer2);

	// enable GPIOTE interrupts
	sd_nvic_EnableIRQ(GPIOTE_IRQn);

}

I use a function generator to simulate the comparator output. It pulls the GPIO pins hi for a specific duration and repeats. About once every 100 samples, the calculated duration is out significantly. Expect 250 ms and see 1559, for example.

Any errors in my code?

Should I be using sd_ppi_channel_alloc() instead of nrfx_ppi_channel_alloc()?

This all happens with a BLE link to a central (iOS device).

Many thanks,

Tim

Parents Reply Children
  • Hi Dmitry,

    Thanks for replying. Good point. I didn't realize all timers on nRF52832 can be used in 32-bit mode. I will need to back port this to work on nRF51822 and the two available timers (TIMER1, TIMER2) are limited to 16 bit. (TIMER0, which supports 32-bit mode, is reserved for SoftDevice.) At maximum prescaler (9), timer will overflow in 2.097152 seconds which, while unlikely, is within my use case.

    Would you suggest other ways to simplify? Can you see any issues with code as it is? Anything that might explain the very occasional errors?

    Do you know when to use nrfx_ppi_channel_alloc() and when to use sd_ppi_channel_alloc()?

    Thanks, appreciate it.

    Tim

  • It's hard to debug a floating problem, perhaps there's a race condition somewhere.

    With a single timer, assing one PPI channel to start timer on rising edge, and a second one to stop and capture timer value on falling edge - that's all you need.

    Do you know when to use nrfx_ppi_channel_alloc() and when to use sd_ppi_channel_alloc()?

    sd_ppi_... functions are softdevice API calls, they should be used when softdevice is enabled, as some resources are blocked by softdevice from direct access.

  • Thanks again Dmitry. I've simplified code as you suggest and am running tests. Still finding errors. Will try to offer more information that might be helpful investigating.

    Again, thanks.

    Tim

  • Hello again,

    I may have discovered the source of intermittent errors. The code is designed to measure a pulse (LoToHi followed by HiToLo) of a signal. Our circuit connects the signal to two GPIO pins so I don't break the one-pin-one-GPIOTE event rule.

    After detecting and measuring a pulse, other random pulses are expected for a couple seconds following the initial pulse. Only the first pulse is of interest, so in the GPIOTE interrupt routine (which is called on HiToLo marking the end of the pulse) I disable GPIOTE interrupts with

    sd_nvic_DisableIRQ(GPIOTE_IRQn);
    

    I set up a timer to fire three seconds later and execute:

    sd_nvic_SetPriority(GPIOTE_IRQn,APP_IRQ_PRIORITY_HIGH);
    sd_nvic_ClearPendingIRQ(GPIOTE_IRQn);
    sd_nvic_EnableIRQ(GPIOTE_IRQn);
    

    Is it possible that this can result in an erroneous GPIOTE event?

    I changed the GPIOTE code to use nrfx_gpiote instead of manipulating GPIOTE registers directly.

    nrfx_err_t err_code = NRF_SUCCESS;
    
    nrfx_gpiote_in_config_t configLoToHi = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
    configLoToHi.pull = NRF_GPIO_PIN_PULLUP;
    
    // do not interrupt on LoToHi
    err_code = nrfx_gpiote_in_init(COMPARATOR_PIN1,&configLoToHi,NULL);
    APP_ERROR_CHECK(err_code);
    
    nrfx_gpiote_in_config_t configHiToLo = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
    configLoToHi.pull = NRF_GPIO_PIN_PULLUP;
    
    // interrupt on HiToLo
    err_code = nrfx_gpiote_in_init(COMPARATOR_PIN2,&configHiToLo,gpiote_event_handler);
    APP_ERROR_CHECK(err_code);
    
    nrfx_gpiote_in_event_enable(COMPARATOR_PIN1,false);
    nrfx_gpiote_in_event_enable(COMPARATOR_PIN2,true);

    Then to ignore events following initial pulse, I use:

    nrfx_gpiote_in_event_disable(COMPARATOR_PIN1);
    nrfx_gpiote_in_event_disable(COMPARATOR_PIN2);
    

    And to enable 3 seconds later:

    nrfx_gpiote_in_event_enable(COMPARATOR_PIN1,false);
    nrfx_gpiote_in_event_enable(COMPARATOR_PIN2,true);
    

    I imagine that the nrfx calls to enable/disable an event are manipulating the NRF_GPIOTE->INTENSET and NRF_GPIOTE->INTENCLR registers and not enabling/disabling GPIOTE interrupts.

    I've been running tests and finding zero errors. I would be glad to hear from Nordic or others confirmation that disabling/enabling GPIOTE interrupts (sd_nvic_EnableIRQ(GPIOTE_IRQn)/sd_nvic_DisableIRQ(GPIOTE_IRQn)) could have been a source of previous intermittent errors. Then I would be confident that current code is stable and reliable.

    Many thanks,

    Tim

  • Hi,

    I think the problem is that GPIOTE event is not cleared when you enable an interrupt (sd_nvic_ClearPendingIRQ is not enough), and right after enabling interrupts you're receiving an event from some random pulse.

Related