summaryrefslogtreecommitdiff
path: root/ports/esp32
diff options
context:
space:
mode:
Diffstat (limited to 'ports/esp32')
-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