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

Related