diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 5cce96d687875..cb8ad2aff514d 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -680,8 +680,8 @@ 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) + r = esp32.RMT(pin=Pin(18), clock_div=8) + r # RMT(pin=18, source_freq=80000000, clock_div=8) # The channel resolution is 100ns (1/(source_freq/clock_div)). r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns @@ -743,8 +743,7 @@ The APA106 driver extends NeoPixel, but internally uses a different colour order ``NeoPixel`` object. For low-level driving of a NeoPixel see `machine.bitstream`. -This low-level driver uses an RMT channel by default. To configure this see -`RMT.bitstream_channel`. +This low-level driver uses an RMT channel by default. APA102 (DotStar) uses a different driver as it has an additional clock pin. diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index dc35e7905e162..5d23abd205bd4 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -177,11 +177,11 @@ 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), clock_div=8) + r # RMT(pin=18, source_freq=80000000, clock_div=8, 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), clock_div=8, tx_carrier=(38000, 50, 1)) # The channel resolution is 100ns (1/(source_freq/clock_div)). r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns @@ -210,13 +210,17 @@ 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, clock_div=8, idle_level=False, num_symbols=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* + optional and a dummy parameter for backward compatibility. *pin* is required + and 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 + channel allowing the resolution to be specified. *num_symbols* specifies the + RMT buffer allocated for this channel (minimum 64), from a small pool of + 512 symbols 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. @@ -241,14 +245,38 @@ For more details see Espressif's `ESP-IDF RMT documentation. 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.disable() + + Disable RMT channel. 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. + +.. method:: RMT.release() + + 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) @@ -278,18 +306,6 @@ 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_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. - - Passing in ``None`` disables the use of RMT and instead selects a bit-banging - implementation for `machine.bitstream`. - - Passing in no argument will not change the channel. This function returns - the current channel number. - Constants --------- diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 0a6b29983c2f5..fb2ee74393d1e 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -237,6 +237,14 @@ target_link_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. target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 6890e16bf79f7..a5ed74000c035 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" + * Copyright (c) 2024 "Elvis Pfützenreuter" * * 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,11 +27,13 @@ #include "py/mphal.h" #include "py/runtime.h" +#include "py/stream.h" #include "modmachine.h" #include "modesp32.h" #include "esp_task.h" -#include "driver/rmt.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: // @@ -47,102 +50,75 @@ // 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) - // 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; -} 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; -} + mp_uint_t cap_items; + rmt_symbol_word_t *items; + int loop_count; + int tx_ongoing; -#else + rmt_encoder_handle_t encoder; + mp_uint_t idle_level; +} esp32_rmt_obj_t; -// 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 - 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_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 = 64} }, }; 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")); - } + mp_uint_t num_symbols = args[5].u_int; if (clock_div < 1 || clock_div > 255) { mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); } + if (num_symbols < 64 || ((num_symbols % 2) == 1)) { + mp_raise_ValueError(MP_ERROR_TEXT("num_symbols must be even and at least 64")); + } + 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->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 = APB_CLK_FREQ / clock_div, + .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; @@ -158,21 +134,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(©_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); } @@ -180,24 +156,52 @@ 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, clock_div=%u, idle_level=%u)", + self->pin, APB_CLK_FREQ, self->clock_div, self->idle_level); } else { mp_printf(print, "RMT()"); } } -static mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { - // fixme: check for valid channel. Return exception if error occurs. +static mp_obj_t esp32_rmt_disable(mp_obj_t self_in) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->enabled) { + // FIXME: panics if called while non-loop tx is ongoing, + // or when the first round of an infinite loop is ongoing. + // (Need to check with ESP-IDF support if this bug is ours or theirs.) + rmt_disable(self->channel); + self->enabled = false; + return mp_const_true; + } + + return mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_disable_obj, esp32_rmt_disable); + +static mp_obj_t esp32_rmt_release(mp_obj_t self_in) { 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_disable(self_in); + 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_true; } + + return mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_release_obj, esp32_rmt_release); + +static mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { + esp32_rmt_release(self_in); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); @@ -214,6 +218,10 @@ 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); + if (self->pin == -1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already released")); + } + return mp_obj_new_int(self->clock_div); } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); @@ -231,29 +239,90 @@ 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 released")); + } 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); + return arg ^ ( + // If tx is ongoing, unset the Read ready flag + (self->tx_ongoing <= 0 ? 0 : MP_STREAM_POLL_RD)); +} + +static const mp_stream_p_t esp32_rmt_stream_p = { + .ioctl = esp32_rmt_stream_ioctl, +}; + 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 released")); + } + + bool loop_en = mp_obj_get_int(loop); + + if (self->enabled && self->loop_count == -1 && !loop_en && self->tx_ongoing > 0) { + // Break ongoing loop + // FIXME: panics if called while the first round of an infinite loop is ongoing. + // (Need to check with ESP-IDF support if this bug is ours or theirs.) + esp32_rmt_disable(self_in); } + + self->loop_count = 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 released")); + } + + 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")); + } + + if (self->enabled && self->loop_count != loop_count && rmt_tx_wait_all_done(self->channel, 0) != ESP_OK) { + // Break ongoing loop + esp32_rmt_disable(self_in); + } + + self->loop_count = 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 released")); + } + + 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; @@ -288,14 +357,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++) { @@ -312,65 +379,30 @@ 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_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; - } else { - mp_int_t channel_id = mp_obj_get_int(args[0]); - if (channel_id < 0 || channel_id > RMT_LAST_TX_CHANNEL) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); - } - esp32_rmt_bitstream_channel_id = channel_id; - } - } - if (esp32_rmt_bitstream_channel_id < 0) { - return mp_const_none; - } else { - return MP_OBJ_NEW_SMALL_INT(esp32_rmt_bitstream_channel_id); - } -} -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_release), MP_ROM_PTR(&esp32_rmt_release_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable), MP_ROM_PTR(&esp32_rmt_disable_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_channel), MP_ROM_PTR(&esp32_rmt_bitstream_channel_obj) }, - // Class methods { MP_ROM_QSTR(MP_QSTR_source_freq), MP_ROM_PTR(&esp32_rmt_source_obj) }, @@ -385,5 +417,6 @@ 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 ); diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index b4e58c51f4d19..5e9622fdd6cad 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -93,81 +93,74 @@ static void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, u /******************************************************************************/ // 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 void 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; + // TODO create a permanent/reserved channel outside this function? Lazy creation? + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = pin, + .mem_block_symbols = 64, + .resolution_hz = APB_CLK_FREQ / clock_div, + .trans_queue_depth = 1, + }; + check_esp_err(rmt_new_tx_channel(&tx_chan_config, &channel)); + 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); @@ -177,10 +170,11 @@ static void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timin // 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 (esp32_rmt_bitstream_channel_id < 0) { + if (false) { + // TODO test whether channel creation was successful? machine_bitstream_high_low_bitbang(pin, timing_ns, buf, len); } else { - machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id); + machine_bitstream_high_low_rmt(pin, timing_ns, buf, len); } } diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index a685b7b38fe6f..57d9f8a8c692a 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,13 +61,9 @@ #define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) -extern int8_t esp32_rmt_bitstream_channel_id; - extern const mp_obj_type_t esp32_nvs_type; extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; -esp_err_t rmt_driver_install_core1(uint8_t channel_id); - #endif // MICROPY_INCLUDED_ESP32_MODESP32_H