summaryrefslogtreecommitdiff
path: root/ports
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 /ports
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>
Diffstat (limited to 'ports')
-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
5 files changed, 314 insertions, 210 deletions
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