Link Layer Frame Send Scheduler, Internal RX Polling, Framework for Generic Frames + Fragmentation

This commit is contained in:
Justin Chow
2025-12-14 02:51:33 -05:00
committed by Johnathon Slightham
parent 9068ff72c4
commit 89a4c03f75
24 changed files with 3735 additions and 818 deletions

View File

@@ -4,38 +4,28 @@ 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).
# About
Specific timings are defined in `RMTSymbols.h` but it has been tested with 10us intervals (each symbol has length 20us) with 1MHz resolution.
This component is a wrapper around the ESP32-S3 RMT API and allows the operation of a physical layer between multiple ESP32-S3 microcontrollers. The goal of this component is to enable network communication encapsulation with higher and more abstract types of communication frames/packets (eg. link layer, application layer).
Physical Layer will be using Manchester encoding (see the doc/detailed_design_timeline document for more information).
## Encoding Method
### 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)
RMT uses the Ethernet Manchester encoding method, where a bit 1 is encoded as a falling edge transition and a bit 0 is encoded as a rising edge transition.
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
Specific timings are defined in `RMTSymbols.h`, which includes bit timings, resolution HZ, and symbol definitions.
## 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).
## Usage
Currently, TX is being transmitted from `RMT_TX_GPIO` and RX being received from `RMT_RX_GPIO`.
This component is not meant to be used directly by the user (should only be exclusively be used by the Link Layer, found in `components/dataLink`).
## Completed Encoding Methods
- Manchester
- NRZ-I
For specific details, see `dataLink/DataLinkManager.cpp`.
## Testing
Use `-D TIME_TEST=1` to measure the average transmission rate (over 1000 iterations) on a chosen encoding scheme.
## Channel Configurations
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.
RMT relies on the ESP32-S3's GPIO pins. This RMT component statically sets a predefined GPIO pin to either a TX or RX for a channel.
### 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)
For TX/RX pin definitions, see `RMTManager.h`. For example, `tx_gpio[]` is a `4` length array. Each index in the array represents one half of a pair that represents the TX/RX channel on the physical layer.
## RMT Internal Async Jobs
See the ESP32-S3 RMT documentation for more information. RMT relies on callback functions to notify the encoding/decoding on TX/RX respectively is completed, or to perform the actual encoding and decoding/char translations.

View File

@@ -30,6 +30,9 @@ esp_err_t RMTManager::init_tx_channel(){
esp_err_t res_tx = ESP_FAIL;
memory_to_free = xQueueCreate(15, sizeof(uint8_t*));
xTaskCreate(RMTManager::freeMemory, "RIPFreeMem", 4096, static_cast<void*>(memory_to_free), 5, NULL);
memory_to_free = xQueueCreate(15, sizeof(uint8_t*));
xTaskCreate(RMTManager::freeMemory, "RIPFreeMem", 4096, static_cast<void*>(memory_to_free), 5, NULL);
for (uint8_t i = 0; i < num_channels; i++){
@@ -104,7 +107,7 @@ esp_err_t RMTManager::init_tx_channel(){
.tx_done_sem = channels[i].tx_done_semaphore,
.transmit_queue = channels[i].tx_queue,
.tx_context = &channels[i].encoder_context,
.free_mem_queue = memory_to_free
.free_mem_queue = memory_to_free,
};
if (channels[i].tx_done_semaphore == NULL){
@@ -150,9 +153,12 @@ bool RMTManager::rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx
QueueHandle_t free_queue = args->free_mem_queue;
TxBuffer buf = {};
BaseType_t xTaskWokenByReceive = pdFALSE;
// xSemaphoreTakeFromISR(mutex, &xTaskWokenByReceive);
xQueueReceiveFromISR(queue, static_cast<TxBuffer*>(&buf), &xTaskWokenByReceive); //remove from the queue
// xSemaphoreGiveFromISR(mutex, &xTaskWokenByReceive);
if (buf.data != nullptr){
xQueueSendFromISR(free_queue, &buf.data, &xTaskWokenByReceive);
}
@@ -397,10 +403,10 @@ esp_err_t RMTManager::send(uint8_t* data, size_t size, rmt_transmit_config_t* co
return ESP_FAIL;
}
memcpy((void*)(new_data_to_send_buf.data), data, size);
memcpy(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){
vPortFree((void*)new_data_to_send_buf.data);
if (xQueueSendToBack(channels[channel_num].tx_queue, &new_data_to_send_buf, (TickType_t) MUTEX_MAX_WAIT_TICKS) != pdPASS){
vPortFree(new_data_to_send_buf.data);
ESP_LOGE(DEBUG_TAG, "Failed to queue data");
return ESP_FAIL;
}
@@ -528,12 +534,12 @@ esp_err_t RMTManager::start_receiving(uint8_t channel_num){
}
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)
return ESP_ERR_NOT_FINISHED;
}
if (channels[channel_num].status == CHANNEL_NOT_READY_STATUS){
ESP_LOGE(DEBUG_TAG, "RX Channel is not ready");
return ESP_FAIL;
return ESP_ERR_INVALID_STATE;
}
if (channels[channel_num].rx_rmt_handle == NULL){
@@ -544,7 +550,6 @@ esp_err_t RMTManager::start_receiving(uint8_t channel_num){
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");
}
@@ -556,7 +561,12 @@ esp_err_t RMTManager::start_receiving(uint8_t channel_num){
/**
* @brief Function to get the received messages
*
* @return int
* @param recv_buf Byte array of the received bytes
* @param size Size of the byte array
* @param output_size Pointer containing the received bytes (will be written)
* @param channel_num Physical channel pair to receive from
*
* @return esp_err_t
*/
esp_err_t RMTManager::receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num){
if (channel_num >= num_channels){
@@ -569,9 +579,9 @@ esp_err_t RMTManager::receive(uint8_t* recv_buf, size_t size, size_t* output_siz
}
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
if (xQueueReceive(channels[channel_num].rx_queue, &rx_data, pdMS_TO_TICKS(150)) != pdTRUE){ //this will wait until a message has arrived or not
// printf("Timeout occurred while waiting for RX event\n");
ESP_LOGW(DEBUG_TAG, "Timeout occurred while waiting for RX event - didn't receive a message in time");
// ESP_LOGE(DEBUG_TAG, "Timeout occurred while waiting for RX event - didn't receive a message in time");
return ESP_FAIL;
}

View File

@@ -22,6 +22,8 @@
#define QUEUE_SIZE 10
#define MUTEX_MAX_WAIT_TICKS 100
/**
* @brief This struct keeps track of the current byte and bit index of the user data being transmmitted via RMT
*
@@ -66,7 +68,12 @@ typedef struct _rmt_channel{
uint8_t status;
} rmt_channel;
/**
* @brief Class representing the RMT/Physical Layer
*
* @author Justin Chow
*
*/
class RMTManager{
public:
RMTManager(uint8_t num_channels);
@@ -100,26 +107,14 @@ class RMTManager{
// rmt_channel_handle_t tx_chan;
const gpio_num_t tx_gpio[MAX_CHANNELS] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_11, GPIO_NUM_13}; //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;
const gpio_num_t tx_gpio[MAX_CHANNELS] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_11, GPIO_NUM_13}; //using pins 4,5,11,13 for channels 0,1,2,3 respectively for tx
QueueHandle_t memory_to_free;
//=====================RX=====================
rmt_channel_handle_t rx_chan;
const gpio_num_t rx_gpio[MAX_CHANNELS] = {GPIO_NUM_3, GPIO_NUM_6, GPIO_NUM_12, GPIO_NUM_14}; //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;
const gpio_num_t rx_gpio[MAX_CHANNELS] = {GPIO_NUM_3, GPIO_NUM_6, GPIO_NUM_12, GPIO_NUM_14}; //using pins 3,6,12,14 for channels 0,1,2,3 respectively for rx
//rx_receive_config
rmt_receive_config_t receive_config = {

View File

@@ -2,8 +2,8 @@
#include "driver/rmt_tx.h"
#define RMT_RESOLUTION_HZ 3 * 1000 * 1000 // 3MHz resolution
#define RMT_DURATION_SYMBOL 1 //0.667us
#define RMT_RESOLUTION_HZ 4 * 1000 * 1000 // 4 MHz resolution
#define RMT_DURATION_SYMBOL 2 //1 us
#define RMT_DURATION_MAX (2 * RMT_DURATION_SYMBOL)