RMT disable followed by enable keeps reading (IDFGH-15289)
Answers checklist.
- [x] I have read the documentation ESP-IDF Programming Guide and the issue is not addressed there.
- [x] I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
- [x] I have searched the issue tracker for a similar issue and not found a similar issue.
IDF version.
v5.5-dev-3598-g7cf5dacd4a
Espressif SoC revision.
ESP32-D0WD-V3 (revision v3.0)
Operating System used.
Linux
How did you build your project?
Command line with idf.py
If you are using Windows, please specify command line type.
None
Development Kit.
ESP32_DevkitC_v4
Power Supply used.
USB
What is the expected behavior?
After calling rmt_disable(handle) it is safe to delete the buffer that was given with an earlier rmt_receive call.
What is the actual behavior?
If after the rmt_disable the RMT is enabled again (using rmt_enable) the buffer is used and an RX-done event is sent.
This can happen if the rmt_disable was invoked before the RMT had completed RX. By enabling the RMT again, it may receive the interrupt from the disabled reception.
Note that the channel->fsm inside rmt_isr_handle_rx_done is not set to RMT_FSM_RUN when this happens.
Steps to reproduce.
- start receiving:
rmt_receive - disable the RMT:
rmt_disable - enable the RMT again (to use for another operation):
rmt_enable. If the RX only finishes after step 3, then the peripheral will act as if thermt_receivewas (of step 1) was still active.
I have attached a small example that uses a PWM to keep the RMT-input non-idle, until the rmt_disable was called.
The relevant code of the attached file:
IRAM_ATTR static bool rx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t* event, void* user_ctx) {
ESP_DRAM_LOGE(TAG, "received data %p", event->received_symbols);
return pdFALSE;
}
void app_main(void)
{
start_pwm();
// Start the RMT RX on the in pin.
rmt_rx_channel_config_t create_cfg = {
...
.resolution_hz= 80000000 / 230,
};
rmt_channel_handle_t handle;
ESP_ERROR_CHECK(rmt_new_rx_channel(&create_cfg, &handle));
rmt_rx_event_callbacks_t callbacks = {
.on_recv_done = rx_done,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(handle, &callbacks, NULL));
ESP_ERROR_CHECK(rmt_enable(handle));
void* buffer = malloc(1024);
rmt_receive_config_t receive_cfg = {
.signal_range_min_ns = 10,
// A very big max value, giving time to disable and enable the RMT RX.
.signal_range_max_ns = 300000 * 230
};
ESP_ERROR_CHECK(rmt_receive(handle, buffer, 1024, &receive_cfg));
ESP_ERROR_CHECK(rmt_disable(handle));
// Since we disabled the RMT, we should be allowed to free the buffer.
ESP_LOGI("RMT", "Releasing buffer %p", buffer);
free(buffer);
ESP_ERROR_CHECK(rmt_enable(handle));
stop_pwm();
// Now that the PWM is stopped, the RMT RX detects the idle signal.
// It should *not* use the buffer we passed to it before since we already freed it.
// However, we can see that the callback is triggered from within the RMT interrupt, using the
// buffer that was already freed.
// Sleep for 10 seconds.
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
Debug Logs.
I (293) RMT: Releasing buffer 0x3ffb4cec
E BUG: received data 0x3ffb4cec
Diagnostic report archive.
idf-diag-48467c29-8599-4af6-8663-0ef6ee7dfc3a.zip
More Information.
No response
This is a very interesting finding! Thanks for the report and your detailed reproduce steps. We will need some time to investigate it. @floitsch
I just wrote the following patch for our local copy of the ESP-IDF: It looks like it gets rid of the memory corruptions we have been seeing.
diff --git a/components/esp_driver_rmt/src/rmt_rx.c b/components/esp_driver_rmt/src/rmt_rx.c
index 4a8ac36b97..bd1755d468 100644
--- a/components/esp_driver_rmt/src/rmt_rx.c
+++ b/components/esp_driver_rmt/src/rmt_rx.c
@@ -581,8 +581,19 @@ static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan)
portENTER_CRITICAL_ISR(&channel->spinlock);
// disable the RX engine, it will be enabled again when next time user calls `rmt_receive()`
rmt_ll_rx_enable(hal->regs, channel_id, false);
+ int state = channel->fsm;
portEXIT_CRITICAL_ISR(&channel->spinlock);
+ // work-around for https://github.com/espressif/esp-idf/issues/15948
+ // when the RX engine is disabled, but then enabled again, it continues
+ // reading the data as if it was still running. However, the user might
+ // have already removed the buffer.
+ // If the state isn't RMT_FSM_RUN, it means the RX engine was disabled
+ // and we shouldn't process the data.
+ if (state != RMT_FSM_RUN) {
+ return need_yield;
+ }
+
uint32_t offset = rmt_ll_rx_get_memory_writer_offset(hal->regs, channel_id);
// sanity check
assert(offset >= rx_chan->mem_off);
Thank you for the research. It has been confirmed that this issue only occurs on ESP32. We will fix it.
@Kainarx Thanks. We are also seeing issues where closing an idle RMT (not actively reading), followed by allocating it and using it for reading doesn't work. That is, the RMT doesn't capture the input signal. Adding a sleep in front of the allocation of the RMT fixes the issue. I have a reproducible test-case in Toit, but not in C yet. I can reduce the test-case if necessary, but it's some work. If you think it's unrelated I can do it, though.
Edit: the reason for the failures were probably due to having two RMTs on the same pin, even though one of them was idle.
@floitsch Thank you for the report! It would be great if you could provide the reproduction code in C language.