Skip to content

Instantly share code, notes, and snippets.

@kevinhikaruevans
Created June 29, 2023 16:00
Show Gist options
  • Save kevinhikaruevans/4a029c27a5f28d5cda06b95738ae2062 to your computer and use it in GitHub Desktop.
Save kevinhikaruevans/4a029c27a5f28d5cda06b95738ae2062 to your computer and use it in GitHub Desktop.
10-bit serial on an ESP32-S3 using RMT on ESP-IDF

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(&copy_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));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment