This demonstrates using the RMT peripheral on the ESP32-S3 as a 10-bit serial transceiver.
The demo uses a shared GPIO pin (6) but can be separated to two pins. It initializes at 100kHz and begins sending a sequence of 10-bit numbers.
| #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 int extract_level(uint16_t *data, int index, const unsigned int level, const unsigned int duration) { | |
| if (level == 1) { | |
| // this is inverted, so this is a zero. we can just increase index | |
| return index + duration / 10; | |
| } | |
| for(int i = 0; i < duration / 10; i++) { | |
| *data |= 1 << index; | |
| index++; | |
| } | |
| return index; | |
| } | |
| 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++) { | |
| rmt_symbol_word_t symbol = rmt_nec_symbols[i]; | |
| data_idx = extract_level(&data, data_idx, symbol.level0, symbol.duration0); | |
| data_idx = extract_level(&data, data_idx, symbol.level1, symbol.duration1); | |
| 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); | |
| } | |
| while(data_idx < 14) { | |
| // stick in remaining bits | |
| data |= 1 << data_idx; | |
| data_idx++; | |
| } | |
| ESP_LOGI(TAG, ">> data = %x", (data >> 1) & 0b1111111111); | |
| 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 rx channel | |
| rmt_rx_channel_config_t rx_channel_config = { | |
| .clk_src=RMT_CLK_SRC_DEFAULT, | |
| .gpio_num=SERIAL_TX_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 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 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=10*1000, | |
| .signal_range_max_ns=140*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 = 0; | |
| 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 )); | |
| rmt_transmit_config_t tx_config = { | |
| .loop_count = 0, | |
| .flags.eot_level = 0, // stop bit: note inverted | |
| }; | |
| 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)); | |
| vTaskDelay(100); | |
| } else { | |
| ESP_LOGI(TAG, "transmitting"); | |
| rmt_transmit(tx_channel_handle, encoder, (void *)&data, sizeof(data), &tx_config); | |
| data++; | |
| // rmt_tx_wait_all_done(serial_channel_handle, 1000); | |
| vTaskDelay(pdMS_TO_TICKS(1000)); | |
| } | |
| } | |
| } |