Skip to content

Commit 98a04f1

Browse files
committed
esp32/encoder: Add Encoder and Counter classes.
Co-Authored-By: robert-hh <robert@hammelrath.com> Co-Authored-By: Jonathan Hogg <me@jonathanhogg.com> Signed-off-by: IhorNehrutsa <Ihor.Nehrutsa@gmail.com>
1 parent 2264340 commit 98a04f1

File tree

13 files changed

+1217
-1
lines changed

13 files changed

+1217
-1
lines changed

docs/esp32/general.rst

Lines changed: 4 additions & 0 deletions
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

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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+
Attention: Setting the counter brokes the IRQ_MATCH and IRQ_ZERO events.
88+
89+
.. method:: Counter.irq(handler=None, trigger=Counter.IRQ_MATCH | Counter.IRQ_ZERO, value=0)
90+
91+
-*handler* specifies a function is called when the respective *trigger* event happens.
92+
The callback function *handler* receives a single argument, which is the Counter object.
93+
All events may share the same callback or have separate callbacks.
94+
The callback will be disabled, when called with handler=None. Counter.irq() disable all callbacks.
95+
The event which triggers the callback can be identified with the ``Counter.status()`` method.
96+
The Counter object which triggers the callback can be identified with the ``Counter.id()`` method.
97+
98+
-*trigger* events may be:
99+
100+
- Counter.IRQ_MATCH triggered when the counter matches the match value.
101+
- Counter.IRQ_ZERO triggered when the counter matches the 0.
102+
- Counter.IRQ_ROLL_OVER triggered when the int16_t counter overloaded.
103+
- Counter.IRQ_ROLL_UNDER triggered when the int16_t counter underloaded.
104+
105+
The default is - trigger=Counter.IRQ_MATCH | Counter.IRQ_ZERO.
106+
The events are triggered when the counter value and match value are identical, but
107+
callbacks have always a latency.
108+
109+
- *value* sets a counter match value. When the counter matches these values,
110+
a callback function can be called. They are 0 by default.
111+
112+
Attention: ``Counter.irq()`` resets counter to 0.
113+
114+
.. method:: Counter.status()
115+
116+
Returns the event status flags of the recent handled Counter interrupt as a bitmap.
117+
118+
===== ==== ======================= =============================================================
119+
bit # mask trigger coment
120+
===== ==== ======================= =============================================================
121+
0 1 if zero event: 0 - when counting up, 1 - when counting down
122+
2 4 Counter.IRQ_MATCH match value event when counting up
123+
3 8 Counter.IRQ_MATCH match value event when counting down
124+
4 16 Counter.IRQ_ROLL_UNDER roll under event
125+
5 32 Counter.IRQ_ROLL_OVER roll over event
126+
6 64 Counter.IRQ_ZERO zero event
127+
===== ==== ======================= =============================================================
128+
129+
130+
.. method:: Counter.id()
131+
132+
Returns id number.
133+
134+
.. method:: Counter.pause()
135+
136+
.. method:: Counter.resume()
137+
138+
Constants
139+
---------
140+
141+
.. data:: Counter.UP
142+
Counter.DOWN
143+
144+
Selects the counter direction.
145+
146+
.. data:: Counter.RISING
147+
Counter.FALLING
148+
149+
Selects the counted edges.
150+
151+
.. data:: Counter.IRQ_MATCH
152+
Counter.IRQ_ZERO
153+
154+
Selects callback triggers.
155+
156+
::
157+
158+
from machine import Counter, Pin
159+
160+
try:
161+
def irq_handler(self):
162+
print('irq_handler()', self.id(), self.status(), self.value())
163+
164+
cnt = Counter(0, src=Pin(17, mode=Pin.IN), direction=Pin(16, mode=Pin.IN))
165+
166+
cnt.pause()
167+
flt = cnt.filter() # return current filter value.
168+
cnt.filter(10_000) # filter delay is 10ms
169+
c = cnt.value(0) # get current counter value, set the counter value to 0
170+
cnt.irq(irq_handler, Counter.IRQ_ZERO) # set irq handler
171+
cnt.resume()
172+
173+
_c = None
174+
while True:
175+
c = cnt.value() # get the counter value
176+
if _c != c:
177+
_c = c
178+
print('Counter =', c)
179+
finally:
180+
cnt.deinit() # free the input pins and counter.
181+
182+
183+
.. _esp32_machine.Encoder:
184+
185+
Encoder
186+
=======
187+
188+
This class provides a Quadrature Incremental Encoder service.
189+
See `Quadrature encoder outputs.
190+
<https://en.wikipedia.org/wiki/Incremental_encoder#Quadrature_outputs>`_
191+
192+
.. image:: img/quad.png
193+
:width: 397px
194+
195+
Constructor
196+
-----------
197+
198+
.. class:: Encoder(id, phase_a=None, phase_b=None, \*, x124=4, filter_ns=0, match=0)
199+
200+
The Encoder starts to count immediately. Filtering is disabled.
201+
202+
- *id*. Values of *id* depend on a particular port and its hardware.
203+
Values 0, 1, etc. are commonly used to select hardware block #0, #1, etc.
204+
205+
- *phase_a*, *phase_b* are input pins :ref:`machine.Pin <machine.Pin>` for monitoring of quadrature encoder pulses.
206+
They are required in the constructor.
207+
208+
- *x124* is a hardware multiplier, possible values is 1, 2, 4. The default value is 4.
209+
More info in `Quadrature decoder state table <https://en.wikipedia.org/wiki/Incremental_encoder#Quadrature_decoder>`_.
210+
When more Encoder resolution is needed, it is possible for the encoder to count the leading
211+
and trailing edges of the quadrature encoder’s pulse train from one channel,
212+
which doubles (x2) the number of pulses. Counting both leading and trailing edges
213+
of both channels (A and B channels) of a quadrature encoder will quadruple (x4) the number of pulses:
214+
215+
- 1 - count the leading(or trailing) edges from one phase channel.
216+
- 2 - count the leading and trailing edges from one phase channel.
217+
- 4 - count both leading and trailing edges of both phase channels.
218+
219+
These keywords are the same as the Counter keywords, see above:
220+
- *filter_ns*
221+
- *match*
222+
223+
Methods
224+
-------
225+
226+
.. method:: Encoder.init(*, phase_a, ...)
227+
228+
Modify the settings of the Encoder object. See the **Constructor** for details about the parameters.
229+
230+
The Encoder has the same methods as the Counter and differs only
231+
in the constructor and internal hardware PCNT initialization.
232+
233+
Constants
234+
---------
235+
236+
.. data:: Encoder.IRQ_MATCH
237+
Encoder.IRQ_ZERO
238+
239+
Selects callback triggers.
240+
241+
::
242+
243+
from machine import Encoder, Pin
244+
245+
try:
246+
n = 0
247+
def irq_handler1(self):
248+
n -= 1
249+
print('irq_handler1()', self.id(), self.value(), n)
250+
251+
def irq_handler2(self):
252+
n += 1
253+
print('irq_handler2()', self.id(), self.value(), n)
254+
255+
enc = Encoder(0, phase_a=Pin(17, mode=Pin.IN), phase_b=Pin(16, mode=Pin.IN), match=1000)
256+
257+
enc.pause()
258+
flt = enc.filter() # return current filter value.
259+
enc.filter(10_000) # filter delay is 10ms
260+
c = enc.value(0) # get current encoder value, set the encoder value to 0
261+
enc.irq(irq_handler1, Encoder.IRQ_MATCH) # set irq handler
262+
enc.resume()
263+
264+
_c = None
265+
while True:
266+
c = enc.value() # get the encoder value
267+
if _c != c:
268+
_c = c
269+
print('Encoder =', c)
270+
finally:
271+
enc.deinit() # free the input pins and encoder.

docs/esp32/quickref.rst

Lines changed: 40 additions & 0 deletions
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
Note that there are several varieties of ESP32 -- ESP32, ESP32C3, ESP32C6, ESP32S2, ESP32S3 --
@@ -769,6 +770,45 @@ The RMT is ESP32-specific and allows generation of accurate digital pulses with
769770
# The channel resolution is 100ns (1/(source_freq/clock_div)).
770771
r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns
771772

773+
Counter (Pulse/Edge Counter)
774+
----------------------------
775+
776+
The Counter counts the number of rising and/or falling edges on any input pin.
777+
It is a 64-bit signed hardware-based counter. Counter and Encoder share the same ESP32 PCNT hardware peripheral,
778+
the total summary available number of Counter and Encoder is up to 8.
779+
780+
See :ref:`machine.Counter <esp32_machine.Counter>` for details. Simplest usage is::
781+
782+
from machine import Pin, Counter
783+
784+
cnt = Counter(0, src=Pin(17, mode=Pin.IN), direction=Pin(16, mode=Pin.IN))
785+
_v = None
786+
while True:
787+
v = cnt.value() # get 64-bit signed value
788+
if _v != v:
789+
_v = v
790+
print('Counter value:', v)
791+
792+
Encoder (Quadrature Incremental Encoder)
793+
----------------------------------------
794+
795+
The Encoder counts the quadrature-encoded pulses on pair of input pins (two square wave signals A and B with
796+
~50% duty cycle and ~90-degree phase difference between them).
797+
It is a 64-bit signed hardware-based counter. Counter and Encoder share the same ESP32 PCNT hardware peripheral,
798+
the total summary available number of Counter and Encoder is up to 8.
799+
800+
See :ref:`machine.Encoder <esp32_machine.Encoder>` for details. Simplest usage is::
801+
802+
from machine import Pin, Encoder
803+
804+
enc = Encoder(0, phase_a=Pin(17, mode=Pin.IN), phase_b=Pin(16, mode=Pin.IN))
805+
_v = None
806+
while True:
807+
v = enc.value() # get 64-bit signed value
808+
if _v != v:
809+
_v = v
810+
print('Encoder value:', v)
811+
772812
OneWire driver
773813
--------------
774814

docs/esp32/tutorial/index.rst

Lines changed: 0 additions & 1 deletion
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
#define MICROPY_HW_BOARD_NAME "ESP32C3 module"
44
#define MICROPY_HW_MCU_NAME "ESP32C3"
55

6+
#define MICROPY_HW_ENABLE_SDCARD (0)
7+
#define MICROPY_PY_MACHINE_I2S (0)
8+
#define MICROPY_PY_MACHINE_PCNT (0)
9+
610
// Enable UART REPL for modules that have an external USB-UART and don't use native USB.
711
#define MICROPY_HW_ENABLE_UART_REPL (1)

ports/esp32/esp32_common.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ list(APPEND MICROPY_SOURCE_PORT
121121
machine_touchpad.c
122122
machine_dac.c
123123
machine_i2c.c
124+
machine_encoder.c
124125
network_common.c
125126
network_lan.c
126127
network_ppp.c

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy