From 8d5e093994a7193d1a6a3c9a288c9a497dcede1b Mon Sep 17 00:00:00 2001 From: Sola85 Date: Fri, 18 Jul 2025 09:22:43 +0200 Subject: [PATCH 1/2] Rmt dma only when necessary * enable dma in rmt only when necessary * Document limitations of PulseIn --- ports/espressif/common-hal/pulseio/PulseIn.c | 31 ++++++++++++++++---- shared-bindings/pulseio/PulseIn.c | 4 +++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/ports/espressif/common-hal/pulseio/PulseIn.c b/ports/espressif/common-hal/pulseio/PulseIn.c index f7e500790562..cddf6f5dffc6 100644 --- a/ports/espressif/common-hal/pulseio/PulseIn.c +++ b/ports/espressif/common-hal/pulseio/PulseIn.c @@ -74,14 +74,20 @@ static bool _done_callback(rmt_channel_handle_t rx_chan, void common_hal_pulseio_pulsein_construct(pulseio_pulsein_obj_t *self, const mcu_pin_obj_t *pin, uint16_t maxlen, bool idle_state) { + + // Only use dma when necessary, since dma-rmt might not be available. + // If dma is not available, this will raise an error below. + bool use_dma = maxlen > 128; + self->buffer = (uint16_t *)m_malloc_without_collect(maxlen * sizeof(uint16_t)); if (self->buffer == NULL) { m_malloc_fail(maxlen * sizeof(uint16_t)); } // We add one to the maxlen version to ensure that two symbols at lease are // captured because we may skip the first portion of a symbol. - self->raw_symbols_size = MIN(64, maxlen / 2 + 1) * sizeof(rmt_symbol_word_t); - self->raw_symbols = (rmt_symbol_word_t *)m_malloc_without_collect(self->raw_symbols_size); + self->raw_symbols_size = (maxlen / 2 + 1) * sizeof(rmt_symbol_word_t); + // RMT DMA mode cannot access PSRAM -> ensure raw_symbols is in internal ram + self->raw_symbols = (rmt_symbol_word_t *)port_malloc(self->raw_symbols_size, use_dma); if (self->raw_symbols == NULL) { m_free(self->buffer); m_malloc_fail(self->raw_symbols_size); @@ -109,17 +115,30 @@ void common_hal_pulseio_pulsein_construct(pulseio_pulsein_obj_t *self, const mcu .clk_src = RMT_CLK_SRC_DEFAULT, // 2 us resolution so we can capture 65ms pulses. The RMT period is only 15 bits. .resolution_hz = 1000000 / 2, - .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .mem_block_symbols = use_dma ? self->raw_symbols_size : SOC_RMT_MEM_WORDS_PER_CHANNEL, + .flags.with_dma = use_dma }; - // If we fail here, the buffers allocated above will be garbage collected. - CHECK_ESP_RESULT(rmt_new_rx_channel(&config, &self->channel)); + // If we fail here, the self->buffer will be garbage collected. + esp_err_t result = rmt_new_rx_channel(&config, &self->channel); + if (result != ESP_OK) { + port_free(self->raw_symbols); + if (result == ESP_ERR_NOT_SUPPORTED) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_maxlen); + } else { + raise_esp_error(result); + } + } rmt_rx_event_callbacks_t rx_callback = { .on_recv_done = _done_callback }; rmt_rx_register_event_callbacks(self->channel, &rx_callback, self); rmt_enable(self->channel); - rmt_receive(self->channel, self->raw_symbols, self->raw_symbols_size, &rx_config); + result = rmt_receive(self->channel, self->raw_symbols, self->raw_symbols_size, &rx_config); + if (result != ESP_OK) { + port_free(self->raw_symbols); + raise_esp_error(result); + } } bool common_hal_pulseio_pulsein_deinited(pulseio_pulsein_obj_t *self) { diff --git a/shared-bindings/pulseio/PulseIn.c b/shared-bindings/pulseio/PulseIn.c index d9e6bfd7aecc..1df577311449 100644 --- a/shared-bindings/pulseio/PulseIn.c +++ b/shared-bindings/pulseio/PulseIn.c @@ -33,6 +33,10 @@ //| :param bool idle_state: Idle state of the pin. At start and after `resume` //| the first recorded pulse will the opposite state from idle. //| +//| **Limitations**: The `maxlen` parameter is limited depending on the specific board. +//| On most ESP32 variants the limit is 128. On ESP32-S3 and ESP32-P4 the first `PulseIn` instance +//| can use `maxlen` up to available RAM; all subsequent instances are limited to 128. +//| //| Read a short series of pulses:: //| //| import pulseio From 70da590e3fe75448f6068f9a174547b816d6ef4c Mon Sep 17 00:00:00 2001 From: Sola85 Date: Sat, 19 Jul 2025 11:00:23 +0200 Subject: [PATCH 2/2] catch second type of error --- ports/espressif/common-hal/pulseio/PulseIn.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/espressif/common-hal/pulseio/PulseIn.c b/ports/espressif/common-hal/pulseio/PulseIn.c index cddf6f5dffc6..73ffecd2ed37 100644 --- a/ports/espressif/common-hal/pulseio/PulseIn.c +++ b/ports/espressif/common-hal/pulseio/PulseIn.c @@ -76,7 +76,7 @@ void common_hal_pulseio_pulsein_construct(pulseio_pulsein_obj_t *self, const mcu uint16_t maxlen, bool idle_state) { // Only use dma when necessary, since dma-rmt might not be available. - // If dma is not available, this will raise an error below. + // If dma is necessary but not available, this will raise an error below. bool use_dma = maxlen > 128; self->buffer = (uint16_t *)m_malloc_without_collect(maxlen * sizeof(uint16_t)); @@ -122,7 +122,8 @@ void common_hal_pulseio_pulsein_construct(pulseio_pulsein_obj_t *self, const mcu esp_err_t result = rmt_new_rx_channel(&config, &self->channel); if (result != ESP_OK) { port_free(self->raw_symbols); - if (result == ESP_ERR_NOT_SUPPORTED) { + if (result == ESP_ERR_NOT_SUPPORTED || result == ESP_ERR_NOT_FOUND) { + // DMA not available, cannot record long pulse sequences. mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_maxlen); } else { raise_esp_error(result);