Files
433_STM32/Core/Src/multi_uart_router.c
zhongxuanzhen 0eea5c1424 3.27_433:实验并验证485发送数据透传至RF433模块,并在外部设备成功接收
- 新增协议识别器状态机,实现指令与透传数据的自动识别
2026-03-27 19:58:20 +08:00

663 lines
18 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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