2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
* @file multi_uart_router.c
|
|
|
|
|
|
* @brief 多UART统一路由核心模块实现
|
|
|
|
|
|
* @author Application Layer
|
|
|
|
|
|
* @version 1.0
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
* @attention
|
|
|
|
|
|
* 本模块实现多UART端口的统一管理
|
|
|
|
|
|
* 设计依据:多通信接口统一指令处理系统开发计划 第3.1、3.3、3.5节及附录A
|
2026-03-27 19:58:20 +08:00
|
|
|
|
*
|
2026-03-27 16:21:00 +08:00
|
|
|
|
* 关键特性:
|
|
|
|
|
|
* 1. 中断+环形缓冲区接收机制
|
|
|
|
|
|
* 2. 非阻塞发送机制
|
|
|
|
|
|
* 3. 端口上下文表管理
|
|
|
|
|
|
* 4. 响应路由表
|
2026-03-27 19:58:20 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 端口映射:
|
|
|
|
|
|
* - PORT_UART1: 连接RF433无线模块(用于无线数据收发)
|
|
|
|
|
|
* - PORT_UART2: 调试串口(用于日志和调试输出)
|
|
|
|
|
|
* - PORT_UART3: 预留扩展端口
|
2026-03-27 16:21:00 +08:00
|
|
|
|
******************************************************************************
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "multi_uart_router.h"
|
|
|
|
|
|
#include "uart2_print.h"
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 调试宏定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/* DEBUG_MULTI_UART: 多UART路由调试日志开关 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
#define DEBUG_MULTI_UART 1
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG_MULTI_UART
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/* 多UART模块日志宏,带前缀"[MUART]" */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[MUART] " fmt "\r\n", ##__VA_ARGS__)
|
|
|
|
|
|
#else
|
|
|
|
|
|
#define DEBUG_LOG(fmt, ...)
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 全局变量定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART端口上下文表
|
|
|
|
|
|
* @note 静态数组,为每个端口维护独立的上下文状态
|
|
|
|
|
|
* 包括接收/发送环形缓冲区、UART句柄、统计计数等
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static uart_port_context_t g_port_ctx[PORT_COUNT];
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART端口与HAL句柄映射表
|
|
|
|
|
|
* @note 通过端口ID索引,快速获取对应的HAL_UART_HandleTypeDef指针
|
|
|
|
|
|
* 使用常量初始化,在编译时确定,无运行时开销
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static UART_HandleTypeDef *const g_port_uart_map[PORT_COUNT] = {
|
2026-03-27 19:58:20 +08:00
|
|
|
|
[PORT_UART1] = &huart1, /**< RF433无线模块 */
|
|
|
|
|
|
[PORT_UART2] = &huart2, /**< 调试串口 */
|
|
|
|
|
|
[PORT_UART3] = &huart3, /**< 预留扩展 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART端口名称映射表
|
|
|
|
|
|
* @note 用于调试日志输出,快速获取端口的可读名称
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static const char *const g_port_name_map[PORT_COUNT] = {
|
2026-03-27 19:58:20 +08:00
|
|
|
|
[PORT_UART1] = "UART1", /**< RF433无线模块 */
|
|
|
|
|
|
[PORT_UART2] = "UART2", /**< 调试串口 */
|
|
|
|
|
|
[PORT_UART3] = "UART3", /**< 预留扩展 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 内部静态函数实现
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 初始化接收环形缓冲区
|
|
|
|
|
|
* @note 重置接收环缓冲区的所有状态指针和计数器
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ring: 待初始化的接收环形缓冲区指针(输入/输出)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static void rx_ring_init(uart_rx_ring_t *ring)
|
|
|
|
|
|
{
|
|
|
|
|
|
ring->head = 0;
|
|
|
|
|
|
ring->tail = 0;
|
|
|
|
|
|
ring->count = 0;
|
|
|
|
|
|
ring->overflow_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 初始化发送环形缓冲区
|
|
|
|
|
|
* @note 重置发送环形缓冲区的所有状态指针、计数器和发送标志
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ring: 待初始化的发送环形缓冲区指针(输入/输出)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static void tx_ring_init(uart_tx_ring_t *ring)
|
|
|
|
|
|
{
|
|
|
|
|
|
ring->head = 0;
|
|
|
|
|
|
ring->tail = 0;
|
|
|
|
|
|
ring->count = 0;
|
|
|
|
|
|
ring->is_sending = false;
|
|
|
|
|
|
ring->overflow_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 接收环形缓冲区写入(生产者)
|
|
|
|
|
|
* @note 将接收到的字节写入缓冲区,中断安全
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ring: 接收环形缓冲区指针(输入/输出)
|
|
|
|
|
|
* @param byte: 待写入的字节(输入)
|
|
|
|
|
|
* @return bool: true=写入成功,false=缓冲区满导致丢弃
|
|
|
|
|
|
*
|
|
|
|
|
|
* 线程安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
* - 确保检查count和写入buffer之间不被中断打断
|
|
|
|
|
|
*
|
|
|
|
|
|
* 溢出处理:
|
|
|
|
|
|
* - 缓冲区满时丢弃数据,不阻塞
|
|
|
|
|
|
* - 增加overflow_count计数供诊断使用
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static bool rx_ring_push(uart_rx_ring_t *ring, uint8_t byte)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool success = true;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
if (ring->count >= UART_RX_BUFFER_SIZE) {
|
|
|
|
|
|
ring->overflow_count++;
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ring->buffer[ring->head] = byte;
|
|
|
|
|
|
ring->head = (ring->head + 1) % UART_RX_BUFFER_SIZE;
|
|
|
|
|
|
ring->count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 接收环形缓冲区读取(消费者)
|
|
|
|
|
|
* @note 从缓冲区读取一个字节,中断安全
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ring: 接收环形缓冲区指针(输入)
|
|
|
|
|
|
* @param byte: 读取的字节输出指针(输出)
|
|
|
|
|
|
* @return uint16_t: 成功读取的字节数(0或1)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 线程安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
*
|
|
|
|
|
|
* 设计说明:
|
|
|
|
|
|
* - 返回0表示缓冲区空,调用方应检查返回值
|
|
|
|
|
|
* - 读取后自动更新tail指针,实现FIFO
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static uint16_t rx_ring_pop(uart_rx_ring_t *ring, uint8_t *byte)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint16_t result = 0;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
if (ring->count > 0) {
|
|
|
|
|
|
*byte = ring->buffer[ring->tail];
|
|
|
|
|
|
ring->tail = (ring->tail + 1) % UART_RX_BUFFER_SIZE;
|
|
|
|
|
|
ring->count--;
|
|
|
|
|
|
result = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送环形缓冲区写入(生产者)
|
|
|
|
|
|
* @note 将待发送数据写入缓冲区,中断安全
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ring: 发送环形缓冲区指针(输入/输出)
|
|
|
|
|
|
* @param data: 待写入数据缓冲区指针(输入)
|
|
|
|
|
|
* @param len: 待写入数据字节数(输入)
|
|
|
|
|
|
* @return uint16_t: 成功写入的字节数
|
|
|
|
|
|
*
|
|
|
|
|
|
* 线程安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
*
|
|
|
|
|
|
* 设计说明:
|
|
|
|
|
|
* - 可能只写入部分数据(缓冲区满时截断)
|
|
|
|
|
|
* - 返回written值,调用方据此判断是否发送完毕
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static uint16_t tx_ring_push(uart_tx_ring_t *ring, const uint8_t *data, uint16_t len)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint16_t written = 0;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
for (uint16_t i = 0; i < len; i++) {
|
|
|
|
|
|
if (ring->count >= UART_TX_BUFFER_SIZE) {
|
|
|
|
|
|
ring->overflow_count++;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
ring->buffer[ring->head] = data[i];
|
|
|
|
|
|
ring->head = (ring->head + 1) % UART_TX_BUFFER_SIZE;
|
|
|
|
|
|
ring->count++;
|
|
|
|
|
|
written++;
|
|
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
return written;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 启动UART发送(Kickoff)
|
|
|
|
|
|
* @note 如果有数据待发送且UART空闲,启动首次发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 发送触发条件:
|
|
|
|
|
|
* - 发送缓冲区有数据(count > 0)
|
|
|
|
|
|
* - UART当前处于空闲状态(is_sending == false)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
*
|
|
|
|
|
|
* 设计说明:
|
|
|
|
|
|
* - 首次发送启动后,后续发送由TxCpltCallback驱动
|
|
|
|
|
|
* - 此函数仅负责"kickoff",不负责连续发送
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
static void tx_kickoff(port_id_t port_id)
|
|
|
|
|
|
{
|
|
|
|
|
|
uart_port_context_t *ctx = &g_port_ctx[port_id];
|
|
|
|
|
|
uart_tx_ring_t *ring = &ctx->tx_ring;
|
|
|
|
|
|
uint8_t byte;
|
|
|
|
|
|
bool has_data = false;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
if (!ring->is_sending && ring->count > 0) {
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/* 取出下一个待发送字节 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
byte = ring->buffer[ring->tail];
|
|
|
|
|
|
ring->tail = (ring->tail + 1) % UART_TX_BUFFER_SIZE;
|
|
|
|
|
|
ring->count--;
|
|
|
|
|
|
ring->is_sending = true;
|
|
|
|
|
|
has_data = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 启动UART中断发送 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (has_data) {
|
|
|
|
|
|
HAL_UART_Transmit_IT(ctx->huart, &byte, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 公共函数实现
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 多UART路由模块初始化
|
|
|
|
|
|
* @note 在系统启动时调用,初始化所有端口的上下文和缓冲区
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 初始化内容:
|
|
|
|
|
|
* 1. 遍历所有端口,初始化上下文
|
|
|
|
|
|
* 2. 建立端口与HAL句柄的关联
|
|
|
|
|
|
* 3. 初始化各端口的收发环形缓冲区
|
|
|
|
|
|
* 4. 重置统计计数器
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调用时机:
|
|
|
|
|
|
* 应在HAL_UART_Init()之后、各外设使用前调用
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_Init(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (port_id_t i = 0; i < PORT_COUNT; i++) {
|
|
|
|
|
|
uart_port_context_t *ctx = &g_port_ctx[i];
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 建立端口与HAL句柄的关联 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
ctx->huart = g_port_uart_map[i];
|
|
|
|
|
|
ctx->name = g_port_name_map[i];
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 初始化收发环形缓冲区 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
rx_ring_init(&ctx->rx_ring);
|
|
|
|
|
|
tx_ring_init(&ctx->tx_ring);
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 重置统计计数器和临时变量 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
ctx->rx_tmp = 0;
|
|
|
|
|
|
ctx->rx_count = 0;
|
|
|
|
|
|
ctx->tx_count = 0;
|
|
|
|
|
|
ctx->error_count = 0;
|
|
|
|
|
|
ctx->initialized = true;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
DEBUG_LOG("Init OK, %d ports configured", PORT_COUNT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向指定端口喂入接收字节
|
|
|
|
|
|
* @note 通常在UART接收中断中调用,将接收到的字节放入缓冲区
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 目标端口ID(输入)
|
|
|
|
|
|
* @param byte: 接收到的字节(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能说明:
|
|
|
|
|
|
* 1. 参数合法性检查
|
|
|
|
|
|
* 2. 端口初始化状态检查
|
|
|
|
|
|
* 3. 将字节写入接收环形缓冲区
|
|
|
|
|
|
* 4. 更新接收统计计数
|
|
|
|
|
|
*
|
|
|
|
|
|
* 溢出处理:
|
|
|
|
|
|
* 缓冲区满时丢弃数据,增加error_count计数
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_FeedByte(port_id_t port_id, uint8_t byte)
|
|
|
|
|
|
{
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/* 参数合法性检查 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (port_id >= PORT_COUNT) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uart_port_context_t *ctx = &g_port_ctx[port_id];
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 检查端口是否已初始化 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (!ctx->initialized) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 写入接收缓冲区,失败时增加错误计数 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (!rx_ring_push(&ctx->rx_ring, byte)) {
|
|
|
|
|
|
ctx->error_count++;
|
|
|
|
|
|
DEBUG_LOG("%s RX overflow", ctx->name);
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
ctx->rx_count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 多UART路由任务函数
|
|
|
|
|
|
* @note 在主循环中周期性调用,处理发送队列
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能说明:
|
|
|
|
|
|
* 轮询各端口的发送缓冲区,调用tx_kickoff启动待发的数据
|
|
|
|
|
|
* 接收处理由中断完成(MultiUART_FeedByte),此函数仅处理发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* 端口跳过说明:
|
|
|
|
|
|
* - 跳过PORT_UART2(调试串口),由UART2_Print模块独立处理
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调用时机:
|
|
|
|
|
|
* 建议在主循环中周期性调用(如10ms定时)
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_Task(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (port_id_t i = 0; i < PORT_COUNT; i++) {
|
|
|
|
|
|
uart_port_context_t *ctx = &g_port_ctx[i];
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 检查端口是否已初始化 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (!ctx->initialized) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 跳过调试串口(UART2) */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (i == PORT_UART2) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 尝试启动发送 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
tx_kickoff(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向指定端口发送数据
|
|
|
|
|
|
* @note 核心发送函数,将数据写入发送缓冲区并启动发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 目标端口ID(输入)
|
|
|
|
|
|
* @param data: 待发送数据缓冲区指针(输入)
|
|
|
|
|
|
* @param len: 待发送数据字节数(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 特殊处理:
|
|
|
|
|
|
* - PORT_UART2直接调用UART2_Print_Send,由调试模块处理
|
|
|
|
|
|
* - 其他端口使用本模块的环形缓冲区机制
|
|
|
|
|
|
*
|
|
|
|
|
|
* 发送流程:
|
|
|
|
|
|
* 1. 写入发送缓冲区
|
|
|
|
|
|
* 2. 调用tx_kickoff启动首次发送(如需要)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - tx_ring_push内部使用临界区保护
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_Send(port_id_t port_id, const uint8_t *data, uint16_t len)
|
|
|
|
|
|
{
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/* 参数合法性检查 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (port_id >= PORT_COUNT || data == NULL || len == 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 调试串口(UART2)特殊处理
|
|
|
|
|
|
* 调试打印不走环形缓冲区,直接发送
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (port_id == PORT_UART2) {
|
|
|
|
|
|
UART2_Print_Send(data, len);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uart_port_context_t *ctx = &g_port_ctx[port_id];
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 检查端口是否已初始化 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (!ctx->initialized) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 写入发送缓冲区并更新计数 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint16_t written = tx_ring_push(&ctx->tx_ring, data, len);
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (written > 0) {
|
|
|
|
|
|
ctx->tx_count += written;
|
|
|
|
|
|
tx_kickoff(port_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向指定端口发送字符串
|
|
|
|
|
|
* @note 封装MultiUART_Send,自动计算字符串长度
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 目标端口ID(输入)
|
|
|
|
|
|
* @param str: 待发送的以'\0'结尾的字符串指针(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用说明:
|
|
|
|
|
|
* str必须为有效指针且以'\0'结尾
|
|
|
|
|
|
* strlen计算长度时不包括终止符
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_SendString(port_id_t port_id, const char *str)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (str == NULL) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
MultiUART_Send(port_id, (const uint8_t *)str, strlen(str));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 向指定端口发送格式化数据
|
|
|
|
|
|
* @note 仿printf风格,支持可变参数格式化输出
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 目标端口ID(输入)
|
|
|
|
|
|
* @param fmt: 格式化字符串(输入)
|
|
|
|
|
|
* @param ...: 可变参数列表(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 实现说明:
|
|
|
|
|
|
* 1. 使用va_list/va_start/va_end处理可变参数
|
|
|
|
|
|
* 2. vsnprintf将格式化参数写入临时缓冲区(128字节)
|
|
|
|
|
|
* 3. 调用MultiUART_Send发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* 缓冲区限制:
|
|
|
|
|
|
* 最大格式化输出为127字节(128-1留作终止符)
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_SendFmt(port_id_t port_id, const char *fmt, ...)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (fmt == NULL) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
char buffer[128];
|
|
|
|
|
|
va_list args;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
|
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
|
|
|
|
va_end(args);
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (len > 0) {
|
|
|
|
|
|
if (len >= (int)sizeof(buffer)) {
|
|
|
|
|
|
len = sizeof(buffer) - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
MultiUART_Send(port_id, (const uint8_t *)buffer, len);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART发送完成回调函数
|
|
|
|
|
|
* @note 应在UART TX完成中断中调用,驱动连续发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 特殊处理:
|
|
|
|
|
|
* - PORT_UART2调用UART2_Print_TxCpltCallback处理
|
|
|
|
|
|
*
|
|
|
|
|
|
* 发送驱动逻辑:
|
|
|
|
|
|
* 1. 清除is_sending标志
|
|
|
|
|
|
* 2. 检查发送缓冲区是否还有数据
|
|
|
|
|
|
* 3. 如有,取出下一字节并启动新发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
void MultiUART_TxCpltCallback(port_id_t port_id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (port_id >= PORT_COUNT) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
/* 调试串口(UART2)特殊处理 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (port_id == PORT_UART2) {
|
|
|
|
|
|
UART2_Print_TxCpltCallback();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uart_port_context_t *ctx = &g_port_ctx[port_id];
|
|
|
|
|
|
uart_tx_ring_t *ring = &ctx->tx_ring;
|
|
|
|
|
|
uint8_t byte;
|
|
|
|
|
|
bool has_more = false;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
ring->is_sending = false;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (ring->count > 0) {
|
|
|
|
|
|
byte = ring->buffer[ring->tail];
|
|
|
|
|
|
ring->tail = (ring->tail + 1) % UART_TX_BUFFER_SIZE;
|
|
|
|
|
|
ring->count--;
|
|
|
|
|
|
ring->is_sending = true;
|
|
|
|
|
|
has_more = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (has_more) {
|
|
|
|
|
|
HAL_UART_Transmit_IT(ctx->huart, &byte, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取端口名称
|
|
|
|
|
|
* @note 返回指定端口的可读名称字符串
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @return const char*: 端口名称字符串指针
|
|
|
|
|
|
*
|
|
|
|
|
|
* 错误处理:
|
|
|
|
|
|
* 端口ID无效时返回"UNKNOWN"
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
const char *MultiUART_GetPortName(port_id_t port_id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (port_id >= PORT_COUNT) {
|
|
|
|
|
|
return "UNKNOWN";
|
|
|
|
|
|
}
|
|
|
|
|
|
return g_port_name_map[port_id];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取端口接收缓冲区中的字节数
|
|
|
|
|
|
* @note 返回指定端口待读取的接收数据数量
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @return uint16_t: 待读取的字节数,无效端口返回0
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用临界区保护读取count
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* - 轮询方式判断是否有数据可读
|
|
|
|
|
|
* - 流量控制诊断
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint16_t MultiUART_GetRxCount(port_id_t port_id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (port_id >= PORT_COUNT) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint16_t count;
|
|
|
|
|
|
__disable_irq();
|
|
|
|
|
|
count = g_port_ctx[port_id].rx_ring.count;
|
|
|
|
|
|
__enable_irq();
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 从端口读取一个字节
|
|
|
|
|
|
* @note 从接收缓冲区读取一个字节(非阻塞)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @param byte: 读取的字节输出指针(输出)
|
|
|
|
|
|
* @return uint16_t: 成功读取的字节数(0或1)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用临界区保护
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* 在主循环中轮询各端口的接收缓冲区,读取数据喂入解析器
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint16_t MultiUART_ReadByte(port_id_t port_id, uint8_t *byte)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (port_id >= PORT_COUNT || byte == NULL) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uart_rx_ring_t *ring = &g_port_ctx[port_id].rx_ring;
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
if (ring->count == 0) {
|
|
|
|
|
|
__enable_irq();
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
*byte = ring->buffer[ring->tail];
|
|
|
|
|
|
ring->tail = (ring->tail + 1) % UART_RX_BUFFER_SIZE;
|
|
|
|
|
|
ring->count--;
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取端口发送缓冲区的可用空间
|
|
|
|
|
|
* @note 返回指定端口还能写入的字节数
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @return uint16_t: 可用空间字节数,无效端口返回0
|
|
|
|
|
|
*
|
|
|
|
|
|
* 计算公式:available = buffer_size - count
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用临界区保护
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint16_t MultiUART_GetTxAvailable(port_id_t port_id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (port_id >= PORT_COUNT) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint16_t available;
|
|
|
|
|
|
__disable_irq();
|
|
|
|
|
|
available = UART_TX_BUFFER_SIZE - g_port_ctx[port_id].tx_ring.count;
|
|
|
|
|
|
__enable_irq();
|
|
|
|
|
|
return available;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 19:58:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取端口的溢出错误计数
|
|
|
|
|
|
* @note 统计接收和发送缓冲区的溢出总次数
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param port_id: 端口ID(输入)
|
|
|
|
|
|
* @return uint32_t: 溢出错误累计次数,无效端口返回0
|
|
|
|
|
|
*
|
|
|
|
|
|
* 计数组成:
|
|
|
|
|
|
* = rx_ring.overflow_count + tx_ring.overflow_count
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* - 诊断缓冲区是否足够大
|
|
|
|
|
|
* - 检测数据产生速度是否超出处理能力
|
|
|
|
|
|
*/
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint32_t MultiUART_GetOverflowCount(port_id_t port_id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (port_id >= PORT_COUNT) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-03-27 19:58:20 +08:00
|
|
|
|
|
|
|
|
|
|
return g_port_ctx[port_id].rx_ring.overflow_count +
|
2026-03-27 16:21:00 +08:00
|
|
|
|
g_port_ctx[port_id].tx_ring.overflow_count;
|
|
|
|
|
|
}
|