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

@ -0,0 +1,207 @@
/**
******************************************************************************
* @file uart3_passthrough.h
* @brief UART3透传引擎模块头文件
* @author Application Layer
* @version 1.0
******************************************************************************
* @attention
* 设计依据UART3智能数据路由与透传功能开发计划 第3.2节
*
* 功能说明:
* 本模块实现透传数据缓冲与发送功能将UART3接收的透传数据转发至UART1
*
* 数据结构严格按文档3.2.2节定义):
* - passthrough_node_t: 透传数据节点
* - passthrough_queue_t: 透传队列
* - passthrough_context_t: 透传引擎上下文
*
* 配置参数严格按附录D定义
* - PASSTHROUGH_NODE_SIZE: 128字节
* - PASSTHROUGH_MAX_NODES: 4个节点
* - PASSTHROUGH_TOTAL_BUFFER: 512字节
******************************************************************************
*/
#ifndef __UART3_PASSTHROUGH_H
#define __UART3_PASSTHROUGH_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
/*==============================================================================
* 透传引擎配置参数
* 设计依据文档附录D配置参数
*============================================================================*/
/**
* @brief 每个透传节点大小
*/
#ifndef PASSTHROUGH_NODE_SIZE
#define PASSTHROUGH_NODE_SIZE 128
#endif
/**
* @brief 最大节点数量
*/
#ifndef PASSTHROUGH_MAX_NODES
#define PASSTHROUGH_MAX_NODES 4
#endif
/**
* @brief 发送缓冲区总大小
*/
#ifndef PASSTHROUGH_TOTAL_BUFFER
#define PASSTHROUGH_TOTAL_BUFFER (PASSTHROUGH_NODE_SIZE * PASSTHROUGH_MAX_NODES)
#endif
/*==============================================================================
* 透传数据节点结构体
* 设计依据文档第3.2.2节核心数据结构
*============================================================================*/
/**
* @brief 透传数据节点
* @note 每个节点存储一段待发送的透传数据
*/
typedef struct {
uint8_t data[PASSTHROUGH_NODE_SIZE]; /**< 数据缓冲区 */
uint16_t length; /**< 有效数据长度 */
uint16_t offset; /**< 已发送偏移 */
bool valid; /**< 节点是否有效 */
} passthrough_node_t;
/*==============================================================================
* 透传队列结构体
* 设计依据文档第3.2.2节核心数据结构
*============================================================================*/
/**
* @brief 透传队列
* @note 管理多个透传数据节点的FIFO队列
*/
typedef struct {
passthrough_node_t nodes[PASSTHROUGH_MAX_NODES]; /**< 节点数组 */
uint8_t write_index; /**< 写入位置 */
uint8_t read_index; /**< 读取位置 */
volatile uint16_t pending_count; /**< 待发送字节总数 */
} passthrough_queue_t;
/*==============================================================================
* 透传统计结构体
* 设计依据文档第3.2.1节模块接口设计
*============================================================================*/
/**
* @brief 透传统计信息
* @note 记录透传引擎的运行状态
*/
typedef struct {
uint32_t total_bytes_sent; /**< 累计发送字节数 */
uint32_t total_packets; /**< 累计发送数据包数 */
uint32_t overflow_count; /**< 缓冲区溢出次数 */
uint32_t busy_count; /**< UART1忙等待次数 */
} passthrough_stats_t;
/*==============================================================================
* 透传引擎上下文结构体
* 设计依据文档第3.2.2节核心数据结构
*============================================================================*/
/**
* @brief 透传引擎上下文
* @note 保存透传引擎全部状态信息
*/
typedef struct {
passthrough_queue_t queue; /**< 透传队列 */
passthrough_stats_t stats; /**< 透传统计 */
bool initialized; /**< 初始化标志 */
} passthrough_context_t;
/*==============================================================================
* 全局变量声明
* 设计依据文档第3.2.2节
*============================================================================*/
extern passthrough_context_t g_passthrough_ctx;
/*==============================================================================
* 函数接口声明
* 设计依据文档第3.2.1节模块接口设计
*============================================================================*/
/**
* @brief 透传引擎初始化
* @note 初始化透传队列和统计信息
* @param 无
* @retval 无
*/
void Passthrough_Init(void);
/**
* @brief 压入单字节到透传缓冲区
* @note 将单个字节添加到透传发送队列
* @param byte: 待发送字节
* @retval bool: true=成功false=缓冲区满
*/
bool Passthrough_PushByte(uint8_t byte);
/**
* @brief 压入数据块到透传缓冲区
* @note 将数据块添加到透传发送队列
* @param data: 数据缓冲区指针
* @param length: 数据长度
* @retval uint16_t: 实际写入的字节数
*/
uint16_t Passthrough_PushBuffer(const uint8_t *data, uint16_t length);
/**
* @brief 透传引擎任务
* @note 在主循环中周期性调用,每次尝试发送一个字节
* @param 无
* @retval 无
*/
void Passthrough_Task(void);
/**
* @brief UART1发送完成回调
* @note 应在HAL_UART_TxCpltCallback中调用触发下一次发送
* @param 无
* @retval 无
*/
void Passthrough_OnTxComplete(void);
/**
* @brief 检查UART1是否可发送
* @note 检查UART1发送缓冲区是否有空间
* @param 无
* @retval bool: true=可发送false=忙
*/
bool Passthrough_CanSend(void);
/**
* @brief 获取透传统计信息
* @note 获取透传引擎的运行统计
* @param stats: 统计结构指针
* @retval 无
*/
void Passthrough_GetStats(passthrough_stats_t *stats);
/**
* @brief 重置统计信息
* @note 清零所有统计计数器
* @param 无
* @retval 无
*/
void Passthrough_ResetStats(void);
/**
* @brief 获取待发送字节数
* @note 获取队列中等待发送的字节总数
* @param 无
* @retval uint16_t: 待发送字节数
*/
uint16_t Passthrough_GetPendingCount(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,150 @@
/**
******************************************************************************
* @file uart3_protocol_discriminator.h
* @brief UART3协议识别器模块头文件
* @author Application Layer
* @version 1.0
******************************************************************************
* @attention
* 设计依据UART3智能数据路由与透传功能开发计划 第3.1节
*
* 功能说明:
* 本模块实现UART3协议识别器用于判断接收数据是ASCII指令还是透传数据
*
* 状态机:
* - INIT: 系统启动/复位状态,等待第一个字节
* - SCAN: 扫描模式,等待指令起始符'$'或透传判定
* - CMD_MODE: 指令解析模式数据喂给CmdParser
* - EXEC: 指令执行模式
* - PASSTHROUGH: 透传模式数据转发至UART1
*
* 识别策略:
* - 以'$'开头视为指令候选
* - 超时(50ms)无'$'则判定为透传
* - 帧间隔检测('\n'后重新判断)
******************************************************************************
*/
#ifndef __UART3_PROTOCOL_DISCRIMINATOR_H
#define __UART3_PROTOCOL_DISCRIMINATOR_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
/*==============================================================================
* 路由结果枚举定义
* 设计依据文档第3.1.2节伪代码
*============================================================================*/
/**
* @brief 路由结果枚举
* @note 定义协议识别器的三种路由结果
*/
typedef enum {
ROUTE_NONE = 0, /**< 暂时缓存,不路由 */
ROUTE_CMD, /**< 路由至指令解析器 */
ROUTE_PASSTHROUGH /**< 路由至透传引擎 */
} route_result_t;
/*==============================================================================
* 协议识别器状态枚举定义
* 设计依据文档第3.1.1节状态转换图
*============================================================================*/
/**
* @brief 协议识别器状态枚举
* @note 定义状态机的所有状态
*/
typedef enum {
STATE_INIT = 0, /**< 初始状态,系统启动/复位 */
STATE_SCAN, /**< 扫描模式,等待指令起始符或透传判定 */
STATE_CMD_MODE, /**< 指令解析模式 */
STATE_EXEC, /**< 指令执行模式 */
STATE_PASSTHROUGH /**< 透传模式 */
} protocol_state_t;
/*==============================================================================
* 协议识别器上下文结构体
* 设计依据文档第3.1.2节伪代码
*============================================================================*/
/**
* @brief UART3协议识别器上下文
* @note 保存状态机全部状态信息
*/
typedef struct {
protocol_state_t state; /**< 当前状态 */
uint32_t last_byte_tick; /**< 上次收到字节的时间 */
uint32_t scan_start_tick; /**< SCAN状态开始时间 */
uint8_t scan_buffer[128]; /**< SCAN状态缓存 */
uint16_t scan_length; /**< SCAN缓存长度 */
bool pending_cmd; /**< CMD_MODE是否有待处理帧 */
uint32_t passthrough_bytes; /**< 透传字节计数 */
} uart3_protocol_context_t;
/*==============================================================================
* 全局变量声明
* 设计依据文档第3.1.2节伪代码
*============================================================================*/
extern uart3_protocol_context_t g_uart3_proto_ctx;
/*==============================================================================
* 函数接口声明
* 设计依据文档第3.1.3节接口定义
*============================================================================*/
/**
* @brief 协议识别器初始化
* @note 初始化状态机和上下文
* @param 无
* @retval 无
*/
void UART3_Protocol_Init(void);
/**
* @brief 协议识别器字节输入
* @note 由UART3中断调用判断字节应该走指令路径还是透传路径
* @param byte: 接收字节
* @param current_tick: 当前系统时间(ms)
* @retval route_result_t: 路由结果 (ROUTE_CMD/ROUTE_PASSTHROUGH/ROUTE_NONE)
*/
route_result_t UART3_Protocol_FeedByte(uint8_t byte, uint32_t current_tick);
/**
* @brief 协议识别器超时检查
* @note 定时调用检查SCAN状态是否超时
* @param current_tick: 当前系统时间(ms)
* @retval bool: true=触发了透传false=继续等待
*/
bool UART3_Protocol_CheckTimeout(uint32_t current_tick);
/**
* @brief 获取透传模式下的缓存数据
* @note 在触发透传后调用,获取待发送的透传数据
* @param buffer: 输出缓冲区指针
* @param length: 数据长度指针(输出)
* @retval bool: true=有数据false=无数据
*/
bool UART3_Protocol_GetPassthroughData(uint8_t *buffer, uint16_t *length);
/**
* @brief 获取当前协议识别状态
* @note 用于调试和状态监控
* @param 无
* @retval uint8_t: 当前状态码
*/
uint8_t UART3_Protocol_GetState(void);
/**
* @brief 重置协议识别器状态
* @note 将状态机恢复到INIT状态
* @param 无
* @retval 无
*/
void UART3_Protocol_Reset(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,143 @@
/**
******************************************************************************
* @file uart3_smart_router_config.h
* @brief UART3智能路由与透传功能配置文件
* @author Application Layer
* @version 1.0
******************************************************************************
* @attention
* 设计依据UART3智能数据路由与透传功能开发计划 附录D
*
* 配置说明:
* 本文件定义所有可配置的参数,包括协议识别器、透传引擎和调试开关
*
* 修改说明:
* 如需调整功能行为,只需修改本文件中的宏定义,无需改动业务代码
******************************************************************************
*/
#ifndef __UART3_SMART_ROUTER_CONFIG_H
#define __UART3_SMART_ROUTER_CONFIG_H
/*==============================================================================
* 协议识别器配置参数
* 设计依据文档第3.1节
*============================================================================*/
/**
* @brief SCAN状态超时时间(ms)
* @note UART3@9600bps1字节≈1.04ms50ms≈48字节
* 用于判断非指令数据何时转为透传
*/
#ifndef SCAN_TIMEOUT_MS
#define SCAN_TIMEOUT_MS 50
#endif
/**
* @brief SCAN缓存大小(字节)
* @note 建议不小于最大指令长度
*/
#ifndef SCAN_BUFFER_SIZE
#define SCAN_BUFFER_SIZE 128
#endif
/**
* @brief 指令帧完整接收超时(ms)
* @note 收到'$'后,等待完整帧(\r\n)的超时时间
*/
#ifndef CMD_COMPLETE_TIMEOUT_MS
#define CMD_COMPLETE_TIMEOUT_MS 200
#endif
/*==============================================================================
* 透传引擎配置参数
* 设计依据文档第3.2节
*============================================================================*/
/**
* @brief 每个透传节点大小(字节)
* @note 单次透传数据块的最大长度
*/
#ifndef PASSTHROUGH_NODE_SIZE
#define PASSTHROUGH_NODE_SIZE 128
#endif
/**
* @brief 最大节点数量
* @note 透传缓冲区可存储的数据块数量
*/
#ifndef PASSTHROUGH_MAX_NODES
#define PASSTHROUGH_MAX_NODES 4
#endif
/**
* @brief 发送缓冲区总大小(字节)
* @note = PASSTHROUGH_NODE_SIZE * PASSTHROUGH_MAX_NODES
*/
#ifndef PASSTHROUGH_TOTAL_BUFFER
#define PASSTHROUGH_TOTAL_BUFFER (PASSTHROUGH_NODE_SIZE * PASSTHROUGH_MAX_NODES)
#endif
/*==============================================================================
* 功能开关配置
* 设计依据文档第3.3节
*============================================================================*/
/**
* @brief UART3智能路由模式使能开关
* @note 1=启用智能路由(协议识别+透传)0=原有逻辑(所有数据给CmdParser)
* 可通过此开关快速切换新旧逻辑
*/
#ifndef UART3_SMART_ROUTING_ENABLED
#define UART3_SMART_ROUTING_ENABLED 1
#endif
/**
* @brief 透传日志输出使能开关
* @note 1=启用透传相关日志0=禁用
* 禁用后可减少日志输出,提升性能
*/
#ifndef PASSTHROUGH_LOG_ENABLED
#define PASSTHROUGH_LOG_ENABLED 1
#endif
/*==============================================================================
* 调试配置参数
* 设计依据文档第3.1.2节、3.2.3节
*============================================================================*/
/**
* @brief 协议识别器调试开关
* @note 1=启用调试日志0=禁用
* 启用后会输出状态转换和路由判定日志
*/
#ifndef DEBUG_PROTOCOL_DISCRIMINATOR
#define DEBUG_PROTOCOL_DISCRIMINATOR 1
#endif
/**
* @brief 透传引擎调试开关
* @note 1=启用调试日志0=禁用
* 启用后会输出发送进度和缓冲区状态日志
*/
#ifndef DEBUG_PASSTHROUGH_ENGINE
#define DEBUG_PASSTHROUGH_ENGINE 1
#endif
/*==============================================================================
* 背压控制参数
* 设计依据文档第3.3.3节透传引擎发送接口
*============================================================================*/
/**
* @brief 缓冲区满警告阈值(字节)
* @note 当pending_count超过此值时记录警告日志
*/
#ifndef PASSTHROUGH_WARNING_THRESHOLD
#define PASSTHROUGH_WARNING_THRESHOLD 200
#endif
/**
* @brief 每次任务调用的最大发送字节数
* @note 限制每次调用发送的字节数避免长时间占用CPU
*/
#ifndef PASSTHROUGH_MAX_SEND_PER_CALL
#define PASSTHROUGH_MAX_SEND_PER_CALL 16
#endif
#endif

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;

View File

@ -107,6 +107,13 @@ static bool report_enabled = true;
* @brief IO事件回调函数指针
* @note 设置后IO状态变化将通过回调函数上报
* 为NULL时使用默认的UART2_Print_String输出
*
* 回调函数原型:
* void callback(uint8_t channel, uint8_t state, const char *event_msg)
*
* 使用场景:
* - 需要将事件发送到其他端口时设置回调
* - 传入NULL恢复默认UART2输出方式
*/
static io_event_callback_t g_event_callback = NULL;
@ -129,17 +136,18 @@ static uint8_t calc_checksum(const char *data, uint8_t len);
/**
* @brief 发送DI状态变化事件
* @note 构造并发送ASCII格式的状态变化消息至UART2
* @note 构造并发送ASCII格式的状态变化消息
* 消息格式: $DI_EVENT,<channel>,<state>*<checksum>\r\n
* 如果设置了回调函数则通过回调发送否则发送到UART2
*
* @param channel: 通道编号从0开始计数(输入)
* @param state: 通道状态0=低电平1=高电平(输入)
* @return 无返回值
*
* 调用说明:
* - 此函数在状态变化被确认后调用,用于通知上位机或日志系统
* - 内部会调用calc_checksum计算校验和
* - 通过UART2_Print_String发送原始字符串
* 事件路由说明:
* 1. 始终通过MultiUART_SendString发送到UART1(RF433)端口
* 2. 如果设置了回调函数(g_event_callback),也通过回调发送
* 3. UART2仅用于调试日志输出
*/
static void send_di_event(uint8_t channel, uint8_t state);
@ -177,10 +185,10 @@ static uint8_t calc_checksum(const char *data, uint8_t len)
* @param state: 通道状态0=低电平1=高电平(输入)
* @return 无返回值
*
* 调用说明:
* - 此函数在状态变化被确认后调用,用于通知上位机或日志系统
* - 内部会调用calc_checksum计算校验和
* - 如果设置了回调函数则通过回调发送否则通过UART2_Print_String发送
* 事件路由说明:
* 1. 始终通过MultiUART_SendString发送到UART1(RF433)端口
* 2. 如果设置了回调函数(g_event_callback),也通过回调发送
* 3. UART2仅用于调试日志输出
*/
static void send_di_event(uint8_t channel, uint8_t state)
{
@ -198,14 +206,20 @@ static void send_di_event(uint8_t channel, uint8_t state)
/* 输出调试日志到UART2记录状态变化 */
DEBUG_LOG("CH%d -> %s", channel + 1, state ? "HIGH" : "LOW");
/* 向UART1(RF433)发送状态变化事件 */
/*----------------------------------------------------------
* 向UART1(RF433模块)发送状态变化事件
* 这是IO事件的主要路由通道用于无线上报到上位机
*----------------------------------------------------------*/
MultiUART_SendString(PORT_UART1, msg);
/* 输出完整消息到UART2方便调试查看 */
DEBUG_LOG("RF433 TX: \"%s\"", msg);
/* 如果设置了回调函数,也通过回调发送 */
/*----------------------------------------------------------
* 如果设置了回调函数,也通过回调发送
* 用于支持额外的自定义处理逻辑
*----------------------------------------------------------*/
if (g_event_callback != NULL) {
g_event_callback(channel, state, msg);
}

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;
}

View File

@ -11,7 +11,7 @@
* 1. 最小切换间隔保护,防止频繁切换损坏继电器
* 2. 状态记录
* 3. 调试日志输出
*
*
* 修订历史:
* v2.0 - 精简为单路继电器控制,移除冗余功能
******************************************************************************
@ -21,49 +21,163 @@
#include "uart2_print.h"
#include "main.h"
/*==============================================================================
* 调试宏定义
*============================================================================*/
/* DEBUG_RELAY: 调试日志开关置1时启用继电器控制过程日志输出 */
#define DEBUG_RELAY 1
#if DEBUG_RELAY
/* 调试日志宏,带模块前缀"[RELAY]"方便过滤日志 */
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[RELAY] " fmt "\r\n", ##__VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif
/*==============================================================================
* 全局变量定义
*============================================================================*/
/**
* @brief 继电器当前状态
* @note 记录继电器的开关状态
*
* 状态含义:
* - false: 继电器处于断开状态(OFF)
* - true: 继电器处于闭合状态(ON)
*
* 初始化说明:
* 初始化为false表示系统上电后继电器默认断开
*/
static bool current_state = false;
/**
* @brief 上一次切换操作的时间戳
* @note 用于实现继电器最小切换间隔保护
*
* 设计目的:
* 继电器是机械开关,频繁切换会缩短其使用寿命。
* 通过记录上次切换时间,可以强制两次切换之间必须间隔
* 最小时间(RELAY_MIN_INTERVAL),防止频繁操作。
*
* 单位说明:
* 毫秒(ms)与HAL_GetTick()返回值单位一致
*/
static uint32_t last_toggle_tick = 0;
/*==============================================================================
* 公共函数实现
*============================================================================*/
/**
* @brief 继电器模块初始化
* @note 在系统启动时调用,复位继电器到安全初始状态
*
* @param 无
* @return 无
*
* 功能说明:
* 1. 硬件控制引脚设置为低电平,强制确保继电器断开
* 2. 重置状态变量为false
* 3. 重置切换时间戳为0
*
* 初始化安全:
* - 此函数应尽早调用,确保系统启动时继电器处于受控状态
* - 继电器初始状态设为OFF是考虑到故障安全(fail-safe)设计
*/
void Relay_Init(void)
{
/*----------------------------------------------------------
* 硬件层面:将继电器控制引脚设置为低电平
* RL_Control_GPIO_Port和RL_Control_Pin定义在main.h或引脚配置中
*----------------------------------------------------------*/
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin, GPIO_PIN_RESET);
/*----------------------------------------------------------
* 软件层面初始化继电器状态为OFF
*----------------------------------------------------------*/
current_state = false;
/*----------------------------------------------------------
* 重置切换保护相关计时器
*----------------------------------------------------------*/
last_toggle_tick = 0;
DEBUG_LOG("Init OK, state=OFF");
}
/**
* @brief 设置继电器状态
* @note 核心控制函数,实现继电器开关操作及保护逻辑
*
* @param state: 目标状态false=关闭(OFF)true=打开(ON)(输入)
* @return 无
*
* 功能说明:
* 1. 切换间隔保护检查(防频繁操作)
* 2. 状态变更判断(避免重复操作)
* 3. 硬件GPIO操作
* 4. 状态记录和统计更新
*
* 算法说明 - 切换间隔保护:
* if (current_tick - last_toggle_tick < RELAY_MIN_INTERVAL)
* return; // 切换过于频繁,直接丢弃
*
* 这个检查确保两次继电器操作之间至少间隔RELAY_MIN_INTERVAL毫秒
* 防止程序错误或通信干扰导致继电器被频繁开关。
*
* 注意/异常:
* - 切换间隔太短时静默忽略,防抖处理
* - 状态与当前相同时静默忽略,不重复操作
*/
void Relay_SetState(bool state)
{
/*----------------------------------------------------------
* 获取当前时间,判断与上次操作的时间间隔
*----------------------------------------------------------*/
uint32_t current_tick = HAL_GetTick();
/*----------------------------------------------------------
* 切换间隔保护检查
*----------------------------------------------------------*/
if (current_tick - last_toggle_tick < RELAY_MIN_INTERVAL) {
DEBUG_LOG("Toggle too fast, ignored");
return;
}
/*----------------------------------------------------------
* 状态变更判断:避免重复设置相同状态
*----------------------------------------------------------*/
if (current_state == state) {
DEBUG_LOG("State unchanged: %s", state ? "ON" : "OFF");
return;
}
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin,
/*----------------------------------------------------------
* 硬件控制设置GPIO引脚电平
*----------------------------------------------------------*/
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin,
state ? GPIO_PIN_SET : GPIO_PIN_RESET);
/*----------------------------------------------------------
* 更新软状态和记录切换时间
*----------------------------------------------------------*/
current_state = state;
last_toggle_tick = current_tick;
DEBUG_LOG("Relay -> %s", state ? "ON" : "OFF");
}
/**
* @brief 获取继电器当前状态
* @note 返回继电器的开关状态
*
* @param 无
* @return bool: 继电器状态false=关闭(OFF)true=打开(ON)
*
* 使用示例:
* if (Relay_GetState()) {
* // 继电器当前处于闭合状态
* }
*/
bool Relay_GetState(void)
{
return current_state;

View File

@ -0,0 +1,269 @@
/**
******************************************************************************
* @file uart3_passthrough.c
* @brief UART3透传引擎模块实现
* @author Application Layer
* @version 1.0
******************************************************************************
* @attention
* 设计依据UART3智能数据路由与透传功能开发计划 第3.2节
*
* 功能说明:
* 本模块实现透传数据缓冲与发送功能将UART3接收的透传数据转发至UART1
*
* 工作流程严格按文档3.2.3节伪代码实现):
* 1. 检查是否有待发送数据
* 2. 检查UART1是否可发送
* 3. 获取当前处理节点
* 4. 发送一个字节
* 5. 更新队列状态和统计
*
* 发送机制:
* - 单字节每次发送由UART1 TX完成中断驱动连续发送
* - 使用独立环形缓冲区,避免与指令解析争用
******************************************************************************
*/
#include "uart3_passthrough.h"
#include "uart3_smart_router_config.h"
#include "multi_uart_router.h"
#include "uart2_print.h"
#include <string.h>
/*==============================================================================
* 调试宏定义
*============================================================================*/
#if DEBUG_PASSTHROUGH_ENGINE
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[PTX] " fmt "\r\n", ##__VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif
/*==============================================================================
* 全局变量定义
* 设计依据文档第3.2.2节
*============================================================================*/
/**
* @brief 透传引擎全局上下文
* @note 保存透传引擎全部状态信息
*/
passthrough_context_t g_passthrough_ctx;
/*==============================================================================
* 公共函数实现
* 设计依据文档第3.2.1节模块接口设计
*============================================================================*/
/**
* @brief 透传引擎初始化
* @note 初始化透传队列和统计信息
*/
void Passthrough_Init(void)
{
memset(&g_passthrough_ctx, 0, sizeof(g_passthrough_ctx));
for (uint8_t i = 0; i < PASSTHROUGH_MAX_NODES; i++) {
g_passthrough_ctx.queue.nodes[i].valid = false;
g_passthrough_ctx.queue.nodes[i].length = 0;
g_passthrough_ctx.queue.nodes[i].offset = 0;
}
g_passthrough_ctx.queue.write_index = 0;
g_passthrough_ctx.queue.read_index = 0;
g_passthrough_ctx.queue.pending_count = 0;
g_passthrough_ctx.stats.total_bytes_sent = 0;
g_passthrough_ctx.stats.total_packets = 0;
g_passthrough_ctx.stats.overflow_count = 0;
g_passthrough_ctx.stats.busy_count = 0;
g_passthrough_ctx.initialized = true;
DEBUG_LOG("Passthrough engine initialized, buffer size: %d bytes", PASSTHROUGH_TOTAL_BUFFER);
}
/**
* @brief 压入单字节到透传缓冲区
* @note 将单个字节添加到透传发送队列
* @param byte: 待发送字节
* @retval bool: true=成功false=缓冲区满
*/
bool Passthrough_PushByte(uint8_t byte)
{
passthrough_context_t *ctx = &g_passthrough_ctx;
if (!ctx->initialized) {
return false;
}
uint8_t next_index = (ctx->queue.write_index + 1) % PASSTHROUGH_MAX_NODES;
if (next_index == ctx->queue.read_index && ctx->queue.pending_count > 0) {
ctx->stats.overflow_count++;
DEBUG_LOG("Buffer full, overflow count: %d", ctx->stats.overflow_count);
return false;
}
passthrough_node_t *node = &ctx->queue.nodes[ctx->queue.write_index];
if (!node->valid || node->length >= PASSTHROUGH_NODE_SIZE) {
if (node->valid) {
ctx->queue.write_index = next_index;
node = &ctx->queue.nodes[ctx->queue.write_index];
}
node->length = 0;
node->offset = 0;
node->valid = true;
}
node->data[node->length++] = byte;
ctx->queue.pending_count++;
return true;
}
/**
* @brief 压入数据块到透传缓冲区
* @note 将数据块添加到透传发送队列
* @param data: 数据缓冲区指针
* @param length: 数据长度
* @retval uint16_t: 实际写入的字节数
*/
uint16_t Passthrough_PushBuffer(const uint8_t *data, uint16_t length)
{
if (data == NULL || length == 0) {
return 0;
}
passthrough_context_t *ctx = &g_passthrough_ctx;
uint16_t written = 0;
for (uint16_t i = 0; i < length; i++) {
uint8_t next_index = (ctx->queue.write_index + 1) % PASSTHROUGH_MAX_NODES;
if (next_index == ctx->queue.read_index && ctx->queue.pending_count > 0) {
ctx->stats.overflow_count++;
DEBUG_LOG("Buffer overflow at byte %d/%d", i, length);
break;
}
passthrough_node_t *node = &ctx->queue.nodes[ctx->queue.write_index];
if (!node->valid || node->length >= PASSTHROUGH_NODE_SIZE) {
if (node->valid) {
ctx->queue.write_index = next_index;
node = &ctx->queue.nodes[ctx->queue.write_index];
}
node->length = 0;
node->offset = 0;
node->valid = true;
}
node->data[node->length++] = data[i];
ctx->queue.pending_count++;
written++;
}
if (written > 0) {
ctx->stats.total_packets++;
DEBUG_LOG("Pushed %d bytes to buffer, pending: %d", written, ctx->queue.pending_count);
}
return written;
}
/**
* @brief 透传引擎任务
* @note 在主循环中周期性调用,每次尝试发送一个字节
* 配合UART1 TX完成中断实现连续发送
*/
void Passthrough_Task(void)
{
passthrough_context_t *ctx = &g_passthrough_ctx;
if (!ctx->initialized) {
return;
}
if (ctx->queue.pending_count == 0) {
return;
}
if (!Passthrough_CanSend()) {
ctx->stats.busy_count++;
return;
}
passthrough_node_t *node = &ctx->queue.nodes[ctx->queue.read_index];
if (!node->valid || node->offset >= node->length) {
if (node->valid) {
node->valid = false;
}
ctx->queue.read_index = (ctx->queue.read_index + 1) % PASSTHROUGH_MAX_NODES;
return;
}
uint8_t byte = node->data[node->offset++];
MultiUART_Send(PORT_UART1, &byte, 1);
ctx->queue.pending_count--;
ctx->stats.total_bytes_sent++;
DEBUG_LOG("Sent byte 0x%02X, pending: %d", byte, ctx->queue.pending_count);
}
/**
* @brief UART1发送完成回调
* @note 应在HAL_UART_TxCpltCallback中调用触发下一次发送
*/
void Passthrough_OnTxComplete(void)
{
Passthrough_Task();
}
/**
* @brief 检查UART1是否可发送
* @note 检查UART1发送缓冲区是否有空间
* @retval bool: true=可发送false=忙
*/
bool Passthrough_CanSend(void)
{
return (MultiUART_GetTxAvailable(PORT_UART1) > 0) &&
(g_passthrough_ctx.queue.pending_count > 0);
}
/**
* @brief 获取透传统计信息
* @note 获取透传引擎的运行统计
* @param stats: 统计结构指针
*/
void Passthrough_GetStats(passthrough_stats_t *stats)
{
if (stats != NULL) {
memcpy(stats, &g_passthrough_ctx.stats, sizeof(passthrough_stats_t));
}
}
/**
* @brief 重置统计信息
* @note 清零所有统计计数器
*/
void Passthrough_ResetStats(void)
{
passthrough_context_t *ctx = &g_passthrough_ctx;
ctx->stats.total_bytes_sent = 0;
ctx->stats.total_packets = 0;
ctx->stats.overflow_count = 0;
ctx->stats.busy_count = 0;
DEBUG_LOG("Stats reset");
}
/**
* @brief 获取待发送字节数
* @note 获取队列中等待发送的字节总数
* @retval uint16_t: 待发送字节数
*/
uint16_t Passthrough_GetPendingCount(void)
{
return g_passthrough_ctx.queue.pending_count;
}

View File

@ -0,0 +1,252 @@
/**
******************************************************************************
* @file uart3_protocol_discriminator.c
* @brief UART3协议识别器模块实现
* @author Application Layer
* @version 1.0
******************************************************************************
* @attention
* 设计依据UART3智能数据路由与透传功能开发计划 第3.1节
*
* 功能说明:
* 本模块实现UART3协议识别器状态机根据接收字节判断数据类型并路由
*
* 状态转换逻辑严格按文档3.1.2节伪代码实现):
* - INIT → SCAN: 收到任何字节(非'$')
* - SCAN → CMD_MODE: 收到'$'
* - SCAN → PASSTHROUGH: 超时(50ms)且有数据 或 收到'\n'
* - CMD_MODE → EXEC: 收到'\n'且帧解析完成
* - CMD_MODE → SCAN: 收到'\r'
* - EXEC → SCAN: 指令执行完成
* - PASSTHROUGH → SCAN: 透传数据全部发送完成
******************************************************************************
*/
#include "uart3_protocol_discriminator.h"
#include "uart3_smart_router_config.h"
#include "uart2_print.h"
#include <string.h>
/*==============================================================================
* 调试宏定义
*============================================================================*/
#if DEBUG_PROTOCOL_DISCRIMINATOR
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[PROTO] " fmt "\r\n", ##__VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif
/*==============================================================================
* 全局变量定义
* 设计依据文档第3.1.2节伪代码
*============================================================================*/
/**
* @brief 协议识别器全局上下文
* @note 保存状态机全部状态信息
*/
uart3_protocol_context_t g_uart3_proto_ctx;
/*==============================================================================
* 内部函数实现
*============================================================================*/
/**
* @brief 检查SCAN状态是否超时
* @note 内部使用,判断是否需要触发透传
* @param current_tick: 当前系统时间(ms)
* @retval bool: true=超时false=未超时
*/
static bool check_scan_timeout(uint32_t current_tick)
{
uart3_protocol_context_t *ctx = &g_uart3_proto_ctx;
if (ctx->state == STATE_SCAN && ctx->scan_length > 0) {
if (current_tick - ctx->scan_start_tick >= SCAN_TIMEOUT_MS) {
return true;
}
}
return false;
}
/*==============================================================================
* 公共函数实现
* 设计依据文档第3.1.2节伪代码
*============================================================================*/
/**
* @brief 协议识别器初始化
* @note 初始化状态机和上下文
*/
void UART3_Protocol_Init(void)
{
memset(&g_uart3_proto_ctx, 0, sizeof(g_uart3_proto_ctx));
g_uart3_proto_ctx.state = STATE_INIT;
DEBUG_LOG("Protocol discriminator initialized");
}
/**
* @brief 协议识别器字节输入
* @note 由UART3中断调用判断字节应该走指令路径还是透传路径
* @param byte: 接收字节
* @param current_tick: 当前系统时间(ms)
* @retval route_result_t: 路由结果
*/
route_result_t UART3_Protocol_FeedByte(uint8_t byte, uint32_t current_tick)
{
uart3_protocol_context_t *ctx = &g_uart3_proto_ctx;
if (ctx->state != STATE_INIT && ctx->state != STATE_SCAN &&
ctx->state != STATE_CMD_MODE && ctx->state != STATE_PASSTHROUGH) {
ctx->state = STATE_INIT;
ctx->scan_length = 0;
DEBUG_LOG("Protocol state invalid(0x%02X), reset to INIT", ctx->state);
}
DEBUG_LOG("FeedByte state=%d, byte=0x%02X", (int)ctx->state, byte);
switch (ctx->state) {
case STATE_INIT:
ctx->last_byte_tick = current_tick;
if (byte == '$') {
ctx->state = STATE_CMD_MODE;
ctx->scan_length = 0;
DEBUG_LOG("INIT → CMD_MODE (got '$')");
return ROUTE_CMD;
} else {
ctx->state = STATE_SCAN;
ctx->scan_start_tick = current_tick;
ctx->scan_buffer[0] = byte;
ctx->scan_length = 1;
DEBUG_LOG("INIT → SCAN (byte: 0x%02X)", byte);
return ROUTE_NONE;
}
case STATE_SCAN:
ctx->last_byte_tick = current_tick;
if (byte == '$') {
if (ctx->scan_length > 0) {
ctx->state = STATE_PASSTHROUGH;
DEBUG_LOG("SCAN → PASSTHROUGH (got '$', %d bytes)", ctx->scan_length);
return ROUTE_PASSTHROUGH;
} else {
ctx->state = STATE_CMD_MODE;
DEBUG_LOG("SCAN → CMD_MODE (empty scan, got '$')");
return ROUTE_CMD;
}
} else if (byte == '\n') {
ctx->state = STATE_PASSTHROUGH;
DEBUG_LOG("SCAN → PASSTHROUGH (got '\\n', %d bytes)", ctx->scan_length);
return ROUTE_PASSTHROUGH;
} else {
if (ctx->scan_length < sizeof(ctx->scan_buffer)) {
ctx->scan_buffer[ctx->scan_length++] = byte;
}
return ROUTE_NONE;
}
case STATE_CMD_MODE:
ctx->last_byte_tick = current_tick;
if (byte == '\n') {
ctx->state = STATE_INIT;
ctx->scan_length = 0;
DEBUG_LOG("CMD_MODE → INIT (got \\n)");
return ROUTE_CMD;
} else if (byte == '\r') {
return ROUTE_CMD;
} else {
DEBUG_LOG("CMD_MODE → CMD (byte: 0x%02X)", byte);
return ROUTE_CMD;
}
case STATE_PASSTHROUGH:
DEBUG_LOG("PASSTHROUGH → PTX (byte: 0x%02X)", byte);
ctx->last_byte_tick = current_tick;
return ROUTE_PASSTHROUGH;
default:
ctx->state = STATE_INIT;
DEBUG_LOG("Unknown state, reset to INIT");
return ROUTE_NONE;
}
}
/**
* @brief 协议识别器超时检查
* @note 定时调用检查SCAN状态和CMD_MODE状态是否超时
* @param current_tick: 当前系统时间(ms)
* @retval bool: true=触发了透传false=继续等待
*/
bool UART3_Protocol_CheckTimeout(uint32_t current_tick)
{
uart3_protocol_context_t *ctx = &g_uart3_proto_ctx;
if (ctx->state == STATE_SCAN && ctx->scan_length > 0) {
if (current_tick - ctx->scan_start_tick >= SCAN_TIMEOUT_MS) {
ctx->state = STATE_PASSTHROUGH;
DEBUG_LOG("SCAN timeout → PASSTHROUGH (%d ms)", SCAN_TIMEOUT_MS);
return true;
}
}
if (ctx->state == STATE_CMD_MODE) {
if (current_tick - ctx->last_byte_tick >= CMD_COMPLETE_TIMEOUT_MS) {
ctx->state = STATE_INIT;
ctx->scan_length = 0;
DEBUG_LOG("CMD_MODE timeout → INIT (%d ms)", CMD_COMPLETE_TIMEOUT_MS);
return true;
}
}
return false;
}
/**
* @brief 获取透传模式下的缓存数据
* @note 在触发透传后调用,获取待发送的透传数据
* @param buffer: 输出缓冲区指针
* @param length: 数据长度指针(输出)
* @retval bool: true=有数据false=无数据
*/
bool UART3_Protocol_GetPassthroughData(uint8_t *buffer, uint16_t *length)
{
uart3_protocol_context_t *ctx = &g_uart3_proto_ctx;
if (ctx->state == STATE_PASSTHROUGH && ctx->scan_length > 0) {
if (buffer != NULL && length != NULL) {
memcpy(buffer, ctx->scan_buffer, ctx->scan_length);
*length = ctx->scan_length;
}
ctx->scan_length = 0;
ctx->state = STATE_INIT;
DEBUG_LOG("GetPassthroughData: %d bytes, back to INIT", *length);
return true;
}
return false;
}
/**
* @brief 获取当前协议识别状态
* @note 用于调试和状态监控
* @param 无
* @retval uint8_t: 当前状态码
*/
uint8_t UART3_Protocol_GetState(void)
{
return (uint8_t)g_uart3_proto_ctx.state;
}
/**
* @brief 重置协议识别器状态
* @note 将状态机恢复到INIT状态
* @param 无
* @retval 无
*/
void UART3_Protocol_Reset(void)
{
uart3_protocol_context_t *ctx = &g_uart3_proto_ctx;
ctx->state = STATE_INIT;
ctx->scan_length = 0;
ctx->last_byte_tick = 0;
ctx->scan_start_tick = 0;
ctx->pending_cmd = false;
DEBUG_LOG("Protocol discriminator reset");
}