From baf1f8d21985ec074ef642648624dca4716d95a8 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Wed, 6 May 2020 12:59:39 +0200 Subject: [PATCH] pulseIn and pulseInLong implemented using hw peripherals: NRF_TIMER2, PPI_CHANNEL and GPIOTE events --- cores/arduino/wiring_pulse.cpp | 203 +++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 cores/arduino/wiring_pulse.cpp diff --git a/cores/arduino/wiring_pulse.cpp b/cores/arduino/wiring_pulse.cpp new file mode 100644 index 00000000..b8001f32 --- /dev/null +++ b/cores/arduino/wiring_pulse.cpp @@ -0,0 +1,203 @@ +#include "Arduino.h" + +#include +#include +#include +#include +#include "nrfx_gpiote.h" +#include "nrfx_ppi.h" + +/* Hot encoded peripherals. Could be chosen with a more clever strategy */ +#define PULSE_TIMER (NRF_TIMER2) +#define TIMER_FIRST_CHANNEL (NRF_TIMER_CC_CHANNEL1) +#define TIMER_SECOND_CHANNEL (NRF_TIMER_CC_CHANNEL2) +#define TIMER_FIRST_CAPTURE (NRF_TIMER_TASK_CAPTURE1) +#define TIMER_SECOND_CAPTURE (NRF_TIMER_TASK_CAPTURE2) + +#define TIMEOUT_US (0) + +/* GPIOTE configuration for pin PPI event */ +static nrfx_gpiote_in_config_t cfg = + { + .sense = NRF_GPIOTE_POLARITY_TOGGLE, + .pull = NRF_GPIO_PIN_NOPULL, + .is_watcher = false, + .hi_accuracy = true, + .skip_gpio_setup = true // skip pin setup, the pin is assumed to be already configured + }; + +/* + * This function enables the pin edge detection hardware and tries to understand the state of the pin at the time of such activation + * If the hardware detection event is enabled on an edge of the pin, it's not possible to understand if that edge would be detected or not + * In such case, TIMEOUT_US constant is returned to indicate this extreme condition + * Else, if the function is able to understand the state of the pin at the time of activation, it returns the index of the desired pulse + */ +static uint8_t measurePulse(PinName pin, PinStatus state, nrf_ppi_channel_group_t firstGroup) +{ + /* three different reads are needed, because it's not easy to synchronize hardware and software */ + uint32_t firstState, secondState, thirdState; + core_util_critical_section_enter(); + firstState = nrf_gpio_pin_read(pin); + /* Enable the hardware detection of the pin edge */ + nrf_ppi_group_enable(firstGroup); + secondState = nrf_gpio_pin_read(pin); + __NOP(); + thirdState = nrf_gpio_pin_read(pin); + core_util_critical_section_exit(); + uint8_t pulseToTake = 0; + /* If no changes on the pin were detected, there are no doubts */ + if (firstState == secondState && firstState == thirdState) { + if (firstState != state) { + pulseToTake = 1; + } else { + pulseToTake = 2; + } + } else { + pulseToTake = TIMEOUT_US; + } + return pulseToTake; +} + +/* + * The pulse pin is assumed to be already configured as an input pin and NOT as an interrupt pin + * Also the serial_api.c from NRF sdk uses ppi channels, but there shouldn't be any issue + * The disadvantage of this approach is that some pulses could be missed, and more time could be necessary for retrying the measure. + * Using two different events for rising and falling edge would be way better + */ +unsigned long pulseIn(PinName pin, PinStatus state, unsigned long timeout) +{ + /* Configure timer */ + nrf_timer_mode_set(PULSE_TIMER, NRF_TIMER_MODE_TIMER); + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_STOP); + nrf_timer_frequency_set(PULSE_TIMER, NRF_TIMER_FREQ_1MHz); + nrf_timer_bit_width_set(PULSE_TIMER, NRF_TIMER_BIT_WIDTH_32); + nrf_timer_cc_write(PULSE_TIMER, TIMER_FIRST_CHANNEL, 0); + nrf_timer_cc_write(PULSE_TIMER, TIMER_SECOND_CHANNEL, 0); + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_CLEAR); + /* Configure pin Toggle Event */ + nrfx_gpiote_in_init(pin, &cfg, NULL); + nrfx_gpiote_in_event_enable(pin, true); + + /* Allocate PPI channels for starting and stopping the timer */ + nrf_ppi_channel_t firstPPIchannel, firstPPIchannelControl; + nrf_ppi_channel_t secondPPIchannel, secondPPIchannelControl; + nrf_ppi_channel_t thirdPPIchannel, thirdPPIchannelControl; + nrfx_ppi_channel_alloc(&firstPPIchannel); + nrfx_ppi_channel_alloc(&firstPPIchannelControl); + nrfx_ppi_channel_alloc(&secondPPIchannel); + nrfx_ppi_channel_alloc(&secondPPIchannelControl); + nrfx_ppi_channel_alloc(&thirdPPIchannel); + nrfx_ppi_channel_alloc(&thirdPPIchannelControl); + /* Allocate PPI Group channels to allow activation and deactivation of channels as PPI tasks */ + nrf_ppi_channel_group_t firstGroup, secondGroup, thirdGroup; + nrfx_ppi_group_alloc(&firstGroup); + nrfx_ppi_group_alloc(&secondGroup); + nrfx_ppi_group_alloc(&thirdGroup); + + /* Insert channels in corresponding group */ + nrfx_ppi_channel_include_in_group(firstPPIchannel, firstGroup); + nrfx_ppi_channel_include_in_group(firstPPIchannelControl, firstGroup); + nrfx_ppi_channel_include_in_group(secondPPIchannel, secondGroup); + nrfx_ppi_channel_include_in_group(secondPPIchannelControl, secondGroup); + nrfx_ppi_channel_include_in_group(thirdPPIchannel, thirdGroup); + nrfx_ppi_channel_include_in_group(thirdPPIchannelControl, thirdGroup); + + /* Configure PPI channels for Start and Stop events */ + /* The first edge on the pin will trigger the timer START task */ + nrf_ppi_channel_endpoint_setup(firstPPIchannel, + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, NRF_TIMER_TASK_START)); + nrf_ppi_channel_and_fork_endpoint_setup(firstPPIchannelControl, + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), + (uint32_t) nrfx_ppi_task_addr_group_enable_get(secondGroup), + (uint32_t) nrfx_ppi_task_addr_group_disable_get(firstGroup)); + /* The second edge will result in a capture of the timer counter into a register. In this way the first impulse is captured */ + nrf_ppi_channel_endpoint_setup(secondPPIchannel, + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, TIMER_FIRST_CAPTURE)); + nrf_ppi_channel_and_fork_endpoint_setup(secondPPIchannelControl, + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), + (uint32_t) nrfx_ppi_task_addr_group_enable_get(thirdGroup), + (uint32_t) nrfx_ppi_task_addr_group_disable_get(secondGroup)); + /* The third edge will capture the second impulse. After that, the pulse corresponding to the correct state must be returned */ + nrf_ppi_channel_and_fork_endpoint_setup(thirdPPIchannel, + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, NRF_TIMER_TASK_STOP), + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, TIMER_SECOND_CAPTURE)); + nrf_ppi_channel_endpoint_setup(thirdPPIchannelControl, + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), + (uint32_t) nrfx_ppi_task_addr_group_disable_get(thirdGroup)); + + uint8_t pulseToTake = TIMEOUT_US; + auto startMicros = micros(); + + pulseToTake = measurePulse(pin, state, firstGroup); + while (pulseToTake == TIMEOUT_US && (micros() - startMicros < timeout)) { + /* In case it wasn't possible to detect the initial state, disable the hardware detection control and retry */ + nrf_ppi_group_disable(firstGroup); + nrf_ppi_group_disable(secondGroup); + nrf_ppi_group_disable(thirdGroup); + /* Stop the timer and clear its registers */ + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_STOP); + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_CLEAR); + nrf_timer_cc_write(PULSE_TIMER, TIMER_FIRST_CHANNEL, 0); + nrf_timer_cc_write(PULSE_TIMER, TIMER_SECOND_CHANNEL, 0); + /* Retry to enable the detection hardware figuring out the starting state of the pin */ + pulseToTake = measurePulse(pin, state, firstGroup); + } + + unsigned long pulseTime = TIMEOUT_US; + unsigned long pulseFirst = TIMEOUT_US; + unsigned long pulseSecond = TIMEOUT_US; + + /* Optionally the time reference could be restarted because here the actual wait for the pulse begins */ + //startMicros = micros(); + + if (pulseToTake >= 1) { + while (!pulseFirst && (micros() - startMicros < timeout) ) { + pulseFirst = nrf_timer_cc_read(PULSE_TIMER, TIMER_FIRST_CHANNEL); + } + pulseTime = pulseFirst; + } + + if (pulseToTake == 2) { + while (!pulseSecond && (micros() - startMicros < timeout) ) { + pulseSecond = nrf_timer_cc_read(PULSE_TIMER, TIMER_SECOND_CHANNEL); + } + pulseTime = pulseSecond ? pulseSecond - pulseFirst : TIMEOUT_US; + } + + /* Deallocate all the PPI channels, events and groups */ + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_SHUTDOWN); + nrfx_gpiote_in_uninit(pin); + nrfx_ppi_group_free(firstGroup); + nrfx_ppi_group_free(secondGroup); + nrfx_ppi_group_free(thirdGroup); + nrf_ppi_channel_group_clear(firstGroup); + nrf_ppi_channel_group_clear(secondGroup); + nrf_ppi_channel_group_clear(thirdGroup); + nrfx_ppi_channel_free(firstPPIchannel); + nrfx_ppi_channel_free(firstPPIchannelControl); + nrfx_ppi_channel_free(secondPPIchannel); + nrfx_ppi_channel_free(secondPPIchannelControl); + nrfx_ppi_channel_free(thirdPPIchannel); + nrfx_ppi_channel_free(thirdPPIchannelControl); + + /* The timer has a frequency of 1 MHz, so its counting value is already in microseconds */ + return pulseTime; +} + +unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout) +{ + return pulseIn(digitalPinToPinName(pin), (PinStatus)state, timeout); +} + +unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout) +{ + return pulseIn(digitalPinToPinName(pin), (PinStatus)state, timeout); +} + +unsigned long pulseInLong(PinName pin, PinStatus state, unsigned long timeout) +{ + return pulseIn(pin, state, timeout); +}