nrf_pcnt
A hardware pulse counter for the nRF52, built from parts on the chip. It counts without CPU intervention.
Why
Moire's soil-moisture probe reports moisture as a frequency, so measuring it means counting pulses over a fixed window. The ESP32 has a pulse-counter peripheral for this; the nRF52 doesn't. Counting in an interrupt handler would wake the CPU on every pulse, which defeats the point of a node that is supposed to spend most of its life asleep.
How it works
The nRF52's Programmable Peripheral Interconnect (PPI) wires one peripheral's event directly to another peripheral's task, with no CPU in between. Two PPI channels and two timers make a gated counter:
Pulse pin ──► GPIOTE IN event ──PPI──► TIMER2 COUNT task (counter, 32-bit)
TIMER3 COMPARE[1] event ──────PPI──► TIMER2 CAPTURE[0] task (gate, 1 MHz)
- GPIOTE raises an event on each falling edge of the pulse pin.
- PPI routes that event to TIMER2's
COUNTtask. TIMER2 runs in counter mode, so each pulse increments it in hardware. - TIMER3 runs at 1 MHz with a compare value equal to the measurement window.
- When the window ends, a second PPI channel fires TIMER2's
CAPTUREtask, latching the count into a register.
After setup the CPU does nothing. Reading the result is one register read whenever the application next wakes up.
API
nrfx_err_t pcntInit(int pin, uint16_t measureTimeMs);
void pcntClearCounter(void); // reset the count
void pcntClearTimer(void); // restart the window
uint32_t pcntGetCount(void); // count latched at end of window
Notes
- Two versions: one for nrfx 2.x (Arduino/PlatformIO, as used in Moire) and one for nrfx 3.x (nRF Connect SDK / Zephyr), with a Zephyr example for the nRF52840 DK.
- Uses TIMER2 and TIMER3 because TIMER0 is commonly reserved by the radio stack.
- Both timers are 32-bit, so the count can't wrap during any realistic window.