342 lines
8.4 KiB
C
342 lines
8.4 KiB
C
/**
|
|
* @file uart_trans.c
|
|
* @brief Bare-metal UART DMA/IDLE transport layer.
|
|
*/
|
|
|
|
#include "uart_trans.h"
|
|
|
|
#include "usart.h"
|
|
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
UART_HandleTypeDef *huart;
|
|
uint8_t rx_dma_buffer[UART_RX_DMA_BUFFER_SIZE];
|
|
uint8_t tx_dma_buffer[UART_TX_DMA_BUFFER_SIZE];
|
|
uint8_t rx_ring[UART_RX_RING_BUFFER_SIZE];
|
|
uint8_t tx_ring[UART_TX_RING_BUFFER_SIZE];
|
|
volatile uint16_t rx_dma_read_index;
|
|
volatile uint16_t rx_head;
|
|
volatile uint16_t rx_tail;
|
|
volatile uint16_t tx_head;
|
|
volatile uint16_t tx_tail;
|
|
volatile uint16_t tx_dma_len;
|
|
volatile bool tx_busy;
|
|
uart_config_t config;
|
|
uart_stats_t stats;
|
|
bool initialized;
|
|
bool running;
|
|
} uart_channel_ctx_t;
|
|
|
|
static uart_channel_ctx_t g_channels[UART_CHANNEL_MAX];
|
|
|
|
static uint16_t ring_used(uint16_t head, uint16_t tail, uint16_t size)
|
|
{
|
|
return (head >= tail) ? (head - tail) : (size - tail + head);
|
|
}
|
|
|
|
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
|
{
|
|
return (uint16_t)(size - ring_used(head, tail, size) - 1u);
|
|
}
|
|
|
|
static void apply_default_config(uart_channel_ctx_t *ctx)
|
|
{
|
|
ctx->config.baudrate = UART_DEFAULT_BAUDRATE;
|
|
ctx->config.data_bits = UART_DEFAULT_DATA_BITS;
|
|
ctx->config.stop_bits = UART_DEFAULT_STOP_BITS;
|
|
ctx->config.parity = UART_DEFAULT_PARITY;
|
|
}
|
|
|
|
static int apply_uart_config(uart_channel_t channel)
|
|
{
|
|
uart_channel_ctx_t *ctx = &g_channels[channel];
|
|
UART_HandleTypeDef *huart = ctx->huart;
|
|
uint32_t word_length;
|
|
uint32_t parity;
|
|
|
|
if (huart == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (ctx->running) {
|
|
HAL_UART_DMAStop(huart);
|
|
ctx->running = false;
|
|
}
|
|
|
|
huart->Init.BaudRate = ctx->config.baudrate;
|
|
huart->Init.StopBits = (ctx->config.stop_bits == 2u) ? UART_STOPBITS_2 : UART_STOPBITS_1;
|
|
|
|
switch (ctx->config.parity) {
|
|
case 1:
|
|
parity = UART_PARITY_ODD;
|
|
break;
|
|
case 2:
|
|
parity = UART_PARITY_EVEN;
|
|
break;
|
|
default:
|
|
parity = UART_PARITY_NONE;
|
|
break;
|
|
}
|
|
|
|
if (parity == UART_PARITY_NONE) {
|
|
word_length = (ctx->config.data_bits == 9u) ? UART_WORDLENGTH_9B : UART_WORDLENGTH_8B;
|
|
} else {
|
|
word_length = (ctx->config.data_bits >= 8u) ? UART_WORDLENGTH_9B : UART_WORDLENGTH_8B;
|
|
}
|
|
|
|
huart->Init.WordLength = word_length;
|
|
huart->Init.Parity = parity;
|
|
|
|
return (HAL_UART_Init(huart) == HAL_OK) ? 0 : -1;
|
|
}
|
|
|
|
static void process_rx_snapshot(uart_channel_t channel, uint16_t dma_write_index)
|
|
{
|
|
uart_channel_ctx_t *ctx = &g_channels[channel];
|
|
|
|
while (ctx->rx_dma_read_index != dma_write_index) {
|
|
uint16_t next_head;
|
|
|
|
next_head = (uint16_t)((ctx->rx_head + 1u) % UART_RX_RING_BUFFER_SIZE);
|
|
if (next_head == ctx->rx_tail) {
|
|
ctx->stats.errors++;
|
|
break;
|
|
}
|
|
|
|
ctx->rx_ring[ctx->rx_head] = ctx->rx_dma_buffer[ctx->rx_dma_read_index];
|
|
ctx->rx_head = next_head;
|
|
ctx->rx_dma_read_index = (uint16_t)((ctx->rx_dma_read_index + 1u) % UART_RX_DMA_BUFFER_SIZE);
|
|
ctx->stats.rx_bytes++;
|
|
}
|
|
}
|
|
|
|
static void kick_tx(uart_channel_t channel)
|
|
{
|
|
uart_channel_ctx_t *ctx = &g_channels[channel];
|
|
uint16_t available;
|
|
uint16_t chunk;
|
|
|
|
if (!ctx->running || ctx->tx_busy) {
|
|
return;
|
|
}
|
|
|
|
available = ring_used(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE);
|
|
if (available == 0u) {
|
|
return;
|
|
}
|
|
|
|
chunk = available;
|
|
if (chunk > UART_TX_DMA_BUFFER_SIZE) {
|
|
chunk = UART_TX_DMA_BUFFER_SIZE;
|
|
}
|
|
|
|
for (uint16_t i = 0; i < chunk; ++i) {
|
|
ctx->tx_dma_buffer[i] = ctx->tx_ring[ctx->tx_tail];
|
|
ctx->tx_tail = (uint16_t)((ctx->tx_tail + 1u) % UART_TX_RING_BUFFER_SIZE);
|
|
}
|
|
|
|
ctx->tx_dma_len = chunk;
|
|
ctx->tx_busy = true;
|
|
ctx->stats.tx_packets++;
|
|
|
|
if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, chunk) != HAL_OK) {
|
|
ctx->tx_busy = false;
|
|
ctx->stats.errors++;
|
|
}
|
|
}
|
|
|
|
int uart_trans_init(void)
|
|
{
|
|
memset(g_channels, 0, sizeof(g_channels));
|
|
|
|
g_channels[UART_CHANNEL_SERVER].huart = &huart2;
|
|
g_channels[UART_CHANNEL_CLIENT].huart = &huart3;
|
|
|
|
apply_default_config(&g_channels[UART_CHANNEL_SERVER]);
|
|
apply_default_config(&g_channels[UART_CHANNEL_CLIENT]);
|
|
|
|
g_channels[UART_CHANNEL_SERVER].initialized = true;
|
|
g_channels[UART_CHANNEL_CLIENT].initialized = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int uart_trans_config(uart_channel_t channel, const uart_config_t *config)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX || config == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
g_channels[channel].config = *config;
|
|
return apply_uart_config(channel);
|
|
}
|
|
|
|
int uart_trans_start(uart_channel_t channel)
|
|
{
|
|
uart_channel_ctx_t *ctx;
|
|
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return -1;
|
|
}
|
|
|
|
ctx = &g_channels[channel];
|
|
if (!ctx->initialized || ctx->huart == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
ctx->rx_dma_read_index = 0u;
|
|
ctx->rx_head = 0u;
|
|
ctx->rx_tail = 0u;
|
|
ctx->tx_head = 0u;
|
|
ctx->tx_tail = 0u;
|
|
ctx->tx_dma_len = 0u;
|
|
ctx->tx_busy = false;
|
|
|
|
__HAL_UART_ENABLE_IT(ctx->huart, UART_IT_IDLE);
|
|
if (HAL_UART_Receive_DMA(ctx->huart, ctx->rx_dma_buffer, UART_RX_DMA_BUFFER_SIZE) != HAL_OK) {
|
|
return -1;
|
|
}
|
|
|
|
ctx->running = true;
|
|
return 0;
|
|
}
|
|
|
|
int uart_trans_stop(uart_channel_t channel)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return -1;
|
|
}
|
|
|
|
HAL_UART_DMAStop(g_channels[channel].huart);
|
|
g_channels[channel].running = false;
|
|
g_channels[channel].tx_busy = false;
|
|
return 0;
|
|
}
|
|
|
|
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX || stats == NULL) {
|
|
return;
|
|
}
|
|
|
|
*stats = g_channels[channel].stats;
|
|
}
|
|
|
|
void uart_trans_reset_stats(uart_channel_t channel)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return;
|
|
}
|
|
|
|
memset(&g_channels[channel].stats, 0, sizeof(g_channels[channel].stats));
|
|
}
|
|
|
|
uint16_t uart_trans_rx_available(uart_channel_t channel)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return 0u;
|
|
}
|
|
|
|
return ring_used(g_channels[channel].rx_head, g_channels[channel].rx_tail, UART_RX_RING_BUFFER_SIZE);
|
|
}
|
|
|
|
uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len)
|
|
{
|
|
uart_channel_ctx_t *ctx;
|
|
uint16_t copied = 0u;
|
|
|
|
if (channel >= UART_CHANNEL_MAX || data == NULL || max_len == 0u) {
|
|
return 0u;
|
|
}
|
|
|
|
ctx = &g_channels[channel];
|
|
while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
|
|
data[copied++] = ctx->rx_ring[ctx->rx_tail];
|
|
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % UART_RX_RING_BUFFER_SIZE);
|
|
}
|
|
|
|
if (copied > 0u) {
|
|
ctx->stats.rx_packets++;
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t len)
|
|
{
|
|
uart_channel_ctx_t *ctx;
|
|
uint16_t written = 0u;
|
|
|
|
if (channel >= UART_CHANNEL_MAX || data == NULL || len == 0u) {
|
|
return 0u;
|
|
}
|
|
|
|
ctx = &g_channels[channel];
|
|
while (written < len && ring_free(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE) > 0u) {
|
|
ctx->tx_ring[ctx->tx_head] = data[written++];
|
|
ctx->tx_head = (uint16_t)((ctx->tx_head + 1u) % UART_TX_RING_BUFFER_SIZE);
|
|
}
|
|
|
|
if (written < len) {
|
|
ctx->stats.errors++;
|
|
}
|
|
|
|
kick_tx(channel);
|
|
return written;
|
|
}
|
|
|
|
void uart_trans_poll(void)
|
|
{
|
|
kick_tx(UART_CHANNEL_SERVER);
|
|
kick_tx(UART_CHANNEL_CLIENT);
|
|
}
|
|
|
|
void uart_trans_idle_handler(uart_channel_t channel)
|
|
{
|
|
UART_HandleTypeDef *huart;
|
|
uint16_t dma_write_index;
|
|
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return;
|
|
}
|
|
|
|
huart = g_channels[channel].huart;
|
|
dma_write_index = (uint16_t)(UART_RX_DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx));
|
|
if (dma_write_index >= UART_RX_DMA_BUFFER_SIZE) {
|
|
dma_write_index = 0u;
|
|
}
|
|
|
|
process_rx_snapshot(channel, dma_write_index);
|
|
}
|
|
|
|
void uart_trans_rx_half_cplt_handler(uart_channel_t channel)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return;
|
|
}
|
|
|
|
process_rx_snapshot(channel, UART_RX_DMA_BUFFER_SIZE / 2u);
|
|
}
|
|
|
|
void uart_trans_rx_cplt_handler(uart_channel_t channel)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return;
|
|
}
|
|
|
|
process_rx_snapshot(channel, 0u);
|
|
}
|
|
|
|
void uart_trans_tx_cplt_handler(uart_channel_t channel)
|
|
{
|
|
if (channel >= UART_CHANNEL_MAX) {
|
|
return;
|
|
}
|
|
|
|
g_channels[channel].tx_busy = false;
|
|
g_channels[channel].stats.tx_bytes += g_channels[channel].tx_dma_len;
|
|
g_channels[channel].tx_dma_len = 0u;
|
|
kick_tx(channel);
|
|
}
|