Fix RIP bugs, add in UART

This commit is contained in:
Johnathon Slightham
2026-03-31 14:26:37 -04:00
committed by Johnathon Slightham
parent d4602012f1
commit 548e8db484
26 changed files with 704 additions and 434 deletions

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "SoftUARTManager.cpp"
PRIV_REQUIRES driver esp_driver_uart esp_driver_gpio
REQUIRES rmt
INCLUDE_DIRS "include")

View File

@@ -0,0 +1,219 @@
#include "SoftUARTManager.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include <cstring>
#define TAG "SoftUART"
SoftUARTManager::SoftUARTManager(uint8_t num_channels) {
if (num_channels == 0 || num_channels > SUART_NUM_CHANNELS) {
ESP_LOGE(TAG, "Invalid num_channels %d (must be 1-%d)", num_channels, SUART_NUM_CHANNELS);
this->num_channels = 0;
return;
}
this->num_channels = num_channels;
memset(hw, 0, sizeof(hw));
memset(ch_status, SUART_CHANNEL_NOT_READY, sizeof(ch_status));
for (uint8_t i = 0; i < num_channels; i++) {
if (init_hw_channel(i) == ESP_OK) {
ch_status[i] = SUART_CHANNEL_READY;
} else {
ESP_LOGE(TAG, "Failed to init HW UART channel %d", i);
}
}
ESP_LOGI(TAG, "SoftUARTManager ready: %d HW UART channel(s)", num_channels);
}
SoftUARTManager::~SoftUARTManager() {
for (uint8_t i = 0; i < num_channels; i++) deinit_hw_channel(i);
}
esp_err_t SoftUARTManager::init_hw_channel(uint8_t idx) {
uart_port_t port = HW_PORT[idx];
hw[idx].port = port;
hw[idx].tx_gpio = HW_TX[idx];
hw[idx].rx_gpio = HW_RX[idx];
const uart_config_t cfg = {
.baud_rate = SUART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.source_clk = UART_SCLK_DEFAULT,
};
esp_err_t res = uart_param_config(port, &cfg);
if (res != ESP_OK) { ESP_LOGE(TAG, "HW[%d] uart_param_config: %s", idx, esp_err_to_name(res)); return res; }
res = uart_set_pin(port, (int)HW_TX[idx], (int)HW_RX[idx], UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (res != ESP_OK) { ESP_LOGE(TAG, "HW[%d] uart_set_pin: %s", idx, esp_err_to_name(res)); return res; }
res = uart_driver_install(port, SUART_RX_BUF_SIZE * 2, SUART_RX_BUF_SIZE * 2, 0, nullptr, 0);
if (res != ESP_OK) { ESP_LOGE(TAG, "HW[%d] uart_driver_install: %s", idx, esp_err_to_name(res)); return res; }
hw[idx].tx_done_sem = xSemaphoreCreateBinary();
if (hw[idx].tx_done_sem == nullptr) return ESP_ERR_NO_MEM;
hw[idx].status = SUART_CHANNEL_READY;
ESP_LOGI(TAG, "HW channel %d ready (UART%d TX=GPIO%d RX=GPIO%d)", idx, (int)port, (int)HW_TX[idx], (int)HW_RX[idx]);
return ESP_OK;
}
void SoftUARTManager::deinit_hw_channel(uint8_t idx) {
if (hw[idx].tx_done_sem) { vSemaphoreDelete(hw[idx].tx_done_sem); hw[idx].tx_done_sem = nullptr; }
if (hw[idx].status != SUART_CHANNEL_NOT_READY) uart_driver_delete(hw[idx].port);
hw[idx].status = SUART_CHANNEL_NOT_READY;
}
esp_err_t SoftUARTManager::send(uint8_t* data, size_t size, void* /*config*/, uint8_t channel_num) {
if (channel_num >= num_channels || data == nullptr || size == 0) return ESP_ERR_INVALID_ARG;
if (ch_status[channel_num] == SUART_CHANNEL_NOT_READY) return ESP_ERR_INVALID_STATE;
return send_hw(channel_num, data, size);
}
esp_err_t SoftUARTManager::receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num) {
if (channel_num >= num_channels || recv_buf == nullptr || output_size == nullptr) return ESP_FAIL;
if (ch_status[channel_num] != SUART_CHANNEL_LISTENING) return ESP_FAIL;
return receive_hw(channel_num, recv_buf, size, output_size);
}
esp_err_t SoftUARTManager::start_receiving(uint8_t channel_num) {
if (channel_num >= num_channels) return ESP_FAIL;
if (ch_status[channel_num] == SUART_CHANNEL_NOT_READY) return ESP_ERR_INVALID_STATE;
if (ch_status[channel_num] == SUART_CHANNEL_LISTENING) return ESP_OK;
uart_flush_input(hw[channel_num].port);
ch_status[channel_num] = SUART_CHANNEL_LISTENING;
return ESP_OK;
}
esp_err_t SoftUARTManager::wait_until_send_complete(uint8_t channel_num) {
if (channel_num >= num_channels) return ESP_FAIL;
SemaphoreHandle_t sem = hw[channel_num].tx_done_sem;
if (sem == nullptr) return ESP_FAIL;
if (xSemaphoreTake(sem, SUART_TX_DONE_WAIT_TICKS) != pdTRUE) {
ESP_LOGE(TAG, "wait_until_send_complete: timeout on channel %d", channel_num);
return ESP_FAIL;
}
uart_wait_tx_done(hw[channel_num].port, SUART_TX_DONE_WAIT_TICKS);
return ESP_OK;
}
esp_err_t SoftUARTManager::send_hw(uint8_t idx, uint8_t* data, size_t size) {
uart_wait_tx_done(hw[idx].port, SUART_TX_DONE_WAIT_TICKS);
int written = uart_write_bytes(hw[idx].port, reinterpret_cast<const char*>(data), size);
if (written < 0 || static_cast<size_t>(written) != size) {
ESP_LOGE(TAG, "HW[%d] uart_write_bytes returned %d", idx, written);
return ESP_FAIL;
}
xSemaphoreGive(hw[idx].tx_done_sem);
return ESP_OK;
}
esp_err_t SoftUARTManager::receive_hw(uint8_t idx, uint8_t* buf, size_t size, size_t* out) {
uart_port_t port = hw[idx].port;
// wait for at least a minimum complete frame
const size_t MIN_FRAME = 10;
size_t available = 0;
if (uart_get_buffered_data_len(port, &available) != ESP_OK || available < MIN_FRAME) {
return ESP_FAIL;
}
//scan for the preamble byte (0xAB)
uint8_t preamble = 0;
int discard_count = 0;
while (true) {
int n = uart_read_bytes(port, &preamble, 1, pdMS_TO_TICKS(2));
if (n < 1) return ESP_FAIL;
if (preamble == 0xAB) break;
discard_count++;
if (discard_count > (int)SUART_RX_BUF_SIZE) {
ESP_LOGW(TAG, "HW[%d] no preamble after %d bytes — flushing", idx, discard_count);
uart_flush_input(port);
return ESP_FAIL;
}
}
if (discard_count > 0) {
ESP_LOGD(TAG, "HW[%d] skipped %d noise byte(s) before preamble", idx, discard_count);
}
buf[0] = preamble;
// read the remaining 7 header bytes
int peeked = uart_read_bytes(port, buf + 1, 7, pdMS_TO_TICKS(20));
if (peeked < 7) {
ESP_LOGE(TAG, "HW[%d] timeout reading header (got %d of 7)", idx, peeked);
return ESP_FAIL;
}
// determine frame type and total length
bool is_control = (buf[5] & 0x80) != 0;
uint16_t len_field = (uint16_t)buf[6] | ((uint16_t)buf[7] << 8);
size_t total_frame_len;
if (is_control) {
total_frame_len = 8 + len_field + 2;
} else {
uint8_t hdr2[4];
int n2 = uart_read_bytes(port, hdr2, 4, pdMS_TO_TICKS(20));
if (n2 < 4) {
ESP_LOGE(TAG, "HW[%d] timeout reading generic header tail", idx);
return ESP_FAIL;
}
memcpy(buf + 8, hdr2, 4);
uint16_t data_len = (uint16_t)hdr2[2] | ((uint16_t)hdr2[3] << 8);
total_frame_len = 12 + data_len + 2;
}
if (total_frame_len > size || total_frame_len < 10 || total_frame_len > 256) {
ESP_LOGW(TAG, "HW[%d] unreasonable frame length %d — draining", idx, (int)total_frame_len);
// Drain remaining bytes of this bad frame so the next preamble scan
// doesn't find false 0xAB matches inside the body.
size_t hdr_consumed = is_control ? 8 : 12;
size_t to_drain = 0;
if (total_frame_len > hdr_consumed && total_frame_len <= 256) {
to_drain = total_frame_len - hdr_consumed;
} else {
size_t avail = 0;
uart_get_buffered_data_len(port, &avail);
to_drain = avail;
}
if (to_drain > 0) {
uint8_t drain_buf[128];
while (to_drain > 0) {
size_t chunk = (to_drain > sizeof(drain_buf)) ? sizeof(drain_buf) : to_drain;
int got = uart_read_bytes(port, drain_buf, chunk, pdMS_TO_TICKS(5));
if (got <= 0) break;
to_drain -= (size_t)got;
}
}
return ESP_FAIL;
}
// read the rest of the frame
size_t already_read = is_control ? 8 : 12;
size_t remaining = total_frame_len - already_read;
if (remaining > 0) {
int nr = uart_read_bytes(port, buf + already_read, remaining, pdMS_TO_TICKS(100));
if (nr < (int)remaining) {
ESP_LOGE(TAG, "HW[%d] incomplete frame: got %d of %d remaining bytes",
idx, nr, (int)remaining);
return ESP_FAIL;
}
}
*out = total_frame_len;
return ESP_OK;
}

View File

@@ -0,0 +1,59 @@
#ifndef SOFT_UART_MANAGER_H
#define SOFT_UART_MANAGER_H
#include "IPhysicalLayer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "soc/gpio_num.h"
#include <cstdint>
#include <cstddef>
#define SUART_NUM_CHANNELS 3
#define SUART_BAUD_RATE 115200
#define SUART_RX_BUF_SIZE 1024
#define SUART_TX_DONE_WAIT_TICKS pdMS_TO_TICKS(5000)
#define SUART_CHANNEL_NOT_READY 0x0
#define SUART_CHANNEL_READY 0x1
#define SUART_CHANNEL_LISTENING 0x2
typedef struct {
uart_port_t port;
gpio_num_t tx_gpio;
gpio_num_t rx_gpio;
uint8_t status;
SemaphoreHandle_t tx_done_sem;
} hw_channel_t;
class SoftUARTManager : public IPhysicalLayer {
public:
explicit SoftUARTManager(uint8_t num_channels);
~SoftUARTManager() override;
esp_err_t send(uint8_t* data, size_t size, void* config, uint8_t channel_num) override;
esp_err_t receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num) override;
esp_err_t start_receiving(uint8_t channel_num) override;
esp_err_t wait_until_send_complete(uint8_t channel_num) override;
private:
uint8_t num_channels;
hw_channel_t hw[SUART_NUM_CHANNELS];
uint8_t ch_status[SUART_NUM_CHANNELS];
esp_err_t init_hw_channel(uint8_t idx);
void deinit_hw_channel(uint8_t idx);
esp_err_t send_hw(uint8_t idx, uint8_t* data, size_t size);
esp_err_t receive_hw(uint8_t idx, uint8_t* buf, size_t size, size_t* out);
static constexpr gpio_num_t HW_TX[SUART_NUM_CHANNELS] = { GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_11 };
static constexpr gpio_num_t HW_RX[SUART_NUM_CHANNELS] = { GPIO_NUM_3, GPIO_NUM_6, GPIO_NUM_12 };
static constexpr uart_port_t HW_PORT[SUART_NUM_CHANNELS] = { UART_NUM_0, UART_NUM_1, UART_NUM_2 };
};
#endif // SOFT_UART_MANAGER_H