|
| 1 | +#include "Arduino.h" |
| 2 | + |
| 3 | +#include <hal/nrf_timer.h> |
| 4 | +#include <hal/nrf_gpiote.h> |
| 5 | +#include <hal/nrf_gpio.h> |
| 6 | +#include <hal/nrf_ppi.h> |
| 7 | +#include "nrfx_gpiote.h" |
| 8 | +#include "nrfx_ppi.h" |
| 9 | + |
| 10 | +/* Hot encoded peripherals. Could be chosen with a more clever strategy */ |
| 11 | +#define PULSE_TIMER (NRF_TIMER2) |
| 12 | +#define TIMER_FIRST_CHANNEL (NRF_TIMER_CC_CHANNEL1) |
| 13 | +#define TIMER_SECOND_CHANNEL (NRF_TIMER_CC_CHANNEL2) |
| 14 | +#define TIMER_FIRST_CAPTURE (NRF_TIMER_TASK_CAPTURE1) |
| 15 | +#define TIMER_SECOND_CAPTURE (NRF_TIMER_TASK_CAPTURE2) |
| 16 | + |
| 17 | +#define TIMEOUT_US (0) |
| 18 | + |
| 19 | +/* GPIOTE configuration for pin PPI event */ |
| 20 | +static nrfx_gpiote_in_config_t cfg = |
| 21 | + { |
| 22 | + .sense = NRF_GPIOTE_POLARITY_TOGGLE, |
| 23 | + .pull = NRF_GPIO_PIN_NOPULL, |
| 24 | + .is_watcher = false, |
| 25 | + .hi_accuracy = true, |
| 26 | + .skip_gpio_setup = true // skip pin setup, the pin is assumed to be already configured |
| 27 | + }; |
| 28 | + |
| 29 | +/* |
| 30 | + * This function enables the pin edge detection hardware and tries to understand the state of the pin at the time of such activation |
| 31 | + * 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 |
| 32 | + * In such case, TIMEOUT_US constant is returned to indicate this extreme condition |
| 33 | + * 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 |
| 34 | + */ |
| 35 | +static uint8_t measurePulse(PinName pin, PinStatus state, nrf_ppi_channel_group_t firstGroup) |
| 36 | +{ |
| 37 | + /* three different reads are needed, because it's not easy to synchronize hardware and software */ |
| 38 | + uint32_t firstState, secondState, thirdState; |
| 39 | + core_util_critical_section_enter(); |
| 40 | + firstState = nrf_gpio_pin_read(pin); |
| 41 | + /* Enable the hardware detection of the pin edge */ |
| 42 | + nrf_ppi_group_enable(firstGroup); |
| 43 | + secondState = nrf_gpio_pin_read(pin); |
| 44 | + __NOP(); |
| 45 | + thirdState = nrf_gpio_pin_read(pin); |
| 46 | + core_util_critical_section_exit(); |
| 47 | + uint8_t pulseToTake = 0; |
| 48 | + /* If no changes on the pin were detected, there are no doubts */ |
| 49 | + if (firstState == secondState && firstState == thirdState) { |
| 50 | + if (firstState != state) { |
| 51 | + pulseToTake = 1; |
| 52 | + } else { |
| 53 | + pulseToTake = 2; |
| 54 | + } |
| 55 | + } else { |
| 56 | + pulseToTake = TIMEOUT_US; |
| 57 | + } |
| 58 | + return pulseToTake; |
| 59 | +} |
| 60 | + |
| 61 | +/* |
| 62 | + * The pulse pin is assumed to be already configured as an input pin and NOT as an interrupt pin |
| 63 | + * Also the serial_api.c from NRF sdk uses ppi channels, but there shouldn't be any issue |
| 64 | + * The disadvantage of this approach is that some pulses could be missed, and more time could be necessary for retrying the measure. |
| 65 | + * Using two different events for rising and falling edge would be way better |
| 66 | + */ |
| 67 | +unsigned long pulseIn(PinName pin, PinStatus state, unsigned long timeout) |
| 68 | +{ |
| 69 | + /* Configure timer */ |
| 70 | + nrf_timer_mode_set(PULSE_TIMER, NRF_TIMER_MODE_TIMER); |
| 71 | + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_STOP); |
| 72 | + nrf_timer_frequency_set(PULSE_TIMER, NRF_TIMER_FREQ_1MHz); |
| 73 | + nrf_timer_bit_width_set(PULSE_TIMER, NRF_TIMER_BIT_WIDTH_32); |
| 74 | + nrf_timer_cc_write(PULSE_TIMER, TIMER_FIRST_CHANNEL, 0); |
| 75 | + nrf_timer_cc_write(PULSE_TIMER, TIMER_SECOND_CHANNEL, 0); |
| 76 | + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_CLEAR); |
| 77 | + /* Configure pin Toggle Event */ |
| 78 | + nrfx_gpiote_in_init(pin, &cfg, NULL); |
| 79 | + nrfx_gpiote_in_event_enable(pin, true); |
| 80 | + |
| 81 | + /* Allocate PPI channels for starting and stopping the timer */ |
| 82 | + nrf_ppi_channel_t firstPPIchannel, firstPPIchannelControl; |
| 83 | + nrf_ppi_channel_t secondPPIchannel, secondPPIchannelControl; |
| 84 | + nrf_ppi_channel_t thirdPPIchannel, thirdPPIchannelControl; |
| 85 | + nrfx_ppi_channel_alloc(&firstPPIchannel); |
| 86 | + nrfx_ppi_channel_alloc(&firstPPIchannelControl); |
| 87 | + nrfx_ppi_channel_alloc(&secondPPIchannel); |
| 88 | + nrfx_ppi_channel_alloc(&secondPPIchannelControl); |
| 89 | + nrfx_ppi_channel_alloc(&thirdPPIchannel); |
| 90 | + nrfx_ppi_channel_alloc(&thirdPPIchannelControl); |
| 91 | + /* Allocate PPI Group channels to allow activation and deactivation of channels as PPI tasks */ |
| 92 | + nrf_ppi_channel_group_t firstGroup, secondGroup, thirdGroup; |
| 93 | + nrfx_ppi_group_alloc(&firstGroup); |
| 94 | + nrfx_ppi_group_alloc(&secondGroup); |
| 95 | + nrfx_ppi_group_alloc(&thirdGroup); |
| 96 | + |
| 97 | + /* Insert channels in corresponding group */ |
| 98 | + nrfx_ppi_channel_include_in_group(firstPPIchannel, firstGroup); |
| 99 | + nrfx_ppi_channel_include_in_group(firstPPIchannelControl, firstGroup); |
| 100 | + nrfx_ppi_channel_include_in_group(secondPPIchannel, secondGroup); |
| 101 | + nrfx_ppi_channel_include_in_group(secondPPIchannelControl, secondGroup); |
| 102 | + nrfx_ppi_channel_include_in_group(thirdPPIchannel, thirdGroup); |
| 103 | + nrfx_ppi_channel_include_in_group(thirdPPIchannelControl, thirdGroup); |
| 104 | + |
| 105 | + /* Configure PPI channels for Start and Stop events */ |
| 106 | + /* The first edge on the pin will trigger the timer START task */ |
| 107 | + nrf_ppi_channel_endpoint_setup(firstPPIchannel, |
| 108 | + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), |
| 109 | + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, NRF_TIMER_TASK_START)); |
| 110 | + nrf_ppi_channel_and_fork_endpoint_setup(firstPPIchannelControl, |
| 111 | + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), |
| 112 | + (uint32_t) nrfx_ppi_task_addr_group_enable_get(secondGroup), |
| 113 | + (uint32_t) nrfx_ppi_task_addr_group_disable_get(firstGroup)); |
| 114 | + /* The second edge will result in a capture of the timer counter into a register. In this way the first impulse is captured */ |
| 115 | + nrf_ppi_channel_endpoint_setup(secondPPIchannel, |
| 116 | + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), |
| 117 | + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, TIMER_FIRST_CAPTURE)); |
| 118 | + nrf_ppi_channel_and_fork_endpoint_setup(secondPPIchannelControl, |
| 119 | + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), |
| 120 | + (uint32_t) nrfx_ppi_task_addr_group_enable_get(thirdGroup), |
| 121 | + (uint32_t) nrfx_ppi_task_addr_group_disable_get(secondGroup)); |
| 122 | + /* The third edge will capture the second impulse. After that, the pulse corresponding to the correct state must be returned */ |
| 123 | + nrf_ppi_channel_and_fork_endpoint_setup(thirdPPIchannel, |
| 124 | + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), |
| 125 | + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, NRF_TIMER_TASK_STOP), |
| 126 | + (uint32_t) nrf_timer_task_address_get(PULSE_TIMER, TIMER_SECOND_CAPTURE)); |
| 127 | + nrf_ppi_channel_endpoint_setup(thirdPPIchannelControl, |
| 128 | + (uint32_t) nrfx_gpiote_in_event_addr_get(pin), |
| 129 | + (uint32_t) nrfx_ppi_task_addr_group_disable_get(thirdGroup)); |
| 130 | + |
| 131 | + uint8_t pulseToTake = TIMEOUT_US; |
| 132 | + auto startMicros = micros(); |
| 133 | + |
| 134 | + pulseToTake = measurePulse(pin, state, firstGroup); |
| 135 | + while (pulseToTake == TIMEOUT_US && (micros() - startMicros < timeout)) { |
| 136 | + /* In case it wasn't possible to detect the initial state, disable the hardware detection control and retry */ |
| 137 | + nrf_ppi_group_disable(firstGroup); |
| 138 | + nrf_ppi_group_disable(secondGroup); |
| 139 | + nrf_ppi_group_disable(thirdGroup); |
| 140 | + /* Stop the timer and clear its registers */ |
| 141 | + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_STOP); |
| 142 | + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_CLEAR); |
| 143 | + nrf_timer_cc_write(PULSE_TIMER, TIMER_FIRST_CHANNEL, 0); |
| 144 | + nrf_timer_cc_write(PULSE_TIMER, TIMER_SECOND_CHANNEL, 0); |
| 145 | + /* Retry to enable the detection hardware figuring out the starting state of the pin */ |
| 146 | + pulseToTake = measurePulse(pin, state, firstGroup); |
| 147 | + } |
| 148 | + |
| 149 | + unsigned long pulseTime = TIMEOUT_US; |
| 150 | + unsigned long pulseFirst = TIMEOUT_US; |
| 151 | + unsigned long pulseSecond = TIMEOUT_US; |
| 152 | + |
| 153 | + /* Optionally the time reference could be restarted because here the actual wait for the pulse begins */ |
| 154 | + //startMicros = micros(); |
| 155 | + |
| 156 | + if (pulseToTake >= 1) { |
| 157 | + while (!pulseFirst && (micros() - startMicros < timeout) ) { |
| 158 | + pulseFirst = nrf_timer_cc_read(PULSE_TIMER, TIMER_FIRST_CHANNEL); |
| 159 | + } |
| 160 | + pulseTime = pulseFirst; |
| 161 | + } |
| 162 | + |
| 163 | + if (pulseToTake == 2) { |
| 164 | + while (!pulseSecond && (micros() - startMicros < timeout) ) { |
| 165 | + pulseSecond = nrf_timer_cc_read(PULSE_TIMER, TIMER_SECOND_CHANNEL); |
| 166 | + } |
| 167 | + pulseTime = pulseSecond ? pulseSecond - pulseFirst : TIMEOUT_US; |
| 168 | + } |
| 169 | + |
| 170 | + /* Deallocate all the PPI channels, events and groups */ |
| 171 | + nrf_timer_task_trigger(PULSE_TIMER, NRF_TIMER_TASK_SHUTDOWN); |
| 172 | + nrfx_gpiote_in_uninit(pin); |
| 173 | + nrfx_ppi_group_free(firstGroup); |
| 174 | + nrfx_ppi_group_free(secondGroup); |
| 175 | + nrfx_ppi_group_free(thirdGroup); |
| 176 | + nrf_ppi_channel_group_clear(firstGroup); |
| 177 | + nrf_ppi_channel_group_clear(secondGroup); |
| 178 | + nrf_ppi_channel_group_clear(thirdGroup); |
| 179 | + nrfx_ppi_channel_free(firstPPIchannel); |
| 180 | + nrfx_ppi_channel_free(firstPPIchannelControl); |
| 181 | + nrfx_ppi_channel_free(secondPPIchannel); |
| 182 | + nrfx_ppi_channel_free(secondPPIchannelControl); |
| 183 | + nrfx_ppi_channel_free(thirdPPIchannel); |
| 184 | + nrfx_ppi_channel_free(thirdPPIchannelControl); |
| 185 | + |
| 186 | + /* The timer has a frequency of 1 MHz, so its counting value is already in microseconds */ |
| 187 | + return pulseTime; |
| 188 | +} |
| 189 | + |
| 190 | +unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout) |
| 191 | +{ |
| 192 | + return pulseIn(digitalPinToPinName(pin), (PinStatus)state, timeout); |
| 193 | +} |
| 194 | + |
| 195 | +unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout) |
| 196 | +{ |
| 197 | + return pulseIn(digitalPinToPinName(pin), (PinStatus)state, timeout); |
| 198 | +} |
| 199 | + |
| 200 | +unsigned long pulseInLong(PinName pin, PinStatus state, unsigned long timeout) |
| 201 | +{ |
| 202 | + return pulseIn(pin, state, timeout); |
| 203 | +} |
0 commit comments