Skip to content

Commit 76ffec5

Browse files
committed
esp32/encoder: Add Encoder and Counter classes.
Signed-off-by: IhorNehrutsa <[email protected]>
1 parent d19371c commit 76ffec5

14 files changed

+1139
-2
lines changed

docs/esp32/general.rst

+4
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ For your convenience, some of technical specifications are provided below:
5050
* SPI: 4 SPI interfaces (one used for FlashROM)
5151
* I2C: 2 I2C (bitbang implementation available on any pins)
5252
* I2S: 2
53+
* CAN bus: 1
5354
* ADC: 12-bit SAR ADC up to 18 channels
5455
* DAC: 2 8-bit DACs
56+
* PCNT: up to 8 channels
57+
* PWM: up to 16 channels
58+
* MCPWM: up to 2 channels
5559
* RMT: 8 channels allowing accurate pulse transmit/receive
5660
* Programming: using BootROM bootloader from UART - due to external FlashROM
5761
and always-available BootROM bootloader, the ESP32 is not brickable

docs/esp32/img/quad.png

88.3 KB
Loading

docs/esp32/pcnt.rst

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
PCNT - Counter and Encoder
2+
==========================
3+
4+
The Counter and Encoder use the ESP32 Pulse Counter (PCNT) hardware peripheral,
5+
see Espressif's `ESP-IDF Pulse Counter documentation.
6+
<https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/pcnt.html>`_
7+
8+
For the counter not to miss any pulses, the pulse duration should be longer than one ESP32 APB_CLK cycle (1 / 80 MHz = 12.5 ns).
9+
The pulses are sampled on the edges of the APB_CLK clock and may be missed if fall between the edges.
10+
With ideal input signal maximum frequency of measured pulses is APB_CLK / 2 = 80 MHz / 2 = 40 MHz.
11+
12+
The inputs have optional filters that can be used to discard unwanted glitches in the signal.
13+
The length of ignored pulses is provided in APB_CLK clock cycles.
14+
* Note: Filter value is a 10-bit value, so the maximum filter value should be limited to 1023.
15+
Maximum filtered glitches delay is 1023 * 12.5 ns = 12.7875 us.
16+
Big filter make cutbacks the input frequency: 1 / (12.7875 us * 2) = 39.1 kHz.
17+
* Note: Do not neglect circuitry methods to reduce noise (right powering and grounding, filtering, shielding,
18+
short conductors, twisted pair cable, differential signals, etc.).
19+
20+
There is only one interrupt for the peripheral, and that is managed inside the module.
21+
The user has no interrupt interface, and no interrupts are generated on each pulse.
22+
Interrupts arrive when the 16-bit hardware counter buffer overflows, so this module has a tiny interrupt footprint
23+
while providing support for up to 8 simultaneous counters (Encoder or Counter objects).
24+
25+
.. _esp32_machine.Counter:
26+
27+
Counter
28+
=======
29+
30+
The Pulse Counter service.
31+
32+
Constructor
33+
-----------
34+
35+
.. class:: Counter(id, src=None, \*, direction=Counter.UP, _src=None, edge=Counter.RISING, filter_ns=0)
36+
37+
The Counter starts to count immediately. Filtering is disabled.
38+
39+
- *id*. Values of *id* depend on a particular port and its hardware.
40+
Values 0, 1, etc. are commonly used to select hardware block #0, #1, etc.
41+
42+
- *src* is the pulse input :ref:`machine.Pin <machine.Pin>` to be monitored.
43+
*src* is required in the constructor.
44+
45+
- *direction* specifies the direction to count. Values for this include the constants
46+
47+
- Counter.UP - (default value) and
48+
- Counter.DOWN to control the direction by software or
49+
- :ref:`machine.Pin <machine.Pin>` object to control the direction externally. If ``Pin.value()``:
50+
- 0 - count down,
51+
- 1 - count up.
52+
53+
- *_src* is the inverse pulse input :ref:`machine.Pin <machine.Pin>` to be monitored.
54+
If the *_src* keyword is present then the *direction* keyword does not matter.
55+
*src* and *_src* count in opposite directions, one in the UP direction
56+
and the other in the DOWN direction, i.e. as an incremental/decremental counter.
57+
58+
- *edge* specifies which edges of the input signal will be counted by Counter:
59+
60+
- Counter.RISING : raise edges,
61+
- Counter.FALLING : fall edges,
62+
- Counter.RISING | Counter.FALLING : both edges.
63+
64+
- *filter_ns* specifies a ns-value for the minimal time a signal has to be stable
65+
at the input to be recognized. The largest value is 12787ns (1023 * 1000000000 / APB_CLK_FREQ).
66+
The default is 0 – no filter.
67+
68+
Methods
69+
-------
70+
71+
.. method:: Counter.init(*, src, ...)
72+
73+
Modify the settings of the Counter object. See the **Constructor** for details about the parameters.
74+
75+
.. method:: Counter.deinit()
76+
77+
Stops the Counter, disables interrupts and releases hardware resources used by the counter.
78+
A Soft Reset involve deinitializing all Encoder objects.
79+
80+
.. method:: Counter.filter([value])
81+
82+
Get, and optionally set, the filter value. 0 disable filtering.
83+
84+
.. method:: Counter.value([value])
85+
86+
Get, and optionally set, the counter *value* as a signed 64-bit integer.
87+
88+
.. method:: Counter.irq(handler=None, trigger=Counter.IRQ_MATCH1 | Counter.IRQ_ZERO, value=0)
89+
90+
-*handler* specifies a function is called when the respective *trigger* event happens.
91+
The callback function *handler* receives a single argument, which is the Counter object.
92+
All events may share the same callback or have separate callbacks.
93+
The callback will be disabled, when called with handler=None. Counter.irq() disable all callbacks.
94+
The event which triggers the callback can be identified with the ``Counter.status()`` method.
95+
The Counter object which triggers the callback can be identified with the ``Counter.id()`` method.
96+
97+
-*trigger* events may be:
98+
99+
- Counter.IRQ_MATCH1 triggered when the counter matches the match1 value.
100+
- Counter.IRQ_ZERO triggered when the counter matches the 0.
101+
102+
The default is - trigger=Counter.IRQ_MATCH1 | Counter.IRQ_ZERO.
103+
The events are triggered when the counter value and match value are identical, but
104+
callbacks have always a latency.
105+
106+
- *value* sets a counter match1 value. When the counter matches these values,
107+
a callback function can be called. They are 0 by default.
108+
109+
Attention: ``Counter.irq()`` resets counter to 0.
110+
111+
.. method:: Counter.status()
112+
113+
Returns the event status flags of the recent handled Counter interrupt as a bitmap.
114+
115+
.. method:: Counter.id()
116+
117+
Returns id number.
118+
119+
.. method:: Counter.pause()
120+
121+
.. method:: Counter.resume()
122+
123+
Constants
124+
---------
125+
126+
.. data:: Counter.UP
127+
Counter.DOWN
128+
129+
Selects the counter direction.
130+
131+
.. data:: Counter.RISING
132+
Counter.FALLING
133+
134+
Selects the counted edges.
135+
136+
.. data:: Counter.IRQ_MATCH1
137+
Counter.IRQ_ZERO
138+
139+
Selects callback triggers.
140+
141+
::
142+
143+
from machine import Counter, Pin
144+
145+
try:
146+
def irq_handler(self):
147+
print('irq_handler()', self.id(), self.status(), self.value())
148+
149+
cnt = Counter(0, src=Pin(17, mode=Pin.IN), direction=Pin(16, mode=Pin.IN))
150+
151+
cnt.pause()
152+
flt = cnt.filter() # return current filter value.
153+
cnt.filter(10_000) # filter delay is 10ms
154+
c = cnt.value(0) # get current counter value, set the counter value to 0
155+
cnt.irq(irq_handler, Counter.IRQ_ZERO) # set irq handler
156+
cnt.resume()
157+
158+
_c = None
159+
while True:
160+
c = cnt.value() # get the counter value
161+
if _c != c:
162+
_c = c
163+
print('Counter =', c)
164+
finally:
165+
cnt.deinit() # free the input pins and counter.
166+
167+
168+
.. _esp32_machine.Encoder:
169+
170+
Encoder
171+
=======
172+
173+
This class provides a Quadrature Incremental Encoder service.
174+
See `Quadrature encoder outputs.
175+
<https://en.wikipedia.org/wiki/Incremental_encoder#Quadrature_outputs>`_
176+
177+
.. image:: img/quad.png
178+
:width: 397px
179+
180+
Constructor
181+
-----------
182+
183+
.. class:: Encoder(id, phase_a=None, phase_b=None, \*, x124=4, filter_ns=0, match1=0)
184+
185+
The Encoder starts to count immediately. Filtering is disabled.
186+
187+
- *id*. Values of *id* depend on a particular port and its hardware.
188+
Values 0, 1, etc. are commonly used to select hardware block #0, #1, etc.
189+
190+
- *phase_a*, *phase_b* are input pins :ref:`machine.Pin <machine.Pin>` for monitoring of quadrature encoder pulses.
191+
They are required in the constructor.
192+
193+
- *x124* is a hardware multiplier, possible values is 1, 2, 4. The default value is 4.
194+
More info in `Quadrature decoder state table <https://en.wikipedia.org/wiki/Incremental_encoder#Quadrature_decoder>`_.
195+
When more Encoder resolution is needed, it is possible for the encoder to count the leading
196+
and trailing edges of the quadrature encoder’s pulse train from one channel,
197+
which doubles (x2) the number of pulses. Counting both leading and trailing edges
198+
of both channels (A and B channels) of a quadrature encoder will quadruple (x4) the number of pulses:
199+
200+
- 1 - count the leading(or trailing) edges from one phase channel.
201+
- 2 - count the leading and trailing edges from one phase channel.
202+
- 4 - count both leading and trailing edges of both phase channels.
203+
204+
These keywords are the same as the Counter keywords, see above:
205+
- *filter_ns*
206+
- *match1*
207+
208+
Methods
209+
-------
210+
211+
.. method:: Encoder.init(*, phase_a, ...)
212+
213+
Modify the settings of the Encoder object. See the **Constructor** for details about the parameters.
214+
215+
The Encoder has the same methods as the Counter and differs only
216+
in the constructor and internal hardware PCNT initialization.
217+
218+
Constants
219+
---------
220+
221+
.. data:: Encoder.IRQ_MATCH1
222+
Encoder.IRQ_ZERO
223+
224+
Selects callback triggers.
225+
226+
::
227+
228+
from machine import Encoder, Pin
229+
230+
try:
231+
n = 0
232+
def irq_handler1(self):
233+
n -= 1
234+
print('irq_handler1()', self.id(), self.value(), n)
235+
236+
def irq_handler2(self):
237+
n += 1
238+
print('irq_handler2()', self.id(), self.value(), n)
239+
240+
enc = Encoder(0, phase_a=Pin(17, mode=Pin.IN), phase_b=Pin(16, mode=Pin.IN), match1=1000)
241+
242+
enc.pause()
243+
flt = enc.filter() # return current filter value.
244+
enc.filter(10_000) # filter delay is 10ms
245+
c = enc.value(0) # get current encoder value, set the encoder value to 0
246+
enc.irq(irq_handler1, Encoder.IRQ_MATCH1) # set irq handler
247+
enc.resume()
248+
249+
_c = None
250+
while True:
251+
c = enc.value() # get the encoder value
252+
if _c != c:
253+
_c = c
254+
print('Encoder =', c)
255+
finally:
256+
enc.deinit() # free the input pins and encoder.

docs/esp32/quickref.rst

+40
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ working with this board it may be useful to get an overview of the microcontroll
1616
:maxdepth: 1
1717

1818
general.rst
19+
pcnt.rst
1920
tutorial/index.rst
2021

2122
Installing MicroPython
@@ -674,6 +675,45 @@ The RMT is ESP32-specific and allows generation of accurate digital pulses with
674675
# The channel resolution is 100ns (1/(source_freq/clock_div)).
675676
r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns
676677

678+
Counter (Pulse/Edge Counter)
679+
----------------------------
680+
681+
The Counter counts the number of rising and/or falling edges on any input pin.
682+
It is a 64-bit signed hardware-based counter. Counter and Encoder share the same ESP32 PCNT hardware peripheral,
683+
the total summary available number of Counter and Encoder is up to 8.
684+
685+
See :ref:`machine.Counter <esp32_machine.Counter>` for details. Simplest usage is::
686+
687+
from machine import Pin, Counter
688+
689+
cnt = Counter(0, src=Pin(17, mode=Pin.IN), direction=Pin(16, mode=Pin.IN))
690+
_v = None
691+
while True:
692+
v = cnt.value() # get 64-bit signed value
693+
if _v != v:
694+
_v = v
695+
print('Counter value:', v)
696+
697+
Encoder (Quadrature Incremental Encoder)
698+
----------------------------------------
699+
700+
The Encoder counts the quadrature-encoded pulses on pair of input pins (two square wave signals A and B with
701+
~50% duty cycle and ~90-degree phase difference between them).
702+
It is a 64-bit signed hardware-based counter. Counter and Encoder share the same ESP32 PCNT hardware peripheral,
703+
the total summary available number of Counter and Encoder is up to 8.
704+
705+
See :ref:`machine.Encoder <esp32_machine.Encoder>` for details. Simplest usage is::
706+
707+
from machine import Pin, Encoder
708+
709+
enc = Encoder(0, phase_a=Pin(17, mode=Pin.IN), phase_b=Pin(16, mode=Pin.IN))
710+
_v = None
711+
while True:
712+
v = enc.value() # get 64-bit signed value
713+
if _v != v:
714+
_v = v
715+
print('Encoder value:', v)
716+
677717
OneWire driver
678718
--------------
679719

docs/esp32/tutorial/index.rst

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ to `<https://www.python.org>`__.
1616

1717
.. toctree::
1818
:maxdepth: 1
19-
:numbered:
2019

2120
intro.rst
2221
pwm.rst

ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#define MICROPY_HW_ENABLE_SDCARD (0)
77
#define MICROPY_PY_MACHINE_I2S (0)
8+
#define MICROPY_PY_MACHINE_PCNT (0)
89

910
// Enable UART REPL for modules that have an external USB-UART and don't use native USB.
1011
#define MICROPY_HW_ENABLE_UART_REPL (1)

ports/esp32/esp32_common.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ list(APPEND MICROPY_SOURCE_PORT
6969
machine_touchpad.c
7070
machine_dac.c
7171
machine_i2c.c
72+
machine_encoder.c
7273
network_common.c
7374
network_lan.c
7475
network_ppp.c

0 commit comments

Comments
 (0)