Skip to content
This repository was archived by the owner on Apr 16, 2021. It is now read-only.

Commit c313d1e

Browse files
polldofacchinm
authored andcommitted
pulseIn and pulseInLong implemented using hw peripherals: NRF_TIMER2, PPI_CHANNEL and GPIOTE events
1 parent 14db0c1 commit c313d1e

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

cores/arduino/wiring_pulse.cpp

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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

Comments
 (0)