esp32_pcnt
PCNT: the ESP32’s pulse counter — eight units, each a signed 16-bit counter fed by an edge signal and steered by a control signal.
What it is for: reading a quadrature encoder as a signed position with direction, or counting a flow meter / tachometer. An edge on the signal input steps the counter; the control input’s level decides whether that step is up or down. A per-unit glitch filter rejects pulses shorter than a configured number of APB clock cycles, so contact bounce and line noise do not show up as phantom counts.
The one non-obvious thing: mode is a matrix, not a direction
Section titled “The one non-obvious thing: mode is a matrix, not a direction”There is no “count up” bit. Each channel has an action for the positive
edge and one for the negative edge (hold / increase / decrease), and then
the control level can modify that action: keep it, invert it, or hold. So
“up when the control pin is high, down when it is low” is expressed as
pos = Increase, control-high = Keep, control-low = Inverse. This is
exactly esp-idf’s model (pcnt_ll_set_edge_action /
pcnt_ll_set_level_action), and getting it wrong reads as a counter that
moves the right amount in the wrong direction.
Register facts
Section titled “Register facts”DR_REG_PCNT_BASE = 0x3FF57000 (esp-idf soc/soc.h). Layout from
soc/pcnt_struct.h; the field placement in conf0 is the part a wrong map
silently corrupts, so it is pinned by the tests below. Each unit owns three
config words (conf0/1/2, 12 bytes) at base + unit*0x0C; the counters are
a separate block of eight words at base + 0x60; the global control /
reset / pause register is at base + 0xB0.
The peripheral is clock-gated off out of reset (DPORT_PCNT_CLK_EN), like
every other ESP32 peripheral — [PcntUnit::new] enables it first, or every
register below reads back zero with no fault.
Structs
Section titled “Structs”ChannelMode
Section titled “ChannelMode”struct ChannelMode { pub pos: EdgeAction, pub neg: EdgeAction, pub high: LevelAction, pub low: LevelAction,}One channel’s behaviour: what each edge does, and how the control level bends it. A unit has two channels; a single-signal encoder uses channel 0.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
References: EdgeAction, LevelAction
PcntUnit
Section titled “PcntUnit”struct PcntUnit { /* private fields */ }One configured PCNT unit.
EdgeAction
Section titled “EdgeAction”enum EdgeAction { Hold, Increase, Decrease,}What a counter does on an edge of the signal input.
Values are the hardware encoding (pcnt_channel_edge_action_t): the field
documents 2'd1 as increase and 2'd2 as decrease, everything else a
no-op.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
Filter
Section titled “Filter”enum Filter { Off, Cycles(u16),}The glitch filter setting: off, or on with a width in APB clock cycles.
A pulse narrower than thres cycles is discarded before it can reach the
counter. thres is clamped to [MAX_FILTER_THRES].
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
LevelAction
Section titled “LevelAction”enum LevelAction { Keep, Inverse, Hold,}How the control input’s level modifies the edge action.
Values are the hardware encoding (pcnt_channel_level_action_t).
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
Constants
Section titled “Constants”MAX_FILTER_THRES
Section titled “MAX_FILTER_THRES”const MAX_FILTER_THRES: u16;The widest glitch-filter threshold the hardware accepts (filter_thres is
ten bits; esp-idf’s PCNT_LL_MAX_GLITCH_WIDTH).
NUM_UNITS
Section titled “NUM_UNITS”const NUM_UNITS: u8;Number of PCNT units on this chip.