Files
esp32-bmc/main/uart_handler.c
Valentin Haudiquet 064e8812a4 Initial commit
ESP32-S3 firmware acting as BMC for another board, controlling an ATX power supply and providing access to UART.

Co-authored with GLM-5
2026-03-11 17:39:43 +01:00

411 lines
9.9 KiB
C

#include "uart_handler.h"
#include "config.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include <string.h>
static const char *TAG = TAG_UART;
// Runtime UART configuration
static bmc_uart_config_t current_config;
static bool uart_initialized = false;
static TaskHandle_t uart_rx_task_handle = NULL;
static SemaphoreHandle_t uart_mutex = NULL;
// Callback for WebSocket bridge
static uart_rx_callback_t rx_callback = NULL;
static SemaphoreHandle_t callback_mutex = NULL;
// Ring buffer for received data
#define UART_RING_BUF_SIZE 4096
static uint8_t *ring_buffer = NULL;
static volatile size_t ring_head = 0;
static volatile size_t ring_tail = 0;
// ============================================================================
// Ring Buffer Functions
// ============================================================================
static size_t ring_buffer_available(void) {
if (ring_head >= ring_tail) {
return ring_head - ring_tail;
} else {
return UART_RING_BUF_SIZE - ring_tail + ring_head;
}
}
static size_t ring_buffer_read(uint8_t *buf, size_t len) {
size_t available = ring_buffer_available();
size_t to_read = (len < available) ? len : available;
for (size_t i = 0; i < to_read; i++) {
buf[i] = ring_buffer[ring_tail];
ring_tail = (ring_tail + 1) % UART_RING_BUF_SIZE;
}
return to_read;
}
static size_t ring_buffer_write(const uint8_t *buf, size_t len) {
size_t written = 0;
for (size_t i = 0; i < len; i++) {
size_t next_head = (ring_head + 1) % UART_RING_BUF_SIZE;
if (next_head == ring_tail) {
// Buffer full, overwrite oldest data
ring_tail = (ring_tail + 1) % UART_RING_BUF_SIZE;
}
ring_buffer[ring_head] = buf[i];
ring_head = next_head;
written++;
}
return written;
}
// ============================================================================
// UART Receive Task
// ============================================================================
static void uart_rx_task(void *arg) {
uint8_t *temp_buf = malloc(256);
if (!temp_buf) {
ESP_LOGE(TAG, "Failed to allocate UART RX buffer");
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "UART RX task started, waiting for data on UART%d", BMC_UART_NUM);
while (uart_initialized) {
// Check for data from UART with longer timeout
int len = uart_read_bytes(BMC_UART_NUM, temp_buf, 256, pdMS_TO_TICKS(100));
if (len > 0) {
// Store in ring buffer
ring_buffer_write(temp_buf, len);
// Call callback if registered (outside mutex to avoid deadlock)
uart_rx_callback_t cb = NULL;
xSemaphoreTake(callback_mutex, pdMS_TO_TICKS(100));
cb = rx_callback;
xSemaphoreGive(callback_mutex);
if (cb) {
cb(temp_buf, len);
}
} else if (len < 0) {
ESP_LOGW(TAG, "UART read error: %d", len);
}
}
free(temp_buf);
ESP_LOGI(TAG, "UART RX task stopped");
vTaskDelete(NULL);
}
// ============================================================================
// Public Functions
// ============================================================================
esp_err_t uart_handler_init(void) {
if (uart_initialized) {
ESP_LOGW(TAG, "UART handler already initialized");
return ESP_OK;
}
ESP_LOGI(TAG, "Initializing UART handler");
// Allocate ring buffer
ring_buffer = calloc(UART_RING_BUF_SIZE, 1);
if (!ring_buffer) {
ESP_LOGE(TAG, "Failed to allocate ring buffer");
return ESP_ERR_NO_MEM;
}
ring_head = 0;
ring_tail = 0;
// Create mutex for UART access
uart_mutex = xSemaphoreCreateMutex();
if (!uart_mutex) {
ESP_LOGE(TAG, "Failed to create UART mutex");
free(ring_buffer);
return ESP_ERR_NO_MEM;
}
// Create mutex for callback
callback_mutex = xSemaphoreCreateMutex();
if (!callback_mutex) {
ESP_LOGE(TAG, "Failed to create callback mutex");
vSemaphoreDelete(uart_mutex);
free(ring_buffer);
return ESP_ERR_NO_MEM;
}
// Set default configuration
current_config.port = BMC_UART_NUM;
current_config.baud_rate = BMC_UART_BAUD_RATE;
current_config.data_bits = UART_DATA_8_BITS;
current_config.parity = UART_PARITY_DISABLE;
current_config.stop_bits = UART_STOP_BITS_1;
current_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
// Configure UART
uart_config_t uart_cfg = {
.baud_rate = current_config.baud_rate,
.data_bits = current_config.data_bits,
.parity = current_config.parity,
.stop_bits = current_config.stop_bits,
.flow_ctrl = current_config.flow_ctrl,
};
esp_err_t ret = uart_param_config(BMC_UART_NUM, &uart_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure UART: %s", esp_err_to_name(ret));
goto cleanup;
}
// Set UART pins
ret = uart_set_pin(BMC_UART_NUM, BMC_UART_TX_GPIO, BMC_UART_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set UART pins: %s", esp_err_to_name(ret));
goto cleanup;
}
// Install UART driver
ret = uart_driver_install(BMC_UART_NUM, BMC_UART_BUF_SIZE * 2, 0, 0, NULL, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to install UART driver: %s", esp_err_to_name(ret));
goto cleanup;
}
uart_initialized = true;
// Start receive task
BaseType_t task_ret =
xTaskCreate(uart_rx_task, "uart_rx", BMC_UART_TASK_STACK, NULL, BMC_UART_TASK_PRIO, &uart_rx_task_handle);
if (task_ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create UART RX task");
uart_driver_delete(BMC_UART_NUM);
uart_initialized = false;
ret = ESP_FAIL;
goto cleanup;
}
ESP_LOGI(TAG, "UART initialized: port=%d, baud=%d, tx=%d, rx=%d", BMC_UART_NUM, current_config.baud_rate,
BMC_UART_TX_GPIO, BMC_UART_RX_GPIO);
return ESP_OK;
cleanup:
vSemaphoreDelete(callback_mutex);
vSemaphoreDelete(uart_mutex);
free(ring_buffer);
return ret;
}
esp_err_t uart_handler_deinit(void) {
if (!uart_initialized) {
return ESP_OK;
}
ESP_LOGI(TAG, "Deinitializing UART handler");
uart_initialized = false;
// Wait for task to finish
if (uart_rx_task_handle) {
vTaskDelay(pdMS_TO_TICKS(100));
uart_rx_task_handle = NULL;
}
// Delete UART driver
uart_driver_delete(BMC_UART_NUM);
// Clean up resources
if (callback_mutex) {
vSemaphoreDelete(callback_mutex);
callback_mutex = NULL;
}
if (uart_mutex) {
vSemaphoreDelete(uart_mutex);
uart_mutex = NULL;
}
if (ring_buffer) {
free(ring_buffer);
ring_buffer = NULL;
}
rx_callback = NULL;
ESP_LOGI(TAG, "UART handler deinitialized");
return ESP_OK;
}
esp_err_t uart_get_config(bmc_uart_config_t *config) {
if (!uart_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (config) {
memcpy(config, &current_config, sizeof(bmc_uart_config_t));
}
return ESP_OK;
}
esp_err_t uart_set_config(const bmc_uart_config_t *config) {
if (!uart_initialized || !config) {
return ESP_ERR_INVALID_STATE;
}
xSemaphoreTake(uart_mutex, portMAX_DELAY);
// Apply new configuration
uart_config_t uart_cfg = {
.baud_rate = config->baud_rate,
.data_bits = config->data_bits,
.parity = config->parity,
.stop_bits = config->stop_bits,
.flow_ctrl = config->flow_ctrl,
};
esp_err_t ret = uart_param_config(BMC_UART_NUM, &uart_cfg);
if (ret == ESP_OK) {
memcpy(&current_config, config, sizeof(bmc_uart_config_t));
ESP_LOGI(TAG, "UART configuration updated: baud=%d", config->baud_rate);
}
xSemaphoreGive(uart_mutex);
return ret;
}
int uart_read_data(uint8_t *buf, size_t len, uint32_t timeout_ms) {
if (!uart_initialized || !buf || len == 0) {
return -1;
}
xSemaphoreTake(uart_mutex, pdMS_TO_TICKS(timeout_ms));
size_t available = ring_buffer_available();
if (available == 0) {
xSemaphoreGive(uart_mutex);
return 0;
}
size_t to_read = (len < available) ? len : available;
size_t read = ring_buffer_read(buf, to_read);
xSemaphoreGive(uart_mutex);
return read;
}
int uart_write_data(const uint8_t *buf, size_t len) {
if (!uart_initialized || !buf || len == 0) {
return -1;
}
xSemaphoreTake(uart_mutex, portMAX_DELAY);
int written = uart_write_bytes(BMC_UART_NUM, buf, len);
xSemaphoreGive(uart_mutex);
if (written < 0) {
ESP_LOGE(TAG, "Failed to write to UART");
return -1;
}
ESP_LOGD(TAG, "Wrote %d bytes to UART", written);
return written;
}
int uart_data_available(void) {
if (!uart_initialized) {
return -1;
}
return ring_buffer_available();
}
esp_err_t uart_flush_buffers(void) {
if (!uart_initialized) {
return ESP_ERR_INVALID_STATE;
}
xSemaphoreTake(uart_mutex, portMAX_DELAY);
// Flush hardware buffers
uart_flush_input(BMC_UART_NUM);
// Clear ring buffer
ring_head = 0;
ring_tail = 0;
xSemaphoreGive(uart_mutex);
ESP_LOGI(TAG, "UART buffers flushed");
return ESP_OK;
}
esp_err_t uart_set_baud_rate(int baud_rate) {
if (!uart_initialized) {
return ESP_ERR_INVALID_STATE;
}
xSemaphoreTake(uart_mutex, portMAX_DELAY);
esp_err_t ret = uart_set_baudrate(BMC_UART_NUM, baud_rate);
if (ret == ESP_OK) {
current_config.baud_rate = baud_rate;
ESP_LOGI(TAG, "UART baud rate changed to %d", baud_rate);
} else {
ESP_LOGE(TAG, "Failed to set baud rate: %s", esp_err_to_name(ret));
}
xSemaphoreGive(uart_mutex);
return ret;
}
int uart_get_baud_rate(void) {
return current_config.baud_rate;
}
// ============================================================================
// WebSocket Bridge Functions
// ============================================================================
esp_err_t uart_register_rx_callback(uart_rx_callback_t callback) {
if (!uart_initialized) {
ESP_LOGW(TAG, "UART not initialized, cannot register callback");
return ESP_ERR_INVALID_STATE;
}
if (!callback_mutex) {
ESP_LOGE(TAG, "callback_mutex is NULL!");
return ESP_ERR_INVALID_STATE;
}
xSemaphoreTake(callback_mutex, portMAX_DELAY);
rx_callback = callback;
xSemaphoreGive(callback_mutex);
ESP_LOGI(TAG, "UART RX callback registered successfully, rx_callback=%p", rx_callback);
return ESP_OK;
}
esp_err_t uart_unregister_rx_callback(void) {
if (!uart_initialized) {
return ESP_ERR_INVALID_STATE;
}
xSemaphoreTake(callback_mutex, portMAX_DELAY);
rx_callback = NULL;
xSemaphoreGive(callback_mutex);
ESP_LOGI(TAG, "UART RX callback unregistered");
return ESP_OK;
}