Skip to content

Commit bcd081d

Browse files
committed
esp32/encoder: Add Encoder and Counter classes.
Signed-off-by: IhorNehrutsa <Ihor.Nehrutsa@gmail.com>
1 parent d19371c commit bcd081d

File tree

14 files changed

+1145
-2
lines changed

14 files changed

+1145
-2
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: 256 additions & 0 deletions
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

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
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

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: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)
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