summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElvis Pfutzenreuter <epxx@epxx.co>2024-11-22 20:38:58 -0300
committerDamien George <damien@micropython.org>2025-11-17 10:45:19 +1100
commit2a3b9b0b4bed6ea51f1053e224278e9dbb266c35 (patch)
tree7f87b28423868f94b5960202add77af51b8614d0
parent27544a2d81da5b0d804a932d98d680f121a22b8f (diff)
esp32/esp32_rmt: Update RMT module to use the new RMT API.HEADorigin/masterorigin/HEADmaster
The current `esp32.RMT` class uses a legacy API from ESP-IDF 4.x. The ESP-IDF 5.x offers a new API, which is overall better, and easier to implement the RX side in the future. This commit updates the module and the documentation, preserving the current MicroPython RMT API as much as possible. The bitstream RMT implementation was updated as well, since ESP-IDF does not allow firmware to reference legacy and new APIs at the same time (it resets right after boot with an error message, even if neither module is imported). The documentation is updated accordingly. Signed-off-by: Elvis Pfutzenreuter <elvis.pfutzenreuter@gmail.com>
-rw-r--r--docs/esp32/quickref.rst9
-rw-r--r--docs/library/esp32.rst108
-rw-r--r--ports/esp32/boards/sdkconfig.base1
-rw-r--r--ports/esp32/esp32_common.cmake8
-rw-r--r--ports/esp32/esp32_rmt.c368
-rw-r--r--ports/esp32/machine_bitstream.c141
-rw-r--r--ports/esp32/modesp32.h6
7 files changed, 396 insertions, 245 deletions
diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst
index 25e52ec32..0cf7d81a7 100644
--- a/docs/esp32/quickref.rst
+++ b/docs/esp32/quickref.rst
@@ -837,9 +837,9 @@ The RMT is ESP32-specific and allows generation of accurate digital pulses with
import esp32
from machine import Pin
- r = esp32.RMT(0, pin=Pin(18), clock_div=8)
- r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8)
- # The channel resolution is 100ns (1/(source_freq/clock_div)).
+ r = esp32.RMT(pin=Pin(18), resolution_hz=10000000)
+ r # RMT(pin=18, source_freq=80000000, resolution_hz=10000000)
+ # The channel resolution is based on resolution_hz, i.e. 100ns for 10000000
r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns
The ESP32-C2 family does not include any RMT peripheral, so this class is
@@ -903,8 +903,7 @@ The APA106 driver extends NeoPixel, but internally uses a different colour order
``NeoPixel`` object.
For low-level driving of a NeoPixel see `machine.bitstream`.
-This low-level driver uses an RMT channel by default. To configure this see
-`RMT.bitstream_channel`.
+This low-level driver uses an RMT channel by default.
APA102 (DotStar) uses a different driver as it has an additional clock pin.
diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst
index 84f33f46e..0f0637741 100644
--- a/docs/library/esp32.rst
+++ b/docs/library/esp32.rst
@@ -362,29 +362,24 @@ used to transmit or receive many other types of digital signals::
import esp32
from machine import Pin
- r = esp32.RMT(0, pin=Pin(18), clock_div=8)
- r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8, idle_level=0)
+ r = esp32.RMT(pin=Pin(18), resolution_hz=10000000)
+ r # RMT(pin=18, source_freq=80000000, resolution_hz=10000000, idle_level=0)
# To apply a carrier frequency to the high output
- r = esp32.RMT(0, pin=Pin(18), clock_div=8, tx_carrier=(38000, 50, 1))
+ r = esp32.RMT(pin=Pin(18), resolution_hz=10000000, tx_carrier=(38000, 50, 1))
- # The channel resolution is 100ns (1/(source_freq/clock_div)).
+ # The channel resolution is 100ns (1/resolution_hz)
r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns
The input to the RMT module is an 80MHz clock (in the future it may be able to
-configure the input clock but, for now, it's fixed). ``clock_div`` *divides*
-the clock input which determines the resolution of the RMT channel. The
-numbers specified in ``write_pulses`` are multiplied by the resolution to
+configure the input clock but, for now, it's fixed). ``resolution_hz`` determines
+the resolution of the RMT channel. The numbers specified in ``write_pulses`` are
+multiplied by the resolution to
define the pulses.
-``clock_div`` is an 8-bit divider (0-255) and each pulse can be defined by
-multiplying the resolution by a 15-bit (1-``PULSE_MAX``) number. There are eight
-channels (0-7) and each can have a different clock divider.
-
-So, in the example above, the 80MHz clock is divided by 8. Thus the
-resolution is (1/(80Mhz/8)) 100ns. Since the ``start`` level is 0 and toggles
-with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns,
-100ns, 4000ns].
+So, in the example above, the resolution is resolution is (1/10Mhz) = 100ns.
+Since the ``start`` level is 0 and toggles with each number, the bitstream is
+``0101`` with durations of [100ns, 2000ns, 100ns, 4000ns].
For more details see Espressif's `ESP-IDF RMT documentation.
<https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html>`_.
@@ -395,13 +390,24 @@ For more details see Espressif's `ESP-IDF RMT documentation.
*beta feature* and the interface may change in the future.
-.. class:: RMT(channel, *, pin=None, clock_div=8, idle_level=False, tx_carrier=None)
+.. class:: RMT(channel, *, pin=None, resolution_hz=10000000, clock_div=None, idle_level=False, num_symbols=48|64, tx_carrier=None)
This class provides access to one of the eight RMT channels. *channel* is
- required and identifies which RMT channel (0-7) will be configured. *pin*,
- also required, configures which Pin is bound to the RMT channel. *clock_div*
- is an 8-bit clock divider that divides the source clock (80MHz) to the RMT
- channel allowing the resolution to be specified. *idle_level* specifies
+ optional and a dummy parameter for backward compatibility. *pin* is required
+ and configures which Pin is bound to the RMT channel.
+ *resolution_hz* defines the resolution/unit of the samples.
+ For example, 1,000,000 means the unit is microsecond. The pulse widths can
+ assume values up to *RMT.PULSE_MAX*, so the resolution should be selected
+ accordingly to the signal to be transmitted.
+ *clock_div* (deprecated) is equivalent to *resolution_hz*, but expressed as
+ a clock divider that divides the source clock (80MHz) to the RMT
+ channel allowing the resolution to be specified. Either *clock_div* and
+ *resolution_hz* may be supplied, but not both.
+ *num_symbols* specifies the
+ RMT buffer allocated for this channel (minimum 48 or 64, depending on chip), from a small pool of
+ symbols (192 to 512, depending on chip) that are shared by all channels. This buffer does not limit the
+ size of the pulse train that you can send, but bigger buffers reduce the
+ CPU load and the potential of glitches/imprecise pulse lengths. *idle_level* specifies
what level the output will be when no transmission is in progress and can
be any value that converts to a boolean, with ``True`` representing high
voltage and ``False`` representing low.
@@ -419,21 +425,52 @@ For more details see Espressif's `ESP-IDF RMT documentation.
.. method:: RMT.clock_div()
Return the clock divider. Note that the channel resolution is
- ``1 / (source_freq / clock_div)``.
+ ``1 / (source_freq / clock_div)``. (Method deprecated. The value may
+ not be faithful if resolution was supplied as *resolution_hz*.)
.. method:: RMT.wait_done(*, timeout=0)
Returns ``True`` if the channel is idle or ``False`` if a sequence of
pulses started with `RMT.write_pulses` is being transmitted. If the
*timeout* keyword argument is given then block for up to this many
- milliseconds for transmission to complete.
+ milliseconds for transmission to complete. Timeout of -1 blocks until
+ transmission is complete (and blocks forever if loop is enabled).
.. method:: RMT.loop(enable_loop)
Configure looping on the channel. *enable_loop* is bool, set to ``True`` to
enable looping on the *next* call to `RMT.write_pulses`. If called with
``False`` while a looping sequence is currently being transmitted then the
- current loop iteration will be completed and then transmission will stop.
+ transmission will stop. (Method deprecated by `RMT.loop_count`.)
+
+.. method:: RMT.loop_count(n)
+
+ Configure looping on the channel. *n* is int. Affects the *next* call to
+ `RMT.write_pulses`. Set to ``0`` to disable looping, ``-1`` to enable
+ infinite looping, or a positive number to loop for a given number of times.
+ If *n* is changed, the current transmission is stopped.
+
+ Note: looping for a finite number of times is not supported by all flavors
+ of ESP32.
+
+.. method:: RMT.active([boolean])
+
+ If called without parameters, returns *True* if there is an ongoing transmission.
+
+ If called with parameter *False*, stops the ongoing transmission.
+ This is useful to stop an infinite transmission loop.
+ The current loop is finished and transmission stops.
+ The object is not invalidated, and the RMT channel is again enabled when a new
+ transmission is started.
+
+ Calling with parameter *True* does not restart transmission. A new transmission
+ should always be initiated by *write_pulses()*.
+
+.. method:: RMT.deinit()
+
+ Release all RMT resources and invalidate the object. All subsequent method
+ calls will raise OSError. Useful to free RMT resources without having to wait
+ for the object to be garbage-collected.
.. method:: RMT.write_pulses(duration, data=True)
@@ -463,17 +500,28 @@ For more details see Espressif's `ESP-IDF RMT documentation.
new sequence of pulses. Looping sequences longer than 126 pulses is not
supported by the hardware.
+.. staticmethod:: RMT.bitstream_rmt([value])
+
+ Configure RMT usage in the `machine.bitstream` implementation.
+
+ If *value* is ``True``, bitstream tries to use RMT if possible. If *value*
+ is ``False``, bitstream sticks to the bit-banging implementation.
+
+ If no parameter is supplied, it returns the current state. The default state
+ is ``True``.
+
.. staticmethod:: RMT.bitstream_channel([value])
- Select which RMT channel is used by the `machine.bitstream` implementation.
- *value* can be ``None`` or a valid RMT channel number. The default RMT
- channel is the highest numbered one.
+ *This function is deprecated and will be replaced by `RMT.bitstream_rmt()`.*
+
+ Passing in no argument will return ``1`` if RMT was enabled for the `machine.bitstream`
+ feature, and ``None`` otherwise.
- Passing in ``None`` disables the use of RMT and instead selects a bit-banging
- implementation for `machine.bitstream`.
+ Passing any non-negative integer argument is equivalent to calling ``RMT.bitstream_rmt(True)``.
- Passing in no argument will not change the channel. This function returns
- the current channel number.
+ .. note:: In previous versions of MicroPython it was necessary to use this function to assign
+ a specific RMT channel number for the bitstream, but the channel number is now assigned
+ dynamically.
Constants
---------
diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base
index e1b30b8e5..6c8368ac4 100644
--- a/ports/esp32/boards/sdkconfig.base
+++ b/ports/esp32/boards/sdkconfig.base
@@ -124,7 +124,6 @@ CONFIG_UART_ISR_IN_IRAM=y
# IDF 5 deprecated
CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN=y
-CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y
CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN=y
CONFIG_ETH_USE_SPI_ETHERNET=y
diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake
index 79a60adac..6922ac5fe 100644
--- a/ports/esp32/esp32_common.cmake
+++ b/ports/esp32/esp32_common.cmake
@@ -262,6 +262,14 @@ target_compile_options(${MICROPY_TARGET} PUBLIC
target_include_directories(${MICROPY_TARGET} PUBLIC
${IDF_PATH}/components/bt/host/nimble/nimble
)
+if (IDF_VERSION VERSION_LESS "5.3")
+# Additional include directories needed for private RMT header.
+# IDF 5.x versions before 5.3.1
+ message(STATUS "Using private rmt headers for ${IDF_VERSION}")
+ target_include_directories(${MICROPY_TARGET} PRIVATE
+ ${IDF_PATH}/components/driver/rmt
+ )
+endif()
# Add additional extmod and usermod components.
if (MICROPY_PY_BTREE)
diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c
index f3bfbecdd..85a98c291 100644
--- a/ports/esp32/esp32_rmt.c
+++ b/ports/esp32/esp32_rmt.c
@@ -4,6 +4,7 @@
* The MIT License (MIT)
*
* Copyright (c) 2019 "Matt Trentini" <matt.trentini@gmail.com>
+ * Copyright (c) 2024 "Elvis Pfützenreuter" <elvis.pfutzenreuter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -26,13 +27,16 @@
#include "py/mphal.h"
#include "py/runtime.h"
+#include "py/stream.h"
#include "modmachine.h"
#include "modesp32.h"
#include "esp_task.h"
#if SOC_RMT_SUPPORTED
-#include "driver/rmt.h"
+#include "esp_clk_tree.h"
+#include "driver/rmt_tx.h"
+#include "driver/rmt_encoder.h"
// This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF:
//
@@ -46,105 +50,101 @@
// Originally designed to generate infrared remote control signals, the module is very
// flexible and quite easy-to-use.
//
-// This current MicroPython implementation lacks some major features, notably receive pulses
-// and carrier output.
-
-// Last available RMT channel that can transmit.
-#define RMT_LAST_TX_CHANNEL (SOC_RMT_TX_CANDIDATES_PER_GROUP - 1)
+// This code exposes the RMT TX feature.
// Forward declaration
extern const mp_obj_type_t esp32_rmt_type;
typedef struct _esp32_rmt_obj_t {
mp_obj_base_t base;
- uint8_t channel_id;
+ rmt_channel_handle_t channel;
+ bool enabled;
gpio_num_t pin;
- uint8_t clock_div;
- mp_uint_t num_items;
- rmt_item32_t *items;
- bool loop_en;
+ uint32_t clock_freq;
+ int resolution_hz;
+ mp_uint_t cap_items;
+ rmt_symbol_word_t *items;
+ int loop_count;
+ int tx_ongoing;
+
+ rmt_encoder_handle_t encoder;
+ mp_uint_t idle_level;
} esp32_rmt_obj_t;
-// Current channel used for machine.bitstream, in the machine_bitstream_high_low_rmt
-// implementation. A value of -1 means do not use RMT.
-int8_t esp32_rmt_bitstream_channel_id = RMT_LAST_TX_CHANNEL;
-
-#if MP_TASK_COREID == 0
-
-typedef struct _rmt_install_state_t {
- SemaphoreHandle_t handle;
- uint8_t channel_id;
- esp_err_t ret;
-} rmt_install_state_t;
-
-static void rmt_install_task(void *pvParameter) {
- rmt_install_state_t *state = pvParameter;
- state->ret = rmt_driver_install(state->channel_id, 0, 0);
- xSemaphoreGive(state->handle);
- vTaskDelete(NULL);
- for (;;) {
- }
-}
-
-// Call rmt_driver_install on core 1. This ensures that the RMT interrupt handler is
-// serviced on core 1, so that WiFi (if active) does not interrupt it and cause glitches.
-esp_err_t rmt_driver_install_core1(uint8_t channel_id) {
- TaskHandle_t th;
- rmt_install_state_t state;
- state.handle = xSemaphoreCreateBinary();
- state.channel_id = channel_id;
- xTaskCreatePinnedToCore(rmt_install_task, "rmt_install_task", 2048 / sizeof(StackType_t), &state, ESP_TASK_PRIO_MIN + 1, &th, 1);
- xSemaphoreTake(state.handle, portMAX_DELAY);
- vSemaphoreDelete(state.handle);
- return state.ret;
-}
-
-#else
+// Decide RMT usage in the machine_bitstream_high_low_rmt implementation.
+bool esp32_rmt_bitstream_enabled = true;
-// MicroPython runs on core 1, so we can call the RMT installer directly and its
-// interrupt handler will also run on core 1.
-esp_err_t rmt_driver_install_core1(uint8_t channel_id) {
- return rmt_driver_install(channel_id, 0, 0);
+static bool IRAM_ATTR esp32_rmt_tx_trans_done(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *edata, void *user_ctx) {
+ esp32_rmt_obj_t *self = user_ctx;
+ self->tx_ongoing -= 1;
+ return false;
}
-#endif // MP_TASK_COREID==0
-
static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
static const mp_arg_t allowed_args[] = {
- { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_id, MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
- { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution
+ { MP_QSTR_resolution_hz, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_idle_level, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, // low voltage
{ MP_QSTR_tx_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // no carrier
+ { MP_QSTR_num_symbols, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = SOC_RMT_MEM_WORDS_PER_CHANNEL} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
- mp_uint_t channel_id = args[0].u_int;
+ // RMT channel is an opaque struct in current RMT API and channel_id is a dummy parameter
+ // mp_uint_t channel_id = args[0].u_int;
gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj);
- mp_uint_t clock_div = args[2].u_int;
- mp_uint_t idle_level = args[3].u_bool;
- mp_obj_t tx_carrier_obj = args[4].u_obj;
- if (esp32_rmt_bitstream_channel_id >= 0 && channel_id == esp32_rmt_bitstream_channel_id) {
- mp_raise_ValueError(MP_ERROR_TEXT("channel used by bitstream"));
+ uint32_t clock_freq;
+ check_esp_err(esp_clk_tree_src_get_freq_hz(RMT_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clock_freq));
+
+ mp_uint_t resolution_hz;
+ if (args[2].u_obj != mp_const_none && args[3].u_obj != mp_const_none) {
+ mp_raise_ValueError(MP_ERROR_TEXT("resolution_hz and clock_div are mutually exclusive"));
+ } else if (args[2].u_obj == mp_const_none && args[3].u_obj == mp_const_none) {
+ // default value
+ resolution_hz = 10000000;
+ } else if (args[2].u_obj != mp_const_none) {
+ resolution_hz = mp_obj_get_int(args[2].u_obj);
+ if (resolution_hz <= 0) {
+ mp_raise_ValueError(MP_ERROR_TEXT("resolution_hz must be positive"));
+ }
+ } else if (args[3].u_obj != mp_const_none) {
+ mp_uint_t clock_div = mp_obj_get_int(args[3].u_obj);
+ if (clock_div < 1 || clock_div > 255) {
+ mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255"));
+ }
+ resolution_hz = clock_freq / clock_div;
}
- if (clock_div < 1 || clock_div > 255) {
- mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255"));
+ mp_uint_t idle_level = args[4].u_bool;
+ mp_obj_t tx_carrier_obj = args[5].u_obj;
+ mp_uint_t num_symbols = args[6].u_int;
+
+ if (num_symbols < SOC_RMT_MEM_WORDS_PER_CHANNEL || ((num_symbols % 2) == 1)) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("num_symbols must be even and at least %d"), SOC_RMT_MEM_WORDS_PER_CHANNEL);
}
esp32_rmt_obj_t *self = mp_obj_malloc_with_finaliser(esp32_rmt_obj_t, &esp32_rmt_type);
- self->channel_id = channel_id;
+ self->channel = NULL;
self->pin = pin_id;
- self->clock_div = clock_div;
- self->loop_en = false;
+ self->clock_freq = clock_freq;
+ self->resolution_hz = resolution_hz;
+ self->loop_count = 0;
+ self->tx_ongoing = 0;
+ self->idle_level = idle_level;
+ self->enabled = false;
+
+ rmt_tx_channel_config_t tx_chan_config = {
+ .clk_src = RMT_CLK_SRC_DEFAULT,
+ .gpio_num = self->pin,
+ .mem_block_symbols = num_symbols,
+ .resolution_hz = resolution_hz,
+ .trans_queue_depth = 4,
+ };
- rmt_config_t config = {0};
- config.rmt_mode = RMT_MODE_TX;
- config.channel = (rmt_channel_t)self->channel_id;
- config.gpio_num = self->pin;
- config.mem_block_num = 1;
- config.tx_config.loop_en = 0;
+ check_esp_err(rmt_new_tx_channel(&tx_chan_config, &self->channel));
if (tx_carrier_obj != mp_const_none) {
mp_obj_t *tx_carrier_details = NULL;
@@ -160,21 +160,21 @@ static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz
mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier duty must be 0..100"));
}
- config.tx_config.carrier_en = 1;
- config.tx_config.carrier_freq_hz = frequency;
- config.tx_config.carrier_duty_percent = duty;
- config.tx_config.carrier_level = level;
- } else {
- config.tx_config.carrier_en = 0;
+ rmt_carrier_config_t tx_carrier_cfg = {
+ .duty_cycle = ((float)duty) / 100.0,
+ .frequency_hz = frequency,
+ .flags.polarity_active_low = !level,
+ };
+ check_esp_err(rmt_apply_carrier(self->channel, &tx_carrier_cfg));
}
- config.tx_config.idle_output_en = 1;
- config.tx_config.idle_level = idle_level;
-
- config.clk_div = self->clock_div;
+ rmt_copy_encoder_config_t copy_encoder_config = {};
+ check_esp_err(rmt_new_copy_encoder(&copy_encoder_config, &self->encoder));
- check_esp_err(rmt_config(&config));
- check_esp_err(rmt_driver_install_core1(config.channel));
+ rmt_tx_event_callbacks_t callbacks = {
+ .on_trans_done = esp32_rmt_tx_trans_done,
+ };
+ check_esp_err(rmt_tx_register_event_callbacks(self->channel, &callbacks, self));
return MP_OBJ_FROM_PTR(self);
}
@@ -182,33 +182,72 @@ static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz
static void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (self->pin != -1) {
- bool idle_output_en;
- rmt_idle_level_t idle_level;
- check_esp_err(rmt_get_idle_level(self->channel_id, &idle_output_en, &idle_level));
- mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u, idle_level=%u)",
- self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div, idle_level);
+ mp_printf(print, "RMT(pin=%u, source_freq=%u, resolution_hz=%u, idle_level=%u)",
+ self->pin, self->clock_freq, self->resolution_hz, self->idle_level);
} else {
mp_printf(print, "RMT()");
}
}
+static void esp32_rmt_deactivate(esp32_rmt_obj_t *self) {
+ if (self->enabled) {
+ // FIXME: panics in ESP32 if called while TX is ongoing and TX sequence is long (>300ms)
+ // Does not panic in ESP32-S3, ESP32-C3 and ESP32-C6.
+ // Tested with ESP-IDF up to 5.5
+ // ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17692
+ //
+ // Cause is Interrupt WDT to trigger because ESP-IDF rmt_disable() disables
+ // interrupts and spinlocks until the ongoing TX sequence is finished.
+ //
+ // Workaround is never try to stop RMT sequences longer than 300ms (which are unusual
+ // anyway). Or apply the patch mentioned at the GitHub issue to ESP-IDF.
+ rmt_disable(self->channel);
+ self->enabled = false;
+ }
+}
+
+static mp_obj_t esp32_rmt_active(size_t n_args, const mp_obj_t *args) {
+ esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0]);
+
+ if (n_args == 1) {
+ return mp_obj_new_bool(self->enabled && self->tx_ongoing > 0);
+ } else if (mp_obj_is_true(args[1])) {
+ mp_raise_ValueError(MP_ERROR_TEXT("activate by calling write_pulses()"));
+ }
+
+ esp32_rmt_deactivate(self);
+
+ return mp_const_false;
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_active_obj, 1, 2, esp32_rmt_active);
+
static mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) {
- // fixme: check for valid channel. Return exception if error occurs.
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
if (self->pin != -1) { // Check if channel has already been deinitialised.
- rmt_driver_uninstall(self->channel_id);
+ esp32_rmt_deactivate(self);
+ rmt_tx_event_callbacks_t callbacks = {
+ .on_trans_done = NULL,
+ };
+ rmt_tx_register_event_callbacks(self->channel, &callbacks, self);
+ rmt_del_encoder(self->encoder);
+ rmt_del_channel(self->channel);
self->pin = -1; // -1 to indicate RMT is unused
+ self->tx_ongoing = 0;
m_free(self->items);
}
+
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit);
// Return the source frequency.
-// Currently only the APB clock (80MHz) can be used but it is possible other
+// Currently only the default clock (80MHz) can be used but it is possible other
// clock sources will added in the future.
static mp_obj_t esp32_rmt_source_freq() {
- return mp_obj_new_int(APB_CLK_FREQ);
+ uint32_t clock_freq;
+ check_esp_err(esp_clk_tree_src_get_freq_hz(RMT_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clock_freq));
+ return mp_obj_new_int(clock_freq);
}
static MP_DEFINE_CONST_FUN_OBJ_0(esp32_rmt_source_freq_obj, esp32_rmt_source_freq);
static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_source_obj, MP_ROM_PTR(&esp32_rmt_source_freq_obj));
@@ -216,7 +255,11 @@ static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_source_obj, MP_ROM_PTR(&esp32_
// Return the clock divider.
static mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) {
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
- return mp_obj_new_int(self->clock_div);
+ if (self->pin == -1) {
+ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized"));
+ }
+
+ return mp_obj_new_int(self->clock_freq / self->resolution_hz);
}
static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div);
@@ -233,29 +276,86 @@ static mp_obj_t esp32_rmt_wait_done(size_t n_args, const mp_obj_t *pos_args, mp_
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj);
+ if (self->pin == -1) {
+ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized"));
+ } else if (!self->enabled) {
+ return mp_const_true;
+ } else if (args[1].u_int == 0 && self->tx_ongoing > 0) {
+ // shortcut to avoid console spamming with timeout msgs by rmt_tx_wait_all_done()
+ return mp_const_false;
+ }
- esp_err_t err = rmt_wait_tx_done(self->channel_id, args[1].u_int / portTICK_PERIOD_MS);
+ esp_err_t err = rmt_tx_wait_all_done(self->channel, args[1].u_int);
return err == ESP_OK ? mp_const_true : mp_const_false;
}
static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done);
+static mp_uint_t esp32_rmt_stream_ioctl(
+ mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
+ if (request != MP_STREAM_POLL) {
+ *errcode = MP_EINVAL;
+ return MP_STREAM_ERROR;
+ }
+ esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_uint_t ret = 0;
+ if ((arg & MP_STREAM_POLL_WR) && self->tx_ongoing == 0) {
+ ret |= MP_STREAM_POLL_WR;
+ }
+ return ret;
+}
+
+static const mp_stream_p_t esp32_rmt_stream_p = {
+ .ioctl = esp32_rmt_stream_ioctl,
+};
+
+static void esp32_rmt_loop_in(esp32_rmt_obj_t *self, int new_loop_count) {
+ if (self->enabled && self->tx_ongoing > 0 && self->loop_count != 0 && new_loop_count == 0) {
+ // Break ongoing loop
+ esp32_rmt_deactivate(self);
+ }
+ self->loop_count = new_loop_count;
+}
+
static mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) {
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
- self->loop_en = mp_obj_get_int(loop);
- if (!self->loop_en) {
- bool loop_en;
- check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en));
- if (loop_en) {
- check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false));
- check_esp_err(rmt_set_tx_intr_en(self->channel_id, true));
- }
+ if (self->pin == -1) {
+ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized"));
}
+
+ bool loop_en = mp_obj_get_int(loop);
+ esp32_rmt_loop_in(self, loop_en ? -1 : 0);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop);
+static mp_obj_t esp32_rmt_loop_count(mp_obj_t self_in, mp_obj_t loop) {
+ esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ if (self->pin == -1) {
+ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized"));
+ }
+
+ int loop_count = mp_obj_get_int(loop);
+ if (loop_count < -1) {
+ mp_raise_ValueError(MP_ERROR_TEXT("arg must be -1, 0 or positive"));
+ }
+ esp32_rmt_loop_in(self, loop_count);
+ return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_count_obj, esp32_rmt_loop_count);
+
static mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) {
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0]);
+ if (self->pin == -1) {
+ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized"));
+ }
+
+ if (self->enabled) {
+ rmt_tx_wait_all_done(self->channel, -1);
+ } else {
+ check_esp_err(rmt_enable(self->channel));
+ self->enabled = true;
+ }
+
mp_obj_t duration_obj = args[1];
mp_obj_t data_obj = n_args > 2 ? args[2] : mp_const_true;
@@ -290,14 +390,12 @@ static mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) {
if (num_pulses == 0) {
mp_raise_ValueError(MP_ERROR_TEXT("No pulses"));
}
- if (self->loop_en && num_pulses > 126) {
- mp_raise_ValueError(MP_ERROR_TEXT("Too many pulses for loop"));
- }
mp_uint_t num_items = (num_pulses / 2) + (num_pulses % 2);
- if (num_items > self->num_items) {
- self->items = (rmt_item32_t *)m_realloc(self->items, num_items * sizeof(rmt_item32_t *));
- self->num_items = num_items;
+
+ if (num_items > self->cap_items) {
+ self->items = (rmt_symbol_word_t *)m_realloc(self->items, num_items * sizeof(rmt_symbol_word_t *));
+ self->cap_items = num_items;
}
for (mp_uint_t item_index = 0, pulse_index = 0; item_index < num_items; item_index++) {
@@ -314,63 +412,62 @@ static mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) {
}
}
- if (self->loop_en) {
- bool loop_en;
- check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en));
- if (loop_en) {
- check_esp_err(rmt_set_tx_intr_en(self->channel_id, true));
- check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false));
- }
- check_esp_err(rmt_wait_tx_done(self->channel_id, portMAX_DELAY));
- }
-
- #if !CONFIG_IDF_TARGET_ESP32S3
- check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false));
- #endif
-
- if (self->loop_en) {
- check_esp_err(rmt_set_tx_intr_en(self->channel_id, false));
- check_esp_err(rmt_set_tx_loop_mode(self->channel_id, true));
- }
+ rmt_transmit_config_t tx_config = {
+ .loop_count = self->loop_count,
+ .flags.eot_level = self->idle_level ? 1 : 0,
+ };
- #if CONFIG_IDF_TARGET_ESP32S3
- check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false));
- #endif
+ rmt_encoder_reset(self->encoder);
+ check_esp_err(rmt_transmit(self->channel, self->encoder, self->items, num_items * sizeof(rmt_symbol_word_t), &tx_config));
+ self->tx_ongoing += 1;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_write_pulses_obj, 2, 3, esp32_rmt_write_pulses);
+static mp_obj_t esp32_rmt_bitstream_rmt(size_t n_args, const mp_obj_t *args) {
+ if (n_args > 0) {
+ esp32_rmt_bitstream_enabled = mp_obj_is_true(args[0]);
+ }
+ return esp32_rmt_bitstream_enabled ? mp_const_true : mp_const_false;
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_rmt_fun_obj, 0, 1, esp32_rmt_bitstream_rmt);
+static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_rmt_obj, MP_ROM_PTR(&esp32_rmt_bitstream_rmt_fun_obj));
+
static mp_obj_t esp32_rmt_bitstream_channel(size_t n_args, const mp_obj_t *args) {
if (n_args > 0) {
if (args[0] == mp_const_none) {
- esp32_rmt_bitstream_channel_id = -1;
+ esp32_rmt_bitstream_enabled = false;
} else {
mp_int_t channel_id = mp_obj_get_int(args[0]);
- if (channel_id < 0 || channel_id > RMT_LAST_TX_CHANNEL) {
+ if (channel_id < 0) {
mp_raise_ValueError(MP_ERROR_TEXT("invalid channel"));
}
- esp32_rmt_bitstream_channel_id = channel_id;
+ esp32_rmt_bitstream_enabled = true;
}
}
- if (esp32_rmt_bitstream_channel_id < 0) {
+ if (!esp32_rmt_bitstream_enabled) {
return mp_const_none;
} else {
- return MP_OBJ_NEW_SMALL_INT(esp32_rmt_bitstream_channel_id);
+ return MP_OBJ_NEW_SMALL_INT(1);
}
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_channel_fun_obj, 0, 1, esp32_rmt_bitstream_channel);
static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_channel_obj, MP_ROM_PTR(&esp32_rmt_bitstream_channel_fun_obj));
+
static const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&esp32_rmt_active_obj) },
{ MP_ROM_QSTR(MP_QSTR_clock_div), MP_ROM_PTR(&esp32_rmt_clock_div_obj) },
{ MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) },
{ MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) },
+ { MP_ROM_QSTR(MP_QSTR_loop_count), MP_ROM_PTR(&esp32_rmt_loop_count_obj) },
{ MP_ROM_QSTR(MP_QSTR_write_pulses), MP_ROM_PTR(&esp32_rmt_write_pulses_obj) },
// Static methods
+ { MP_ROM_QSTR(MP_QSTR_bitstream_rmt), MP_ROM_PTR(&esp32_rmt_bitstream_rmt_obj) },
{ MP_ROM_QSTR(MP_QSTR_bitstream_channel), MP_ROM_PTR(&esp32_rmt_bitstream_channel_obj) },
// Class methods
@@ -387,7 +484,8 @@ MP_DEFINE_CONST_OBJ_TYPE(
MP_TYPE_FLAG_NONE,
make_new, esp32_rmt_make_new,
print, esp32_rmt_print,
- locals_dict, &esp32_rmt_locals_dict
+ locals_dict, &esp32_rmt_locals_dict,
+ protocol, &esp32_rmt_stream_p
);
#endif // SOC_RMT_SUPPORTED
diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c
index ed7fcc407..60addcc15 100644
--- a/ports/esp32/machine_bitstream.c
+++ b/ports/esp32/machine_bitstream.c
@@ -91,96 +91,95 @@ static void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, u
}
#if SOC_RMT_SUPPORTED
+
/******************************************************************************/
// RMT implementation
-#include "driver/rmt.h"
-
-// Logical 0 and 1 values (encoded as a rmt_item32_t).
-// The duration fields will be set later.
-static rmt_item32_t bitstream_high_low_0 = {{{ 0, 1, 0, 0 }}};
-static rmt_item32_t bitstream_high_low_1 = {{{ 0, 1, 0, 0 }}};
-
-// See https://github.com/espressif/esp-idf/blob/master/examples/common_components/led_strip/led_strip_rmt_ws2812.c
-// This is called automatically by the IDF during rmt_write_sample in order to
-// convert the byte stream to rmt_item32_t's.
-static void IRAM_ATTR bitstream_high_low_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) {
- if (src == NULL || dest == NULL) {
- *translated_size = 0;
- *item_num = 0;
- return;
- }
-
- size_t size = 0;
- size_t num = 0;
- uint8_t *psrc = (uint8_t *)src;
- rmt_item32_t *pdest = dest;
- while (size < src_size && num < wanted_num) {
- for (int i = 0; i < 8; i++) {
- // MSB first
- if (*psrc & (1 << (7 - i))) {
- pdest->val = bitstream_high_low_1.val;
- } else {
- pdest->val = bitstream_high_low_0.val;
- }
- num++;
- pdest++;
- }
- size++;
- psrc++;
- }
-
- *translated_size = size;
- *item_num = num;
-}
-
-// Use the reserved RMT channel to stream high/low data on the specified pin.
-static void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len, uint8_t channel_id) {
- rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel_id);
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0)
+#include "rmt_private.h"
+#endif
+#include "driver/rmt_tx.h"
+#include "driver/rmt_encoder.h"
+static bool machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) {
// Use 40MHz clock (although 2MHz would probably be sufficient).
- config.clk_div = 2;
-
- // Install the driver on this channel & pin.
- check_esp_err(rmt_config(&config));
- check_esp_err(rmt_driver_install_core1(config.channel));
+ uint32_t clock_div = 2;
+ rmt_channel_handle_t channel = NULL;
+ rmt_tx_channel_config_t tx_chan_config = {
+ .clk_src = RMT_CLK_SRC_DEFAULT,
+ .gpio_num = pin,
+ .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
+ .resolution_hz = APB_CLK_FREQ / clock_div,
+ .trans_queue_depth = 1,
+ };
+ if (rmt_new_tx_channel(&tx_chan_config, &channel) != ESP_OK) {
+ return false;
+ }
+ check_esp_err(rmt_enable(channel));
// Get the tick rate in kHz (this will likely be 40000).
- uint32_t counter_clk_khz = 0;
- check_esp_err(rmt_get_counter_clock(config.channel, &counter_clk_khz));
-
+ uint32_t counter_clk_khz = APB_CLK_FREQ / clock_div;
counter_clk_khz /= 1000;
// Convert nanoseconds to pulse duration.
- bitstream_high_low_0.duration0 = (counter_clk_khz * timing_ns[0]) / 1e6;
- bitstream_high_low_0.duration1 = (counter_clk_khz * timing_ns[1]) / 1e6;
- bitstream_high_low_1.duration0 = (counter_clk_khz * timing_ns[2]) / 1e6;
- bitstream_high_low_1.duration1 = (counter_clk_khz * timing_ns[3]) / 1e6;
-
- // Install the bits->highlow translator.
- rmt_translator_init(config.channel, bitstream_high_low_rmt_adapter);
-
- // Stream the byte data using the translator.
- check_esp_err(rmt_write_sample(config.channel, buf, len, true));
-
- // Wait 50% longer than we expect (if every bit takes the maximum time).
- uint32_t timeout_ms = (3 * len / 2) * (1 + (8 * MAX(timing_ns[0] + timing_ns[1], timing_ns[2] + timing_ns[3])) / 1000);
- check_esp_err(rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(timeout_ms)));
-
- // Uninstall the driver.
- check_esp_err(rmt_driver_uninstall(config.channel));
+ // Example: 500ns = 40000 * 500 / 1e6 = 20 ticks
+ // 20 ticks / 40MHz = 500e-9
+ rmt_bytes_encoder_config_t bytes_encoder_config = {
+ .bit0 = {
+ .level0 = 1,
+ .duration0 = (counter_clk_khz * timing_ns[0]) / 1e6,
+ .level1 = 0,
+ .duration1 = (counter_clk_khz * timing_ns[1]) / 1e6,
+ },
+ .bit1 = {
+ .level0 = 1,
+ .duration0 = (counter_clk_khz * timing_ns[2]) / 1e6,
+ .level1 = 0,
+ .duration1 = (counter_clk_khz * timing_ns[3]) / 1e6,
+ },
+ .flags.msb_first = 1
+ };
+
+ // Install the bits->highlow encoder.
+ rmt_encoder_handle_t encoder;
+ check_esp_err(rmt_new_bytes_encoder(&bytes_encoder_config, &encoder));
+
+ rmt_transmit_config_t tx_config = {
+ .loop_count = 0,
+ .flags.eot_level = 0,
+ };
+
+ // Stream the byte data using the encoder.
+ rmt_encoder_reset(encoder);
+ check_esp_err(rmt_transmit(channel, encoder, buf, len, &tx_config));
+
+ // Wait until completion.
+ rmt_tx_wait_all_done(channel, -1);
+
+ // Disable and release channel.
+ check_esp_err(rmt_del_encoder(encoder));
+ rmt_disable(channel);
+ #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0)
+ channel->del(channel);
+ #else
+ rmt_del_channel(channel);
+ #endif
// Cancel RMT output to GPIO pin.
esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false);
+
+ return true;
}
-#endif
+
+#endif // SOC_RMT_SUPPORTED
+
/******************************************************************************/
// Interface to machine.bitstream
void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) {
#if SOC_RMT_SUPPORTED
- if (esp32_rmt_bitstream_channel_id >= 0) {
- machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id);
+ if (esp32_rmt_bitstream_enabled && machine_bitstream_high_low_rmt(pin, timing_ns, buf, len)) {
+ // Use of RMT was successful.
return;
}
#endif
diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h
index 81ab94dc6..60c386565 100644
--- a/ports/esp32/modesp32.h
+++ b/ports/esp32/modesp32.h
@@ -1,6 +1,8 @@
#ifndef MICROPY_INCLUDED_ESP32_MODESP32_H
#define MICROPY_INCLUDED_ESP32_MODESP32_H
+#include "driver/rmt_tx.h"
+
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define RTC_VALID_EXT_PINS \
@@ -59,7 +61,7 @@
#define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS)
-extern int8_t esp32_rmt_bitstream_channel_id;
+extern bool esp32_rmt_bitstream_enabled;
extern const mp_obj_type_t esp32_nvs_type;
extern const mp_obj_type_t esp32_partition_type;
@@ -72,6 +74,4 @@ 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