esp32/esp32_rmt: Update RMT module to use the new RMT API.
Some checks failed
JavaScript code lint and formatting with Biome / eslint (push) Has been cancelled
Check code formatting / code-formatting (push) Has been cancelled
Check spelling with codespell / codespell (push) Has been cancelled
Build docs / build (push) Has been cancelled
Check examples / embedding (push) Has been cancelled
Package mpremote / build (push) Has been cancelled
.mpy file format and tools / test (push) Has been cancelled
Build ports metadata / build (push) Has been cancelled
alif port / build_alif (alif_ae3_build) (push) Has been cancelled
cc3200 port / build (push) Has been cancelled
esp32 port / build_idf (esp32_build_c2_c6) (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3) (push) Has been cancelled
esp8266 port / build (push) Has been cancelled
mimxrt port / build (push) Has been cancelled
nrf port / build (push) Has been cancelled
powerpc port / build (push) Has been cancelled
qemu port / build_and_test_arm (bigendian) (push) Has been cancelled
qemu port / build_and_test_arm (sabrelite) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_hardfp) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_softfp) (push) Has been cancelled
qemu port / build_and_test_rv32 (push) Has been cancelled
qemu port / build_and_test_rv64 (push) Has been cancelled
renesas-ra port / build_renesas_ra_board (push) Has been cancelled
rp2 port / build (push) Has been cancelled
samd port / build (push) Has been cancelled
stm32 port / build_stm32 (stm32_misc_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_nucleo_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_pyb_build) (push) Has been cancelled
unix port / minimal (push) Has been cancelled
unix port / reproducible (push) Has been cancelled
unix port / standard (push) Has been cancelled
unix port / standard_v2 (push) Has been cancelled
unix port / coverage (push) Has been cancelled
unix port / coverage_32bit (push) Has been cancelled
unix port / nanbox (push) Has been cancelled
unix port / longlong (push) Has been cancelled
unix port / float (push) Has been cancelled
unix port / gil_enabled (push) Has been cancelled
unix port / stackless_clang (push) Has been cancelled
unix port / float_clang (push) Has been cancelled
unix port / settrace_stackless (push) Has been cancelled
unix port / repr_b (push) Has been cancelled
unix port / macos (push) Has been cancelled
unix port / qemu_mips (push) Has been cancelled
unix port / qemu_arm (push) Has been cancelled
unix port / qemu_riscv64 (push) Has been cancelled
unix port / sanitize_address (push) Has been cancelled
unix port / sanitize_undefined (push) Has been cancelled
webassembly port / build (push) Has been cancelled
windows port / build-vs (Debug, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, dev) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, standard) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, dev) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, standard) (push) Has been cancelled
windows port / cross-build-on-linux (push) Has been cancelled
zephyr port / build (push) Has been cancelled
Python code lint and formatting with ruff / ruff (push) Has been cancelled

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>
This commit is contained in:
Elvis Pfutzenreuter
2024-11-22 20:38:58 -03:00
committed by Damien George
parent 27544a2d81
commit 2a3b9b0b4b
7 changed files with 392 additions and 241 deletions

View File

@@ -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.

View File

@@ -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 ``None`` disables the use of RMT and instead selects a bit-banging
implementation for `machine.bitstream`.
Passing in no argument will return ``1`` if RMT was enabled for the `machine.bitstream`
feature, and ``None`` otherwise.
Passing in no argument will not change the channel. This function returns
the current channel number.
Passing any non-negative integer argument is equivalent to calling ``RMT.bitstream_rmt(True)``.
.. 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
---------

View File

@@ -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

View File

@@ -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)

View File

@@ -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;
// Decide RMT usage in the machine_bitstream_high_low_rmt implementation.
bool esp32_rmt_bitstream_enabled = true;
#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 (;;) {
}
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;
}
// 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
// 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);
}
#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_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;
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,
};
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;
rmt_copy_encoder_config_t copy_encoder_config = {};
check_esp_err(rmt_new_copy_encoder(&copy_encoder_config, &self->encoder));
config.clk_div = self->clock_div;
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));
}
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
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));
}
#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

View File

@@ -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;
// 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 translator.
rmt_translator_init(config.channel, bitstream_high_low_rmt_adapter);
// Install the bits->highlow encoder.
rmt_encoder_handle_t encoder;
check_esp_err(rmt_new_bytes_encoder(&bytes_encoder_config, &encoder));
// Stream the byte data using the translator.
check_esp_err(rmt_write_sample(config.channel, buf, len, true));
rmt_transmit_config_t tx_config = {
.loop_count = 0,
.flags.eot_level = 0,
};
// 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)));
// Stream the byte data using the encoder.
rmt_encoder_reset(encoder);
check_esp_err(rmt_transmit(channel, encoder, buf, len, &tx_config));
// Uninstall the driver.
check_esp_err(rmt_driver_uninstall(config.channel));
// 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

View File

@@ -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