diff --git a/docs/esp32/general.rst b/docs/esp32/general.rst index 8137c042d9214..40e97f1e8baf2 100644 --- a/docs/esp32/general.rst +++ b/docs/esp32/general.rst @@ -50,8 +50,12 @@ For your convenience, some of technical specifications are provided below: * SPI: 4 SPI interfaces (one used for FlashROM) * I2C: 2 I2C (bitbang implementation available on any pins) * I2S: 2 +* CAN bus: 1 * ADC: 12-bit SAR ADC up to 18 channels * DAC: 2 8-bit DACs +* PCNT: up to 8 channels +* PWM: up to 16 channels +* MCPWM: up to 2 channels * RMT: 8 channels allowing accurate pulse transmit/receive * Programming: using BootROM bootloader from UART - due to external FlashROM and always-available BootROM bootloader, the ESP32 is not brickable diff --git a/docs/esp32/img/quad.png b/docs/esp32/img/quad.png new file mode 100644 index 0000000000000..a47a38df8d3db Binary files /dev/null and b/docs/esp32/img/quad.png differ diff --git a/docs/esp32/pcnt.rst b/docs/esp32/pcnt.rst new file mode 100644 index 0000000000000..f402a86914817 --- /dev/null +++ b/docs/esp32/pcnt.rst @@ -0,0 +1,271 @@ +PCNT - Counter and Encoder +========================== + +The Counter and Encoder use the ESP32 Pulse Counter (PCNT) hardware peripheral, +see Espressif's `ESP-IDF Pulse Counter documentation. +`_ + +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). +The pulses are sampled on the edges of the APB_CLK clock and may be missed if fall between the edges. +With ideal input signal maximum frequency of measured pulses is APB_CLK / 2 = 80 MHz / 2 = 40 MHz. + +The inputs have optional filters that can be used to discard unwanted glitches in the signal. +The length of ignored pulses is provided in APB_CLK clock cycles. +* Note: Filter value is a 10-bit value, so the maximum filter value should be limited to 1023. +Maximum filtered glitches delay is 1023 * 12.5 ns = 12.7875 us. +Big filter make cutbacks the input frequency: 1 / (12.7875 us * 2) = 39.1 kHz. +* Note: Do not neglect circuitry methods to reduce noise (right powering and grounding, filtering, shielding, +short conductors, twisted pair cable, differential signals, etc.). + +There is only one interrupt for the peripheral, and that is managed inside the module. +The user has no interrupt interface, and no interrupts are generated on each pulse. +Interrupts arrive when the 16-bit hardware counter buffer overflows, so this module has a tiny interrupt footprint +while providing support for up to 8 simultaneous counters (Encoder or Counter objects). + +.. _esp32_machine.Counter: + +Counter +======= + +The Pulse Counter service. + +Constructor +----------- + +.. class:: Counter(id, src=None, \*, direction=Counter.UP, _src=None, edge=Counter.RISING, filter_ns=0) + + The Counter starts to count immediately. Filtering is disabled. + + - *id*. Values of *id* depend on a particular port and its hardware. + Values 0, 1, etc. are commonly used to select hardware block #0, #1, etc. + + - *src* is the pulse input :ref:`machine.Pin ` to be monitored. + *src* is required in the constructor. + + - *direction* specifies the direction to count. Values for this include the constants + + - Counter.UP - (default value) and + - Counter.DOWN to control the direction by software or + - :ref:`machine.Pin ` object to control the direction externally. If ``Pin.value()``: + - 0 - count down, + - 1 - count up. + + - *_src* is the inverse pulse input :ref:`machine.Pin ` to be monitored. + If the *_src* keyword is present then the *direction* keyword does not matter. + *src* and *_src* count in opposite directions, one in the UP direction + and the other in the DOWN direction, i.e. as an incremental/decremental counter. + + - *edge* specifies which edges of the input signal will be counted by Counter: + + - Counter.RISING : raise edges, + - Counter.FALLING : fall edges, + - Counter.RISING | Counter.FALLING : both edges. + + - *filter_ns* specifies a ns-value for the minimal time a signal has to be stable + at the input to be recognized. The largest value is 12787ns (1023 * 1000000000 / APB_CLK_FREQ). + The default is 0 – no filter. + +Methods +------- + +.. method:: Counter.init(*, src, ...) + + Modify the settings of the Counter object. See the **Constructor** for details about the parameters. + +.. method:: Counter.deinit() + + Stops the Counter, disables interrupts and releases hardware resources used by the counter. + A Soft Reset involve deinitializing all Encoder objects. + +.. method:: Counter.filter([value]) + + Get, and optionally set, the filter value. 0 disable filtering. + +.. method:: Counter.value([value]) + + Get, and optionally set, the counter *value* as a signed 64-bit integer. + Attention: Setting the counter brokes the IRQ_MATCH and IRQ_ZERO events. + +.. method:: Counter.irq(handler=None, trigger=Counter.IRQ_MATCH | Counter.IRQ_ZERO, value=0) + + -*handler* specifies a function is called when the respective *trigger* event happens. + The callback function *handler* receives a single argument, which is the Counter object. + All events may share the same callback or have separate callbacks. + The callback will be disabled, when called with handler=None. Counter.irq() disable all callbacks. + The event which triggers the callback can be identified with the ``Counter.status()`` method. + The Counter object which triggers the callback can be identified with the ``Counter.id()`` method. + + -*trigger* events may be: + + - Counter.IRQ_MATCH triggered when the counter matches the match value. + - Counter.IRQ_ZERO triggered when the counter matches the 0. + - Counter.IRQ_ROLL_OVER triggered when the int16_t counter overloaded. + - Counter.IRQ_ROLL_UNDER triggered when the int16_t counter underloaded. + + The default is - trigger=Counter.IRQ_MATCH | Counter.IRQ_ZERO. + The events are triggered when the counter value and match value are identical, but + callbacks have always a latency. + + - *value* sets a counter match value. When the counter matches these values, + a callback function can be called. They are 0 by default. + +Attention: ``Counter.irq()`` resets counter to 0. + +.. method:: Counter.status() + + Returns the event status flags of the recent handled Counter interrupt as a bitmap. + +===== ==== ======================= ============================================================= +bit # mask trigger coment +===== ==== ======================= ============================================================= + 0 1 if zero event: 0 - when counting up, 1 - when counting down + 2 4 Counter.IRQ_MATCH match value event when counting up + 3 8 Counter.IRQ_MATCH match value event when counting down + 4 16 Counter.IRQ_ROLL_UNDER roll under event + 5 32 Counter.IRQ_ROLL_OVER roll over event + 6 64 Counter.IRQ_ZERO zero event +===== ==== ======================= ============================================================= + + +.. method:: Counter.id() + + Returns id number. + +.. method:: Counter.pause() + +.. method:: Counter.resume() + +Constants +--------- + +.. data:: Counter.UP + Counter.DOWN + + Selects the counter direction. + +.. data:: Counter.RISING + Counter.FALLING + + Selects the counted edges. + +.. data:: Counter.IRQ_MATCH + Counter.IRQ_ZERO + + Selects callback triggers. + +:: + + from machine import Counter, Pin + + try: + def irq_handler(self): + print('irq_handler()', self.id(), self.status(), self.value()) + + cnt = Counter(0, src=Pin(17, mode=Pin.IN), direction=Pin(16, mode=Pin.IN)) + + cnt.pause() + flt = cnt.filter() # return current filter value. + cnt.filter(10_000) # filter delay is 10ms + c = cnt.value(0) # get current counter value, set the counter value to 0 + cnt.irq(irq_handler, Counter.IRQ_ZERO) # set irq handler + cnt.resume() + + _c = None + while True: + c = cnt.value() # get the counter value + if _c != c: + _c = c + print('Counter =', c) + finally: + cnt.deinit() # free the input pins and counter. + + +.. _esp32_machine.Encoder: + +Encoder +======= + +This class provides a Quadrature Incremental Encoder service. +See `Quadrature encoder outputs. +`_ + +.. image:: img/quad.png + :width: 397px + +Constructor +----------- + +.. class:: Encoder(id, phase_a=None, phase_b=None, \*, x124=4, filter_ns=0, match=0) + + The Encoder starts to count immediately. Filtering is disabled. + + - *id*. Values of *id* depend on a particular port and its hardware. + Values 0, 1, etc. are commonly used to select hardware block #0, #1, etc. + + - *phase_a*, *phase_b* are input pins :ref:`machine.Pin ` for monitoring of quadrature encoder pulses. + They are required in the constructor. + + - *x124* is a hardware multiplier, possible values is 1, 2, 4. The default value is 4. + More info in `Quadrature decoder state table `_. + When more Encoder resolution is needed, it is possible for the encoder to count the leading + and trailing edges of the quadrature encoder’s pulse train from one channel, + which doubles (x2) the number of pulses. Counting both leading and trailing edges + of both channels (A and B channels) of a quadrature encoder will quadruple (x4) the number of pulses: + + - 1 - count the leading(or trailing) edges from one phase channel. + - 2 - count the leading and trailing edges from one phase channel. + - 4 - count both leading and trailing edges of both phase channels. + + These keywords are the same as the Counter keywords, see above: + - *filter_ns* + - *match* + +Methods +------- + +.. method:: Encoder.init(*, phase_a, ...) + + Modify the settings of the Encoder object. See the **Constructor** for details about the parameters. + +The Encoder has the same methods as the Counter and differs only +in the constructor and internal hardware PCNT initialization. + +Constants +--------- + +.. data:: Encoder.IRQ_MATCH + Encoder.IRQ_ZERO + + Selects callback triggers. + +:: + + from machine import Encoder, Pin + + try: + n = 0 + def irq_handler1(self): + n -= 1 + print('irq_handler1()', self.id(), self.value(), n) + + def irq_handler2(self): + n += 1 + print('irq_handler2()', self.id(), self.value(), n) + + enc = Encoder(0, phase_a=Pin(17, mode=Pin.IN), phase_b=Pin(16, mode=Pin.IN), match=1000) + + enc.pause() + flt = enc.filter() # return current filter value. + enc.filter(10_000) # filter delay is 10ms + c = enc.value(0) # get current encoder value, set the encoder value to 0 + enc.irq(irq_handler1, Encoder.IRQ_MATCH) # set irq handler + enc.resume() + + _c = None + while True: + c = enc.value() # get the encoder value + if _c != c: + _c = c + print('Encoder =', c) + finally: + enc.deinit() # free the input pins and encoder. diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index d65782e501ac7..de2c4f11db9aa 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -16,6 +16,7 @@ working with this board it may be useful to get an overview of the microcontroll :maxdepth: 1 general.rst + pcnt.rst tutorial/index.rst 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 # The channel resolution is 100ns (1/(source_freq/clock_div)). r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns +Counter (Pulse/Edge Counter) +---------------------------- + +The Counter counts the number of rising and/or falling edges on any input pin. +It is a 64-bit signed hardware-based counter. Counter and Encoder share the same ESP32 PCNT hardware peripheral, +the total summary available number of Counter and Encoder is up to 8. + +See :ref:`machine.Counter ` for details. Simplest usage is:: + + from machine import Pin, Counter + + cnt = Counter(0, src=Pin(17, mode=Pin.IN), direction=Pin(16, mode=Pin.IN)) + _v = None + while True: + v = cnt.value() # get 64-bit signed value + if _v != v: + _v = v + print('Counter value:', v) + +Encoder (Quadrature Incremental Encoder) +---------------------------------------- + +The Encoder counts the quadrature-encoded pulses on pair of input pins (two square wave signals A and B with +~50% duty cycle and ~90-degree phase difference between them). +It is a 64-bit signed hardware-based counter. Counter and Encoder share the same ESP32 PCNT hardware peripheral, +the total summary available number of Counter and Encoder is up to 8. + +See :ref:`machine.Encoder ` for details. Simplest usage is:: + + from machine import Pin, Encoder + + enc = Encoder(0, phase_a=Pin(17, mode=Pin.IN), phase_b=Pin(16, mode=Pin.IN)) + _v = None + while True: + v = enc.value() # get 64-bit signed value + if _v != v: + _v = v + print('Encoder value:', v) + OneWire driver -------------- diff --git a/docs/esp32/tutorial/index.rst b/docs/esp32/tutorial/index.rst index 2435f2ecd2f59..241d9f3de3aef 100644 --- a/docs/esp32/tutorial/index.rst +++ b/docs/esp32/tutorial/index.rst @@ -16,7 +16,6 @@ to ``__. .. toctree:: :maxdepth: 1 - :numbered: intro.rst pwm.rst diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h index 988c7db8a144b..61b486bc9142f 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h @@ -3,5 +3,9 @@ #define MICROPY_HW_BOARD_NAME "ESP32C3 module" #define MICROPY_HW_MCU_NAME "ESP32C3" +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_MACHINE_PCNT (0) + // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 6473f04a53500..1c4e54627247e 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -121,6 +121,7 @@ list(APPEND MICROPY_SOURCE_PORT machine_touchpad.c machine_dac.c machine_i2c.c + machine_encoder.c network_common.c network_lan.c network_ppp.c diff --git a/ports/esp32/machine_encoder.c b/ports/esp32/machine_encoder.c new file mode 100755 index 0000000000000..7979f8cefa66f --- /dev/null +++ b/ports/esp32/machine_encoder.c @@ -0,0 +1,832 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * This file was generated by micropython-extmod-generator https://github.com/prusnak/micropython-extmod-generator + * from Python stab file pcnt.py + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021, 2025 Ihor Nehrutsa + * Copyright (c) 2021 Jonathan Hogg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* +ESP32 Quadrature Counter based on Pulse Counter(PCNT) +Based on +https://github.com/madhephaestus/ESP32Encoder +https://github.com/bboser/MicroPython_ESP32_psRAM_LoBo/blob/quad_decoder/MicroPython_BUILD/components/micropython/esp32/machine_dec.c +*/ + +#include "py/mpprint.h" +#include "py/runtime.h" +#include "mphalport.h" +#include "modmachine.h" + +#if MICROPY_PY_MACHINE_PCNT + +#include "rom/gpio.h" +#include "driver/pcnt.h" +#include "driver/pulse_cnt.h" +#include "soc/pcnt_struct.h" +#include "hal/pcnt_ll.h" +#include "esp_err.h" + +#include "machine_encoder.h" + +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, " | %d at %s\n", __LINE__, __FILE__); + +extern const mp_obj_type_t machine_pin_type; + +#define GET_INT mp_obj_get_int_truncated +// #define GET_INT mp_obj_get_ll_int // need PR: py\obj.c: Get 64-bit integer arg. #80896 + +static pcnt_isr_handle_t pcnt_isr_handle = NULL; +static mp_pcnt_obj_t *pcnts[PCNT_UNIT_MAX] = {}; + +#define check(x) x // no checks for speed up in release build +// #define check(x) check_esp_err(x) + +static void IRAM_ATTR pcnt_intr_handler(void *arg) { + /* + uint32_t intr_status = pcnt_ll_get_intr_status(0); + pcnt_ll_clear_intr_status(0, intr_status); + */ + uint32_t intr_status = PCNT.int_st.val; + // PCNT.int_clr.val = PCNT.int_st.val; // clear interrupts + for (int id = 0; id < PCNT_UNIT_MAX; ++id) { + if (intr_status & BIT(id)) { + // PCNT.int_clr.val |= BIT(id); // clear the interrupt + mp_pcnt_obj_t *self = pcnts[id]; + if (self != NULL) { + // self->event_status = PCNT.status_unit[id].val; + pcnt_get_event_status(id, &self->event_status); + if (self->event_status & PCNT_EVT_H_LIM) { + // when counting up + // debug_printf("H"); + self->counter += INT16_ROLL; + self->counter_accum += INT16_ROLL; + if (self->handler_roll_over != MP_OBJ_NULL) { + mp_sched_schedule(self->handler_roll_over, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); + } + } else if (self->event_status & PCNT_EVT_L_LIM) { + // when counting down + // debug_printf("L"); + self->counter -= INT16_ROLL; + self->counter_accum -= INT16_ROLL; + if (self->handler_roll_under != MP_OBJ_NULL) { + mp_sched_schedule(self->handler_roll_under, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); + } + } + if (self->event_status & PCNT_EVT_THRES_1) { + // when counting up & treshold value > 0 + // debug_printf("1"); + if (self->counter_accum == self->counter_match) { + mp_sched_schedule(self->handler_match, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); + } + } else if (self->event_status & PCNT_EVT_THRES_0) { + // when counting down & treshold value < 0 + // debug_printf("0"); + if (self->counter_accum == self->counter_match + INT16_ROLL) { + mp_sched_schedule(self->handler_match, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); + } + } + if (self->event_status & PCNT_EVT_ZERO) { + // when counting up/down + // debug_printf("Z"); + if (self->counter == 0) { + mp_sched_schedule(self->handler_zero, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); + } + } + } + // PCNT.int_clr.val |= BIT(id); // clear the interrupt + } + } + PCNT.int_clr.val = PCNT.int_st.val; // clear interrupts +} + +static void register_isr_handler(void) { + if (pcnt_isr_handle == NULL) { + check_esp_err(pcnt_isr_register(pcnt_intr_handler, (void *)0, (int)0, &pcnt_isr_handle)); + if (pcnt_isr_handle == NULL) { + mp_raise_msg(&mp_type_Exception, MP_ERROR_TEXT("wrap interrupt failed")); + } + PCNT.int_clr.val = PCNT.int_st.val; // clear interrupts + } +} + +// TODO: Remove after: esp32/machine_pin.c: Allow small int argument in machine_pin_get_id(). #8113 +// from ports/esp32/machine_sdcadr.c +static gpio_num_t pin_or_int(const mp_obj_t arg) { + if (mp_obj_is_small_int(arg)) { + return MP_OBJ_SMALL_INT_VALUE(arg); + } else { + // This raises a value error if the argument is not a Pin. + return machine_pin_get_id(arg); + } +} + +// Calculate the filter parameters based on an ns value +// 1 / 80MHz = 12.5ns - min filter period +// 12.5ns * FILTER_MAX = 12.5ns * 1023 = 12787.5ns - max filter period +#define ns_to_filter(ns) ((ns * (APB_CLK_FREQ / 1000000) + 500) / 1000) +#define filter_to_ns(filter) (filter * 1000 / (APB_CLK_FREQ / 1000000)) + +static uint16_t get_filter_value_ns(pcnt_unit_t unit) { + uint16_t value; + check_esp_err(pcnt_get_filter_value(unit, &value)); + return filter_to_ns(value); +} + +static void set_filter_value(pcnt_unit_t unit, int16_t value) { + if (value < 0) { + value = 0; + } else if (value > FILTER_MAX) { + value = FILTER_MAX; + } + + if (value) { + check_esp_err(pcnt_set_filter_value(unit, value)); + check_esp_err(pcnt_filter_enable(unit)); + } else { + check_esp_err(pcnt_filter_disable(unit)); + } +} + +static void pcnt_disable_events(mp_pcnt_obj_t *self) { + if (self->handler_roll_over != MP_OBJ_NULL) { + self->handler_roll_over = MP_OBJ_NULL; + } + if (self->handler_roll_under != MP_OBJ_NULL) { + self->handler_roll_under = MP_OBJ_NULL; + } + if (self->handler_match != MP_OBJ_NULL) { + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_THRES_1)); + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_THRES_0)); + self->handler_match = MP_OBJ_NULL; + } + if (self->handler_zero != MP_OBJ_NULL) { + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_ZERO)); + self->handler_zero = MP_OBJ_NULL; + } +} + +static void reset(mp_pcnt_obj_t *self) { + self->aPinNumber = PCNT_PIN_NOT_USED; + self->bPinNumber = PCNT_PIN_NOT_USED; + + self->counter = 0; + self->counter_accum = 0; + + self->match = 0; + self->counter_match = 0; + self->handler_match = MP_OBJ_NULL; + self->handler_zero = MP_OBJ_NULL; + self->handler_roll_over = MP_OBJ_NULL; + self->handler_roll_under = MP_OBJ_NULL; + self->event_status = 0; + + self->filter = 0; + self->edge = RISING; +} + +static void pcnt_deinit(mp_pcnt_obj_t *self) { + if (self != NULL) { + check_esp_err(pcnt_counter_pause(self->unit)); + check_esp_err(pcnt_intr_disable(self->unit)); + + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_L_LIM)); + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_H_LIM)); + pcnt_disable_events(self); + + check_esp_err(pcnt_set_pin(self->unit, PCNT_CHANNEL_0, PCNT_PIN_NOT_USED, PCNT_PIN_NOT_USED)); + check_esp_err(pcnt_set_pin(self->unit, PCNT_CHANNEL_1, PCNT_PIN_NOT_USED, PCNT_PIN_NOT_USED)); + + check_esp_err(pcnt_counter_clear(self->unit)); + reset(self); + pcnts[self->unit] = NULL; + } +} + +// This called from Ctrl-D soft reboot +void machine_encoder_deinit_all(void) { + for (int id = 0; id < PCNT_UNIT_MAX; ++id) { + pcnt_deinit(pcnts[id]); + } + if (pcnt_isr_handle != NULL) { + check_esp_err(pcnt_isr_unregister(pcnt_isr_handle)); + pcnt_isr_handle = NULL; + } +} + +// ================================================================= +// Common classes methods +static mp_obj_t machine_PCNT_deinit(mp_obj_t self_obj) { + pcnt_deinit(MP_OBJ_TO_PTR(self_obj)); + return MP_ROM_NONE; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_PCNT_deinit_obj, machine_PCNT_deinit); + +// ----------------------------------------------------------------- +static mp_obj_t machine_PCNT_filter(size_t n_args, const mp_obj_t *args) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t value = get_filter_value_ns(self->unit); + if (n_args > 1) { + set_filter_value(self->unit, ns_to_filter(mp_obj_get_int(args[1]))); + } + return MP_OBJ_NEW_SMALL_INT(value); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_PCNT_filter_obj, 1, 2, machine_PCNT_filter); + +// ----------------------------------------------------------------- +static mp_obj_t machine_PCNT_count(size_t n_args, const mp_obj_t *args) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + check_esp_err(pcnt_intr_disable(self->unit)); // to prevent recalc of self->counter in pcnt_intr_handler + + int16_t count; + check_esp_err(pcnt_get_counter_value(self->unit, &count)); + int64_t counter = self->counter; + + if (n_args > 1) { + uint64_t new_counter = GET_INT(args[1]); + check_esp_err(pcnt_counter_clear(self->unit)); + self->counter_accum = new_counter; // ??? setting self->counter brokes matching with PCNT_EVT_THRES_1 ! + self->counter = new_counter; + // TODO: set new irq counter_accum + } + + check_esp_err(pcnt_intr_enable(self->unit)); + + return mp_obj_new_int_from_ll(counter + count); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_PCNT_count_obj, 1, 2, machine_PCNT_count); + +// ----------------------------------------------------------------- +static mp_obj_t machine_PCNT_pause(mp_obj_t self_obj) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_obj); + + check_esp_err(pcnt_counter_pause(self->unit)); + return MP_ROM_NONE; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_PCNT_pause_obj, machine_PCNT_pause); + +// ----------------------------------------------------------------- +static mp_obj_t machine_PCNT_resume(mp_obj_t self_obj) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_obj); + + check_esp_err(pcnt_counter_resume(self->unit)); + return MP_ROM_NONE; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_PCNT_resume_obj, machine_PCNT_resume); + +// ----------------------------------------------------------------- +static mp_obj_t machine_PCNT_id(mp_obj_t self_obj) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_obj); + return MP_OBJ_NEW_SMALL_INT(self->unit); +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_PCNT_id_obj, machine_PCNT_id); + +// ----------------------------------------------------------------- +mp_obj_t machine_PCNT_status(mp_obj_t self_in) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->event_status); +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_PCNT_status_obj, machine_PCNT_status); + +static inline counter_t remainder_of_division(counter_t divident, counter_t divider) { + return divident - divident / divider * divider; +} + +// ----------------------------------------------------------------- +static mp_obj_t machine_PCNT_irq(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = PCNT_EVT_THRES_1 | PCNT_EVT_ZERO} }, + { MP_QSTR_value, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + mp_pcnt_obj_t *self = pos_args[0]; + if (self->aPinNumber == PCNT_PIN_NOT_USED) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PCNT inactive")); + } + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t handler = args[ARG_handler].u_obj; + mp_uint_t trigger = args[ARG_trigger].u_int; + + if (trigger & ~(PCNT_EVT_THRES_1 | PCNT_EVT_ZERO | PCNT_EVT_H_LIM | PCNT_EVT_L_LIM)) { + mp_raise_ValueError(MP_ERROR_TEXT("trigger")); + } + + check_esp_err(pcnt_intr_disable(self->unit)); + + if (handler == mp_const_none) { + if (trigger & PCNT_EVT_THRES_1) { + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_THRES_1)); + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_THRES_0)); + self->handler_match = MP_OBJ_NULL; + } + if (trigger & PCNT_EVT_ZERO) { + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_ZERO)); + self->handler_zero = MP_OBJ_NULL; + } + if (trigger & PCNT_EVT_H_LIM) { + self->handler_roll_over = MP_OBJ_NULL; + } + if (trigger & PCNT_EVT_L_LIM) { + self->handler_roll_under = MP_OBJ_NULL; + } + } else { + if (trigger & PCNT_EVT_THRES_1) { + if (args[ARG_value].u_obj != MP_OBJ_NULL) { + counter_t match = GET_INT(args[ARG_value].u_obj); + if (self->match != match) { + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_THRES_1)); + check_esp_err(pcnt_event_disable(self->unit, PCNT_EVT_THRES_0)); + self->match = match; + + int16_t count; + check(pcnt_get_counter_value(self->unit, &count)); + self->counter += count; + + counter_t match_dif = match - self->counter; + int16_t event_value = match_dif % INT16_ROLL; + self->counter_accum = 0; + self->counter_match = match_dif - event_value; + + debug_printf("match=%d, self->counter=%d, self->counter_accum=%d, count=0=%d, match_dif=%d, self->counter_match=%d, event_value=%d, %d", match, self->counter, self->counter_accum, count, match_dif, self->counter_match, event_value, event_value - INT16_ROLL); + + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_1, event_value)); + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_0, event_value - INT16_ROLL)); + check_esp_err(pcnt_counter_clear(self->unit)); + } + } + self->handler_match = handler; + check_esp_err(pcnt_event_enable(self->unit, PCNT_EVT_THRES_1)); + check_esp_err(pcnt_event_enable(self->unit, PCNT_EVT_THRES_0)); + } + if (trigger & PCNT_EVT_H_LIM) { + self->handler_roll_over = handler; + } + if (trigger & PCNT_EVT_L_LIM) { + self->handler_roll_under = handler; + } + if (trigger & PCNT_EVT_ZERO) { + /* + int16_t count; + check(pcnt_get_counter_value(self->unit, &count)); + debug_printf("ZERO: self->counter=%d, self->counter_accum=%d, count=%d, self->counter_match=%d", self->counter, self->counter_accum, count, self->counter_match); + */ + self->handler_zero = handler; + check_esp_err(pcnt_event_enable(self->unit, PCNT_EVT_ZERO)); + } + } + + check_esp_err(pcnt_intr_enable(self->unit)); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_PCNT_irq_obj, 1, machine_PCNT_irq); + +static void set_filter_and_start(mp_pcnt_obj_t *self) { + // Filter out bounces and noise + set_filter_value(self->unit, self->filter); + pcnts[self->unit] = self; + + check_esp_err(pcnt_intr_disable(self->unit)); + // Enable interrupts for PCNT unit + check_esp_err(pcnt_counter_pause(self->unit)); + register_isr_handler(); + // Enable events on maximum and minimum limit values + check_esp_err(pcnt_event_enable(self->unit, PCNT_EVT_H_LIM)); + check_esp_err(pcnt_event_enable(self->unit, PCNT_EVT_L_LIM)); + check_esp_err(pcnt_counter_clear(self->unit)); + self->counter_accum = 0; + self->counter = 0; + check_esp_err(pcnt_intr_enable(self->unit)); + check_esp_err(pcnt_counter_resume(self->unit)); +} +// ================================================================= +// class Counter(object): +static void mp_machine_Counter_init_helper(mp_pcnt_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_src, ARG_direction, ARG__src, ARG_edge, ARG_filter_ns }; + const mp_arg_t allowed_args[] = { + { MP_QSTR_src, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_direction, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR__src, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_edge, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = self->edge} }, + { MP_QSTR_filter_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self->bPinNumber = COUNTER_UP; + self->edge = args[ARG_edge].u_int; + + if (args[ARG_src].u_obj != MP_OBJ_NULL) { + self->aPinNumber = pin_or_int(args[ARG_src].u_obj); + } + if (self->aPinNumber == PCNT_PIN_NOT_USED) { + mp_raise_ValueError(MP_ERROR_TEXT("src")); + } + + mp_obj_t direction = args[ARG_direction].u_obj; + if (args[ARG__src].u_obj != MP_OBJ_NULL) { + self->bPinNumber = pin_or_int(args[ARG__src].u_obj); + self->x124 = -1; + if (direction != MP_OBJ_NULL) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("direction parameter is omitted")); + } + } else { + self->x124 = 0; + if (direction != MP_OBJ_NULL) { + if (mp_obj_is_type(direction, &machine_pin_type) || mp_obj_is_small_int(direction)) { + self->bPinNumber = pin_or_int(direction); + } else { + self->bPinNumber = mp_obj_get_int(direction); + if (!((self->bPinNumber == COUNTER_UP) || (self->bPinNumber == COUNTER_DOWN))) { + mp_raise_ValueError(MP_ERROR_TEXT("direction")); + } + } + } + } + + // Prepare configuration for the PCNT unit + pcnt_config_t r_enc_config; + r_enc_config.unit = self->unit; + // Set the maximum and minimum limit values to watch + r_enc_config.counter_h_lim = INT16_ROLL; + r_enc_config.counter_l_lim = -INT16_ROLL; + + // channel 0 + r_enc_config.channel = PCNT_CHANNEL_0; + + // What to do on the positive / negative edge of pulse input? + if (self->edge & RISING) { + r_enc_config.pos_mode = PCNT_COUNT_INC; // Count up on the positive edge + } else { + r_enc_config.pos_mode = PCNT_COUNT_DIS; // Keep the counter value on the positive edge + } + if (self->edge & FALLING) { + r_enc_config.neg_mode = PCNT_COUNT_INC; // Count up on the negative edge + } else { + r_enc_config.neg_mode = PCNT_COUNT_DIS; // Keep the counter value on the negative edge + } + + r_enc_config.pulse_gpio_num = self->aPinNumber; // Pulses // Pulse input GPIO number, a negative value will be ignored + + if (args[ARG__src].u_obj != MP_OBJ_NULL) { + r_enc_config.ctrl_gpio_num = PCNT_PIN_NOT_USED; // Control signal input GPIO number, a negative value will be ignored + } else { + r_enc_config.ctrl_gpio_num = self->bPinNumber; // Direction // Control signal input GPIO number, a negative value will be ignored + + // What to do when control input is low or high? + if (self->bPinNumber == COUNTER_UP) { + r_enc_config.lctrl_mode = PCNT_MODE_KEEP; // Keep the primary counter mode if low + r_enc_config.hctrl_mode = PCNT_MODE_REVERSE; // Reverse counting direction if high + } else { + r_enc_config.lctrl_mode = PCNT_MODE_REVERSE; // Reverse counting direction if low + r_enc_config.hctrl_mode = PCNT_MODE_KEEP; // Keep the primary counter mode if high + } + } + check_esp_err(pcnt_unit_config(&r_enc_config)); + + // channel 1 + r_enc_config.channel = PCNT_CHANNEL_1; // channel 1 + + // make sure channel 1 is not set + r_enc_config.lctrl_mode = PCNT_MODE_DISABLE; // disabling channel 1 + r_enc_config.hctrl_mode = PCNT_MODE_DISABLE; // disabling channel 1 + r_enc_config.pos_mode = PCNT_COUNT_DIS; // disabling channel 1 + r_enc_config.neg_mode = PCNT_COUNT_DIS; // disabling channel 1 + + if (args[ARG__src].u_obj != MP_OBJ_NULL) { + // What to do on the positive / negative edge of pulse input? + if (self->edge & RISING) { + r_enc_config.pos_mode = PCNT_COUNT_DEC; // Count down on the positive edge + } + if (self->edge & FALLING) { + r_enc_config.neg_mode = PCNT_COUNT_DEC; // Count down on the negative edge + } + + r_enc_config.pulse_gpio_num = self->bPinNumber; // Pulse input GPIO number, a negative value will be ignored + r_enc_config.ctrl_gpio_num = PCNT_PIN_NOT_USED; // Control signal input GPIO number, a negative value will be ignored + + // What to do when control input is low or high? + r_enc_config.hctrl_mode = PCNT_MODE_KEEP; // Keep the primary counter mode if high + r_enc_config.lctrl_mode = PCNT_MODE_KEEP; // Keep the primary counter mode if low + } + + check_esp_err(pcnt_unit_config(&r_enc_config)); + + if (args[ARG_filter_ns].u_int != -1) { + self->filter = ns_to_filter(args[ARG_filter_ns].u_int); + } + set_filter_and_start(self); +} + +static int find_free_unit() { + for (int id = 0; id < PCNT_UNIT_MAX; ++id) { + if (!pcnts[id]) { + return id; + } + } + return -1; +} + +static void pcnt_init_new(mp_pcnt_obj_t *self, size_t n_args, const mp_obj_t *args) { + reset(self); + + self->unit = mp_obj_get_int(args[0]); + if (self->unit == -1) { + self->unit = find_free_unit(); + if (self->unit < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of PCNT units:%d"), PCNT_UNIT_MAX - 1); + } + } + if ((self->unit < 0) || (self->unit >= PCNT_UNIT_MAX)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("id must be from 0 to %d"), PCNT_UNIT_MAX - 1); + } + if (pcnts[self->unit] != MP_OBJ_NULL) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("already used")); + } + + if (n_args >= 2) { + self->aPinNumber = pin_or_int(args[1]); + } +} + +static mp_obj_t machine_Counter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 4, true); + + // create Counter object for the given unit + mp_pcnt_obj_t *self = m_new_obj(mp_pcnt_obj_t); + self->base.type = &machine_Counter_type; + + pcnt_init_new(self, n_args, args); + + // Process the remaining parameters + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_Counter_init_helper(self, n_args - 1, args + 1, &kw_args); + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t machine_Counter_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + mp_machine_Counter_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_Counter_init_obj, 1, machine_Counter_init); + +static void common_print_kw(const mp_print_t *print, mp_pcnt_obj_t *self) { + if (self->handler_roll_over != MP_OBJ_NULL) { + mp_printf(print, ", roll_over=%ld", INT16_ROLL); + } + if (self->handler_roll_under != MP_OBJ_NULL) { + mp_printf(print, ", roll_under=%ld", -INT16_ROLL); + } + if (self->handler_match != MP_OBJ_NULL) { + mp_printf(print, ", match=%ld", self->match); + } + if (self->handler_zero != MP_OBJ_NULL) { + mp_printf(print, ", match0=0"); + } + mp_printf(print, ", filter_ns=%d)", filter_to_ns(self->filter)); +} + +static void machine_Counter_print(const mp_print_t *print, mp_obj_t self_obj, mp_print_kind_t kind) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_obj); + + mp_printf(print, "Counter(%u", self->unit); + if (self->aPinNumber == PCNT_PIN_NOT_USED) { + mp_printf(print, ")"); + return; + } + mp_printf(print, "), src=Pin(%u)", self->aPinNumber); + if (self->x124 < 0) { + mp_printf(print, ", _src=Pin(%u)", self->bPinNumber); + } else { + if (self->bPinNumber >= 0) { + mp_printf(print, ", direction=Pin(%u)", self->bPinNumber); + } else { + mp_printf(print, ", direction=Counter.%s", self->bPinNumber == COUNTER_UP ? "UP" : "DOWN"); + } + } + mp_printf(print, ", edge=Counter.%s", self->edge == RISING ? "RISING" : self->edge == FALLING ? "FALLING" : "RISING | Counter.FALLING"); + common_print_kw(print, self); +} + +// Register class methods +#define COMMON_METHODS \ + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_PCNT_deinit_obj) }, \ + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_PCNT_deinit_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_PCNT_count_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_filter_ns), MP_ROM_PTR(&machine_PCNT_filter_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_pause), MP_ROM_PTR(&machine_PCNT_pause_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_resume), MP_ROM_PTR(&machine_PCNT_resume_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&machine_PCNT_id_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&machine_PCNT_status_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_PCNT_irq_obj) } + +#define COMMON_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_IRQ_ZERO), MP_ROM_INT(PCNT_EVT_ZERO) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_ROLL_OVER), MP_ROM_INT(PCNT_EVT_H_LIM) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_ROLL_UNDER), MP_ROM_INT(PCNT_EVT_L_LIM) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_MATCH), MP_ROM_INT(PCNT_EVT_THRES_1) } + +static const mp_rom_map_elem_t machine_Counter_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_Counter_init_obj) }, + COMMON_METHODS, + COMMON_CONSTANTS, + { MP_ROM_QSTR(MP_QSTR_RISING), MP_ROM_INT(RISING) }, + { MP_ROM_QSTR(MP_QSTR_FALLING), MP_ROM_INT(FALLING) }, + { MP_ROM_QSTR(MP_QSTR_UP), MP_ROM_INT(COUNTER_UP) }, + { MP_ROM_QSTR(MP_QSTR_DOWN), MP_ROM_INT(COUNTER_DOWN) }, +}; +static MP_DEFINE_CONST_DICT(machine_Counter_locals_dict, machine_Counter_locals_dict_table); + +// Create the class-object itself +MP_DEFINE_CONST_OBJ_TYPE( + machine_Counter_type, + MP_QSTR_Counter, + MP_TYPE_FLAG_NONE, + make_new, machine_Counter_make_new, + print, machine_Counter_print, + locals_dict, &machine_Counter_locals_dict + ); + +// ================================================================= +// class Encoder(object): +static void mp_machine_Encoder_init_helper(mp_pcnt_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_phase_a, ARG_phase_b, ARG_x124, ARG_filter_ns }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_phase_a, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_phase_b, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_x124, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_filter_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t src = args[ARG_phase_a].u_obj; + if (src != MP_OBJ_NULL) { + self->aPinNumber = pin_or_int(src); + } + if (self->aPinNumber == PCNT_PIN_NOT_USED) { + mp_raise_ValueError(MP_ERROR_TEXT("phase_a")); + } + src = args[ARG_phase_b].u_obj; + if (src != MP_OBJ_NULL) { + self->bPinNumber = pin_or_int(src); + } + if (self->bPinNumber == PCNT_PIN_NOT_USED) { + mp_raise_ValueError(MP_ERROR_TEXT("phase_b")); + } + + if (args[ARG_x124].u_int != -1) { + if ((args[ARG_x124].u_int == 1) || (args[ARG_x124].u_int == 2) || (args[ARG_x124].u_int == 4)) { + self->x124 = args[ARG_x124].u_int; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("x124 must be 1, 2, 4")); + } + } + + // Set up Encoder PCNT configuration + pcnt_config_t r_enc_config; + r_enc_config.unit = self->unit; + r_enc_config.channel = PCNT_CHANNEL_0; // channel 0 + + r_enc_config.pulse_gpio_num = self->aPinNumber; // Rotary Encoder Chan A + r_enc_config.ctrl_gpio_num = self->bPinNumber; // Rotary Encoder Chan B + + r_enc_config.pos_mode = PCNT_COUNT_INC; // X1 X2 X4 Count on Rising-Edges + r_enc_config.neg_mode = (self->x124 != 1) ? PCNT_COUNT_DEC : PCNT_COUNT_DIS; // X2 X4 Count on Falling-Edges + + r_enc_config.lctrl_mode = PCNT_MODE_KEEP; // Rising A on HIGH B + r_enc_config.hctrl_mode = PCNT_MODE_REVERSE; // Rising A on LOW B + + // Set the maximum and minimum limit values to watch + r_enc_config.counter_h_lim = INT16_ROLL; + r_enc_config.counter_l_lim = -INT16_ROLL; + + check_esp_err(pcnt_unit_config(&r_enc_config)); + + if (self->x124 == 4) { // X4 + // set up second channel for full quad + r_enc_config.unit = self->unit; + r_enc_config.channel = PCNT_CHANNEL_1; // channel 1 + + r_enc_config.pulse_gpio_num = self->bPinNumber; // make prior control into signal + r_enc_config.ctrl_gpio_num = self->aPinNumber; // and prior signal into control + + r_enc_config.pos_mode = PCNT_COUNT_INC; // Count on Rising-Edges + r_enc_config.neg_mode = PCNT_COUNT_DEC; // Count on Falling-Edge + + r_enc_config.lctrl_mode = PCNT_MODE_REVERSE; // prior high mode is now low + r_enc_config.hctrl_mode = PCNT_MODE_KEEP; // prior low mode is now high + + check_esp_err(pcnt_unit_config(&r_enc_config)); + } else { + // make sure channel 1 is not set when not full quad + r_enc_config.unit = self->unit; + r_enc_config.channel = PCNT_CHANNEL_1; // channel 1 + + r_enc_config.pos_mode = PCNT_COUNT_DIS; // disabling channel 1 + r_enc_config.neg_mode = PCNT_COUNT_DIS; // disabling channel 1 + + r_enc_config.lctrl_mode = PCNT_MODE_DISABLE; // disabling channel 1 + r_enc_config.hctrl_mode = PCNT_MODE_DISABLE; // disabling channel 1 + + check_esp_err(pcnt_unit_config(&r_enc_config)); + } + + if (args[ARG_filter_ns].u_int != -1) { + self->filter = ns_to_filter(args[ARG_filter_ns].u_int); + } + set_filter_and_start(self); +} + +static mp_obj_t machine_Encoder_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 5, true); + + // create Encoder object for the given unit + mp_pcnt_obj_t *self = m_new_obj(mp_pcnt_obj_t); + self->base.type = &machine_Encoder_type; + + pcnt_init_new(self, n_args, args); + + if (n_args >= 3) { + self->bPinNumber = pin_or_int(args[2]); + } + self->x124 = 4; + + // Process the remaining parameters + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_Encoder_init_helper(self, n_args - 1, args + 1, &kw_args); + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t machine_Encoder_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + mp_machine_Encoder_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_Encoder_init_obj, 1, machine_Encoder_init); + +static void machine_Encoder_print(const mp_print_t *print, mp_obj_t self_obj, mp_print_kind_t kind) { + mp_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_obj); + mp_printf(print, "Encoder(%u", self->unit); + if (self->aPinNumber == PCNT_PIN_NOT_USED) { + mp_printf(print, ")"); + return; + } + mp_printf(print, ", phase_a=Pin(%u), phase_b=Pin(%u), x124=%d", self->aPinNumber, self->bPinNumber, self->x124); + common_print_kw(print, self); +} + +// Register class methods +static const mp_rom_map_elem_t machine_Encoder_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_Encoder_init_obj) }, + COMMON_METHODS, + COMMON_CONSTANTS +}; +static MP_DEFINE_CONST_DICT(machine_Encoder_locals_dict, machine_Encoder_locals_dict_table); + +// Create the class-object itself +MP_DEFINE_CONST_OBJ_TYPE( + machine_Encoder_type, + MP_QSTR_Encoder, + MP_TYPE_FLAG_NONE, + make_new, machine_Encoder_make_new, + print, machine_Encoder_print, + locals_dict, &machine_Encoder_locals_dict + ); + +#endif // MICROPY_PY_MACHINE_PCNT diff --git a/ports/esp32/machine_encoder.h b/ports/esp32/machine_encoder.h new file mode 100644 index 0000000000000..fef0ee8c2a0d1 --- /dev/null +++ b/ports/esp32/machine_encoder.h @@ -0,0 +1,46 @@ +#ifndef MICROPY_INCLUDED_MACHINE_ENCODER_H +#define MICROPY_INCLUDED_MACHINE_ENCODER_H + +// #define USE_INT64 +#ifdef USE_INT64 +typedef int64_t counter_t; +#else +typedef int32_t counter_t; +#endif + +#define INT16_ROLL ((counter_t)32767) + +#define FILTER_MAX 1023 + +enum edge_bit_mask { + RISING = 0x1, + FALLING = 0x2 +}; + +#define COUNTER_UP (-2) +#define COUNTER_DOWN (-4) + +typedef struct _mp_pcnt_obj_t { + mp_obj_base_t base; + int unit; + + int aPinNumber; + int bPinNumber; + + volatile counter_t counter; // the absolute value of the counter from the 0 (in constructor) or from the set value(x) method + volatile counter_t counter_accum; // = counter - match // relative value of counter from the absolute value to the match value; it is updated after match events + + counter_t match; // match value + counter_t counter_match; // (match - counter) - (match - counter) % INT16_ROLL + mp_obj_t handler_match; + mp_obj_t handler_zero; + mp_obj_t handler_roll_over; + mp_obj_t handler_roll_under; + uint32_t event_status; + + int filter; + enum edge_bit_mask edge; // Counter only + int8_t x124; // Encoder: multiplier 1, 2 or 4 // Counter: 0 is 'direction=' keyword used, -1 is '_src=' keyword used +} mp_pcnt_obj_t; + +#endif // MICROPY_INCLUDED_MACHINE_ENCODER_H diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 4f0c27ee078f8..69c7f02fbb6e0 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -197,6 +197,9 @@ void mp_task(void *pvParameter) { // deinitialise peripherals machine_pwm_deinit_all(); // TODO: machine_rmt_deinit_all(); + #if MICROPY_PY_MACHINE_PCNT + machine_encoder_deinit_all(); + #endif machine_pins_deinit(); machine_deinit(); #if MICROPY_PY_SOCKET_EVENTS diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 0c1b94d02d940..a8c3f93146688 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -51,6 +51,13 @@ #define MICROPY_PY_MACHINE_TOUCH_PAD_ENTRY #endif +#if MICROPY_PY_MACHINE_PCNT +#define MICROPY_PY_MACHINE_PCNT_ENTRY { MP_ROM_QSTR(MP_QSTR_Encoder), MP_ROM_PTR(&machine_Encoder_type) }, \ + { MP_ROM_QSTR(MP_QSTR_Counter), MP_ROM_PTR(&machine_Counter_type) }, +#else +#define MICROPY_PY_MACHINE_PCNT_ENTRY +#endif + #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, \ \ @@ -58,6 +65,7 @@ MICROPY_PY_MACHINE_SDCARD_ENTRY \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ MICROPY_PY_MACHINE_TOUCH_PAD_ENTRY \ + MICROPY_PY_MACHINE_PCNT_ENTRY \ { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, \ \ /* wake abilities */ \ diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index 9a5e6a566eb83..fb7fedc1b5bf6 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -12,6 +12,8 @@ typedef enum { extern const mp_obj_type_t machine_touchpad_type; extern const mp_obj_type_t machine_dac_type; extern const mp_obj_type_t machine_sdcard_type; +extern const mp_obj_type_t machine_Counter_type; +extern const mp_obj_type_t machine_Encoder_type; void machine_init(void); void machine_deinit(void); @@ -19,6 +21,7 @@ void machine_pins_init(void); void machine_pins_deinit(void); void machine_pwm_deinit_all(void); // TODO: void machine_rmt_deinit_all(void); +void machine_encoder_deinit_all(void); void machine_timer_deinit_all(void); void machine_i2s_init0(); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index b5b7d63a56333..d6d7c82fba799 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -138,6 +138,11 @@ #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) +#define MICROPY_PY_MACHINE_SPI_MSB (0) +#define MICROPY_PY_MACHINE_SPI_LSB (1) +#ifndef MICROPY_PY_MACHINE_PCNT +#define MICROPY_PY_MACHINE_PCNT (1) +#endif #define MICROPY_PY_MACHINE_SOFTSPI (1) #ifndef MICROPY_PY_MACHINE_DAC #define MICROPY_PY_MACHINE_DAC (SOC_DAC_SUPPORTED) 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