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,12 @@
* @attention
* 本模块实现指令路由与响应分发功能
* 设计依据:多通信接口统一指令处理系统开发计划 第3.2节
*
*
* 核心职责:
* - 从各UART端口读取数据并喂入解析器
* - 根据指令来源端口路由响应
* - 管理响应路由表
*
*
* 工作流程:
* 1. 中断中将接收字节写入对应端口的环形缓冲区
* 2. 主循环中CmdRouter_Task轮询各端口缓冲区
@ -27,195 +27,541 @@
#include "cmd_parser.h"
#include "uart2_print.h"
#include "debug_log.h"
#include "uart3_protocol_discriminator.h"
#include "uart3_passthrough.h"
#include "uart3_smart_router_config.h"
#include <string.h>
/*==============================================================================
* 调试宏定义
*============================================================================*/
/* DEBUG_CMD_ROUTER: 调试日志开关置1时启用路由过程日志输出 */
#define DEBUG_CMD_ROUTER 1
#if DEBUG_CMD_ROUTER
/* 路由模块日志宏,带模块前缀"[ROUTER]"方便过滤 */
#define ROUTER_LOG(fmt, ...) UART2_Print_Printf("[ROUTER] " fmt "\r\n", ##__VA_ARGS__)
#else
#define ROUTER_LOG(fmt, ...)
#endif
/*==============================================================================
* 常量定义
*============================================================================*/
/**
* @brief 接收日志缓冲区大小
* @note 用于暂存接收到的原始字节,便于调试跟踪
* 当超时或帧完成后统一打印到调试串口
*/
#define RX_LOG_BUFFER_SIZE 128
/*==============================================================================
* 数据结构定义
*============================================================================*/
/**
* @brief 端口解析器状态结构
* @note 跟踪每个UART端口的解析状态和统计信息
*
* 设计目的:
* 每个UART端口都可能接收到指令需要独立跟踪其接收状态、
* 最后接收时间、字节计数等,用于诊断和路由决策。
*
* 字段说明:
* - source_port: 源端口ID编号
* - last_feed_tick: 上次喂入数据的系统时间戳(毫秒)
* - rx_count: 累计接收字节数
* - active: 端口是否处于活跃状态(接收到有效数据)
*/
typedef struct {
uint8_t source_port;
uint32_t last_feed_tick;
uint32_t rx_count;
bool active;
uint8_t source_port; /**< 源端口ID编号 */
uint32_t last_feed_tick; /**< 上次喂入数据的时间戳 */
uint32_t rx_count; /**< 累计接收字节数 */
bool active; /**< 端口活跃状态标志 */
} port_parser_state_t;
/*==============================================================================
* 全局变量定义
*============================================================================*/
/**
* @brief 各端口解析器状态表
* @note 静态数组为PORT_COUNT个端口各自维护解析状态
*/
static port_parser_state_t g_port_states[PORT_COUNT];
/**
* @brief 响应处理器回调函数指针
* @note 当设置此回调时,路由响应会调用此处理器而非直接发送
* 可用于自定义响应处理逻辑(如添加时间戳、过滤等)
*/
static cmd_response_handler_t g_response_handler = NULL;
/**
* @brief 已处理指令帧累计计数
* @note 统计所有端口成功解析的指令帧总数
*/
static uint32_t g_processed_count = 0;
/**
* @brief 已路由响应累计计数
* @note 统计成功发送的响应消息总数
*/
static uint32_t g_routed_count = 0;
/**
* @brief 当前正在解析的端口ID
* @note 用于在多端口环境下跟踪当前处理哪个端口的数据
*/
static uint8_t g_current_parsing_port = PORT_UART2;
/**
* @brief 接收原始字节日志缓冲区
* @note 三维数组:为每个端口维护一个独立的日志缓冲区
* 便于调试时还原完整的接收字节序列
*/
static uint8_t g_rx_log_buffer[PORT_COUNT][RX_LOG_BUFFER_SIZE];
/**
* @brief 各端口日志缓冲区的当前写入长度
*/
static uint16_t g_rx_log_len[PORT_COUNT];
/**
* @brief 各端口日志缓冲区最后写入时间戳
* @note 用于实现超时自动刷新机制
*/
static uint32_t g_rx_log_last_tick[PORT_COUNT];
/**
* @brief 接收日志超时阈值(毫秒)
* @note 如果超过此时间没有新字节,则自动刷新日志
*/
#define RX_LOG_TIMEOUT_MS 50
/*==============================================================================
* 内部静态函数声明
*============================================================================*/
static void flush_rx_log(port_id_t port_id);
static void append_rx_log(port_id_t port_id, uint8_t byte);
static void cmd_parser_response_callback(uint8_t source_port, const char *response);
/*==============================================================================
* 内部静态函数实现
*============================================================================*/
/**
* @brief 刷新指定端口的接收日志
* @note 将暂存的接收字节格式化打印到调试串口,然后清空缓冲区
*
* @param port_id: 待刷新的端口ID(输入)
* @return 无
*
* 格式化规则:
* - 可打印ASCII字符(0x20-0x7E)直接输出
* - '\r'显示为"\\r"
* - '\n'显示为"\\n"
* - 其他字符显示为"\\xXX"十六进制格式
*
* 调用时机:
* - 接收到完整帧时
* - 接收超时(RX_LOG_TIMEOUT_MS)时
*/
static void flush_rx_log(port_id_t port_id)
{
/* 无日志数据时直接返回 */
if (g_rx_log_len[port_id] == 0) {
return;
}
/* 打印日志头部,包含端口名称 */
UART2_Print_Printf("[ROUTER] %s RX: \"", MultiUART_GetPortName(port_id));
/*----------------------------------------------------------
* 遍历日志缓冲区,格式化输出每个字节
*----------------------------------------------------------*/
for (uint16_t i = 0; i < g_rx_log_len[port_id]; i++) {
uint8_t c = g_rx_log_buffer[port_id][i];
if (c >= 0x20 && c < 0x7F) {
/* 可打印ASCII字符直接输出 */
UART2_Print_Send(&c, 1);
} else if (c == '\r') {
/* 回车符:显示为转义序列 */
UART2_Print_Send((const uint8_t *)"\\r", 2);
} else if (c == '\n') {
/* 换行符:显示为转义序列 */
UART2_Print_Send((const uint8_t *)"\\n", 2);
} else {
/* 其他字符:显示为十六进制格式 */
char hex[4];
snprintf(hex, sizeof(hex), "\\x%02X", c);
UART2_Print_Send((const uint8_t *)hex, 4);
}
}
/* 打印日志尾部并换行 */
UART2_Print_String("\"\r\n");
/* 清空日志缓冲区 */
g_rx_log_len[port_id] = 0;
}
/**
* @brief 添加字节到接收日志缓冲区
* @note 暂存接收到的原始字节,支持调试回溯
*
* @param port_id: 端口ID(输入)
* @param byte: 待添加的字节(输入)
* @return 无
*
* 设计说明:
* 缓冲区大小有限(RX_LOG_BUFFER_SIZE),超出时丢弃最旧数据。
* 同时记录最后写入时间戳,用于超时检测。
*/
static void append_rx_log(port_id_t port_id, uint8_t byte)
{
/* 缓冲区未满时写入,否则丢弃(溢出处理) */
if (g_rx_log_len[port_id] < RX_LOG_BUFFER_SIZE) {
g_rx_log_buffer[port_id][g_rx_log_len[port_id]++] = byte;
}
/* 更新最后写入时间戳 */
g_rx_log_last_tick[port_id] = HAL_GetTick();
}
/**
* @brief 解析器响应回调函数
* @note 作为CmdParser的响应回调将响应路由到正确的源端口
*
* @param source_port: 响应目标端口ID(输入)
* @param response: 待发送的响应字符串(输入)
* @return 无
*
* 路由逻辑:
* 1. 如果设置了自定义响应处理器(g_response_handler),调用它
* 2. 否则直接通过MultiUART_Send发送到源端口
* 3. 每次成功路由增加routed_count计数
*
* 调试日志:
* 使用LOG_INFO输出TX路由详情便于问题诊断
*/
static void cmd_parser_response_callback(uint8_t source_port, const char *response)
{
if (g_response_handler != NULL) {
/* 使用自定义响应处理器 */
g_response_handler((port_id_t)source_port, response, strlen(response));
g_routed_count++;
} else {
/* 直接发送到源端口 */
MultiUART_SendString((port_id_t)source_port, response);
g_routed_count++;
}
LOG_INFO("ROUTER", "TX[%s]: %s",
/* 输出TX路由调试日志 */
LOG_INFO("ROUTER", "TX[%s]: %s",
MultiUART_GetPortName((port_id_t)source_port), response);
}
/*==============================================================================
* 公共函数实现
*============================================================================*/
/**
* @brief 指令路由模块初始化
* @note 初始化所有端口状态、统计计数,并注册解析器回调
*
* @param 无
* @return 无
*
* 初始化内容:
* 1. 重置所有端口的解析状态
* 2. 清零所有日志缓冲区
* 3. 重置统计计数器
* 4. 设置默认解析端口为PORT_UART2
* 5. 注册响应回调函数到解析器
*/
void CmdRouter_Init(void)
{
/*----------------------------------------------------------
* 初始化各端口解析器状态
*----------------------------------------------------------*/
for (port_id_t i = 0; i < PORT_COUNT; i++) {
g_port_states[i].source_port = i;
g_port_states[i].last_feed_tick = 0;
g_port_states[i].rx_count = 0;
g_port_states[i].active = false;
g_rx_log_len[i] = 0;
g_rx_log_last_tick[i] = 0;
}
/*----------------------------------------------------------
* 初始化统计计数器和状态变量
*----------------------------------------------------------*/
g_response_handler = NULL;
g_processed_count = 0;
g_routed_count = 0;
g_current_parsing_port = PORT_UART2;
/*----------------------------------------------------------
* 注册响应回调函数
* 将cmd_parser的响应路由到正确的源端口
*----------------------------------------------------------*/
CmdParser_SetResponseCallback(cmd_parser_response_callback);
/*----------------------------------------------------------
* 初始化UART3智能路由模块
*----------------------------------------------------------*/
#if UART3_SMART_ROUTING_ENABLED
UART3_Protocol_Init();
Passthrough_Init();
ROUTER_LOG("UART3 Smart Router enabled");
#endif
ROUTER_LOG("Init OK, %d ports registered", PORT_COUNT);
}
/**
* @brief 指令路由主任务函数
* @note 在主循环中周期性调用,处理所有端口的接收数据和路由
*
* @param 无
* @return 无
*
* 工作流程:
* 1. UART1: 原有逻辑,所有数据喂给解析器
* 2. UART3: 智能路由模式(协议识别+透传)
* 3. 处理日志缓冲区超时
* 4. 调用解析器任务处理完整帧
*
* UART3智能路由(当UART3_SMART_ROUTING_ENABLED=1时)
* 1. 读取UART3接收缓冲区字节
* 2. 协议识别判断是CMD还是透传
* 3. CMD → CmdParser_FeedByte()
* 4. PASSTHROUGH → Passthrough_PushByte()
* 5. 检查SCAN超时触发透传
* 6. 执行透传引擎任务
*/
void CmdRouter_Task(void)
{
uint32_t current_tick = HAL_GetTick();
/*----------------------------------------------------------
* 第一阶段UART1处理(保持原有逻辑)
*----------------------------------------------------------*/
{
port_id_t port_id = PORT_UART1;
uint16_t rx_count = MultiUART_GetRxCount(port_id);
if (rx_count > 0) {
g_current_parsing_port = port_id;
CmdParser_SetSourcePort(port_id);
uint8_t byte;
while (MultiUART_ReadByte(port_id, &byte) > 0) {
append_rx_log(port_id, byte);
CmdParser_FeedByte(byte, current_tick);
g_port_states[port_id].rx_count++;
}
if (CmdParser_HasCompleteFrame(NULL)) {
flush_rx_log(port_id);
g_processed_count++;
g_port_states[port_id].active = true;
}
}
}
/*----------------------------------------------------------
* 第二阶段UART3智能路由处理
* 设计依据文档第3.3.1节 修改CmdRouter_Task
*----------------------------------------------------------*/
#if UART3_SMART_ROUTING_ENABLED
{
uint8_t byte;
while (MultiUART_ReadByte(PORT_UART3, &byte) > 0) {
route_result_t route = UART3_Protocol_FeedByte(byte, current_tick);
switch (route) {
case ROUTE_CMD:
CmdParser_SetSourcePort(PORT_UART3);
CmdParser_FeedByte(byte, current_tick);
LOG_DEBUG("UART3", "CMD byte: 0x%02X", byte);
break;
case ROUTE_PASSTHROUGH:
Passthrough_PushByte(byte);
LOG_DEBUG("UART3", "PTX byte: 0x%02X", byte);
break;
case ROUTE_NONE:
break;
}
}
if (UART3_Protocol_CheckTimeout(current_tick)) {
uint8_t buffer[128];
uint16_t length;
if (UART3_Protocol_GetPassthroughData(buffer, &length)) {
Passthrough_PushBuffer(buffer, length);
LOG_INFO("UART3", "PASSTHROUGH: %d bytes queued", (int)length);
}
}
Passthrough_Task();
}
#else
{
port_id_t port_id = PORT_UART3;
uint16_t rx_count = MultiUART_GetRxCount(port_id);
if (rx_count > 0) {
g_current_parsing_port = port_id;
CmdParser_SetSourcePort(port_id);
uint8_t byte;
while (MultiUART_ReadByte(port_id, &byte) > 0) {
append_rx_log(port_id, byte);
CmdParser_FeedByte(byte, current_tick);
g_port_states[port_id].rx_count++;
}
if (CmdParser_HasCompleteFrame(NULL)) {
flush_rx_log(port_id);
g_processed_count++;
g_port_states[port_id].active = true;
}
}
}
#endif
/*----------------------------------------------------------
* 第三阶段:检查各端口日志缓冲区的超时状态
*----------------------------------------------------------*/
for (port_id_t port_id = 0; port_id < PORT_COUNT; port_id++) {
if (port_id == PORT_UART2) {
continue;
}
if (g_rx_log_len[port_id] > 0 &&
if (g_rx_log_len[port_id] > 0 &&
(current_tick - g_rx_log_last_tick[port_id]) >= RX_LOG_TIMEOUT_MS) {
flush_rx_log(port_id);
}
}
for (port_id_t port_id = 0; port_id < PORT_COUNT; port_id++) {
if (port_id == PORT_UART2) {
continue;
}
uint16_t rx_count = MultiUART_GetRxCount(port_id);
if (rx_count == 0) {
continue;
}
g_current_parsing_port = port_id;
CmdParser_SetSourcePort(port_id);
uint8_t byte;
while (MultiUART_ReadByte(port_id, &byte) > 0) {
append_rx_log(port_id, byte);
CmdParser_FeedByte(byte, HAL_GetTick());
g_port_states[port_id].rx_count++;
}
if (CmdParser_HasCompleteFrame(NULL)) {
flush_rx_log(port_id);
g_processed_count++;
g_port_states[port_id].active = true;
}
}
/*----------------------------------------------------------
* 第四阶段:调用解析器任务处理完整帧
*----------------------------------------------------------*/
CmdParser_Task();
}
/**
* @brief 设置自定义响应处理器
* @note 用于在响应发送前进行自定义处理(如添加时间戳、加密等)
*
* @param handler: 响应处理回调函数指针NULL则使用默认行为(输入)
* @return 无
*
* 回调函数原型:
* void handler(port_id_t port, const char *response, uint16_t len)
*
* 使用场景:
* - 需要在响应中添加时间戳
* - 需要对响应数据进行编码/加密
* - 需要过滤特定响应
*/
void CmdRouter_SetResponseHandler(cmd_response_handler_t handler)
{
g_response_handler = handler;
ROUTER_LOG("Response handler %s", handler ? "set" : "cleared");
}
/**
* @brief 向指定端口发送响应
* @note 封装MultiUART_Send统一响应发送接口
*
* @param port: 目标端口ID(输入)
* @param response: 待发送的响应字符串(输入)
* @param len: 响应长度(输入)
* @return 无
*
* 参数检查:
* - port超出范围、response为NULL或len为0时直接返回
*
* 统计更新:
* 每次成功发送增加g_routed_count计数
*/
void CmdRouter_SendResponse(port_id_t port, const char *response, uint16_t len)
{
/* 参数合法性检查 */
if (port >= PORT_COUNT || response == NULL || len == 0) {
return;
}
/* 通过多UART模块发送数据 */
MultiUART_Send(port, (const uint8_t *)response, len);
g_routed_count++;
LOG_INFO("ROUTER", "SendResponse[%s]: %.*s",
LOG_INFO("ROUTER", "SendResponse[%s]: %.*s",
MultiUART_GetPortName(port), len, response);
}
/**
* @brief 广播响应到所有端口
* @note 将同一响应同时发送到所有UART端口
*
* @param response: 待发送的响应字符串(输入)
* @param len: 响应长度(输入)
* @return 无
*
* 使用场景:
* - 系统公告广播
* - 状态变更通知所有监听者
*
* 注意:
* UART2(调试端口)也会收到广播
*/
void CmdRouter_BroadcastResponse(const char *response, uint16_t len)
{
/* 参数合法性检查 */
if (response == NULL || len == 0) {
return;
}
/* 遍历所有端口发送 */
for (port_id_t port = 0; port < PORT_COUNT; port++) {
MultiUART_Send(port, (const uint8_t *)response, len);
g_routed_count++;
}
LOG_INFO("ROUTER", "Broadcast: %.*s", len, response);
}
/**
* @brief 获取已处理指令帧累计计数
* @note 统计所有端口成功解析的指令帧总数
*
* @param 无
* @return uint32_t: 已处理帧累计次数
*
* 用途:
* - 监控系统负载
* - 评估解析器性能
*/
uint32_t CmdRouter_GetProcessedCount(void)
{
return g_processed_count;
}
/**
* @brief 获取已路由响应累计计数
* @note 统计成功发送的响应消息总数
*
* @param 无
* @return uint32_t: 已路由响应累计次数
*
* 用途:
* - 监控系统通信量
* - 检测是否有响应发送失败
*/
uint32_t CmdRouter_GetRoutedCount(void)
{
return g_routed_count;