rmt + some of link layer

This commit is contained in:
Justin Chow
2025-07-06 00:28:01 -04:00
committed by Johnathon Slightham
parent 701f56dc31
commit e16f332476
18 changed files with 2555 additions and 169 deletions

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "RMTManager.cpp"
PRIV_REQUIRES driver esp_event nvs_flash esp_netif
REQUIRES esp_driver_rmt
INCLUDE_DIRS "include")

41
components/rmt/README.md Normal file
View File

@@ -0,0 +1,41 @@
# ESP32-S3 RMT
WIP
The related ESP32-S3 latest documentation on RMT can be found [here](https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/rmt.html).
## Encoding
Using the Ethernet Manchester encoding method where a bit 1 is encoded as a falling edge transition (high -> low) and a bit 0 is encoded as a rising edge transition (low -> high).
Specific timings are defined in `RMTSymbols.h` but it has been tested with 10us intervals (each symbol has length 20us) with 1MHz resolution.
Physical Layer will be using Manchester encoding (see the doc/detailed_design_timeline document for more information).
### Testing with other Encoding Methods
We may test and compare with other encoding methods to see which has better performance. Currently, we are using manchester at 1MHz resolution - probably good enough for 10Mbps? (needs to be tested)
The following are some potential other encoding methods (inspired from http://units.folder101.com/cisco/sem1/Notes/ch7-technologies/encoding.htm)
- Manchester with shorter symbol durations (and with higher resolutions?) - Used by 100Mbps Ethernet
- NRZ Inverted - Used by 100BASE-FX networks
- 8b/10b with NRZ - Used by 1000BASE-X
## Transceiver Operations
We are currently only using one channel for TX and one channel for RX. This will be changed in the future to use multiple channels at the same time (transmitting separate data however/transmitting independent of each other).
Currently, TX is being transmitted from `RMT_TX_GPIO` and RX being received from `RMT_RX_GPIO`.
## Completed Encoding Methods
- Manchester
- NRZ-I
## Testing
Use `-D TIME_TEST=1` to measure the average transmission rate (over 1000 iterations) on a chosen encoding scheme.
To change encoding schemes, use one of the following compiler flags:
- `MANCHESTER_40=1` for 40MHz resolution Manchester
- `NRZ-INVERTED=1` for Non-Return-to-Zero Inverted
- with no specified encoding schemes, the program will use Manchester at 1MHz resolution.
### Notes
You may need to perform a `idf.py clean` or `idf.py fullclean` to undefine the unwanted compiler flags previously set (eg. when changing encoding schemes or not running the time test)

View File

@@ -0,0 +1,733 @@
#include "RMTManager.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
RMTManager::RMTManager(){
esp_err_t res = init();
if (res != ESP_OK){
//failed
ESP_LOGE(DEBUG_TAG, "Failed to initialize the RMTManager");
return;
}
ESP_LOGD(DEBUG_TAG, "RMTManager has been initialized");
}
esp_err_t RMTManager::init_tx_channel(){
esp_err_t res_tx = ESP_FAIL;
//setup encoder config
for (uint8_t i = 0; i < MAX_CHANNELS; i++){
reset_encoder_context(&channels[i].encoder_context); //ensure the encoder context is initialized
rmt_simple_encoder_config_t encoder_config = {
.callback = encoder_callback,
.arg = &channels[i].encoder_context
};
//create encoder
res_tx = rmt_new_simple_encoder(&encoder_config, &channels[i].encoder);
if (res_tx != ESP_OK){
// printf("Failed to create encoder\n");
ESP_LOGE(DEBUG_TAG, "Failed to create encoder");
channels[i].encoder = NULL;
return ESP_FAIL;
}
//enable the callback
rmt_tx_event_callbacks_t tx_cbs = {
.on_trans_done = RMTManager::rmt_tx_done_callback
};
rmt_tx_channel_config_t tx_channel_config_template = {
.gpio_num = tx_gpio[i],
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = RMT_SYMBOL_BLOCK_SIZE, //giving each channel ~192B of memory
.trans_queue_depth = 4,
.flags = {
.invert_out = 0,
.with_dma = 0,
}
};
channels[i].tx_gpio = tx_gpio[i];
channels[i].status = CHANNEL_NOT_READY_STATUS;
if (channels[i].tx_rmt_handle != NULL) {
rmt_disable(channels[i].tx_rmt_handle);
rmt_del_channel(channels[i].tx_rmt_handle);
channels[i].tx_rmt_handle = NULL;
}
if (channels[i].tx_done_semaphore != NULL) {
vSemaphoreDelete(channels[i].tx_done_semaphore);
channels[i].tx_done_semaphore = NULL;
}
channels[i].tx_queue = xQueueCreate(QUEUE_SIZE, sizeof(TxBuffer)); //can store up to 10 queued transmissions (each transmission size being 192B; based ont he RMT_SYMBOL_BLOCK_SIZE)
res_tx = rmt_new_tx_channel(&tx_channel_config_template, &channels[i].tx_rmt_handle);
//init tx channel
if (res_tx != ESP_OK) {
// printf("Failed to init TX channel\n");
ESP_LOGE(DEBUG_TAG, "Failed to init TX channel %d", i);
continue;
}
if (channels[i].tx_rmt_handle == NULL) {
// printf("TX channel handle is NULL\n");
ESP_LOGE(DEBUG_TAG, "TX channel handle is NULL on channel %d", i);
continue;
}
channels[i].tx_done_semaphore = xSemaphoreCreateBinary(); //create a binary sem
TxCallbackContext* tx_callback_ctx = new TxCallbackContext {
.tx_done_sem = channels[i].tx_done_semaphore,
.transmit_queue = channels[i].tx_queue,
.tx_context = &channels[i].encoder_context
};
if (channels[i].tx_done_semaphore == NULL){
ESP_LOGE(DEBUG_TAG, "Failed to create TX done semaphore on channel %d", i);
continue;
}
// res_tx = rmt_tx_register_event_callbacks(channels[i].tx_rmt_handle, &tx_cbs, channels[i].tx_done_semaphore);
res_tx = rmt_tx_register_event_callbacks(channels[i].tx_rmt_handle, &tx_cbs, static_cast<void*>(tx_callback_ctx));
if (res_tx != ESP_OK) {
// printf("Failed to register TX callback\n");
ESP_LOGE(DEBUG_TAG, "Failed to register TX callback on channel %d", i);
continue;
}
//enable tx channels
res_tx = rmt_enable(channels[i].tx_rmt_handle);
if (res_tx != ESP_OK) {
// printf("Failed to enable TX channel\n");
ESP_LOGE(DEBUG_TAG, "Failed to enable TX channel %d", i);
continue;
}
printf("Successfully enabled TX channel %d\n", i);
}
return ESP_OK;
}
bool RMTManager::rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *edata, void *user_data){
BaseType_t high_task_wakeup = pdFALSE;
// SemaphoreHandle_t sem = (SemaphoreHandle_t)user_data;
TxCallbackContext* args = static_cast<TxCallbackContext*>(user_data);
SemaphoreHandle_t sem = args->tx_done_sem;
QueueHandle_t queue = args->transmit_queue;
rmt_encoder_context_t* encoder_context = args->tx_context;
TxBuffer buf = {};
BaseType_t xTaskWokenByReceive = pdFALSE;
xQueueReceiveFromISR(queue, static_cast<TxBuffer*>(&buf), &xTaskWokenByReceive); //remove from the queue
if (buf.data != nullptr){
vPortFree((void*)buf.data);
}
if (encoder_context != nullptr){
encoder_context->bit_index = 0;
encoder_context->byte_index = 0;
encoder_context->num_symbols = 0;
}
xSemaphoreGiveFromISR(sem, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
esp_err_t RMTManager::wait_until_send_complete(uint8_t channel_num){
if (channel_num >= MAX_CHANNELS){
ESP_LOGE(DEBUG_TAG, "Invalid channel number");
return ESP_FAIL;
}
if(this->channels[channel_num].tx_done_semaphore == NULL){
return ESP_FAIL;
}
if (xSemaphoreTake(this->channels[channel_num].tx_done_semaphore, pdMS_TO_TICKS(10000)) == pdTRUE){
return ESP_OK;
}
ESP_LOGE(DEBUG_TAG, "Timeout of 10000 ms when waiting for RMT TX to complete");
return ESP_FAIL;
}
bool RMTManager::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
BaseType_t res = xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
if (res != pdTRUE){
// printf("RX Callback: Failed to enqueue received data\n");
ESP_LOGE(DEBUG_TAG, "RX Callback: Failed to enqueue received data");
}
// return whether any task is woken up
return high_task_wakeup == pdTRUE;
}
esp_err_t RMTManager::init_rx_channel(){
for (uint8_t i = 0; i < MAX_CHANNELS; i++){
rmt_rx_channel_config_t rx_channel_config = {
.gpio_num = rx_gpio[i],
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = RMT_SYMBOL_BLOCK_SIZE,
.flags = {
.invert_in = false,
.with_dma = 0
}
}; //temp for one rx channel
//temp
channels[i].rx_gpio = rx_gpio[i];
esp_err_t res_rx = rmt_new_rx_channel(&rx_channel_config, &channels[i].rx_rmt_handle);
if (res_rx != ESP_OK) {
// printf("Failed to init RX channel - reason %s\n", esp_err_to_name(res_rx));
ESP_LOGE(DEBUG_TAG, "Failed to init RX channel - reason %s", esp_err_to_name(res_rx));
return ESP_FAIL;
}
if (channels[i].rx_rmt_handle == NULL) {
// printf("RX channel handle is NULL\n");
ESP_LOGE(DEBUG_TAG, "RX channel handle is NULL");
return ESP_FAIL;
}
channels[i].rx_queue = xQueueCreate(QUEUE_SIZE, sizeof(rmt_rx_done_event_data_t)); //creating queue with some random size
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = RMTManager::rmt_rx_done_callback
};
rmt_rx_register_event_callbacks(channels[i].rx_rmt_handle, &cbs, channels[i].rx_queue);
res_rx = rmt_enable(channels[i].rx_rmt_handle);
if (res_rx != ESP_OK) {
// printf("Failed to enable RX channel\n");
ESP_LOGE(DEBUG_TAG, "Failed to enable RX channel");
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t RMTManager::init(){
esp_err_t res = this->init_tx_channel();
if (res != ESP_OK) {
// printf("Failed to init TX channel\n");
ESP_LOGE(DEBUG_TAG, "Failed to init TX channel");
return ESP_FAIL;
}
res = this->init_rx_channel();
if (res != ESP_OK) {
// printf("Failed to init RX channel\n");
ESP_LOGE(DEBUG_TAG, "Failed to init RX channel");
return ESP_FAIL;
}
for (uint8_t i = 0; i < MAX_CHANNELS; i++){
if (channels[i].tx_rmt_handle != NULL && channels[i].rx_rmt_handle != NULL && channels[i].tx_done_semaphore != NULL && channels[i].rx_queue != NULL){
channels[i].status = CHANNEL_READY_STATUS;
}
}
// printf("Free heap before encoder creation: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
// heap_caps_print_heap_info(MALLOC_CAP_DEFAULT);
// printf("Free DMA-capable heap before encoder creation: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_DMA));
// heap_caps_print_heap_info(MALLOC_CAP_DMA);
return ESP_OK;
}
/**
* @brief This is a callback function called by RMT when transmitting. This function will encode the user data `data` with rising and falling edges based on the bit.a64l
* The symbols are defined in `RMTManager.h`, where a bit 1 is transmitted as a `RMT_SYMBOL_ONE` and a bit 0 is transmitted as a `RMT_SYMBOL_ZERO`
*
* @param data
* @param data_size
* @param symbols_written
* @param symbols_free
* @param symbols
* @param done
* @param arg
*/
size_t RMTManager::encoder_callback(const void* data, size_t data_size, size_t symbols_written,
size_t symbols_free, rmt_symbol_word_t* symbols, bool* done, void* arg){
rmt_encoder_context_t* ctx = (rmt_encoder_context_t*) arg; //get the current context
if (symbols_free == 0){ //no space in the tx buffer; don't encode any more bytes until there is space left
*done = (ctx->byte_index >= data_size);
return 0;
}
const uint8_t* bytes = (const uint8_t*)data; //get the user data as an array of bytes
size_t symbols_used = 0; //number of symbols used
while (ctx->byte_index < data_size && symbols_used < symbols_free){ //loop until we have reached the end of the data or filled the RMT symbol buffer (`symbols_free`)
uint8_t byte = bytes[ctx->byte_index]; //get the byte from the data
uint8_t bit = (byte >> (7 - ctx->bit_index)) & 0x01; //get the current bit, as determined from the bit index (MSB first)
#ifndef NRZ_INVERTED
//Manchester (Ethernet Standard) Encoding
symbols[symbols_used++] = bit ? RMT_SYMBOL_ONE : RMT_SYMBOL_ZERO; //if the bit is a 1, transmit a 1 symbol; otherwise, transmit 0 symbol
ctx->num_symbols++;
#else
//NRZ-I encoding. Must change the voltage level whenever a bit 1 is detected
if (ctx->byte_index == 0 && ctx->bit_index == 0){
//MSB of the first byte - send a rising edge 1 to allow any succeeding 0s to be detected by the receiver
symbols[symbols_used++] = RMT_SYMBOL_ONE_RISING;
ctx->current_level = !ctx->current_level; //current level is high
ctx->num_symbols++;
}
if (ctx->zero_count == CONSEC_ZERO_THRESHOLD){
ctx->current_level = !ctx->current_level;
symbols[symbols_used++] = ctx->current_level ? RMT_SYMBOL_ONE_RISING : RMT_SYMBOL_ONE_FALLING;
ctx->num_symbols++;
ctx->zero_count = 0;
// Don't advance to next bit reprocess the current bit
continue;
}
if (bit == 1){
ctx->current_level = !ctx->current_level; //invert current level
symbols[symbols_used++] = ctx->current_level ? RMT_SYMBOL_ONE_RISING : RMT_SYMBOL_ONE_FALLING; //if current level is 0 (low), it must be a falling edge. otherwise, it is a rising edge
ctx->num_symbols++;
ctx->zero_count = 0;
} else {
//bit 0s, maintain current level
if (ctx->current_level){
//check if the previous symbol was RMT_SYMBOL_ZERO_HIGH. if it is, simply add another RMT_DURATION_MAX on duration1 (this is a slight optimization to send less symbols)
if (symbols[symbols_used-1].level0 == 1 && symbols[symbols_used-1].level1 == 1){
symbols[symbols_used-1].duration1 += RMT_DURATION_MAX;
} else {
//previous symbol was not RMT_SYMBOL_ZERO_HIGH
symbols[symbols_used++] = RMT_SYMBOL_ZERO_HIGH;
ctx->num_symbols++;
}
} else {
if (symbols[symbols_used-1].level0 == 0 && symbols[symbols_used-1].level1 == 0){
symbols[symbols_used-1].duration1 += RMT_DURATION_MAX;
} else {
symbols[symbols_used++] = ctx->current_level ? RMT_SYMBOL_ZERO_HIGH : RMT_SYMBOL_ZERO_LOW;
ctx->num_symbols++;
}
}
ctx->zero_count++;
}
#endif //NRZ_INVERTED
ctx->bit_index++;
if (ctx->bit_index >= 8) {
//reached the end of the byte; go to the next byte
ctx->bit_index = 0;
ctx->byte_index++;
}
}
*done = (ctx->byte_index >= data_size); //if the transmit is done, set the `done` flag to true (all bytes have been encoded)
return symbols_used;
}
void RMTManager::reset_encoder_context(rmt_encoder_context_t* ctx){
ctx->bit_index = 0;
ctx->byte_index = 0;
ctx->num_symbols = 0;
#ifdef NRZ_INVERTED
ctx->current_level = false;
#endif //NRZ_INVERTED
}
/**
* @brief Sends the string `data` of size `size`, with config `config`
*
* @param data
* @param size
* @param config
* @return int
*/
int RMTManager::send(uint8_t* data, size_t size, rmt_transmit_config_t* config, uint8_t channel_num){
if (channel_num >= MAX_CHANNELS){
ESP_LOGE(DEBUG_TAG, "send() error: invalid channel number");
}
if (channels[channel_num].status == CHANNEL_NOT_READY_STATUS){
ESP_LOGE(DEBUG_TAG, "send() error: Channel %d is not ready", channel_num);
}
if (this->channels[channel_num].tx_rmt_handle == nullptr) {
// printf("send() error: tx_chan is NULL\n");
ESP_LOGE(DEBUG_TAG, "send() error: tx_chan is NULL");
return ESP_FAIL;
}
if (this->channels[channel_num].encoder == nullptr) {
// printf("send() error: encoder is NULL\n");
ESP_LOGE(DEBUG_TAG, "send() error: encoder is NULL");
return ESP_FAIL;
}
if (data == nullptr || size == 0 || size > (RMT_SYMBOL_BLOCK_SIZE*4)) {
// printf("send() error: data pointer NULL or size 0\n");
ESP_LOGE(DEBUG_TAG, "send() error: data pointer NULL or size 0");
return ESP_FAIL;
}
if (config == nullptr) {
// printf("send() error: config pointer is NULL\n");
ESP_LOGE(DEBUG_TAG, "send() error: config pointer is NULL");
return ESP_FAIL;
}
TxBuffer new_data_to_send_buf = {
.data = (uint8_t*)pvPortMalloc(size), //this may not be thread safe but each channel should be on its own thread so maybe it's ok???
.length = size
};
if (new_data_to_send_buf.data == nullptr){
ESP_LOGE(DEBUG_TAG, "failed to malloc");
return ESP_FAIL;
}
memcpy((void*)(new_data_to_send_buf.data), data, size);
if (xQueueSendToBack(channels[channel_num].tx_queue, (void*)&new_data_to_send_buf, (TickType_t) 10) != pdPASS){ //note this may not work very well since im not checking the return value; this function can fail if the queue is full
vPortFree((void*)new_data_to_send_buf.data);
ESP_LOGE(DEBUG_TAG, "Failed to queue data");
return ESP_FAIL;
}
esp_err_t res = rmt_transmit(this->channels[channel_num].tx_rmt_handle, this->channels[channel_num].encoder, new_data_to_send_buf.data, new_data_to_send_buf.length, config);
if (res != ESP_OK){
// printf("Failed to send %s\n", data);
vPortFree((void*)new_data_to_send_buf.data);
ESP_LOGE(DEBUG_TAG, "Failed to send %s", data);
return ESP_FAIL;
}
return ESP_OK;
}
/**
* @brief This function, given the `symbols` and the length `num`, will convert the received symbols into the symbols defined in `RMTSymbols.h`
* this somehow works first try????? (tested with 't', 'O', and 'THIS IS A SAMPLE TEXT MESSAGE')
* @param symbols received symbols
* @param num number of received symbols
* @param decoded decoded symbol string
* @param output_num size of `decoded`
* @return int - returns the number of symbols written to the buffer
*/
int RMTManager::decode_symbols(rmt_symbol_word_t* symbols, size_t num, rmt_symbol_word_t* decoded, size_t output_num){
if (symbols == NULL || decoded == NULL || num == 0 || output_num == 0){
return ESP_FAIL;
}
size_t output_index = 0;
size_t i = 0;
bool curr_high_low = true; //flag to maintain where we are (either high or low)
#ifdef NRZ_INVERTED
uint32_t num_0_symbols_duration = 0, num_0_symbols = 0;
uint8_t consecutive_zeros = 0;
#endif //NRZ_INVERTED
while (output_index < output_num && i < num){
// printf("duration0 %d level0 %d duration1 %d level1 %d\n", symbols[i].duration0, symbols[i].level0, symbols[i].duration1, symbols[i].level1); //dummy print receive
#ifndef NRZ_INVERTED
//manchester encoding
/*there are two cases in the beginning:
1. if duration0 = 20, then we are in between two symbols (low to high and high to low).
in this case, we need to insert a low in the beginning and "split" the current symbol into 2
2. if duration0 = 10, then the first symbol should be high to low
*/
if (symbols[i].duration0 != RMT_DURATION_SYMBOL){
if (i != 0){
if (curr_high_low){
decoded[output_index++] = RMT_SYMBOL_ONE;
} else {
decoded[output_index++] = RMT_SYMBOL_ZERO;
}
curr_high_low = !curr_high_low;
} else {
//need to insert a 0 before received symbols
decoded[output_index++] = RMT_SYMBOL_ZERO;
}
}
if (curr_high_low){
decoded[output_index++] = RMT_SYMBOL_ONE;
} else {
decoded[output_index++] = RMT_SYMBOL_ZERO;
}
//if duration1 = 20, then we are starting low
if (symbols[i].duration1 != RMT_DURATION_SYMBOL){
curr_high_low = !curr_high_low;
}
#else
//nrz-i encoding - bit stuffing doesn't work
//there is always a rising edge (period of RMT_DURATION_SYMBOL on high as the first half isn't captured)
// if (i == 0){
// curr_high_low = true;
// if (symbols[i].duration0 == RMT_DURATION_MAX){
// //next symbol is a 1 - can continue (first RMT_DURATION is from the first symbol (init rising edge). second RMT_DURATION is second symbol)
// i++;
// continue;
// }
// }
//need to "split"
if (symbols[i].duration0 % (RMT_DURATION_SYMBOL * 2) != 0){
num_0_symbols_duration = symbols[i].duration0 - RMT_DURATION_SYMBOL; //last waveform has duration0 with some duration that's only a multiple of RMT_DURATION_SYMBOL
}else {
num_0_symbols_duration = symbols[i].duration0 - RMT_DURATION_SYMBOL * 2; //one from the rising edge, one from the falling edge
}
num_0_symbols = num_0_symbols_duration / RMT_DURATION_MAX; //should be the number of 0 symbols
for (int j = 0; j < num_0_symbols && output_index < output_num; j++){
decoded[output_index++] = curr_high_low ? RMT_SYMBOL_ZERO_HIGH : RMT_SYMBOL_ZERO_LOW;
consecutive_zeros++;
}
curr_high_low = !curr_high_low;
if (output_index >= output_num){
break;
}
if (!curr_high_low){
decoded[output_index++] = RMT_SYMBOL_ONE_FALLING;
} else {
decoded[output_index++] = RMT_SYMBOL_ONE_RISING;
}
// if (consecutive_zeros == MAX_ZER){
// consecutive_zeros = 0;
// } else {
// if (!curr_high_low) {
// decoded[output_index++] = RMT_SYMBOL_ONE_FALLING;
// } else {
// decoded[output_index++] = RMT_SYMBOL_ONE_RISING;
// }
// consecutive_zeros = 0; // reset zero count after a real 1 bit
// }
if (symbols[i].duration1 == 0){
break; //last waveform has duration1 = 0
}
num_0_symbols_duration = symbols[i].duration1 - RMT_DURATION_SYMBOL * 2; //one from the falling edge, one from the rising edge
num_0_symbols = num_0_symbols_duration / RMT_DURATION_MAX; //should be the number of 0 symbols
for (int j = 0; j < num_0_symbols && output_index < output_num; j++){
decoded[output_index++] = curr_high_low ? RMT_SYMBOL_ZERO_HIGH : RMT_SYMBOL_ZERO_LOW;
}
curr_high_low = !curr_high_low;
if (output_index >= output_num){
break;
}
if (!curr_high_low){
decoded[output_index++] = RMT_SYMBOL_ONE_FALLING;
} else {
decoded[output_index++] = RMT_SYMBOL_ONE_RISING;
}
// if (consecutive_zeros == 5){
// consecutive_zeros = 0;
// } else {
// if (!curr_high_low) {
// decoded[output_index++] = RMT_SYMBOL_ONE_FALLING;
// } else {
// decoded[output_index++] = RMT_SYMBOL_ONE_RISING;
// }
// consecutive_zeros = 0; // reset zero count after a real 1 bit
// }
#endif //NRZ_INVERTED
i++;
}
return (int)output_index;
}
/**
* @brief This converts the parsed symbols into a string of size `output_index`
*
* @param symbols Parsed received symbols (see `RMTSymbols.h` for the definitions of the symbols)
* @param num Length of `symbols`
* @param string Output string encoded by the symbols
* @param output_num `length of the char array`
* @return int - length of the output string (-1 if failure)
*/
int RMTManager::convert_symbols_to_char(rmt_symbol_word_t* symbols, size_t num, uint8_t* string, size_t output_num){
if (symbols == NULL || string == NULL || num == 0 || output_num == 0){
return ESP_FAIL;
}
size_t bit_count = 0;
char byte = 0;
size_t output_index = 0;
int i = 0;
while (i < num && output_index < output_num){
#ifndef NRZ_INVERTED
if (symbols[i].level0 == 0 && symbols[i].level1 == 1){
//zero
byte = byte << 1;
}else if (symbols[i].level0 == 1 && symbols[i].level1 == 0) {
byte = (byte << 1) + 1;
} else {
return ESP_FAIL;
}
#else
//nrz-i
if (symbols[i].level0 != symbols[i].level1){
//bit 1
byte = (byte << 1) + 1;
} else if (symbols[i].level0 == symbols[i].level1){
//bit 0
byte = byte << 1;
} else {
return ESP_FAIL;
}
#endif //NRZ_INVERTED
bit_count++;
if (bit_count == 8){
//a byte has been parsed
// printf("inserting %b\n", byte);
string[output_index++] = byte;
byte = 0;
bit_count = 0;
}
i++;
}
printf("output_index %d\n", output_index);
return (int)output_index;
}
/**
* @brief Start async RX job
*
* @return esp_err_t
*/
esp_err_t RMTManager::start_receiving(uint8_t channel_num){
if (channel_num >= MAX_CHANNELS){
return ESP_FAIL;
}
if (channels[channel_num].status == CHANNEL_LISTENING){
return ESP_OK; //failed to receive earlier; no need to start the async rx job again (alreayd running)
}
if (channels[channel_num].status == CHANNEL_NOT_READY_STATUS){
ESP_LOGE(DEBUG_TAG, "RX Channel is not ready");
return ESP_FAIL;
}
if (channels[channel_num].rx_rmt_handle == NULL){
ESP_LOGE(DEBUG_TAG, "RX Channel not ready");
return ESP_FAIL;
}
esp_err_t res = rmt_receive(channels[channel_num].rx_rmt_handle, channels[channel_num].raw_symbols, sizeof(channels[channel_num].raw_symbols), &this->receive_config);
if (res != ESP_OK){
// printf("Failed to start receive\n");
ESP_LOGE(DEBUG_TAG, "Failed to start receive");
}
channels[channel_num].status = CHANNEL_LISTENING;
return res;
}
/**
* @brief Function to get the received messages
*
* @return int
*/
int RMTManager::receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num){
if (channel_num >= MAX_CHANNELS){
return ESP_FAIL;
}
if (channels[channel_num].status != CHANNEL_LISTENING){
ESP_LOGE(DEBUG_TAG, "receive(): Receive channel %d is not ready to receive due to init fail or async job was not started", channel_num);
return ESP_FAIL;
}
rmt_rx_done_event_data_t rx_data;
if (xQueueReceive(channels[channel_num].rx_queue, &rx_data, pdMS_TO_TICKS(15000)) != pdTRUE){ //this will wait until a message has arrived or not
// printf("Timeout occurred while waiting for RX event\n");
ESP_LOGE(DEBUG_TAG, "Timeout occurred while waiting for RX event - didn't receive a message in time");
return ESP_FAIL;
}
channels[channel_num].status = CHANNEL_READY_STATUS;
// printf("Got %d symbols\n", rx_data.num_symbols);
// printf("raw symbols:\n");
// for (int i = 0; i < rx_data.num_symbols; i++){
// printf("duration0 %d level0 %d duration1 %d level1 %d\n", rx_data.received_symbols[i].duration0, rx_data.received_symbols[i].level0, rx_data.received_symbols[i].duration1, rx_data.received_symbols[i].level1);
// }
int num = this->decode_symbols(rx_data.received_symbols, rx_data.num_symbols, channels[channel_num].decoded_recv_symbols, sizeof(channels[channel_num].decoded_recv_symbols));
if (num < 0){
return ESP_FAIL;
}
// printf("\n\nparsed symbols:\n");
// for (int i = 0; i < num; i++){
// printf("duration0 %d level0 %d duration1 %d level1 %d\n", decoded_recv_symbols[i].duration0, decoded_recv_symbols[i].level0, decoded_recv_symbols[i].duration1, decoded_recv_symbols[i].level1);
// }
*output_size = this->convert_symbols_to_char(channels[channel_num].decoded_recv_symbols, num, recv_buf, size);
if (*output_size < 0){
return ESP_FAIL;
}
return ESP_OK;
}
RMTManager::~RMTManager(){
for (uint8_t i = 0; i < MAX_CHANNELS; i++){
if (this->channels[i].tx_rmt_handle) {
rmt_disable(this->channels[i].tx_rmt_handle);
rmt_del_channel(this->channels[i].tx_rmt_handle);
}
if (channels[i].rx_rmt_handle) {
rmt_disable(channels[i].rx_rmt_handle);
rmt_del_channel(channels[i].rx_rmt_handle);
}
if (channels[i].rx_queue) {
vQueueDelete(channels[i].rx_queue);
}
}
}

View File

@@ -0,0 +1,163 @@
#ifndef RMT_COMMUNICATIONS
#define RMT_COMMUNICATIONS
#include "esp_event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "soc/gpio_num.h"
#include "RMTSymbols.h"
#include <cstring>
#define MAX_CHANNELS 4
#define RMT_SYMBOL_BLOCK_SIZE 48
#define RECEIVE_BUFFER_SIZE 1024 //this is some value (we should probably set it to some packet size that we predetermine in some custom protocol:tm:)
#define DEBUG_TAG "RMTManager"
#define CHANNEL_LISTENING (0x2) //channel waiting to receive
#define CHANNEL_READY_STATUS (0x1) //channel able to send and ready to start receive async job
#define CHANNEL_NOT_READY_STATUS (0x0) //channel is not ready (cannot send and/or receive)
#define QUEUE_SIZE 10
/**
* @brief This struct keeps track of the current byte and bit index of the user data being transmmitted via RMT
*
*/
typedef struct {
size_t byte_index; //which byte is currently being encoded when transmitting
uint8_t bit_index; //which bit in the `byte_index` is currently being encoded (into high/low waveforms)
size_t num_symbols; //temp
#ifdef NRZ_INVERTED
bool current_level;
uint8_t zero_count;
#endif //NRZ_INVERTED
} rmt_encoder_context_t;
typedef struct _rmt_channel{
//TX
uint8_t tx_gpio;
rmt_channel_handle_t tx_rmt_handle;
SemaphoreHandle_t tx_done_semaphore;
QueueHandle_t tx_queue;
rmt_encoder_handle_t encoder; //encoder config
rmt_encoder_context_t encoder_context;
//RX
uint8_t rx_gpio;
rmt_channel_handle_t rx_rmt_handle;
QueueHandle_t rx_queue;
rmt_symbol_word_t raw_symbols[RECEIVE_BUFFER_SIZE]; //buffer to store the symbols on receive
rmt_symbol_word_t decoded_recv_symbols[RECEIVE_BUFFER_SIZE]; //allocating some dummy size buffer for decoded string
//General
uint8_t status;
} rmt_channel;
class RMTManager{
public:
RMTManager();
~RMTManager();
int send(uint8_t* data, size_t size, rmt_transmit_config_t* config, uint8_t channel_num); //temp function to send some string data
int receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num);
static size_t encoder_callback(const void* data, size_t data_size, size_t symbols_written,
size_t symbols_free, rmt_symbol_word_t* symbols, bool* done, void* arg);
static bool rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data);
static bool rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *edata, void *user_data);
esp_err_t start_receiving(uint8_t channel_num);
esp_err_t wait_until_send_complete(uint8_t channel_num);
private:
esp_err_t init();
void reset_encoder_context(rmt_encoder_context_t* ctx);
esp_err_t init_tx_channel();
esp_err_t init_rx_channel();
int decode_symbols(rmt_symbol_word_t* symbols, size_t num, rmt_symbol_word_t* decoded, size_t output_num);
int convert_symbols_to_char(rmt_symbol_word_t* symbols, size_t num, uint8_t* string, size_t output_num);
rmt_channel channels[MAX_CHANNELS] = {0};
//=====================TX=====================
// rmt_channel_handle_t tx_chan;
const gpio_num_t tx_gpio[MAX_CHANNELS] = {GPIO_NUM_1, GPIO_NUM_2, GPIO_NUM_3, GPIO_NUM_4}; //using pins 1,2,3,4 for channels 0,1,2,3 respectively for tx
// gpio_num_t tx_gpio[MAX_CHANNELS] = {GPIO_NUM_1}; //using pins 1,2,3,4 for channels 0,1,2,3 respectively for tx
// rmt_encoder_context_t encoder_context = {0};
//semaphore to indicate it is done
// SemaphoreHandle_t tx_done_semaphore;
//will be used to temporarily hold the bits that are being wait to be sent -- not working
// QueueHandle_t transmit_queue = NULL;
// TxCallbackContext tx_context;
//=====================RX=====================
rmt_channel_handle_t rx_chan;
const gpio_num_t rx_gpio[MAX_CHANNELS] = {GPIO_NUM_12, GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15}; //using pins 12,13,14,15 for channels 0,1,2,3 respectively for rx
// gpio_num_t rx_gpio[MAX_CHANNELS] = {GPIO_NUM_12}; //using pins 12,13,14,15 for channels 0,1,2,3 respectively for rx
// QueueHandle_t receive_queue = NULL;
//rx_receive_config
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 100,
.signal_range_max_ns = 200 * 1000,
.flags = {
.en_partial_rx = true
}
};
// bool ready_to_receive = false;
};
//will need to keep the data alive until it has been transmitted (not working or being used atm)
struct TxCallbackContext{
SemaphoreHandle_t tx_done_sem;
QueueHandle_t transmit_queue;
rmt_encoder_context_t* tx_context;
};
typedef struct {
const uint8_t* data;
size_t length;
} TxBuffer;
typedef struct _gpio_channel_pair {
gpio_num_t tx_pin;
gpio_num_t rx_pin;
} GPIO_Channel_Pair;
static const GPIO_Channel_Pair gpio_channel_pairs[MAX_CHANNELS] = {
{
.tx_pin = GPIO_NUM_1,
.rx_pin = GPIO_NUM_12
},
{
.tx_pin = GPIO_NUM_2,
.rx_pin = GPIO_NUM_13
},
{
.tx_pin = GPIO_NUM_3,
.rx_pin = GPIO_NUM_14
},
{
.tx_pin = GPIO_NUM_4,
.rx_pin = GPIO_NUM_15
}
}; //todo: use these pairs directly instead of the two arrays in the class definition above
#endif //RMT_COMMUNICATIONS

View File

@@ -0,0 +1,136 @@
#ifdef RMT_COMMUNICATIONS
#include "driver/rmt_tx.h"
// #ifdef MANCHESTER_40
// #define RMT_RESOLUTION_HZ 40 * 1000 * 1000 // 40MHz resolution
// #define RMT_DURATION_SYMBOL 10 //0.25 us bit duration
// #elif NRZ_INVERTED
// #ifdef NRZ_INVERTED_40_HZ
// #define RMT_RESOLUTION_HZ 40 * 1000 * 1000 // 40MHz resolution
// #else
// #define RMT_RESOLUTION_HZ 20 * 1000 * 1000 // 20MHz resolution
// #endif //NRZ_INVERTED_40_HZ
// #ifdef NRZ_INVERTED_10
// #define RMT_DURATION_SYMBOL 10 //0.5us bit duration
// #elif NRZ_INVERTED_20
// #define RMT_DURATION_SYMBOL 5 //0.25 us bit duration
// #elif NRZ_INVERTED_2
// #define RMT_DURATION_SYMBOL 2 //0.1 us bit duration
// #else
// #define RMT_DURATION_SYMBOL 20 //1us bit duration
// #endif //NRZ_INVERTED_10
// #else
// #define RMT_RESOLUTION_HZ 1 * 1000 * 1000 // 1MHz resolution
// #define RMT_DURATION_SYMBOL 10
// #endif //MANCHESTER_40
// #define NRZ_INVERTED //using NRZ_I
#define RMT_RESOLUTION_HZ 40 * 1000 * 1000 // 40MHz resolution
#define RMT_DURATION_SYMBOL 12 //0.6us
// #define RMT_DURATION_SYMBOL ((RMT_RESOLUTION_HZ * 3) / 1000000) // duration time for a symbol - this is 3us
#define RMT_DURATION_MAX (2 * RMT_DURATION_SYMBOL)
// #define RMT_TX_GPIO GPIO_NUM_1 //RMT will use GPIO pin 1 to transmit (on one channel)
// #define RMT_RX_GPIO GPIO_NUM_12 // RMT will use GPIO pin 12 to receive (on one channel)
#ifndef NRZ_INVERTED
//MANCHESTER ENCODING (ETHERNET STANDARD)
/**
* @brief This struct represents a 1 symbol being transmitted over RMT. This will create a falling edge (low for `RMT_DURATION_SYMBOL` and high for `RMT_DURATION_SYMBOL`)
*
*/
static const rmt_symbol_word_t RMT_SYMBOL_ONE = {
.duration0 = RMT_DURATION_SYMBOL,
.level0 = 1,
.duration1 = RMT_DURATION_SYMBOL,
.level1 = 0,
};
/**
* @brief This struct represents a 0 symbol being transmitted over RMT. This will create a rising edge (low for `RMT_DURATION_SYMBOL` and high for `RMT_DURATION_SYMBOL`)
*
*/
static const rmt_symbol_word_t RMT_SYMBOL_ZERO = {
.duration0 = RMT_DURATION_SYMBOL,
.level0 = 0,
.duration1 = RMT_DURATION_SYMBOL,
.level1 = 1,
};
#else
//Non-Return-to-Zero Inverted (NRZ-I)
#define CONSEC_ZERO_THRESHOLD 3 //max number of consecutive zeros before adding a bit 1
// Logic 1 inverts the current voltage state
/**
* @brief This struct represents a 1 symbol being transmitted over RMT. This will create a falling edge (low for `RMT_DURATION_SYMBOL` and high for `RMT_DURATION_SYMBOL`)
*
*/
static const rmt_symbol_word_t RMT_SYMBOL_ONE_FALLING = {
.duration0 = RMT_DURATION_SYMBOL,
.level0 = 1,
.duration1 = RMT_DURATION_SYMBOL,
.level1 = 0,
};
/**
* @brief This struct represents a 1 symbol being transmitted over RMT. This will create a rising edge (low for `RMT_DURATION_SYMBOL` and high for `RMT_DURATION_SYMBOL`)
*
*/
static const rmt_symbol_word_t RMT_SYMBOL_ONE_RISING = {
.duration0 = RMT_DURATION_SYMBOL,
.level0 = 0,
.duration1 = RMT_DURATION_SYMBOL,
.level1 = 1,
};
/**
* @brief This struct will represent a bit 0. In NRZ-I, this represents a no change in the voltage
*
*/
static const rmt_symbol_word_t RMT_SYMBOL_ZERO_HIGH = {
.duration0 = RMT_DURATION_SYMBOL,
.level0 = 1,
.duration1 = RMT_DURATION_SYMBOL,
.level1 = 1,
};
/**
* @brief This struct will represent a bit 0. In NRZ-I, this represents a no change in the voltage
*
*/
static const rmt_symbol_word_t RMT_SYMBOL_ZERO_LOW = {
.duration0 = RMT_DURATION_SYMBOL,
.level0 = 0,
.duration1 = RMT_DURATION_SYMBOL,
.level1 = 0,
};
#endif //NRZ_INVERTED
//not used at the moment
// static const rmt_symbol_word_t RMT_SYMBOL_HIGH_STOP = {
// .duration0 = RMT_DURATION_SYMBOL / 2,
// .level0 = 1,
// .duration1 = RMT_DURATION_SYMBOL / 2,
// .level1 = 0,
// };
// static const rmt_symbol_word_t RMT_SYMBOL_LOW_STOP = {
// .duration0 = RMT_DURATION_SYMBOL / 2,
// .level0 = 0,
// .duration1 = RMT_DURATION_SYMBOL / 2,
// .level1 = 1,
// };
#endif //RMT_COMMUNICATIONS