Can't get receiving data to work with bidirectional RMT peripheral
Posted: Wed Jun 11, 2025 12:20 am
Hi all,
I have a project where I am attempting to communicate with a device using a one wire bidirectional data protocol via the RMT peripheral.
The general flow of the transmission protocol is as follows:
ESP32 Transmits
- 15 high bits (SOT)
- Start bit (0)
- Data bit
- Data bit
- Data bit
- Data bit
- Data bit
- Data bit
- Data bit
- Stop bit (1)
- Even parity bit
- 3 high bits (EOT)
ESP32 stops transmitting, starts listening.
ESP32 Receives
- Start bit (0)
- Data bit
- Data Bit
- Data Bit
- Data Bit
- 4 low bits
Repeat from the top.
I have gotten the TX part to work as intended and to be in spec. The device I am communicating with recognizes my ESP32 and reads the data bits I am sending.
The problem is that when I stop transmitting and wait for 10 milliseconds for a response, the line is being held low by the ESP32 data pin (even with an external pullup resistor).
If I have the microcontroller not wait that 10ms, so it instantly starts transmitting the next SOT (high for 15ms), you can see on the oscilloscope where the external device is trying to transmit data.
Things I have tried:
Here is my test firmware for reference:
I have a project where I am attempting to communicate with a device using a one wire bidirectional data protocol via the RMT peripheral.
The general flow of the transmission protocol is as follows:
ESP32 Transmits
- 15 high bits (SOT)
- Start bit (0)
- Data bit
- Data bit
- Data bit
- Data bit
- Data bit
- Data bit
- Data bit
- Stop bit (1)
- Even parity bit
- 3 high bits (EOT)
ESP32 stops transmitting, starts listening.
ESP32 Receives
- Start bit (0)
- Data bit
- Data Bit
- Data Bit
- Data Bit
- 4 low bits
Repeat from the top.
I have gotten the TX part to work as intended and to be in spec. The device I am communicating with recognizes my ESP32 and reads the data bits I am sending.
The problem is that when I stop transmitting and wait for 10 milliseconds for a response, the line is being held low by the ESP32 data pin (even with an external pullup resistor).
If I have the microcontroller not wait that 10ms, so it instantly starts transmitting the next SOT (high for 15ms), you can see on the oscilloscope where the external device is trying to transmit data.
Things I have tried:
- Calling rmt_enable to enable the TX channel before my transmission, and then rmt_disable to disable it before waiting for the response.
- Setting io_loop_back to all four combos of TT/TF/FT/FF for TX and RX respectively
- Disabling open drain mode when transmitting data, and then enabling it when receiving data.
- Having separate tasks for TX and RX, as well as using the same task for both
Here is my test firmware for reference:
Code: Select all
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "driver/rmt_encoder.h"
#include "esp_check.h"
#include "esp_log.h"
#include <stdint.h>
#include <string.h>
#include "rom/ets_sys.h"
#include "driver/gpio.h"
#define TAG_DEMO "DEMO"
#define dataDemo GPIO_NUM_12
#define RMT_RESOLUTION_HZ 1000000
// Transmission protocol
// TX
// - 15 high bits (SOT)
// - Start bit (0)
// - Data bit
// - Data bit
// - Data bit
// - Data bit
// - Data bit
// - Data bit
// - Data bit
// - Stop bit (1)
// - Even parity bit
// - 3 high bits (EOT)
// RX
// - Start bit (0)
// - Data bit
// - Data bit
// - Data bit
// - Data bit
// - 4 low bits
//
// Repeat from the top
rmt_encoder_handle_t copy_encoder_demo = NULL;
rmt_copy_encoder_config_t copy_encoder_demo_config = {};
rmt_channel_handle_t demo_tx_channel = NULL;
rmt_tx_channel_config_t demo_tx_config = {
.gpio_num = dataDemo,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = 64,
.trans_queue_depth = 4,
.flags = {
.invert_out = false,
.with_dma = false,
.io_loop_back = true,
.io_od_mode = false
}
};
rmt_channel_handle_t demo_rx_channel = NULL;
rmt_rx_channel_config_t demo_rx_config = {
.gpio_num = dataDemo,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = 64,
.flags = {
.invert_in = false,
.with_dma = false,
.io_loop_back = true
}
};
// Receive configuration
rmt_receive_config_t receive_config = {
.signal_range_max_ns = 12000 * 1000, // 12 ms max pulse width
.signal_range_min_ns = 2000, // Setting this higher than 2550 gives an error
};
rmt_transmit_config_t transmit_config = {
.loop_count = 0,
};
// Each bit is 975us
const static rmt_symbol_word_t bits[2] = {
{ // bits[0]
.level0 = 0,
.duration0 = 487,
.level1 = 0,
.duration1 = 488,
},
{ // bits[1]
.level0 = 1,
.duration0 = 487,
.level1 = 1,
.duration1 = 488
},
};
static bool rmt_demoRX_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) {
BaseType_t high_task_wakeup = pdFALSE;
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
// send the received RMT symbols to the parser task
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
static void rmt_demo_task(void *pvParameter) {
rmt_symbol_word_t raw_symbols[64];
rmt_rx_done_event_data_t rx_data;
// init rx queue and callbacks
QueueHandle_t receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t));
rmt_rx_event_callbacks_t cbs = {
.on_recv_done=rmt_demoRX_done_callback
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(demo_rx_channel, &cbs, receive_queue));
ESP_ERROR_CHECK(rmt_receive(demo_rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
while (1) {
if (xQueueReceive(receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdPASS) {
for (int i = 0; i < rx_data.num_symbols; i++) {
ESP_LOGI(TAG_DEMO, "Bit: %d L0: %d D0: %d, L1: %d D1: %d", i, rx_data.received_symbols[i].level0, rx_data.received_symbols[i].duration0, rx_data.received_symbols[i].level1, rx_data.received_symbols[i].duration1);
}
ESP_ERROR_CHECK(rmt_receive(demo_rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
}
}
}
static void rmt_demoTX_task(void *pvParameter) {
while (1) {
// Start of next transmission is 15 high bits
rmt_symbol_word_t sot[15];
for (int i = 0; i < 15; i++) {
sot[i] = bits[1];
}
ESP_ERROR_CHECK(rmt_transmit(demo_tx_channel, copy_encoder_demo, sot, sizeof(sot), &transmit_config));
ESP_ERROR_CHECK(rmt_tx_wait_all_done(demo_tx_channel, 1000));
rmt_symbol_word_t report[13];
report[0] = bits[0]; // Start Bit (0)
report[1] = bits[0]; // Data Bit0
report[2] = bits[0]; // Data Bit1
report[3] = bits[0]; // Data Bit2
report[4] = bits[0]; // Data Bit3
report[5] = bits[0]; // Data Bit4
report[6] = bits[0]; // Data Bit5
report[7] = bits[0]; // Data Bit6
report[8] = bits[1]; // Stop Bit (1)
report[9] = bits[1]; // Even Parity Bit
report[10] = bits[1]; // EOT
report[11] = bits[1]; // EOT
report[12] = bits[1]; // EOT
ESP_ERROR_CHECK(rmt_transmit(demo_tx_channel, copy_encoder_demo, report, sizeof(report), &transmit_config));
ESP_ERROR_CHECK(rmt_tx_wait_all_done(demo_tx_channel, 1000));
// Wait 10ms for response
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void demoStart() {
ESP_LOGI(TAG_DEMO, "Create demo RMT RX channel");
ESP_ERROR_CHECK(rmt_new_rx_channel(&demo_rx_config, &demo_rx_channel));
ESP_LOGI(TAG_DEMO, "Create demo RMT TX channel");
ESP_ERROR_CHECK(rmt_new_tx_channel(&demo_tx_config, &demo_tx_channel));
ESP_ERROR_CHECK(rmt_new_copy_encoder(©_encoder_demo_config, ©_encoder_demo));
ESP_ERROR_CHECK(rmt_enable(demo_rx_channel));
ESP_ERROR_CHECK(rmt_enable(demo_tx_channel));
xTaskCreate(&rmt_demo_task, "rmt_demo_task", 8192, NULL, 10, NULL);
xTaskCreate(&rmt_demoTX_task, "rmt_demoTX_task", 8192, NULL, 10, NULL);
}