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