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)); | |
} | |
} | |
} |