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,3 @@
idf_component_register(SRCS "DataLinkManager.cpp"
PRIV_REQUIRES driver esp_event nvs_flash esp_netif rmt
INCLUDE_DIRS "include")

View File

@@ -0,0 +1,635 @@
#include "DataLinkManager.h"
#include "RMTManager.h"
#include "esp_log.h"
#include "nvs_flash.h"
/**
* @brief Construct a new Data Link Manager:: Data Link Manager object
*
* @param board_id Board ID of the current board. Will be written to the NVM under key "board" if not already written.
*/
DataLinkManager::DataLinkManager(uint8_t board_id){
//init table for this board and set up link layer priority queue
phys_comms = std::make_unique<RMTManager>();
if (phys_comms == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "RMT object was not created. Link layer communications will not function.");
return;
}
if (get_board_id(this_board_id) != ESP_OK){
//failed to read from NVM for board id under key "board". Will write a new entry
this_board_id = board_id;
set_board_id(this_board_id);
}
if (this_board_id != board_id){
//NVM board id is different from `board_id` -> update entry to the new board id
this_board_id = board_id;
set_board_id(this_board_id);
}
init_rip();
}
DataLinkManager::~DataLinkManager(){
phys_comms.reset(); //not strictly necessary to do this explicitly
}
esp_err_t DataLinkManager::set_board_id(uint8_t board_id){
if (board_id == BROADCAST_ADDR || board_id == PC_ADDR){
ESP_LOGE(DEBUG_LINK_TAG, "Invalid board id");
return ESP_FAIL;
}
nvs_handle_t handle;
esp_err_t res = nvs_open("board", NVS_READWRITE, &handle);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to open NVS Handle");
return res;
}
res = nvs_set_u8(handle, "id", board_id);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to write ID %d to NVM", board_id);
nvs_close(handle);
return res;
}
res = nvs_commit(handle);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to commit write");
nvs_close(handle);
return res;
}
this_board_id = board_id;
printf("Successfully wrote %d to NVM\n", board_id);
nvs_close(handle);
return ESP_OK;
}
esp_err_t DataLinkManager::get_board_id(uint8_t& board_id){
nvs_handle_t handle;
esp_err_t res = nvs_open("board", NVS_READWRITE, &handle);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to open NVS Handle");
return res;
}
res = nvs_get_u8(handle, "id", &board_id);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to get ID from NVM. Please make sure NVM is already assigned a board id!");
nvs_close(handle);
return res;
}
printf("Successfully got board id %d from NVM\n", board_id);
nvs_close(handle);
return ESP_OK;
}
/**
* @brief Sends a frame to another board (node to node communication) via RMT (physical layer)
*
* @param dest_board 8 bit ID of the destination board
* @param data
* @param data_len Length of the data in bytes
* @param type
* @return esp_err_t
*/
esp_err_t DataLinkManager::send(uint8_t dest_board, uint8_t* data, uint16_t data_len, FrameType type, uint8_t curr_channel){
if (phys_comms == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to send frame due to no RMT object");
return ESP_FAIL;
}
if (data == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "Data array does not exist");
return ESP_FAIL;
}
if (this_board_id == PC_ADDR){
ESP_LOGE(DEBUG_LINK_TAG, "This board is not assigned a board id");
return ESP_FAIL;
}
if (curr_channel >= MAX_CHANNELS){
return ESP_FAIL;
}
if (IS_CONTROL_FRAME(static_cast<uint8_t>(type))){
//control frame
if (data_len > MAX_CONTROL_DATA_LEN){
ESP_LOGE(DEBUG_LINK_TAG, "Data for control frame is too large. Maximum size is %d. Current data length is %d", MAX_CONTROL_DATA_LEN, data_len);
return ESP_FAIL;
}
control_frame new_frame = {
.preamble = START_OF_FRAME,
.sender_id = this_board_id,
.receiver_id = dest_board,
.seq_num = sequence_num_map[dest_board]++,
.type_flag = static_cast<uint8_t>(type),
.data_len = static_cast<uint8_t>(data_len),
.crc_16 = 0, //not made yet
};
// printf("size of control frame %d\n", sizeof(control_frame));
// printf("size of message %d\n", new_frame.data_len);
// printf("message %s\n", data);
// print_buffer_binary(data, new_frame.data_len);
size_t frame_size = sizeof(control_frame) + new_frame.data_len - MAX_CONTROL_DATA_LEN;
// printf("frame size %d\n", frame_size);
uint8_t send_data[frame_size];
size_t offset = 0;
send_data[offset++] = new_frame.preamble;
send_data[offset++] = new_frame.sender_id;
send_data[offset++] = new_frame.receiver_id;
send_data[offset++] = new_frame.seq_num & 0xFF;
send_data[offset++] = (new_frame.seq_num >> 8) & 0xFF;
send_data[offset++] = new_frame.type_flag;
send_data[offset++] = new_frame.data_len;
memcpy(&send_data[offset], data, new_frame.data_len);
offset += new_frame.data_len;
geneate_crc_16(send_data, offset, &new_frame.crc_16);
send_data[offset++] = new_frame.crc_16 & 0xFF;
send_data[offset++] = (new_frame.crc_16 >> 8) & 0xFF;
rmt_transmit_config_t config = {
.loop_count = 0,
.flags = {
.eot_level = 0 // typically 0 or 1, depending on your output idle level
}
};
// printf("sending message:\n");
// print_buffer_binary(send_data, frame_size);
phys_comms->send(send_data, offset, &config, curr_channel);
//can wait for the rmt to finish
// esp_err_t res = phys_comms->wait_until_send_complete(curr_channel); //this cannot be here in deployment but until the RMT manager can hold this copy of data this will have to be here
// if (res != ESP_OK){
// ESP_LOGE(DEBUG_LINK_TAG, "Failed to send message");
// return ESP_FAIL;
// } else{
// // printf("Sent message to board %d\n", dest_board);
// }
} else {
//generic frame
printf("not implemented yet\n");
}
return ESP_OK;
}
void DataLinkManager::print_binary(uint8_t byte) {
for (int i = 7; i >= 0; --i) {
printf("%d", (byte >> i) & 1);
}
}
void DataLinkManager::print_buffer_binary(const uint8_t* buffer, size_t length) {
for (size_t i = 0; i < length; ++i) {
print_binary(buffer[i]);
printf(" ");
}
printf("\n");
}
/**
* @brief Starts the RMT async receive job to start listening for a new frame over a given channel
*
* @param curr_channel
* @return esp_err_t
*/
esp_err_t DataLinkManager::start_receive_frames(uint8_t curr_channel){
if (curr_channel >= MAX_CHANNELS){
return ESP_FAIL;
}
return phys_comms->start_receiving(curr_channel);
}
esp_err_t DataLinkManager::receive(uint8_t* data, size_t data_len, size_t* recv_len, uint8_t curr_channel){
if (data == NULL){
ESP_LOGE(DEBUG_LINK_TAG, "Invalid data array");
return ESP_FAIL;
}
if (curr_channel >= MAX_CHANNELS){
return ESP_FAIL;
}
if (data_len < MAX_CONTROL_DATA_LEN + CONTROL_FRAME_OVERHEAD){
return ESP_FAIL;
}
// uint8_t recv_buf[256];
esp_err_t res = phys_comms->receive(data, data_len, recv_len, curr_channel);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "RMT Failed to receive");
return ESP_FAIL;
}
if (*recv_len > MAX_CONTROL_DATA_LEN + CONTROL_FRAME_OVERHEAD){
ESP_LOGE(DEBUG_LINK_TAG, "Invalid control frame");
return ESP_FAIL;
}
//check for a rip frame
if (static_cast<FrameType>((data[5])) == FrameType::RIP_TABLE_CONTROL){
printf("Got a RIP frame\n");
uint8_t rip_message[rip_table_valid_rows*2] = {};
size_t rip_message_size = 0;
res = get_data_from_frame(data, *recv_len, rip_message, &rip_message_size);
if (res != ESP_OK){
return ESP_FAIL; //crc or data len failed
}
for (size_t i = 0; i < rip_message_size-1; i+=2){
uint8_t board_id = rip_message[i];
uint8_t hops = rip_message[i+1];
printf("Received: board_id %d and number of hops %d on channel %d\n", board_id, hops, curr_channel);
RIPRow* entry = nullptr;
res = rip_find_entry(board_id, &entry);
if (res != ESP_OK){
return ESP_FAIL;
}
if (entry == nullptr){
return ESP_FAIL; //no room for more entries in the table
}
if (entry->valid == RIP_INVALID_ROW){
//adding a new entry
rip_add_entry(board_id, hops + 1, curr_channel, &entry);
} else {
//updating an entry
rip_update_entry(hops + 1, curr_channel, &entry);
}
}
*recv_len = 0;
}
return ESP_OK;
}
esp_err_t DataLinkManager::get_data_from_frame(uint8_t* data, size_t data_len, uint8_t* message, size_t* message_size){
if (data == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "Invalid data array");
return ESP_FAIL;
}
if (message == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "Invalid message array");
return ESP_FAIL;
}
if (message_size == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "Invalid message size ptr");
return ESP_FAIL;
}
if (IS_CONTROL_FRAME(data[5])){
control_frame temp = {0};
temp.preamble = data[0];
temp.sender_id = data[1];
temp.receiver_id = data[2];
temp.seq_num = (uint16_t)data[3] | ((uint16_t)data[4] << 8);
temp.type_flag = data[5];
temp.data_len = data[6];
if (temp.data_len > data_len){
return ESP_FAIL;
}
*message_size = temp.data_len;
memcpy(temp.data, &data[7], temp.data_len);
memcpy(message, &data[7], temp.data_len);
geneate_crc_16(data, 7*sizeof(uint8_t) + temp.data_len, &temp.crc_16);
if (((uint16_t)data[7 + temp.data_len] | ((uint16_t)data[8 + temp.data_len] << 8)) != temp.crc_16){
//CRC mismatch
ESP_LOGE(DEBUG_LINK_TAG, "CRC Mismatch");
return ESP_FAIL;
}
// printf("Frame Information:\n");
// printf("%-10s %-12s %-13s %-15s %-12s %-10s %-6s\n",
// "Preamble", "Sender ID", "Receiver ID", "Sequence Num", "Type+Flag", "Data Len", "CRC");
// printf("0x%02X %-12d %-13d %-15d 0x%02X %-10d 0x%04X\n",
// temp.preamble, temp.sender_id, temp.receiver_id, temp.seq_num, temp.type_flag, temp.data_len, temp.crc_16);
} else {
//not implemented yet
}
return ESP_OK;
}
/**
* @brief This function implements the CRC-16/CCITT algorithm
*
* @param data
* @param data_len
* @param crc
* @return esp_err_t
*/
esp_err_t DataLinkManager::geneate_crc_16(uint8_t* data, size_t data_len, uint16_t* crc){
if (data == nullptr){
return ESP_FAIL;
}
if (data_len == 0){
return ESP_FAIL; //fail if the data len is 0
}
*crc = 0x0;
for (size_t i = 0; i < data_len; i++){
uint8_t tbl_idx = (*crc >> 8) ^ data[i];
*crc = (*crc << 8) ^ crc16_table[tbl_idx];
}
return ESP_OK;
}
esp_err_t DataLinkManager::print_frame_info(uint8_t* data, size_t data_len, uint8_t* message){
printf("Received frame of size %d:\n", data_len);
size_t message_size;
// print_buffer_binary(data, data_len);
return get_data_from_frame(data, data_len, message, &message_size);
}
/**
* @brief Initializes the RIP table
*
*/
void DataLinkManager::init_rip(){
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
rip_table[i] = {
.info = {
.board_id = BROADCAST_ADDR, //invalid addr
.hops = RIP_MAX_HOPS + 1, //infinite
},
.channel = MAX_CHANNELS + 1, //invalid channels
.ttl = 0,
.valid = RIP_INVALID_ROW,
.row_sem = NULL
};
rip_table[i].row_sem = xSemaphoreCreateMutexStatic(&rip_table[i].mutex_buf);
}
//add the self route to the table
rip_table[0].info = {
.board_id = this_board_id,
.hops = 0,
};
rip_table[0].channel = MAX_CHANNELS + 1;
rip_table[0].ttl = RIP_TTL_START;
rip_table[0].valid = 1;
rip_table_valid_rows++;
//temp debug
rip_table[1].info = {
.board_id = 69,
.hops = 69,
};
rip_table[1].channel = MAX_CHANNELS + 1;
rip_table[1].ttl = RIP_TTL_START;
rip_table[1].valid = 1;
rip_table_valid_rows++;
rip_table[2].info = {
.board_id = 3,
.hops = 2,
};
rip_table[2].channel = MAX_CHANNELS + 1,
rip_table[2].ttl = RIP_TTL_START,
rip_table[2].valid = 1;
rip_table_valid_rows++;
start_rip_tasks();
}
esp_err_t DataLinkManager::rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t channel, RIPRow** entry){
if (entry == nullptr){
return ESP_FAIL;
}
if (xSemaphoreTake((*entry)->row_sem, (TickType_t)RIP_MAX_SEM_WAIT) != pdTRUE){
return ESP_FAIL;
}
(*entry)->channel = channel;
(*entry)->info = {
.board_id = board_id,
.hops = hops
};
(*entry)->ttl = RIP_TTL_START;
(*entry)->valid = 1;
ESP_LOGI(DEBUG_LINK_TAG, "board_id %d now has hops %d from channel %d", (*entry)->info.board_id, (*entry)->info.hops, channel);
xSemaphoreGive((*entry)->row_sem);
return ESP_OK;
}
esp_err_t DataLinkManager::rip_reset_entry_ttl(uint8_t board_id){
RIPRow* entry = nullptr;
esp_err_t res;
res = rip_find_entry(board_id, &entry);
if (res != ESP_OK){
return ESP_FAIL;
}
if (entry == nullptr){
return ESP_FAIL; //board doesn't exist
}
if (xSemaphoreTake(entry->row_sem, (TickType_t)RIP_MAX_SEM_WAIT) != pdTRUE){
return ESP_FAIL;
}
entry->ttl = RIP_TTL_START;
xSemaphoreGive(entry->row_sem);
return ESP_OK;
}
esp_err_t DataLinkManager::rip_update_entry(uint8_t new_hop, uint8_t channel, RIPRow** entry){
if (entry == nullptr){
return ESP_FAIL; //board doesn't exist
}
if (xSemaphoreTake((*entry)->row_sem, (TickType_t)RIP_MAX_SEM_WAIT) != pdTRUE){
return ESP_FAIL;
}
if (new_hop < (*entry)->info.hops){
(*entry)->info.hops = new_hop;
(*entry)->channel = channel;
}
(*entry)->ttl = RIP_TTL_START;
(*entry)->valid = 1;
ESP_LOGI(DEBUG_LINK_TAG, "board_id %d now has hops %d from channel %d", (*entry)->info.board_id, (*entry)->info.hops, channel);
xSemaphoreGive((*entry)->row_sem);
return ESP_OK;
}
/**
* @brief Finds the board_id in the table. If board_id does not exist in the table, `entry` will contain an empty row to write into.
* TODO: use an unordered map instead of an array?
*
* @param board_id
* @param entry
* @return esp_err_t
*/
esp_err_t DataLinkManager::rip_find_entry(uint8_t board_id, RIPRow** entry){
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
if (xSemaphoreTake(rip_table[i].row_sem, (TickType_t)RIP_MAX_SEM_WAIT) != pdTRUE){
return ESP_FAIL;
}
if (rip_table[i].valid != RIP_INVALID_ROW && rip_table[i].info.board_id == board_id){
*entry = &rip_table[i];
xSemaphoreGive(rip_table[i].row_sem);
break;
}
if (rip_table[i].valid == RIP_INVALID_ROW){
*entry = &rip_table[i];
}
xSemaphoreGive(rip_table[i].row_sem);
}
return ESP_OK;
}
esp_err_t DataLinkManager::broadcast_rip_frame(){
//use the control frame for the demo (as the number of rows increase, we will need to use the generic frame)
//data will be [board_id (1), hops (1), board_id (2), hops (2), ...]
uint8_t rip_message[rip_table_valid_rows*2] = {};
size_t message_idx = 0;
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
xSemaphoreTake(rip_table[i].row_sem, (TickType_t)RIP_MAX_SEM_WAIT);
if (rip_table[i].valid == RIP_INVALID_ROW){
xSemaphoreGive(rip_table[i].row_sem);
continue;
}
rip_message[message_idx++] = rip_table[i].info.board_id;
rip_message[message_idx++] = rip_table[i].info.hops;
xSemaphoreGive(rip_table[i].row_sem);
}
esp_err_t res;
for (uint8_t channel = 0; channel < MAX_CHANNELS; channel++){
ESP_LOGI(DEBUG_LINK_TAG, "sending type %x",static_cast<uint8_t>(FrameType::RIP_TABLE_CONTROL));
res = send(BROADCAST_ADDR, rip_message, message_idx, FrameType::RIP_TABLE_CONTROL, channel);
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to send rip frame on channel %d", channel);
}
}
return ESP_OK;
}
[[noreturn]] void DataLinkManager::rip_broadcast_timer_function(void* args){
DataLinkManager* link_layer_obj = static_cast<DataLinkManager*>(args);
if (link_layer_obj == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "RIP Broadacst task failed to start due to invalid pointer");
vTaskDelete(nullptr);
}
ESP_LOGI(DEBUG_LINK_TAG, "Starting RIP broadcast task");
esp_err_t res;
while(true){
vTaskDelay(pdMS_TO_TICKS(RIP_BROADCAST_INTERVAL)); //wait RIP_BROADCAST_INTERVAL ms
ESP_LOGI(DEBUG_LINK_TAG, "Broadcasting table..."); //debug
res = link_layer_obj->broadcast_rip_frame();
if (res != ESP_OK){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to broadcast rip frame");
}
}
}
[[noreturn]] void DataLinkManager::rip_ttl_decrement_task(void* args){
DataLinkManager* link_layer_obj = static_cast<DataLinkManager*>(args);
if (link_layer_obj == nullptr){
ESP_LOGE(DEBUG_LINK_TAG, "RIP Broadacst task failed to start due to invalid pointer");
vTaskDelete(nullptr);
}
ESP_LOGI(DEBUG_LINK_TAG, "Starting RIP ttl decrement task");
while(true){
vTaskDelay(pdMS_TO_TICKS(RIP_MS_TO_SEC)); //run every second
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
ESP_LOGI(DEBUG_LINK_TAG, "Decrementing ttl on entry %d", i);
if (xSemaphoreTake(link_layer_obj->rip_table[i].row_sem, (TickType_t)RIP_MAX_SEM_WAIT) !=pdTRUE){
ESP_LOGE(DEBUG_LINK_TAG, "Failed to get sem from entry %d", i);
continue;
}
if (link_layer_obj->rip_table[i].valid == RIP_INVALID_ROW){
xSemaphoreGive(link_layer_obj->rip_table[i].row_sem);
continue;
}
if (link_layer_obj->rip_table[i].ttl == 0){
link_layer_obj->rip_table[i].valid = RIP_INVALID_ROW;
} else {
link_layer_obj->rip_table[i].ttl--;
}
ESP_LOGI(DEBUG_LINK_TAG, "Entry %d now has ttl %d", i, link_layer_obj->rip_table[i].ttl);
xSemaphoreGive(link_layer_obj->rip_table[i].row_sem);
}
}
}
/**
* @brief This function will start the tasks required for RIP to function.
* Currently, this function will:
* - start the task to periodically broadcast the board's current copy of the RIP table to all other boards via the 4 RMT channels
* - start a task to periodically decrement the ttl values of each row in the RIP table (WIP) - this will require some sort of mutex on the table itself
*/
void DataLinkManager::start_rip_tasks(){
ESP_LOGI(DEBUG_LINK_TAG, "Starting RIP Broadcast task");
xTaskCreate(DataLinkManager::rip_broadcast_timer_function, "RIPBroadcastTask", 4096, static_cast<void*>(this), 5, NULL);
ESP_LOGI(DEBUG_LINK_TAG, "Starting RIP TTL task");
xTaskCreate(DataLinkManager::rip_ttl_decrement_task, "RIPTTLTask", 4096, static_cast<void*>(this), 5, NULL);
}

View File

@@ -0,0 +1,21 @@
# Data Link Layer
This component represents the data link layer. It will handle board to board communication (abstracting away the RMT specific details of transmitting physical raw bits over wires).
## Frame Definitions
See `./include/Frames.h` for frame definitions.
There will be two types of frames: Control and Generic.
Control frames will contain all control information (eg. Spinning a DC motor, moving a servo to a particular angle, sensor information). These frames will have a limit of 32B of data size with a total packet size of 41B. These frames will not be fragmented.
Generic frames will contain all other information. They will have a max data size of 8KiB with a total packet size of 8206B (8.013672 KiB). These frames can be fragmented.
To differentiate between the two frames, the `type` field in both frames will be compared against. If the MSB is set to 1, it will be determined to be a control packet.
## Routing Information Protocol (RIP) Table
See `./include/Tables.h` for table definitions.
WIP

View File

@@ -0,0 +1,91 @@
#ifndef DATA_LINK
#define DATA_LINK
#include "Frames.h"
#include "Tables.h"
#include "esp_event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <queue> //c++ priority queue
#include <memory>
#include "RMTManager.h"
#include <unordered_map>
#define DEBUG_LINK_TAG "LinkLayer"
#define CRC_POLYNOMIAL 0x1021
static const uint16_t crc16_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
}; //look up table for crc
class DataLinkManager{
public:
DataLinkManager(uint8_t board_id);
~DataLinkManager();
esp_err_t send(uint8_t dest_board, uint8_t* data, uint16_t data_len, FrameType type, uint8_t curr_channel);
esp_err_t start_receive_frames(uint8_t curr_channel);
esp_err_t receive(uint8_t* data, size_t data_len, size_t* recv_len, uint8_t curr_channel);
esp_err_t print_frame_info(uint8_t* data, size_t data_len, uint8_t* message);
esp_err_t send_discover_frame();
private:
uint8_t this_board_id = 0;
std::priority_queue<Frame, std::vector<Frame>, FrameCompare> frame_queue; //create a priority queue
std::unique_ptr<RMTManager> phys_comms;
std::unordered_map<uint8_t, uint16_t> sequence_num_map;
esp_err_t set_board_id(uint8_t board_id);
esp_err_t get_board_id(uint8_t& board_id);
void print_binary(uint8_t byte);
void print_buffer_binary(const uint8_t* buffer, size_t length);
esp_err_t get_data_from_frame(uint8_t* data, size_t data_len, uint8_t* message, size_t* message_size);
esp_err_t geneate_crc_16(uint8_t* data, size_t data_len, uint16_t* crc);
//==== RIP related functions ====
/**
* TODO for RIP:
* Periodic Routing Updates via timer (broadcast routing table every 30 seconds)
* Handle RIP table updates when a RIP table arrives (ensure there are no loops between boards of sending tables back and forth)
* TTL handling and route expiration
* TTLs in all rows should decrement at once via timer
*/
void init_rip();
esp_err_t rip_find_entry(uint8_t board_id, RIPRow** entry);
esp_err_t rip_update_entry(uint8_t new_hop, uint8_t channel, RIPRow** entry);
esp_err_t rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t channel, RIPRow** entry);
esp_err_t rip_reset_entry_ttl(uint8_t board_id);
//this is stored locally with metadata `ttl`
// std::unordered_map<uint8_t, RIPRow> rip_table; //using a hash map to store the routes to other boards - will be used as we scale up
RIPRow rip_table[RIP_MAX_ROUTES]; //temp using a static array
uint8_t rip_table_valid_rows = 0;
void start_rip_tasks();
esp_err_t broadcast_rip_frame();
[[noreturn]] static void rip_broadcast_timer_function(void* args);
[[noreturn]] static void rip_ttl_decrement_task(void* args);
esp_err_t route_frame(uint8_t dest_id, uint8_t* channel_to_send);
};
#endif //DATA_LINK

View File

@@ -0,0 +1,98 @@
#ifdef DATA_LINK
#include "freertos/FreeRTOS.h"
#include <variant>
#include <cstdint>
#define BROADCAST_ADDR 0xFF //used for discovery (finding the board's neighbours). this will mean the board ids will have 2^8-2 = 254 unique IDs that could be assigned
#define PC_ADDR 0x0 //setting 0 to be the PC
#define START_OF_FRAME 0xAB //0b1010_1011 - denotes the start of frame
#define MAX_GENERIC_DATA_LEN (180) //Max 180B
#define MAX_CONTROL_DATA_LEN (1 << 5) // Max 32B
//Flags
#define FLAG_FRAG 0x8 //0b1000 //this fragmented frame is part of a larger frame
#define FLAG_DISCOVERY 0x4 //0b0100
#define FLAG_NEIGH_TABLE 0x2 //0b0010 - used to denote the frame contains the neighbour tables (used for finding the configuration/topology of the network); similar to an ARP or MAC table
#define FLAG_ACK 0x1 //0b0001_0000 - used for confirming receipt of different types of frames from the neighbours
#define GET_TYPE(x) ((x) & 0xF0)
#define GET_FLAG(x) ((x) & 0x0F)
#define MAKE_TYPE_FLAG(type, flag) ((type) | (flag))
#define IS_CONTROL_FRAME(x) (((x) & 0x80) != 0)
#define CONTROL_FRAME_OVERHEAD 9
#define CONTROL_FRAME_TYPE 0x80 //if the frame type MSB is set to 1, use the control frame
//Types (total 2^4 = 16 different types)
enum class FrameType : uint8_t {
MOTOR_TYPE = 0x80, //0b1000_0000
SERVO_TYPE = 0xC0, //0b1100_0000
DISTANCE_SENSOR_TYPE = 0xE0, //0b1110_0000
DEBUG_CONTROL_TYPE = 0xC0, //0b1100_0000
DEBUG_GENERIC_TYPE = 0x00, //0b0000_0000
SYSTEM_TYPE = 0x30, //0x0011_0000 - used for statuses, discovery, and other maintainence requests
RIP_TABLE_CONTROL = 0x90, //0b1001_0000 - using the control frame to broadcast the RIP table
RIP_TABLE_GENERIC = 0x10 //0b0001_000 - using the generic frame to broadcast the RIP table
};
#pragma pack(push, 1) //these structs will be transmitted as is (ensure the structs are structured using 1B alignment - no padding)
typedef struct _control_frame{
uint8_t preamble; //Start of Frame
uint8_t sender_id; //sender board id
uint8_t receiver_id; //receiver board id
uint16_t seq_num; //sequence number to differentiate frames being sent from sender to receiver
uint8_t type_flag; //(type << 4) | flag - both are 4 bits
uint8_t data_len; //Data Length (max 32B)
uint8_t data[MAX_CONTROL_DATA_LEN]; //Variable Length of Data
uint16_t crc_16; //CRC-16
} control_frame; //this will have a max size of 9 + 32B = 41B
typedef struct _data_link_frame{
uint8_t preamble; //Start of Frame
uint8_t sender_id; //sender board id
uint8_t receiver_id; //receiver board id
uint16_t seq_num; //sequence number to differentiate frames being sent from sender to receiver
uint8_t type_flag; //(type << 4) | flag - both are 4 bits
uint16_t frag_info; //(total_frag_num << 8) | frag_num - total_frag_num denotes the total number of fragmented frames to expect for this sequence number(?) and frag_num denotes the fragment frame num
uint16_t data_len; //Data Length (max 178B)
uint8_t data[MAX_GENERIC_DATA_LEN]; //Variable Length of Data
uint16_t crc_16; //CRC-16
} data_link_frame; //this will have a max size of 12 + 180 B = 192B
#pragma pack(pop)
using Frame = std::variant<control_frame, data_link_frame>;
//defining a comparison operation for comparing two frames -- not tested
struct FrameCompare {
bool operator()(const Frame& a, const Frame& b) const {
auto msb_set = [](uint8_t type_flag) {
return (type_flag & 0x80) != 0; // 0x80 == 1000 0000
};
auto get_type_flag = [](const Frame& pkt) -> uint8_t {
return std::visit([](auto&& p) -> uint8_t {
return p.type_flag;
}, pkt);
};
uint8_t type_flag_a = get_type_flag(a);
uint8_t type_flag_b = get_type_flag(b);
bool a_msb = msb_set(type_flag_a);
bool b_msb = msb_set(type_flag_b);
if (a_msb != b_msb) {
return !a_msb; // Frame with MSB set (true) should come first
}
// Tie-breaker: use seq_num if MSB is the same
return std::visit([](auto&& p1, auto&& p2) {
return p1.seq_num > p2.seq_num; // smaller seq_num = higher priority (older)
}, a, b);
}
};
#endif //DATA_LINK

View File

@@ -0,0 +1,32 @@
#pragma once
#ifdef DATA_LINK
#include "freertos/FreeRTOS.h"
#define RIP_MAX_HOPS 15 //16 or more is infinite
#define RIP_MAX_ROUTES 10 //for the demo we will use up to 10 boards in total (9 other boards will be connected = 9 rows)
#define RIP_INVALID_ROW 0
// #define RIP_BROADCAST_INTERVAL 30000 //broadcast every 30 seconds (30000ms)
#define RIP_BROADCAST_INTERVAL 3000 //temp broadcast every 3 seconds (3000ms)
#define RIP_TTL_START 180 //seconds
#define RIP_MS_TO_SEC 1000 //1000 ms to 1 sec
#define RIP_MAX_SEM_WAIT 30
/**
* @brief Routing data to a board
* This struct will be sent to other boards
*/
typedef struct _rip_hops{
uint8_t board_id; //ID of the destination
uint8_t hops; //hop count to `board_id`
} RIPHop;
typedef struct _rip_row{
RIPHop info;
uint8_t channel; //rmt channel
uint8_t ttl; //how long this entry is valid for. starting value is 180 seconds
uint8_t valid; //is this a valid entry?
StaticSemaphore_t mutex_buf; //where mutex state is stored
SemaphoreHandle_t row_sem; //mutex sem handle of mutex_buf
} RIPRow;
#endif //DATA_LINK

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