diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 3ab4e8f5ecd96..41ea3803c0597 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -450,6 +450,45 @@ supported ADC resolutions: - ``ADC.WIDTH_12BIT`` = 12 +Pulse Counter (pin pulse/edge counting) +--------------------------------------- + +The ESP32 provides up to 8 pulse counter peripherals depending on the hardware, +with id 0..7. These can be configured to count rising and/or falling edges on +any input pin. + +Use the :ref:`esp32.PCNT ` class:: + + from machine import Pin + from esp32 import PCNT + + counter = PCNT(0, pin=Pin(2), rising=PCNT.INCREMENT) # create counter + counter.start() # start counter + count = counter.value() # read count, -32768..32767 + counter.value(0) # reset counter + count = counter.value(0) # read and reset + +The PCNT hardware supports monitoring multiple pins in a single unit to +implement quadrature decoding or up/down signal counters. + +See the :ref:`machine.Counter ` and +:ref:`machine.Encoder ` classes for simpler abstractions of +common pulse counting applications. These classes are implemented as thin Python +shims around the ``PCNT()`` class:: + + from machine import Pin, Counter + + counter = Counter(0, Pin(2)) # create a counter as above and start it + count = counter.value() # read the count as an arbitrary precision signed integer + + encoder = Encoder(0, Pin(12), Pin(14)) # create an encoder and begin counting + count = encoder.value() # read the count as an arbitrary precision signed integer + +Note that the id of these ``Counter()`` and ``Encoder()`` objects is an +arbitrary number, each uniquely identified object will be allocated a free PCNT +unit. + + Software SPI bus ---------------- diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index dc35e7905e162..f6e648d1fa550 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -164,6 +164,129 @@ Constants Used in `idf_heap_info`. + +.. _esp32.PCNT: + +PCNT +---- + +This class provides access to the ESP32 hardware support for pulse counting. +There are 8 pulse counter units, with id 0..7. + +.. class:: PCNT([id], *, ...) + + Create a new PCNT object with the given unit ``id`` or return the existing + object if it has already been created. If the ``id`` positional argument is + not supplied then a new object is created for the first unallocated PCNT + unit. Keyword arguments are passed to the ``init()`` method as described + below. + +.. method:: PCNT.init(*, ...) + + (Re-)initialise a pulse counter unit. Supported keyword arguments are: + + - ``pin``: the input Pin to monitor for pulses + - ``rising``: an action to take on a rising edge - one of + ``PCNT.INCREMENT``, ``PCNT.DECREMENT`` or ``PCNT.IGNORE`` (the default) + - ``falling``: an action to take on a falling edge + - ``mode_pin``: ESP32 pulse counters support monitoring a second pin and + altering the behaviour of the counter based on its level - set this + keyword to any input Pin + - ``mode_low``: set to either ``PCNT.HOLD`` or ``PCNT.REVERSE`` to + either suspend counting or reverse the direction of the counter (i.e., + ``PCNT.INCREMENT`` behaves as ``PCNT.DECREMENT`` and vice versa) + when ``mode_pin`` is low + - ``mode_high``: as ``mode_low`` but for the behaviour when ``mode_pin`` + is high + - ``filter``: set to a value 1..1023, in ticks of the 80MHz clock, to + enable the pulse width filter + - ``minimum``: set to the minimum level of the counter value when + decrementing (-32768..-1) or 0 to disable + - ``maximum``: set to the maximum level of the counter value when + incrementing (1..32767) or 0 to disable + - ``threshold0``: sets the counter value for the + ``PCNT.IRQ_THRESHOLD0`` event (see ``irq`` method) + - ``threshold1``: sets the counter value for the + ``PCNT.IRQ_THRESHOLD1`` event (see ``irq`` method) + - ``value``: reset the counter value (must be 0 if specified) + - ``channel``: see description below + + The hardware initialisation is done in stages and so some of the keyword + arguments can be used in groups or in isolation to partially reconfigure a + unit: + + - the ``pin`` keyword (optionally combined with ``mode_pin``) can be used + to change just the bound pin(s) + - ``rising`, ``falling``, ``mode_low`` and ``mode_high`` can be used + (singly or together) to change the counting logic - omitted keywords + use their default (``PCNT.IGNORE`` or ``PCNT.NORMAL``) + - ``filter`` can be used to change only the pulse width filter (with 0 + disabling it) + - each of ``minimum``, ``maximum``, ``threshold0`` and ``threshold1`` can + be used to change these limit/event values individually; however, + setting any will reset the counter to zero (i.e., they imply + ``value=0``) + + Each pulse counter unit supports two channels, 0 and 1, each able to + monitor different pins with different counting logic but updating the same + counter value. Use ``channel=1`` with the ``pin``, ``rising``, ``falling``, + ``mode_pin``, ``mode_low`` and ``mode_high`` keywords to configure the + second channel. + + The second channel can be used to configure 4X quadrature decoding with a + single counter unit:: + + pin_a = Pin(2, Pin.INPUT, pull=Pin.PULL_UP) + pin_b = Pin(3, Pin.INPUT, pull=Pin.PULL_UP) + rotary = PCNT(0, pin=pin_a, falling=PCNT.INCREMENT, rising=PCNT.DECREMENT, mode_pin=pin_b, mode_low=PCNT.REVERSE) + rotary.init(channel=1, pin=pin_b, falling=PCNT.DECREMENT, rising=PCNT.INCREMENT, mode_pin=pin_a, mode_low=PCNT.REVERSE) + rotary.start() + +.. method:: PCNT.value([value]) + + Call this method with no arguments to return the current counter value or + pass the value 0 to reset the counter and return the value before reset. + ESP32 pulse counters do not support being set to any value other than 0. + Read and reset is not atomic and so it is possible for a pulse to be + missed. + +.. method:: PCNT.irq(handler=None, trigger=PCNT.IRQ_ZERO) + + ESP32 pulse counters support interrupts on these counter events: + + - ``PCNT.IRQ_ZERO``: the counter has reset to zero + - ``PCNT.IRQ_MINIMUM``: the counter has hit the ``minimum`` value + - ``PCNT.IRQ_MAXIMUM``: the counter has hit the ``maximum`` value + - ``PCNT.IRQ_THRESHOLD0``: the counter has hit the ``threshold0`` value + - ``PCNT.IRQ_THRESHOLD1``: the counter has hit the ``threshold1`` value + + ``trigger`` should be the desired events or'ed together. The ``handler`` + function should take a single argument which will be a bit mask indicating + which counter event(s) occurred. + + The handler is called with the MicroPython scheduler and so will run at a + point after the interrupt. If another interrupt occurs before the handler + has been called then the events will be coalesced together into a single + call and the bit mask will indicate all events that have occurred. + + To avoid race conditions between a handler being called and retrieving the + current counter value, the ``value()`` method will force execution of any + pending events before returning the current counter value (and potentially + resetting the value). + + Only one handler can be in place per-unit. Set ``handler`` to ``None`` to + disable the event interrupt (or call ``irq()`` with no arguments). + +.. Note:: + ESP32 pulse counters reset to *zero* when reaching the minimum or maximum + value. Thus the ``IRQ_ZERO`` event will also trigger when either of these + events occurs. + +See the :ref:`machine.Counter ` and +:ref:`machine.Encoder ` classes for simpler abstractions of +common pulse counting applications. + + .. _esp32.RMT: RMT diff --git a/docs/library/machine.Counter.rst b/docs/library/machine.Counter.rst new file mode 100644 index 0000000000000..0d0eff9e8da1e --- /dev/null +++ b/docs/library/machine.Counter.rst @@ -0,0 +1,74 @@ +.. currentmodule:: machine +.. _machine.Counter: + +class Counter -- pulse counter +============================== + +Counter implements pulse counting by monitoring an input signal and counting +rising or falling edges. + +Minimal example usage:: + + from machine import Pin, Counter + + counter = Counter(0, Pin(0, Pin.IN)) # create Counter for pin 0 and begin counting + value = counter.value() # retrieve current pulse count + +Availability: esp32 port + +Constructors +------------ + +.. class:: Counter(id, ...) + + Construct a Counter object with the given 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. Additional arguments are passed to the + ``init()`` method described below. + +Methods +------- + +.. method:: Counter.init(src, *, ...) + + Initialise the Counter with the given parameters: + + - *src* specifies the input pin as a :ref:`machine.Pin ` object. + May be omitted on ports that have a predefined pin for a given hardware + block. + + Additional keyword-only parameters that may be supported by a port are: + + - *edge* specifies the edge to count. Either ``Counter.RISING`` (the default) + or ``Counter.FALLING``. + + - *direction* specifies the direction to count. Either ``Counter.UP`` (the + default) or ``Counter.DOWN``. + + - *filter_ns* specifies a minimum period of time in nanoseconds that the + source signal needs to be stable for a pulse to be counted. Implementations + should use the longest filter supported by the hardware that is less than + or equal to this value. The default is 0 (no filter). + +.. method:: Counter.deinit() + + Stops the Counter, disabling any interrupts and releasing hardware resources. + A Soft Reset should deinitialize all Counter objects. + +.. method:: Counter.value([value]) + + Get, and optionally set, the counter value as a signed integer. + Implementations should aim to do the get and set atomically. + +Constants +--------- + +.. data:: Counter.RISING + Counter.FALLING + + Select the pulse edge. + +.. data:: Counter.UP + Counter.DOWN + + Select the counting direction. diff --git a/docs/library/machine.Encoder.rst b/docs/library/machine.Encoder.rst new file mode 100644 index 0000000000000..8b45857d1e524 --- /dev/null +++ b/docs/library/machine.Encoder.rst @@ -0,0 +1,64 @@ +.. currentmodule:: machine +.. _machine.Encoder: + +class Encoder -- quadrature decoding +==================================== + +Encoder implements decoding of quadrature signals as commonly output from +rotary encoders, by counting either up or down depending on the order of two +input pulses. + +Minimal example usage:: + + from machine import Pin, Encoder + + counter = Counter(0, Pin(0, Pin.IN), Pin(1, Pin.IN)) # create Encoder for pins 0, 1 and begin counting + value = counter.value() # retrieve current count + +Availability: esp32 port + +Constructors +------------ + +.. class:: Encoder(id, ...) + + Construct an Encoder object with the given 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. Additional arguments are passed to the + ``init()`` method described below. + +Methods +------- + +.. method:: Encoder.init(phase_a, phase_b, *, ...) + + Initialise the Encoder with the given parameters: + + - *phase_a* specifies the first input pin as a + :ref:`machine.Pin ` object. + + - *phase_a* specifies the second input pin as a + :ref:`machine.Pin ` object. + + These pins may be omitted on ports that have predefined pins for a given + hardware block. + + Additional keyword-only parameters that may be supported by a port are: + + - *filter_ns* specifies a minimum period of time in nanoseconds that the + source signal needs to be stable for a pulse to be counted. Implementations + should use the longest filter supported by the hardware that is less than + or equal to this value. The default is 0 (no filter). + + - *phases* specifies the number of signal edges to count and thus the + granularity of the decoding. Ports may support either 1, 2 or 4 phases. + +.. method:: Encoder.deinit() + + Stops the Encoder, disabling any interrupts and releasing hardware resources. + A Soft Reset should deinitialize all Encoder objects. + +.. method:: Encoder.value([value]) + + Get, and optionally set, the encoder value as a signed integer. + Implementations should aim to do the get and set atomically. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 7d2eb26a7ea34..2858002786a96 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -268,6 +268,8 @@ Classes machine.I2S.rst machine.RTC.rst machine.Timer.rst + machine.Counter.rst + machine.Encoder.rst machine.WDT.rst machine.SD.rst machine.SDCard.rst diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h index 0dbfae03a367c..b0cf328b09bec 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h @@ -8,3 +8,4 @@ // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) +#define MICROPY_PY_ESP32_PCNT (0) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 565f6feec2324..eee5302258466 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -112,6 +112,7 @@ list(APPEND MICROPY_SOURCE_PORT modesp.c esp32_nvs.c esp32_partition.c + esp32_pcnt.c esp32_rmt.c esp32_ulp.c modesp32.c diff --git a/ports/esp32/esp32_pcnt.c b/ports/esp32/esp32_pcnt.c new file mode 100644 index 0000000000000..603b8d6fa910c --- /dev/null +++ b/ports/esp32/esp32_pcnt.c @@ -0,0 +1,450 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021-22 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. + */ + + +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/obj.h" + +#if MICROPY_PY_ESP32_PCNT + +#include "modesp32.h" +#include "driver/pcnt.h" + + +typedef struct _esp32_pcnt_obj_t { + mp_obj_base_t base; + pcnt_unit_t unit; + struct _esp32_pcnt_obj_t *next; + mp_obj_t handler; + uint32_t pending; +} esp32_pcnt_obj_t; + +MP_REGISTER_ROOT_POINTER(struct _esp32_pcnt_obj_t *esp32_pcnt_obj_head); + +static bool pcnt_isr_service_installed = false; + +static mp_sched_node_t pcnt_irq_sched_node = {0}; + +static void IRAM_ATTR esp32_pcnt_run_irq_handlers(mp_sched_node_t *node) { + (void)node; + esp32_pcnt_obj_t *pcnt = MP_STATE_PORT(esp32_pcnt_obj_head); + while (pcnt != NULL) { + uint32_t status = pcnt->pending; + pcnt->pending ^= status; + if (status && pcnt->handler != MP_OBJ_NULL) { + mp_call_function_1_protected(pcnt->handler, MP_OBJ_NEW_SMALL_INT(status)); + } + pcnt = pcnt->next; + } +} + +static void esp32_pcnt_intr_handler(void *arg) { + esp32_pcnt_obj_t *self = (esp32_pcnt_obj_t *)arg; + pcnt_unit_t unit = self->unit; + #if ((ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 2)) || ESP_IDF_VERSION_MAJOR > 4 + uint32_t status; + pcnt_get_event_status(unit, &status); + #else + uint32_t status = PCNT.status_unit[unit].val; + #endif + self->pending |= status; + mp_sched_schedule_node(&pcnt_irq_sched_node, esp32_pcnt_run_irq_handlers); +} + +void esp32_pcnt_deinit_all(void) { + esp32_pcnt_obj_t **pcnt = &MP_STATE_PORT(esp32_pcnt_obj_head); + while (*pcnt != NULL) { + esp32_pcnt_obj_t *next = (*pcnt)->next; + m_del_obj(esp32_pcnt_obj_t, *pcnt); + *pcnt = next; + } +} + +static void esp32_pcnt_init_helper(esp32_pcnt_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_channel, + ARG_pin, + ARG_rising, + ARG_falling, + ARG_mode_pin, + ARG_mode_low, + ARG_mode_high, + ARG_minimum, + ARG_maximum, + ARG_filter, + ARG_threshold0, + ARG_threshold1, + ARG_value, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_rising, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_falling, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode_low, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode_high, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_minimum, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_maximum, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_filter, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_threshold0, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_threshold1, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_uint_t channel = args[ARG_channel].u_int; + if (channel >= PCNT_CHANNEL_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("channel")); + } + + if (args[ARG_pin].u_obj != MP_OBJ_NULL || args[ARG_mode_pin].u_obj != MP_OBJ_NULL) { + if (args[ARG_pin].u_obj == MP_OBJ_NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("pin")); + } + mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj); + mp_hal_pin_obj_t mode_pin = args[ARG_mode_pin].u_obj == MP_OBJ_NULL ? PCNT_PIN_NOT_USED : mp_hal_get_pin_obj(args[ARG_mode_pin].u_obj); + pcnt_set_pin(self->unit, channel, pin, mode_pin); + } + + if ( + args[ARG_rising].u_obj != MP_OBJ_NULL || args[ARG_falling].u_obj != MP_OBJ_NULL || + args[ARG_mode_low].u_obj != MP_OBJ_NULL || args[ARG_mode_high].u_obj != MP_OBJ_NULL + ) { + mp_uint_t rising = args[ARG_rising].u_obj == MP_OBJ_NULL ? PCNT_COUNT_DIS : mp_obj_get_int(args[ARG_rising].u_obj); + mp_uint_t falling = args[ARG_falling].u_obj == MP_OBJ_NULL ? PCNT_COUNT_DIS : mp_obj_get_int(args[ARG_falling].u_obj); + mp_uint_t mode_low = args[ARG_mode_low].u_obj == MP_OBJ_NULL ? PCNT_MODE_KEEP : mp_obj_get_int(args[ARG_mode_low].u_obj); + mp_uint_t mode_high = args[ARG_mode_high].u_obj == MP_OBJ_NULL ? PCNT_MODE_KEEP : mp_obj_get_int(args[ARG_mode_high].u_obj); + if (rising >= PCNT_COUNT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("rising")); + } + if (falling >= PCNT_COUNT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("falling")); + } + if (mode_low >= PCNT_MODE_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("mode_low")); + } + if (mode_high >= PCNT_MODE_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("mode_high")); + } + pcnt_set_mode(self->unit, channel, rising, falling, mode_high, mode_low); + } + + if (args[ARG_filter].u_obj != MP_OBJ_NULL) { + mp_uint_t filter = mp_obj_get_int(args[ARG_filter].u_obj); + if (filter > 1023) { + mp_raise_ValueError(MP_ERROR_TEXT("filter")); + } + if (filter) { + check_esp_err(pcnt_set_filter_value(self->unit, filter)); + check_esp_err(pcnt_filter_enable(self->unit)); + } else { + check_esp_err(pcnt_filter_disable(self->unit)); + } + } + + bool clear = false; + + if (args[ARG_minimum].u_obj != MP_OBJ_NULL) { + mp_int_t minimum = mp_obj_get_int(args[ARG_minimum].u_obj); + if (minimum < -32768 || minimum > 0) { + mp_raise_ValueError(MP_ERROR_TEXT("minimum")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_L_LIM, minimum)); + clear = true; + } + + if (args[ARG_maximum].u_obj != MP_OBJ_NULL) { + mp_int_t maximum = mp_obj_get_int(args[ARG_maximum].u_obj); + if (maximum < 0 || maximum > 32767) { + mp_raise_ValueError(MP_ERROR_TEXT("maximum")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_H_LIM, maximum)); + clear = true; + } + + if (args[ARG_threshold0].u_obj != MP_OBJ_NULL) { + mp_int_t threshold0 = mp_obj_get_int(args[ARG_threshold0].u_obj); + if (threshold0 < -32768 || threshold0 > 32767) { + mp_raise_ValueError(MP_ERROR_TEXT("threshold0")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_0, threshold0)); + clear = true; + } + + if (args[ARG_threshold1].u_obj != MP_OBJ_NULL) { + mp_int_t threshold1 = mp_obj_get_int(args[ARG_threshold1].u_obj); + if (threshold1 < -32768 || threshold1 > 32767) { + mp_raise_ValueError(MP_ERROR_TEXT("threshold1")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_1, threshold1)); + clear = true; + } + + if (args[ARG_value].u_obj != MP_OBJ_NULL) { + mp_int_t value = mp_obj_get_int(args[ARG_value].u_obj); + if (value != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("can only set value to 0")); + } + clear = true; + } + + if (clear) { + check_esp_err(pcnt_counter_clear(self->unit)); + } +} + +static void esp32_pcnt_disable_events_helper(esp32_pcnt_obj_t *self) { + if (self->handler != MP_OBJ_NULL) { + for (pcnt_evt_type_t evt_type = PCNT_EVT_THRES_1; evt_type <= PCNT_EVT_ZERO; evt_type <<= 1) { + check_esp_err(pcnt_event_disable(self->unit, evt_type)); + } + check_esp_err(pcnt_isr_handler_remove(self->unit)); + self->handler = MP_OBJ_NULL; + self->pending = 0; + } +} + +static void esp32_pcnt_deinit_helper(esp32_pcnt_obj_t *self) { + esp32_pcnt_disable_events_helper(self); + + pcnt_config_t channel_config = { + .unit = self->unit, + .channel = 0, + .pulse_gpio_num = PCNT_PIN_NOT_USED, + .pos_mode = PCNT_COUNT_DIS, + .neg_mode = PCNT_COUNT_DIS, + .ctrl_gpio_num = PCNT_PIN_NOT_USED, + .lctrl_mode = PCNT_MODE_KEEP, + .hctrl_mode = PCNT_MODE_KEEP, + .counter_l_lim = 0, + .counter_h_lim = 0, + }; + check_esp_err(pcnt_unit_config(&channel_config)); + channel_config.channel = 1; + check_esp_err(pcnt_unit_config(&channel_config)); + + check_esp_err(pcnt_filter_disable(self->unit)); + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_0, 0)); + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_1, 0)); + check_esp_err(pcnt_counter_pause(self->unit)); + check_esp_err(pcnt_counter_clear(self->unit)); +} + +static mp_obj_t esp32_pcnt_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + if (n_pos_args > 1) { + mp_raise_TypeError(MP_ERROR_TEXT("function takes at most 1 positional argument")); + } + + pcnt_unit_t unit = PCNT_UNIT_MAX; + if (n_pos_args == 1) { + unit = mp_obj_get_int(args[0]); + if (unit < 0 || unit >= PCNT_UNIT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid id")); + } + } + + esp32_pcnt_obj_t *self = MP_STATE_PORT(esp32_pcnt_obj_head); + mp_uint_t allocated = 0; + while (self != NULL) { + if (self->unit == unit) { + break; + } + allocated |= 1 << self->unit; + self = self->next; + } + if (self == NULL) { + if (unit == PCNT_UNIT_MAX) { + for (unit = 0; unit < PCNT_UNIT_MAX; unit++) { + if (!(allocated & 1)) { + break; + } + allocated >>= 1; + } + if (unit == PCNT_UNIT_MAX) { + mp_raise_TypeError(MP_ERROR_TEXT("no unused PCNT unit")); + } + } + self = m_new_obj(esp32_pcnt_obj_t); + self->base.type = &esp32_pcnt_type; + self->unit = unit; + self->next = MP_STATE_PORT(esp32_pcnt_obj_head); + MP_STATE_PORT(esp32_pcnt_obj_head) = self; + esp32_pcnt_deinit_helper(self); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + esp32_pcnt_init_helper(self, 0, args + n_pos_args, &kw_args); + + if (!pcnt_isr_service_installed) { + check_esp_err(pcnt_isr_service_install(ESP_INTR_FLAG_IRAM)); + pcnt_isr_service_installed = true; + } + check_esp_err(pcnt_intr_enable(self->unit)); + + return MP_OBJ_FROM_PTR(self); +} + +static void esp32_pcnt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PCNT(%u)", self->unit); +} + +static mp_obj_t esp32_pcnt_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + esp32_pcnt_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_pcnt_init_obj, 1, esp32_pcnt_init); + +static mp_obj_t esp32_pcnt_deinit(mp_obj_t self_in) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + esp32_pcnt_deinit_helper(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_pcnt_deinit_obj, esp32_pcnt_deinit); + +static mp_obj_t esp32_pcnt_value(size_t n_args, const mp_obj_t *pos_args) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + int16_t value; + if (n_args == 2) { + value = mp_obj_get_int(pos_args[1]); + if (value != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("can only set value to 0")); + } + } + while (true) { + check_esp_err(pcnt_get_counter_value(self->unit, &value)); + if (self->pending && self->handler != MP_OBJ_NULL) { + esp32_pcnt_run_irq_handlers(NULL); + } else { + break; + } + } + if (n_args == 2) { + check_esp_err(pcnt_counter_clear(self->unit)); + } + return MP_OBJ_NEW_SMALL_INT(value); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_pcnt_value_obj, 1, 2, esp32_pcnt_value); + +static mp_obj_t esp32_pcnt_irq(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_handler, + ARG_trigger, + }; + + 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_ZERO} }, + }; + + esp32_pcnt_obj_t *self = pos_args[0]; + 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 || trigger >= (PCNT_EVT_ZERO << 1)) { + mp_raise_ValueError(MP_ERROR_TEXT("trigger")); + } + + if (handler != mp_const_none) { + if (self->handler == MP_OBJ_NULL) { + pcnt_isr_handler_add(self->unit, esp32_pcnt_intr_handler, (void *)self); + } + self->handler = handler; + for (pcnt_evt_type_t evt_type = PCNT_EVT_THRES_1; evt_type <= PCNT_EVT_ZERO; evt_type <<= 1) { + if (trigger & evt_type) { + pcnt_event_enable(self->unit, evt_type); + } else { + pcnt_event_disable(self->unit, evt_type); + } + } + } else { + esp32_pcnt_disable_events_helper(self); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_pcnt_irq_obj, 1, esp32_pcnt_irq); + +static mp_obj_t esp32_pcnt_start(mp_obj_t self_in) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(pcnt_counter_resume(self->unit)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_pcnt_start_obj, esp32_pcnt_start); + +static mp_obj_t esp32_pcnt_stop(mp_obj_t self_in) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(pcnt_counter_pause(self->unit)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_pcnt_stop_obj, esp32_pcnt_stop); + +static const mp_rom_map_elem_t esp32_pcnt_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_pcnt_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&esp32_pcnt_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&esp32_pcnt_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&esp32_pcnt_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&esp32_pcnt_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_pcnt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_pcnt_deinit_obj) }, + + // Constants + { MP_ROM_QSTR(MP_QSTR_IGNORE), MP_ROM_INT(PCNT_COUNT_DIS) }, + { MP_ROM_QSTR(MP_QSTR_INCREMENT), MP_ROM_INT(PCNT_COUNT_INC) }, + { MP_ROM_QSTR(MP_QSTR_DECREMENT), MP_ROM_INT(PCNT_COUNT_DEC) }, + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(PCNT_MODE_KEEP) }, + { MP_ROM_QSTR(MP_QSTR_REVERSE), MP_ROM_INT(PCNT_MODE_REVERSE) }, + { MP_ROM_QSTR(MP_QSTR_HOLD), MP_ROM_INT(PCNT_MODE_DISABLE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_ZERO), MP_ROM_INT(PCNT_EVT_ZERO) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_THRESHOLD0), MP_ROM_INT(PCNT_EVT_THRES_0) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_THRESHOLD1), MP_ROM_INT(PCNT_EVT_THRES_1) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_MINIMUM), MP_ROM_INT(PCNT_EVT_L_LIM) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_MAXIMUM), MP_ROM_INT(PCNT_EVT_H_LIM) }, +}; +static MP_DEFINE_CONST_DICT(esp32_pcnt_locals_dict, esp32_pcnt_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + esp32_pcnt_type, + MP_QSTR_PCNT, + MP_TYPE_FLAG_NONE, + make_new, esp32_pcnt_make_new, + print, esp32_pcnt_print, + locals_dict, &esp32_pcnt_locals_dict + ); + + +#endif // MICROPY_PY_ESP32_PCNT diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 03dc0807a025c..1484240afe4a7 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -58,6 +58,7 @@ #include "uart.h" #include "usb.h" #include "usb_serial_jtag.h" +#include "modesp32.h" #include "modmachine.h" #include "modnetwork.h" @@ -178,6 +179,10 @@ void mp_task(void *pvParameter) { machine_timer_deinit_all(); + #if MICROPY_PY_ESP32_PCNT + esp32_pcnt_deinit_all(); + #endif + #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 0a73a0faf06ef..1c8840a61bc65 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -232,6 +232,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, + #if MICROPY_PY_ESP32_PCNT + { MP_ROM_QSTR(MP_QSTR_PCNT), MP_ROM_PTR(&esp32_pcnt_type) }, + #endif { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 { MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) }, diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index a685b7b38fe6f..81ab94dc633ae 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -66,6 +66,12 @@ extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; +#if MICROPY_PY_ESP32_PCNT +extern const mp_obj_type_t esp32_pcnt_type; + +void esp32_pcnt_deinit_all(void); +#endif + esp_err_t rmt_driver_install_core1(uint8_t channel_id); #endif // MICROPY_INCLUDED_ESP32_MODESP32_H diff --git a/ports/esp32/modules/machine.py b/ports/esp32/modules/machine.py new file mode 100644 index 0000000000000..48606cef478cd --- /dev/null +++ b/ports/esp32/modules/machine.py @@ -0,0 +1,98 @@ +from umachine import * + +import esp32 + +if hasattr(esp32, "PCNT"): + + class Counter: + _INSTANCES = {} + _PCNT = esp32.PCNT + RISING = 1 + FALLING = 2 + UP = _PCNT.INCREMENT + DOWN = _PCNT.DECREMENT + + def __new__(cls, unit_id, *args, **kwargs): + self = cls._INSTANCES.get(unit_id) + if self is None: + self = super(Counter, cls).__new__(cls) + self._pcnt = self._PCNT(minimum=-32000, maximum=32000) + self._offset = 0 + cls._INSTANCES[unit_id] = self + if args or kwargs: + self.init(*args, **kwargs) + return self + + def __init__(self, unit_id, *args, **kwargs): + pass + + def init(self, *args, **kwargs): + self._init(*args, **kwargs) + self._pcnt.irq(self._wrap, self._PCNT.IRQ_MINIMUM | self._PCNT.IRQ_MAXIMUM) + self._pcnt.start() + + def deinit(self): + self._pcnt.deinit() + self._pcnt.init(minimum=-32000, maximum=32000) + self._offset = 0 + + def value(self, value=None): + # This loop deals with the possibility that a PCNT wrap occurs + # between retrieving self._offset and self._pcnt.value() + while True: + offset = self._offset + current = self._pcnt.value() + if self._offset == offset: + break + current += offset + if value is not None: + # In-place addition for atomicity + self._offset += value - current + return current + + def _wrap(self, mask): + if mask & self._PCNT.IRQ_MINIMUM: + self._offset -= 32000 + elif mask & self._PCNT.IRQ_MAXIMUM: + self._offset += 32000 + + def _init(self, src, edge=RISING, direction=UP, filter_ns=0): + self._pcnt.init( + pin=src, + rising=direction if edge & self.RISING else self._PCNT.IGNORE, + falling=direction if edge & self.FALLING else self._PCNT.IGNORE, + filter=min(max(0, filter_ns * 80 // 1000), 1023), + ) + + class Encoder(Counter): + _INSTANCES = {} + + def _init(self, phase_a, phase_b, filter_ns=0, phases=4): + if phases not in (1, 2, 4): + raise ValueError("phases") + self._pcnt.init( + pin=phase_a, + falling=self._PCNT.INCREMENT, + rising=self._PCNT.DECREMENT, + mode_pin=phase_b, + mode_low=self._PCNT.HOLD if phases == 1 else self._PCNT.REVERSE, + filter=min(max(0, filter_ns * 80 // 1000), 1023), + ) + if phases == 4: + self._pcnt.init( + channel=1, + pin=phase_b, + falling=self._PCNT.DECREMENT, + rising=self._PCNT.INCREMENT, + mode_pin=phase_a, + mode_low=self._PCNT.REVERSE, + ) + else: + self._pcnt.init(channel=1, pin=None, rising=self._PCNT.IGNORE) + self._phases = phases + + def phases(self): + return self._phases + + +del esp32 diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index e36d12bc056ac..bf361bfff0708 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -69,6 +69,7 @@ #define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys #define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf #define MICROPY_SCHEDULER_DEPTH (8) +#define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_VFS (1) // control over Python builtins @@ -186,6 +187,9 @@ #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_SOCKET_EVENTS (MICROPY_PY_WEBREPL) #define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1) +#ifndef MICROPY_PY_ESP32_PCNT +#define MICROPY_PY_ESP32_PCNT (1) +#endif // fatfs configuration #define MICROPY_FATFS_ENABLE_LFN (1) 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