-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
esp32/esp32_pcnt: Add PCNT class and Counter/Encoder shims in machine #7582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
esp32/esp32_pcnt: Add PCNT class and Counter/Encoder shims in machine #7582
Conversation
794f821
to
a4b35c2
Compare
Examples: from machine import Counter, Pin
# Simple counter:
def callback(counter):
print("Counter value:", counter.value())
simple = Counter(0, pin=Pin(0, Pin.IN, pull=Pin.PULL_UP), rising=Counter.INCREMENT, threshold1=5, maximum=10, filter=1000)
simple.irq(handler=callback, trigger=Counter.IRQ_THRESHOLD1)
simple.start()
# Rotary encoder, 1X detection:
pin_a = Pin(12, Pin.IN, pull=Pin.PULL_UP)
pin_b = Pin(14, Pin.IN, pull=Pin.PULL_UP)
encoder = Counter(1, pin=pin_a, falling=Counter.INCREMENT, rising=Counter.DECREMENT, mode_pin=pin_b, mode_low=Counter.HOLD)
# 2X detection:
encoder = Counter(1, pin=pin_a, falling=Counter.INCREMENT, rising=Counter.DECREMENT, mode_pin=pin_b, mode_low=Counter.REVERSE)
# 4X detection:
encoder = Counter(1, filter=1000, minimum=-100, maximum=100)
encoder.init(0, pin=pin_a, falling=Counter.INCREMENT, rising=Counter.DECREMENT, mode_pin=pin_b, mode_low=Counter.REVERSE)
encoder.init(1, pin=pin_b, falling=Counter.DECREMENT, rising=Counter.INCREMENT, mode_pin=pin_a, mode_low=Counter.REVERSE) |
See #6639 |
I did actually take a look at this implementation (I tried to hunt down all I could find), but I preferred to just expose the whole PCNT API through a single class rather than having a separate class for quadrature decoding – the latter being trivially implemented with the former. That said – as per the comment in #5216 – I wrote this code for my own use case and opened this PR as an FYI for anyone interested in counters. It may make more sense for MicroPython in the general case to use a separate quadrature decoder class as this might map better to other ports (I see STM has its own way of doing quad decoding with counters). Rotary encoders are such a common use case that a specific API for them makes sense (kind of like the neopixel library). |
Thanks, it looks interesting. If this were to be accepted (as part of the machine module) it would need documentation and the possibility to eventually implement it for other ports, based on the docs. |
de61a90
to
3f93fb0
Compare
@dpgeorge: OK, you've sucked me in to writing some documentation for it – even though I said I wasn't going to try and see this through to mainline 😉 Notes:
I'm conscious of not wanting to step on the toes of the people who have tried to get a pulse counter PR into MicroPython before and I can see the value in @IhorNehrutsa's idea to have a separate class for quadrature decoding – although whether this ought to be in a pure-Python wrapper like the That said, I'm committed to supporting this code as it's going into production hardware this year whether it goes mainline or not. |
3f93fb0
to
31fd9b6
Compare
31fd9b6
to
d96b007
Compare
d96b007
to
ca8a6bb
Compare
Hi, Jonathan. |
The idea is to have a core API that can be implemented in other ports using their counter hardware (or in software with pin interrupts), but that is flexible enough to extend to the full features of the ESP32 PCNT hardware. So I chose the class name to be non-port-specific. |
@jonathanhogg I have the impression that the way you have chosen the API is very port-specific for ESP32. As you said, it exposes the RTOS API to Python.
|
Hiya, Ta for the feedback. In rough order:
Honestly, it's great to see people engaging in this topic as I'd prefer to see a consensus-driven version hit mainline and not have to maintain a slew of my own patches. Jonathan |
ca8a6bb
to
2171786
Compare
2171786
to
aea74be
Compare
Leaving this PR around for reference and because I'm using this code in production devices. However, I'm not going to work on it any more. I'm leaving @IhorNehrutsa and @robert-hh to come up with a common API for counters and encoders (#6639 and #7911 respectively) in the hope that one day we see it hitting mainline and I can throw this one in the bin. |
That's a little bit sad. A combination of your driver and the one of @IhorNehrutsa would be more complete. I just saw you made a great work on ADC, which improves it a lot. I hope it gets merged. It may take a while until the Encoder/Counter support will be in the main line, if ever. It does not seem to be a desperately sought feature. |
Yeah, I thought about this, but I think the main (only?) purpose of the modal functionality in the ESP32 PCNT API is to support encoders and this is going to be more understandable to a user wrapped in an Encoder class – and certainly the only way we'll see a cross-port API. The event callbacks are neat, but are going to be hard to merge in with the wide-counter support in @IhorNehrutsa's work and I feel like that's probably more useful to an end user.
Yes, I'm hoping so too. Support for ADC block 2 has been outstanding for ages and the ESP32 calibrated voltage API is really great.
I actually had someone email me recently in desperate need of counter support on ESP32 and asking for me to cut them a custom build of my branch! Having good PRs covering common API documentation, and MIMXRT and ESP32 implementations will be a great start. I feel like one more port would tip it over the line. STM32 seemed like an obvious choice, but hardware timer support is bound up in the |
@jonathanhogg |
aea74be
to
bcd9efc
Compare
bcd9efc
to
0e38ebb
Compare
PCNT is very ESP specific. Encoder and Counter on the other hand are independent from that and don't even have to be frozen into MP as they can be added in pure python on top of PCNT later. I don't understand why the integration of PCNT needs to be delayed until Encoder and Counter are stable. |
From user perspective, I found working Encoder and CAN code with examples running on NuttX RTOS that builds and flashes under a minute so I will switch to that platform, big delays of useful verified stuff into a release is a big disadvantage of MicroPython project, please reconsider.. |
Unfortunately, this is not the first and not the only case of delays in solving problems in Micropython. Unfortunately for all... :( |
Just a remark here, PCNT is also useful for Flow meters, where neither Counter or Encoder are required (we need to assert the pulse per time period, not the count of pulses, and ideally, we don't want to run an IRQ per pulse). So the argument about waiting for Encoder or Counter to be merged first seems like it's counter intuitive to me. |
Would it be possible to implement this as a native module or is it relying on some symbols that's not in default micropython firmware on ESP32 ? |
May you post some link of Flow meters timing chart? |
flow meters are very slow, compared to an rotary/axial encoder. The typical frequency will be in the hundred of Hz, not kHz. Their duty cycle is completely random either, since they are based on a magnet + hall effect sensor + Schmidt Trigger. However, they are observing a physical system with friction and resistance so their answer is very non linear:
So one must apply different calibration table based on what regime they are in. This means that the number of pulse isn't very important, but the number of pulse per second is, since it allows to find out what regime they are in, and then you can interpret the pulse count. So basically, you'll have to sort the pulses in different buckets, one bucket for each (calibrated) regime. So an implementation that's based on a Pin input IRQ and a getTickUs() will not work well, since you always have the issue of not knowing when the IRQ triggered (and the time between the handler is executing the get time function). Worse: you'll usually zero your counter in the handler and store the "last" time in a variable too only if some period elapsed. With a PCNT, and a Timer, you'll have an interrupt for the timer at a very precise interval, where you can read the pulse counter and deduce the regime you're in and the accumulated flow/pulses, while still having the PCNT running and counting new pulses. Also, you can sleep while the HW is counting the pulses. |
It relies on symbols that are not available from a native module. The IDF PCNT driver isn't even compiled in without being explicitly linked to. There is actually surprisingly little that you can do from a native module as they require an explicit MicroPython lookup table for linking to anything at all. On the ESP32 you can't even do floating point division in a native module. Believe me, I've bounced up against these limitations often.
Honestly, I don't think that anything other that a lack of interest / core-developer time is holding up mainlining a PCNT implementation. There have been numerous runs at it over the last couple of years and if a particular API was or wasn't the problem that that would have been indicated and resolved by now. As noted before, I'm still committed to maintaining this PR. I've been working on higher-level stuff for the last few months, but I'll rebase this to 1.20 in a moment. |
0cef532
to
7ece16c
Compare
For the records: I have built the latest version and I am using it on a daily basis and everything still works as expected. Let me know if there's anything I can do to help getting this merged. |
7ece16c
to
4ed5510
Compare
@jonathanhogg @harbaum
It seems that it is impossible... :( |
No, you can only change I think that being able to change the thresholds in the simple.init(threshold1=50) I don't have any hardware in front of me to test with, but that's how the code was designed. Obviously you're going to need to change that |
MicroPython v1.20.0-223-g4ed55102f on 2023-08-15; ESP32 module with ESP32
Traceback (most recent call last): |
@IhorNehrutsa Oh, sorry! I just noticed that you're using |
4ed5510
to
ad7de60
Compare
So I've just rebased this and tweaked the code slightly so that it will build against ESP IDF v5.0.2, which is the release that MicroPython production builds are now being made with – and therefore what 1.21 will be using. In exciting news, ESP IDF v5.0 has a completely new PCNT driver Also, I'm not in any hurry as I'm not actually doing any projects at the moment that are using MicroPython and I have a significant amount of other work I should be doing… |
Superseded by #12346. |
ad7de60
to
e892d1a
Compare
Add a new `PCNT` class to the `esp32` module that provides complete, low-level support to the ESP32 PCNT pulse counting hardware units.
Document the new `esp32.PCNT` class for hardware pulse counting.
Add thin Python shims around `esp32.PCNT` to implement the `machine.Counter` and `machine.Encoder` classes (as per micropython#8072 API).
Add documentation for the core Counter/Encoder API as currently implemented by the esp32 port. This documentation is a simplification of that in PR micropython#8072.
e892d1a
to
0c3aa6e
Compare
Thank you. Which esp-idf version should I use when compiling firmware? |
It should be compatible with all of the 4 and 5 versions. Think last time I compiled it I did so against 5.2, but I'm away from my laptop so I can't check. You may get a warning about use of the deprecated PCNT API, but it still works fine. |
Provides pretty complete support for the ESP32 PCNT peripheral including control pin, second channel and events.