-
Notifications
You must be signed in to change notification settings - Fork 7.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RMT: Cannot transmit and receive on same GPIO pin when io_loop_back=true
(IDFGH-10524)
#11768
Comments
io_loop_back=true
io_loop_back=true
(IDFGH-10524)
Interestingly, I can set the rx pin to a new pin, then physically jump the rx/tx together and it works as-expected. Here's the code I'm using with jumped separate pins (6 and 7): #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>
#define SERIAL_FREQ_HZ 1000000
#define SERIAL_TX_GPIO_NUM 6
#define SERIAL_RX_GPIO_NUM 7
static const char TAG[] = "main";
const static rmt_symbol_word_t bits[2] = {
// inverted!
/* 0 */ {
.level0 = 1,
.duration0 = 5,
.level1 = 1,
.duration1 = 5,
},
/* 1 */ {
.level0 = 0,
.duration0 = 5,
.level1 = 0,
.duration1 = 5
},
};
// #define RMT_ENCODING_RESET 0
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *copy_encoder;
rmt_symbol_word_t start_bit;
int state;
rmt_symbol_word_t end_bit;
} rmt_cur_encoder_t;
static size_t rmt_encode_cur(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *out_state) {
rmt_cur_encoder_t *cur_encoder = __containerof(encoder, rmt_cur_encoder_t, base);
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
rmt_encode_state_t state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
uint16_t raw = *((uint16_t *)primary_data);
rmt_encoder_t *copy_encoder = cur_encoder->copy_encoder;
switch(cur_encoder->state) {
case 0: // send start bit
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &cur_encoder->start_bit, sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
cur_encoder->state = 1; // go to next state
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out;
}
// fall-through
case 1: // send data symbols
for(int i = 0; i < 10; i++) {
// iterate through bits in data
bool bit = (raw >> i) & 0b1;
rmt_symbol_word_t symbol = bits[bit];
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &symbol, sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out;
}
}
// data is encoded :)
cur_encoder->state = 2;
state |= RMT_ENCODING_COMPLETE;
// fall through
case 2: // end end bit
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &cur_encoder->end_bit, sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
cur_encoder->state = RMT_ENCODING_RESET; // go to next state
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out;
}
// all done!
}
out:
*out_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_cur_encoder(rmt_encoder_t *encoder) {
rmt_cur_encoder_t *cur_encoder = __containerof(encoder, rmt_cur_encoder_t, base);
rmt_del_encoder(cur_encoder->copy_encoder);
free(cur_encoder);
return ESP_OK;
}
static esp_err_t rmt_cur_encoder_reset(rmt_encoder_t *encoder) {
rmt_cur_encoder_t *cur_encoder = __containerof(encoder, rmt_cur_encoder_t, base);
rmt_encoder_reset(cur_encoder->copy_encoder);
cur_encoder->state = RMT_ENCODING_RESET;
return ESP_OK;
}
esp_err_t rmt_new_cur_encoder(rmt_encoder_handle_t *out_encoder) {
rmt_cur_encoder_t *cur_encoder = calloc(1, sizeof(rmt_cur_encoder_t));
// TODO: error check
cur_encoder->state = 0;
cur_encoder->base.encode = rmt_encode_cur;
cur_encoder->base.del = rmt_del_cur_encoder;
cur_encoder->base.reset = rmt_cur_encoder_reset;
rmt_copy_encoder_config_t copy_encoder_config = {};
rmt_new_copy_encoder(©_encoder_config, &cur_encoder->copy_encoder);
// construct start, end bit
cur_encoder->start_bit = bits[0];
cur_encoder->end_bit = bits[1];
*out_encoder = &cur_encoder->base;
return ESP_OK;
}
static bool rmt_rx_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 parse_frame(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num)
{
printf("frame start---\r\n");
uint16_t data = 0;
int data_idx = 0;
for (size_t i = 0; i < symbol_num; i++) {
// should maybe use rounding here, in case we have like <10 us
// this also does not work correctly, I'm messing something up rn
rmt_symbol_word_t symbol = rmt_nec_symbols[i];
// first symbol:
if (symbol.level0 == 1) {
int duration = symbol.duration1 / 10;
if (data_idx == 0) {
duration--;
}
data_idx += duration;
}
if (symbol.level1 == 0) {
int duration = symbol.duration0 / 10;
if (data_idx == 0) {
// start bit, ignore
duration--;
}
for(int j = 0; j < duration; j++) {
data |= 1 << data_idx;
data_idx++;
}
}
printf("{%d:%d},{%d:%d}\r\n", rmt_nec_symbols[i].level0, rmt_nec_symbols[i].duration0,
rmt_nec_symbols[i].level1, rmt_nec_symbols[i].duration1);
}
ESP_LOGI(TAG, ">> data = %x", data);
printf("---frame end: ");
}
void app_main() {
ESP_LOGI(TAG, "create rmt tx channel");
rmt_channel_handle_t tx_channel_handle = NULL;
rmt_channel_handle_t rx_channel_handle = NULL;
// init tx channel
rmt_tx_channel_config_t tx_channel_config = {
.clk_src=RMT_CLK_SRC_DEFAULT,
.gpio_num = SERIAL_TX_GPIO_NUM,
.mem_block_symbols=48,
.resolution_hz=SERIAL_FREQ_HZ,
.trans_queue_depth=3,
.flags.invert_out=true,
.flags.with_dma=false,
.flags.io_loop_back=true
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_config, &tx_channel_handle));
// init rx channel
rmt_rx_channel_config_t rx_channel_config = {
.clk_src=RMT_CLK_SRC_DEFAULT,
.gpio_num=SERIAL_RX_GPIO_NUM,
.mem_block_symbols=48,
.resolution_hz=SERIAL_FREQ_HZ,
.flags.invert_in=true,
.flags.with_dma=false,
.flags.io_loop_back=true,
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_config, &rx_channel_handle));
// 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_rx_done_callback
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel_handle, &cbs, receive_queue));
rmt_receive_config_t receive_config = {
.signal_range_min_ns=100*1000,
.signal_range_max_ns=130*1000
};
// create tx encoder
rmt_encoder_handle_t encoder = NULL;
rmt_new_cur_encoder(&encoder);
ESP_ERROR_CHECK(rmt_enable(tx_channel_handle));
ESP_ERROR_CHECK(rmt_enable(rx_channel_handle));
uint16_t data = 0x2E8;
rmt_symbol_word_t raw_symbols[64] = {0}; // more than enough
rmt_rx_done_event_data_t rx_data;
ESP_ERROR_CHECK(rmt_receive(rx_channel_handle, raw_symbols, sizeof(raw_symbols), &receive_config ));
for(;;) {
if (xQueueReceive(receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdPASS) {
ESP_LOGI(TAG, "received data");
parse_frame(rx_data.received_symbols, rx_data.num_symbols);
// start receive again
ESP_ERROR_CHECK(rmt_receive(rx_channel_handle, raw_symbols, sizeof(raw_symbols), &receive_config));
} else {
ESP_LOGI(TAG, "transmitting");
rmt_transmit_config_t tx_config = {
.loop_count = 0,
.flags.eot_level = 0, // stop bit: note inverted
};
rmt_transmit(tx_channel_handle, encoder, (void *)&data, sizeof(data), &tx_config);
// rmt_tx_wait_all_done(serial_channel_handle, 1000);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
} |
TLDR, you may need to install the RX channel first, then the TX channel. Please check the test code we used: https://github.com/espressif/esp-idf/blob/master/components/driver/test_apps/rmt/main/test_rmt_rx.c#L43 |
That worked. I called It would be nice to have a note on the RMT page under Question: Why does the rx channel need to be installed before the tx channel? |
Yes, we will update the doc. Thanks. Because this code will change the gpio matrix signal connection. If the TX was set first, then during the RX set up, the previous RMT channel signal will be overridden by the GPIO control signal. |
Added a note about RX Channel and TX Channel initialization order when bound RX and TX to the same gpio. Closes #11768
Added a note about RX Channel and TX Channel initialization order when bound RX and TX to the same gpio. Closes #11768
Answers checklist.
IDF version.
5.0.1
Operating System used.
Linux
How did you build your project?
Using PlatformIO on VSCode
If you are using Windows, please specify command line type.
None
Development Kit.
esp32-s3-devkitc-1
Power Supply used.
USB
What is the expected behavior?
When using RMT with RX/TX channels using the same GPIO with
.flags.io_loop_back=true
, I expect to be able to transmit and receive on both channels.What is the actual behavior?
When using both tx+rx channels, the GPIO pin is pulled always low. Specifically, calling
rmt_new_rx_channel(...)
pulls the GPIO pin low and I cannot receive or transmit on that pin.When I disable the rx channel, I can transmit just fine and the output is as-expected:
Steps to reproduce.
I basically followed the NEC RMT example, but tweaked a few things, so I can transmit 10-bit serial. Feel free to comment if I'm doing something wrong or missing it.
Debug Logs.
The text was updated successfully, but these errors were encountered: