ESP32 Pulse Counter Driver
Add a driver for ESP32 Pulse Counter Hardware.
Related to: #5214
esp32: Add machine.Counter and machine.Encoder.
This builds on the extensive work done by @IhorNehrutsa and @jonathanhogg (with contributions from many others including @robert-hh and @harbaum). Thank you to everyone involved!!
It combines the design + documentation in PR #8072 with the implementation in PR #7582. This allows us to provide the full-featured interface to the ESP32 hardware, while also providing a lightweight wrapper providing the simplified portable Counter and Encoder classes.
I note that compared to the implementation in #8766 this does not support setting IRQs on the Encoder and Counter classes, however this is still possible with the esp32.PCNT class (and I think we should add support for this to Encoder and Counter anyway).
Also, as @jonathanhogg has pointed out, this uses the now-deprecated IDF pcnt driver, but updating it to the new one isn't that difficult. @projectgus suggests that we should consider moving to the HAL instead, as the driver doesn't offer a significant benefit.
I have made the following additional changes:
esp32.PCNT:
- Remove the ability to set a value, instead just a reset flag. (The hardware doesn't support it, and requiring the value being set to zero is a bit awkward). (The Encoder and Counter classes still support setting a value though).
- Minor code cleanups and commenting (especially around some of the more trickier aspects around avoiding race conditions with IRQ handlers).
- Remove the auto-assigning of unit IDs. We don't do this for other peripherals.
machine.Counter:
- Simplify the way
__new__and__init__work to implement the singletons. - Make the
idpassed to Counter (and Encoder) correspond directly to the PCNT id. This makes Encoder and Counter share the same "namespace" which I think is natural considering they do use the same underlying resource. - Make it count overflows rather than pulses, so that it can count for longer before overflowing small integer. This is important to avoid needing to allocate in the IRQ handler. This also changes the way we handle setting a value (and I think also fixes a race condition).
- Comments to explain the subtleties in avoiding race conditions.
machine.Encoder:
- Didn't previously work for phases=1 or 2. (Due to the way the pin arguments were handled in PCNT).
- Make phases=1 the default (not 4). We should provide the same default on all ports, and a port may not be able to provide 2x or 4x quadrature.
Docs:
- Minor clarifications to ESP32-specific support & availability.
- Explain the overflow and small integer behavior.
Other:
- Make this enabled by default on OG,S2,S3 (rather than explicitly disabling on C3).
- Use the "new" sys.path trick for having the frozen machine.py extend the built-in one, rather than
import umachine. Also use a lazy-loadinggetattr(similar to what we do elsewhere) to avoid needing to copy the entiremachinelocals dict into RAM. - Tested up to 1MHz on ESP32.
I look forward to seeing this implemented on other ports! @robert-hh I hope it should be relatively straightforward to merge #7911 now.
Although much of the work was done by the people already mentioned, I should mention that my work on this was funded through GitHub Sponsors (as well as some time for @projectgus and @dpgeorge... we spent a lot of time discussing this). As always, this is much appreciated and very helpful for getting these more involved bits of work reviewed and merged. Sorry for how long it's taken for us to get to this and the lack of communication from us, it's a very useful feature!