diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 76a3827e36..337f87b66f 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -573,6 +573,9 @@ For low-level driving of a NeoPixel:: 400kHz) devices by passing ``timing=0`` when constructing the ``NeoPixel`` object. +The low-level driver uses an RMT channel by default. To configure this see +`RMT.bitstream_channel`. + APA102 (DotStar) uses a different driver as it has an additional clock pin. Capacitive touch diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index e3c25d2653..ff1c99a553 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -250,6 +250,17 @@ 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. Ultra-Low-Power co-processor ---------------------------- diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 941f20818e..639e0467a8 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -66,6 +66,10 @@ typedef struct _rmt_install_state_t { esp_err_t ret; } rmt_install_state_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_CHANNEL_MAX - 1; + STATIC void rmt_install_task(void *pvParameter) { rmt_install_state_t *state = pvParameter; state->ret = rmt_driver_install(state->channel_id, 0, 0); @@ -104,8 +108,8 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_uint_t idle_level = args[3].u_bool; mp_obj_t tx_carrier_obj = args[4].u_obj; - if (channel_id == MICROPY_HW_ESP32_RMT_CHANNEL_BITSTREAM) { - mp_raise_ValueError(MP_ERROR_TEXT("reserved channel id")); + if (esp32_rmt_bitstream_channel_id >= 0 && channel_id == esp32_rmt_bitstream_channel_id) { + mp_raise_ValueError(MP_ERROR_TEXT("channel used by bitstream")); } if (clock_div < 1 || clock_div > 255) { @@ -314,6 +318,27 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { } 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_CHANNEL_MAX) { + 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) }, @@ -322,6 +347,9 @@ STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { 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_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) }, }; STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index 5f9eb32c3f..4284b5f8ba 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -26,12 +26,70 @@ #include "py/mpconfig.h" #include "py/mphal.h" +#include "modesp32.h" #if MICROPY_PY_MACHINE_BITSTREAM -#include "driver/rmt.h" +/******************************************************************************/ +// Bit-bang implementation -#include "modesp32.h" +#define NS_TICKS_OVERHEAD (6) + +// This is a translation of the cycle counter implementation in ports/stm32/machine_bitstream.c. +STATIC void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { + uint32_t pin_mask, gpio_reg_set, gpio_reg_clear; + #if !CONFIG_IDF_TARGET_ESP32C3 + if (pin >= 32) { + pin_mask = 1 << (pin - 32); + gpio_reg_set = GPIO_OUT1_W1TS_REG; + gpio_reg_clear = GPIO_OUT1_W1TC_REG; + } else + #endif + { + pin_mask = 1 << pin; + gpio_reg_set = GPIO_OUT_W1TS_REG; + gpio_reg_clear = GPIO_OUT_W1TC_REG; + } + + // Convert ns to cpu ticks [high_time_0, period_0, high_time_1, period_1]. + uint32_t fcpu_mhz = ets_get_cpu_frequency(); + for (size_t i = 0; i < 4; ++i) { + timing_ns[i] = fcpu_mhz * timing_ns[i] / 1000; + if (timing_ns[i] > NS_TICKS_OVERHEAD) { + timing_ns[i] -= NS_TICKS_OVERHEAD; + } + if (i % 2 == 1) { + // Convert low_time to period (i.e. add high_time). + timing_ns[i] += timing_ns[i - 1]; + } + } + + uint32_t irq_state = mp_hal_quiet_timing_enter(); + + for (size_t i = 0; i < len; ++i) { + uint8_t b = buf[i]; + for (size_t j = 0; j < 8; ++j) { + GPIO_REG_WRITE(gpio_reg_set, pin_mask); + uint32_t start_ticks = mp_hal_ticks_cpu(); + uint32_t *t = &timing_ns[b >> 6 & 2]; + while (mp_hal_ticks_cpu() - start_ticks < t[0]) { + ; + } + GPIO_REG_WRITE(gpio_reg_clear, pin_mask); + b <<= 1; + while (mp_hal_ticks_cpu() - start_ticks < t[1]) { + ; + } + } + } + + mp_hal_quiet_timing_exit(irq_state); +} + +/******************************************************************************/ +// RMT implementation + +#include "driver/rmt.h" #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) // This convenience macro was not available in earlier IDF versions. @@ -93,8 +151,8 @@ STATIC void IRAM_ATTR bitstream_high_low_rmt_adapter(const void *src, rmt_item32 } // Use the reserved RMT channel to stream high/low data on the specified pin. -void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, MICROPY_HW_ESP32_RMT_CHANNEL_BITSTREAM); +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); // Use 40MHz clock (although 2MHz would probably be sufficient). config.clk_div = 2; @@ -138,4 +196,15 @@ void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false); } +/******************************************************************************/ +// 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) { + 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); + } +} + #endif // MICROPY_PY_MACHINE_BITSTREAM diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index 368b40db04..d76b3a49a2 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -26,14 +26,13 @@ #define RTC_LAST_EXT_PIN 39 #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; -// Reserve the last channel for machine.bitstream. -#define MICROPY_HW_ESP32_RMT_CHANNEL_BITSTREAM (RMT_CHANNEL_MAX - 1) - esp_err_t rmt_driver_install_core1(uint8_t channel_id); #endif // MICROPY_INCLUDED_ESP32_MODESP32_H