/** ****************************************************************************** * @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 #include #include /*============================================================================== * 调试宏定义 *============================================================================*/ /* 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; }