3.27_433:实验并验证485发送数据透传至RF433模块,并在外部设备成功接收

- 新增协议识别器状态机,实现指令与透传数据的自动识别
This commit is contained in:
2026-03-27 19:58:20 +08:00
parent 268667e335
commit 0eea5c1424
71 changed files with 6051 additions and 2620 deletions

View File

@ -8,12 +8,17 @@
* @attention
* 本模块实现多UART端口的统一管理
* 设计依据:多通信接口统一指令处理系统开发计划 第3.1、3.3、3.5节及附录A
*
*
* 关键特性:
* 1. 中断+环形缓冲区接收机制
* 2. 非阻塞发送机制
* 3. 端口上下文表管理
* 4. 响应路由表
*
* 端口映射:
* - PORT_UART1: 连接RF433无线模块(用于无线数据收发)
* - PORT_UART2: 调试串口(用于日志和调试输出)
* - PORT_UART3: 预留扩展端口
******************************************************************************
*/
@ -23,28 +28,60 @@
#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,
[PORT_UART2] = &huart2,
[PORT_UART3] = &huart3,
[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",
[PORT_UART2] = "UART2",
[PORT_UART3] = "UART3",
[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;
@ -53,6 +90,13 @@ static void rx_ring_init(uart_rx_ring_t *ring)
ring->overflow_count = 0;
}
/**
* @brief 初始化发送环形缓冲区
* @note 重置发送环形缓冲区的所有状态指针、计数器和发送标志
*
* @param ring: 待初始化的发送环形缓冲区指针(输入/输出)
* @return 无
*/
static void tx_ring_init(uart_tx_ring_t *ring)
{
ring->head = 0;
@ -62,10 +106,26 @@ static void tx_ring_init(uart_tx_ring_t *ring)
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++;
@ -76,14 +136,29 @@ static bool rx_ring_push(uart_rx_ring_t *ring, uint8_t byte)
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];
@ -92,14 +167,30 @@ static uint16_t rx_ring_pop(uart_rx_ring_t *ring, uint8_t *byte)
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) {
@ -112,19 +203,38 @@ static uint16_t tx_ring_push(uart_tx_ring_t *ring, const uint8_t *data, uint16_t
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--;
@ -132,95 +242,197 @@ static void tx_kickoff(port_id_t port_id)
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) {
@ -229,19 +441,36 @@ void MultiUART_SendString(port_id_t port_id, const char *str)
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;
@ -250,25 +479,44 @@ void MultiUART_SendFmt(port_id_t port_id, const char *fmt, ...)
}
}
/**
* @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;
@ -277,12 +525,22 @@ void MultiUART_TxCpltCallback(port_id_t port_id)
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) {
@ -291,12 +549,26 @@ const char *MultiUART_GetPortName(port_id_t port_id)
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;
@ -304,34 +576,60 @@ uint16_t MultiUART_GetRxCount(port_id_t port_id)
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;
@ -339,12 +637,26 @@ uint16_t MultiUART_GetTxAvailable(port_id_t port_id)
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 +
return g_port_ctx[port_id].rx_ring.overflow_count +
g_port_ctx[port_id].tx_ring.overflow_count;
}