40 KiB
多通信接口统一指令处理系统开发计划
项目名称:E32-433TBH-SC 多接口统一指令处理扩展 版本:V1.0 制定日期:2026-03-27 适用范围:STM32F103嵌入式系统
1. 项目概述
1.1 背景与目标
现有系统状况:
- UART2作为调试专用接口,已实现ASCII指令(
$CMD,param*CS格式)解析 - 指令覆盖继电器控制(RL)、数字输入查询(DI)、回显测试(ECHO)
- 四路DI状态变化通过UART2自动上报(
$DI_EVENT格式) - RF433模块通过UART1通信,485模块预留给UART3
目标:在不破坏现有UART2调试接口功能的前提下,将UART1(RF433)和UART3(485)纳入统一指令处理体系,使这三个接口能够:
- 接收相同格式指令并执行相同的业务操作
- 向指令来源接口返回响应(而非统一从UART2返回)
- 所有收发数据在UART2上打印详细调试日志
1.2 范围与边界
| 纳入范围 | 排除范围 |
|---|---|
| UART1(RF433)、UART3(485)指令接收 | 现有RF433 TX/RX应用逻辑修改 |
| 多路响应路由机制 | 物理层驱动修改 |
| 全链路调试日志系统 | 协议层(protocol.h)重构 |
| 共享资源并发保护 |
1.3 预期交付物
| 交付物 | 说明 |
|---|---|
multi_uart_router.[c/h] |
多UART统一路由核心模块 |
cmd_router.[c/h] |
指令路由与响应分发模块 |
debug_log.[c/h] |
增强型调试日志系统 |
修改后的 cmd_parser.c |
支持多实例解析器(可选)或响应路由接口 |
修改后的 main.c |
中断回调和任务调度整合 |
| 测试用例与验证方案 |
2. 系统架构设计
2.1 当前架构分析
┌─────────────────────────────────────────────────────────────────────┐
│ main.c │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ UART2_Rx │ │ UART1_Rx │ │ UART3_Rx │ │
│ │ Interrupt │ │ (RF433) │ │ (RS485) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CmdParser (仅UART2专用) │ │
│ │ CmdParser_FeedByte() + Task() │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Relay_Ctrl │ │ IO_Monitor │ │ (其他) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ UART2_Print │ ◄── 所有响应固定从UART2输出 │
│ │ (Ring Buffer) │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
关键问题:
cmd_parser.c中send_response_ok()和send_response_err()硬编码使用UART2_Print_String()IO_Monitor中send_di_event()同样硬编码使用UART2_Print_String()- UART1和UART3的接收中断未接入指令解析体系
- 缺乏统一的响应路由机制
2.2 目标架构设计
2.2.1 整体数据流图
UART2_Print (调试日志)
▲
│ DEBUG_LOG()
│
┌─────────────────────────────────────┴─────────────────────────────────────┐
│ [UART2 - 调试专用通道] │
│ 调试命令输入 ──► Rx Interrupt ──► CmdParser ──► 执行 ──► 响应UART2 │
└───────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│ [UART1 - RF433无线模块] │
│ RF433数据 ──► Rx Interrupt ──► ┬── FeedByte() ──► 解析 ──► 执行 ──► 响应UART1
│ │ ▲ │
│ │ │ │
│ ┌──────────────────────────────┴────────────────────┴────────────────┐ │
│ │ Multi-UART Command Router (新增核心) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │UART1_Recv │ │UART2_Recv │ │UART3_Recv │ │ │
│ │ │RingBuffer │ │RingBuffer │ │RingBuffer │ │ │
│ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └───────────────┼───────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Unified Parser │ ◄── 共享解析状态机 │ │
│ │ │ (CmdParser) │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────┼──────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │Relay_Ctrl │ │IO_Monitor│ │ (Future) │ │ │
│ │ └─────┬─────┘ └─────┬─────┘ └───────────┘ │ │
│ │ │ │ │ │
│ │ └──────────────┼─────────────────────────────────────┐ │ │
│ │ │ Response Router │ │ │
│ │ ▼ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │ │
│ │ │ port_table[] = { │ │ │ │
│ │ │ {UART1, &huart1, "RF433"}, │ │ │ │
│ │ │ {UART2, &huart2, "DEBUG"}, // 保持原样 │ │ │ │
│ │ │ {UART3, &huart3, "RS485"} │ │ │ │
│ │ │ } │ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │ │
└────┴──────────────────────────────────────────────────────────────┴──────┘
┌───────────────────────────────────────────────────────────────────────────┐
│ [UART3 - RS485有线模块] │
│ 485数据 ──► Rx Interrupt ──► ──► FeedByte() ──► 解析 ──► 执行 ──► 响应UART3
└───────────────────────────────────────────────────────────────────────────┘
2.2.2 模块划分与职责
| 模块名 | 职责 | 文件位置 |
|---|---|---|
| Multi-UART Router | 三个UART接收通道管理、环形缓冲区、响应路由表 | multi_uart_router.[c/h](新增) |
| Cmd Router | 指令解析器封装、响应目标指定、响应构造与发送 | cmd_router.[c/h](新增) |
| Debug Log | 统一日志接口,支持来源标签、十六进制 dump、时间戳 | debug_log.[c/h](新增) |
| CmdParser | 状态机解析(修改:移除硬编码响应,改为回调方式) | 修改现有 |
| UART2_Print | 底层环形缓冲区发送(复用) | 现有 |
2.3 关键设计决策
2.3.1 指令格式一致性策略
决策:保持现有$CMD,param1,param2*CS格式不变,三个UART共用同一解析器。
理由:
- 现有
cmd_parser.c已实现完整的状态机解析,去抖、超时、校验和验证 - 复用解析器避免代码膨胀(STM32F103资源有限:64KB Flash,20KB RAM)
- 统一格式降低后续维护复杂度
2.3.2 响应路由机制
方案:引入端口上下文表(port_context_table)
typedef struct {
UART_HandleTypeDef *huart; // 指向具体UART句柄
const char *port_name; // 端口名称标签,用于日志
ring_buffer_t *rx_ring; // 接收环形缓冲区
uint8_t rx_tmp; // 单字节接收暂存
} uart_port_context_t;
路由原理:
- 指令解析完成后,业务函数返回响应数据和来源端口句柄
Response_Send()根据句柄查找对应UART发送- UART2保持原有直接调用方式,不经过路由层(调试专用)
2.3.3 日志记录策略
分层日志级别:
| 级别 | 宏定义 | 触发条件 | 示例输出 |
|---|---|---|---|
| ERROR | LOG_ERROR() |
校验失败、超时、参数错误 | [UART1] ERR: CS mismatch recv=0xA1 calc=0x3C |
| WARN | LOG_WARN() |
缓冲区边缘、异常状态 | [UART3] WARN: RX buffer 90% full |
| INFO | LOG_INFO() |
指令接收、响应发送 | [UART1] RX: $RL,1*2F → [UART1] TX: $OK,RL,1*00 |
| DEBUG | LOG_DEBUG() |
十六进制 dump、详细状态 | [UART1] DUMP: [A1 B2 C3 D4] |
日志格式模板:
[TIMESTAMP][PORT] <LEVEL>: <message>
示例:
[012345][UART1] INFO: CMD=RL P1=1 P2=空
[012346][UART1] INFO: Relay -> ON
[012347][UART1] TX: $OK,RL,1*00
3. 详细设计
3.1 多UART数据接收层
3.1.1 UART1/UART3中断+DMA/环形缓冲区设计
推荐方案:中断+环形缓冲区(对比DMA方案)
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 中断+环形缓冲区 | 实现简单,代码量小 | 中断频繁(约每字节一次) | 数据量小、指令简短 |
| DMA+环形缓冲区 | 中断少,CPU效率高 | 实现复杂,内存占用大 | 高速大数据流 |
选定:中断+环形缓冲区(现有UART2已验证可行)
环形缓冲区设计:
#define UART_RX_BUFFER_SIZE 128 // 每个端口接收缓冲区大小
typedef struct {
uint8_t buffer[UART_RX_BUFFER_SIZE];
volatile uint16_t head; // 写入位置(中断写入)
volatile uint16_t tail; // 读取位置(主循环读取)
volatile uint16_t count; // 有效数据计数
} uart_rx_ring_t;
关键约束:
head和tail使用volatile防止编译器优化- 缓冲区大小需为2的幂次,便于模运算优化(本项目128=2^7)
- 中断中只负责将数据压入缓冲区,禁止复杂逻辑
3.1.2 数据接收状态机
┌─────────────────────────────────────────┐
│ UART ISR (每字节) │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ 写入 rx_ring.buffer[head] │
│ head = (head + 1) % RX_BUFFER_SIZE │
│ count++ │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ count >= RX_BUFFER_SIZE ? │
│ overflow_count++ (丢弃) │
│ head 回绕覆盖旧数据 │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ 启动下次接收: HAL_UART_Receive_IT() │
└─────────────────────────────────────────┘
防溢出策略:
- 新数据覆盖最旧数据(Lossy Ring Buffer)
- 溢出时记录
overflow_count,供诊断使用
3.2 统一指令路由模块
3.2.1 模块接口设计(关键函数原型)
/**
* @brief 指令路由模块初始化
* @note 初始化所有UART端口的接收缓冲区和解析器
* @param 无
* @retval 无
*/
void CmdRouter_Init(void);
/**
* @brief 向指定端口的解析器喂入数据
* @note 由UART中断回调调用,线程安全
* @param port_id: 端口ID (PORT_UART1/PORT_UART2/PORT_UART3)
* @param byte: 接收到的字节
* @param current_tick: 系统时间戳
* @retval 无
*/
void CmdRouter_FeedByte(port_id_t port_id, uint8_t byte, uint32_t current_tick);
/**
* @brief 指令路由任务(主循环调用)
* @note 轮询所有端口的解析器状态,执行已就绪的指令
* @param 无
* @retval 无
*/
void CmdRouter_Task(void);
/**
* @brief 发送响应到指定端口
* @note 根据port_id查找对应UART句柄,发送响应数据
* @param port_id: 目标端口ID
* @param data: 响应数据缓冲区
* @param len: 数据长度
* @retval 无
*/
void CmdRouter_SendResponse(port_id_t port_id, const uint8_t *data, uint16_t len);
/**
* @brief 发送格式化响应
* @note 支持printf风格格式化,自动计算校验和
* @param port_id: 目标端口ID
* @param fmt: 格式化字符串
* @retval 无
*/
void CmdRouter_SendResponseFmt(port_id_t port_id, const char *fmt, ...);
3.2.2 指令包结构定义
/** 端口ID枚举 */
typedef enum {
PORT_UART1 = 0, /**< RF433模块 */
PORT_UART2 = 1, /**< 调试串口 */
PORT_UART3 = 2, /**< RS485模块 */
PORT_COUNT
} port_id_t;
/**
* @brief 带源端口信息的指令帧结构
* @note 解析完成后携带来源端口信息,用于响应路由
*/
typedef struct {
cmd_frame_t frame; /**< 原始指令帧数据 */
port_id_t source_port; /**< 指令来源端口 */
uint32_t recv_tick; /**< 接收完成时间戳 */
} routed_cmd_frame_t;
/**
* @brief 端口上下文结构
* @note 管理每个UART端口的接收缓冲区和解析状态
*/
typedef struct {
UART_HandleTypeDef *huart; /**< UART句柄指针 */
const char *name; /**< 端口名称(用于日志) */
uart_rx_ring_t rx_ring; /**< 接收环形缓冲区 */
parser_context_t parser; /**< 解析器上下文 */
uint8_t rx_tmp; /**< 中断接收暂存 */
uint32_t rx_count; /**< 累计接收字节数 */
uint32_t error_count; /**< 解析错误计数 */
} uart_port_context_t;
3.2.3 与现有CmdParser的集成方式
方案:扩展而非修改
- 新增
CmdRouter层:封装现有CmdParser,添加端口追踪能力 - 保持
CmdParser独立:不修改现有cmd_parser.c,通过回调钩子实现集成
集成接口设计:
/**
* @brief 注册指令处理回调
* @note 当指令解析完成时调用,传入来源端口信息
* @param callback: 回调函数指针
* @retval 无
*/
void CmdParser_RegisterCallback(void (*callback)(const routed_cmd_frame_t *frame));
替代方案(可选):
如果项目允许适度修改cmd_parser.c,推荐直接将send_response_ok/err()改为通过回调输出:
// cmd_parser.h 新增
typedef void (*response_callback_t)(port_id_t port_id, const char *response, uint16_t len);
void CmdParser_SetResponseCallback(response_callback_t callback);
3.3 多路响应处理模块
3.3.1 响应路由表设计
/**
* @brief 响应路由表
* @note 静态表,根据port_id索引查找对应UART句柄
*/
static UART_HandleTypeDef* const g_port_uart_map[PORT_COUNT] = {
[PORT_UART1] = &huart1, // RF433
[PORT_UART2] = &huart2, // DEBUG
[PORT_UART3] = &huart3, // RS485
};
/**
* @brief 端口名称表(用于日志输出)
*/
static const char* const g_port_name_map[PORT_COUNT] = {
[PORT_UART1] = "UART1",
[PORT_UART2] = "UART2",
[PORT_UART3] = "UART3",
};
3.3.2 响应发送队列管理
设计原则:
- 复用现有
UART2_Print的环形缓冲区机制 - 为UART1和UART3各创建一个发送环形缓冲区
- 发送流程:业务层构造响应 → 写入目标端口缓冲区 → UART Tx ISR驱动发送
/** 发送环形缓冲区(与uart2_print结构兼容) */
typedef struct {
uint8_t buffer[UART_TX_BUFFER_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
volatile uint16_t count;
volatile bool is_sending;
} uart_tx_ring_t;
/** 端口发送上下文 */
typedef struct {
uart_tx_ring_t tx_ring;
UART_HandleTypeDef *huart;
volatile uint16_t overflow_count;
} uart_tx_context_t;
关键API:
/**
* @brief 初始化指定端口的发送缓冲区
* @param port_id: 端口ID
* @retval 无
*/
void MultiUART_TxInit(port_id_t port_id);
/**
* @brief 发送数据到指定端口(非阻塞)
* @param port_id: 端口ID
* @param data: 数据缓冲区
* @param len: 数据长度
* @retval 无
*/
void MultiUART_Send(port_id_t port_id, const uint8_t *data, uint16_t len);
/**
* @brief 指定端口发送完成回调(供HAL_UART_TxCpltCallback调用)
* @param port_id: 端口ID
* @retval 无
*/
void MultiUART_TxCpltCallback(port_id_t port_id);
3.4 增强型调试日志系统
3.4.1 日志格式标准(具体示例)
基础格式:
[HHHHHH][PORT] LEVEL: message\r\n
│ │ │ │
│ │ │ └── 具体日志内容
│ │ └───────── 日志级别 (ERROR/WARN/INFO/DEBUG)
│ └─────────────── 来源端口 (UART1/UART2/UART3/ALL)
└─────────────────────── 系统运行时间(十六进制,毫秒)
示例输出:
[0001F4][UART1] INFO: RX len=12 "$RL,1*2F\r\n"
[0001F5][UART1] INFO: CMD=RL P1=1 valid=true
[0001F6][UART1] INFO: Relay -> ON
[0001F7][UART1] TX: "$OK,RL,1*00\r\n"
[0001F8][UART3] INFO: RX len=15 "$DI,2*5A\r\n"
[0001F9][UART3] INFO: CMD=DI P1=2 valid=true
[0001FA][UART3] INFO: DI2 = HIGH
[0001FB][UART3] TX: "$OK,DI,2,1*2B\r\n"
[00020A][UART1] ERROR: CS mismatch recv=0xA1 calc=0x3C
[00020B][UART1] TX: "$ERR,CS*XX\r\n"
十六进制Dump格式(DEBUG级别):
[0001FC][UART1] DEBUG: HEX dump (16 bytes)
[0001FC] 24 52 4C 2C 31 2A 32 46 0D 0A FF FF FF FF FF FF FF
$ R L , 1 * 2 F CR LF
3.4.2 日志分级与过滤机制
编译期分级(通过DEBUG_LOG_LEVEL宏):
#define DEBUG_LEVEL_NONE 0 // 全部禁用
#define DEBUG_LEVEL_ERROR 1 // 仅错误
#define DEBUG_LEVEL_WARN 2 // 错误+警告
#define DEBUG_LEVEL_INFO 3 // 错误+警告+信息
#define DEBUG_LEVEL_DEBUG 4 // 全部
#ifndef DEBUG_LOG_LEVEL
#define DEBUG_LOG_LEVEL DEBUG_LEVEL_INFO
#endif
// 日志宏定义
#if DEBUG_LOG_LEVEL >= DEBUG_LEVEL_ERROR
#define LOG_ERROR(fmt, ...) UART2_Print_Printf("[%06X][%s] ERROR: " fmt "\r\n", \
(unsigned int)HAL_GetTick(), port_name, ##__VA_ARGS__)
#else
#define LOG_ERROR(fmt, ...)
#endif
#if DEBUG_LOG_LEVEL >= DEBUG_LEVEL_WARN
#define LOG_WARN(fmt, ...) UART2_Print_Printf("[%06X][%s] WARN: " fmt "\r\n", \
(unsigned int)HAL_GetTick(), port_name, ##__VA_ARGS__)
#else
#define LOG_WARN(fmt, ...)
#endif
#if DEBUG_LOG_LEVEL >= DEBUG_LEVEL_INFO
#define LOG_INFO(fmt, ...) UART2_Print_Printf("[%06X][%s] INFO: " fmt "\r\n", \
(unsigned int)HAL_GetTick(), port_name, ##__VA_ARGS__)
#else
#define LOG_INFO(fmt, ...)
#endif
#if DEBUG_LOG_LEVEL >= DEBUG_LEVEL_DEBUG
#define LOG_DEBUG(fmt, ...) UART2_Print_Printf("[%06X][%s] DEBUG: " fmt "\r\n", \
(unsigned int)HAL_GetTick(), port_name, ##__VA_ARGS__)
#define LOG_HEXDUMP(data, len) Print_HexDump(port_name, data, len)
#else
#define LOG_DEBUG(fmt, ...)
#define LOG_HEXDUMP(data, len)
#endif
3.4.3 性能影响评估
| 指标 | 估算值 | 说明 |
|---|---|---|
| Flash占用增量 | +4KB ~ +6KB | 路由层+日志系统 |
| RAM占用增量 | +1KB ~ +2KB | 环形缓冲区(RX+TX各两个UART) |
| CPU开销 | <5% | 日志仅在指令收发时产生,中断驱动 |
| UART2带宽占用 | 峰值约30% | 假设每帧响应产生约50字节日志 |
优化措施:
- 使用
__attribute__((section(".flash.text")))将日志函数放入Flash - 环形缓冲区大小根据实际数据量调整,避免过大
- 日志输出异步化,不阻塞主流程
3.5 资源保护与并发控制
3.5.1 共享资源访问冲突解决方案
共享资源识别:
| 共享资源 | 访问方 | 潜在冲突 |
|---|---|---|
Relay_SetState() |
多指令入口 | 同时控制继电器 |
IO_Monitor_GetState() |
多指令入口 | 读操作,可并发 |
IO_Monitor_GetAllStates() |
多指令入口 | 读操作,可并发 |
| 环形缓冲区(TX/RX) | 中断 + 主循环 | 需要临界区保护 |
解决方案:优先级继承 + 临界区
/**
* @brief 继电器控制(线程安全版本)
* @note 内部使用临界区保护,防止并发访问
* @param port_id: 来源端口(用于日志)
* @param state: 目标状态
* @retval 无
*/
void Relay_SetState_Safe(port_id_t port_id, bool state)
{
uint32_t irq_flags;
__disable_irq(); // 进入临界区
irq_flags = __get_PRIMASK(); // 保存中断状态
// 继电器操作
Relay_SetState(state);
// 日志输出(允许中断嵌套,日志本身有保护)
LOG_INFO("Relay -> %s (from %s)", state ? "ON" : "OFF",
g_port_name_map[port_id]);
__set_PRIMASK(irq_flags); // 恢复中断状态
__enable_irq(); // 退出临界区
}
中断优先级配置建议:
// 在SystemClock_Config()之后调用
void Configure_UART_Priorities(void)
{
// STM32F103中断优先级(数字越小优先级越高)
// 建议配置:
// - UART1 (RF433): 优先级=5,子优先级=0(较高优先级)
// - UART2 (DEBUG): 优先级=6,子优先级=0(中优先级)
// - UART3 (RS485): 优先级=5,子优先级=1(与UART1同级别)
// - Systick: 优先级=15(最低,保证系统心跳)
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
HAL_NVIC_SetPriority(USART2_IRQn, 6, 0);
HAL_NVIC_SetPriority(USART3_IRQn, 5, 1);
}
4. 实施路线图
阶段1:架构验证与框架搭建(第1-2周)
目标:验证环形缓冲区机制和路由架构的可行性
| 任务 | 交付物 | 验收标准 |
|---|---|---|
1.1 创建multi_uart_router.c/h基础框架 |
源码文件 | 编译通过,无语法错误 |
| 1.2 实现UART1/UART3接收环形缓冲区 | 单元测试 | 收发数据一致,无丢失 |
| 1.3 实现端口上下文表和初始化函数 | 函数实现 | 三个端口均可正确初始化 |
1.4 实现CmdRouter_FeedByte()接口 |
函数实现 | 字节可正确路由到对应解析器 |
关键里程碑代码片段:
// multi_uart_router.h
#ifndef __MULTI_UART_ROUTER_H
#define __MULTI_UART_ROUTER_H
#include "usart.h"
#include "cmd_parser.h"
typedef enum {
PORT_UART1 = 0,
PORT_UART2 = 1,
PORT_UART3 = 2,
PORT_COUNT
} port_id_t;
void MultiUART_Init(void);
void MultiUART_FeedByte(port_id_t port_id, uint8_t byte, uint32_t tick);
void MultiUART_Task(void);
void MultiUART_Send(port_id_t port_id, const uint8_t *data, uint16_t len);
void MultiUART_SendString(port_id_t port_id, const char *str);
#endif
阶段2:UART1/RF433接口实现(第3-4周)
目标:UART1能够接收指令并返回响应
| 任务 | 交付物 | 验收标准 |
|---|---|---|
2.1 修改main.c中UART1中断回调 |
修改后main.c | 中断正确调用MultiUART_FeedByte() |
| 2.2 实现UART1 TX环形缓冲区发送 | MultiUART_Send() |
响应正确发送到UART1 |
| 2.3 实现响应路由功能 | 路由表 | UART1指令响应返回UART1 |
| 2.4 全链路日志打印 | 日志输出 | UART2显示完整收发流程 |
关键修改:
// main.c 修改 - UART1中断回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
MultiUART_FeedByte(PORT_UART1, rf433_uart_rx_tmp, HAL_GetTick());
HAL_UART_Receive_IT(&huart1, &rf433_uart_rx_tmp, 1);
}
// ... 其他端口保持原样 ...
}
阶段3:UART3/485接口实现(第5-6周)
目标:UART3能够接收指令并返回响应
| 任务 | 交付物 | 验收标准 |
|---|---|---|
| 3.1 实现UART3接收中断处理 | 中断处理 | 指令正确喂入解析器 |
| 3.2 实现UART3 TX发送功能 | 发送函数 | 响应正确发送到UART3 |
| 3.3 RS485方向控制(如果需要) | 方向控制逻辑 | 半双工切换正确 |
| 3.4 多端口并发测试 | 测试报告 | 两端口同时收发正常 |
阶段4:集成测试与优化(第7-8周)
目标:系统稳定性和性能优化
| 任务 | 交付物 | 验收标准 |
|---|---|---|
| 4.1 多接口并发指令测试 | 测试报告 | 无竞争条件、无数据丢失 |
| 4.2 大数据量压力测试 | 压力测试报告 | 1000帧连续收发无错误 |
| 4.3 异常情况处理测试 | 异常测试报告 | 校验失败、超时等处理正确 |
| 4.4 性能优化与内存调整 | 优化报告 | RAM<70%,CPU<80% |
阶段5:文档完善与交付(第9周)
| 任务 | 交付物 |
|---|---|
| 5.1 API接口文档 | cmd_router_api.md |
| 5.2 修改说明文档 | migration_guide.md |
| 5.3 测试验证报告 | validation_report.md |
| 5.4 最终代码归档 | 完整源码包 |
5. 测试验证计划
5.1 单元测试策略
测试框架:使用unity或自定义最小测试框架(嵌入式友好)
| 模块 | 测试用例 | 验证点 |
|---|---|---|
| 环形缓冲区 | test_ring_push_pop() |
数据先进先出,无损坏 |
| 环形缓冲区 | test_ring_overflow() |
溢出时旧数据被覆盖 |
| 环形缓冲区 | test_ring_concurrent() |
中断+主循环并发安全 |
| 路由表 | test_port_lookup() |
端口ID正确映射到UART句柄 |
| 响应构造 | test_response_format() |
校验和计算正确 |
5.2 集成测试用例
5.2.1 多接口并发指令测试
测试场景:同时从UART1和UART3发送继电器控制指令
测试步骤:
- UART1发送:
$RL,1*2F(期望Relay开启) - UART3发送:
$RL,0*2E(期望Relay关闭) - 间隔50ms交替发送10轮
验收标准:
- 每次操作后继电器状态与最后一条指令一致
- 两个端口各自收到正确的响应帧
- UART2日志完整记录所有收发过程
5.2.2 大数据量压力测试
测试场景:连续快速发送1000帧指令
测试步骤:
- UART1以10ms间隔连续发送DI查询指令
- 记录丢包率、错误率
- 监测RAM占用峰值
验收标准:
- 丢包率 < 0.1%
- 无内存溢出
- 平均响应时间 < 20ms
5.2.3 异常情况处理测试
| 测试场景 | 输入 | 期望行为 |
|---|---|---|
| 校验和错误 | $RL,1*A1(实际应为2F) |
返回$ERR,CS*XX,日志记录 |
| 未知命令 | $ABC*00 |
返回$ERR,CMD*XX |
| 参数越界 | $DI,5*XX(通道1-4) |
返回$ERR,PARAM*XX |
| 帧超时 | 发送$RL后等待2秒 |
解析器重置,日志输出timeout |
| 端口同时发送相同指令 | UART1和UART3同时发$ECHO |
各自收到独立响应 |
5.3 验收标准
| 指标 | 目标值 | 测量方法 |
|---|---|---|
| 指令处理成功率 | ≥99.5% | 1000帧测试统计 |
| 响应延迟 | ≤50ms(P99) | 示波器或时间戳统计 |
| Flash占用增量 | ≤8KB | 编译后.map文件分析 |
| RAM占用增量 | ≤2KB | 运行时内存分析 |
| CPU空闲率 | ≥70% | Systick空闲计数 |
6. 风险评估与应对
6.1 技术风险
| 风险 | 概率 | 影响 | 应对措施 |
|---|---|---|---|
| 数据丢失:高频率指令导致缓冲区溢出 | 中 | 高 | 1. 动态调整缓冲区大小2. 实现流量控制(XOFF/XON)3. 丢帧时日志记录 |
| 响应延迟:复杂指令阻塞发送 | 中 | 中 | 1. 指令处理异步化2. 预计算校验和3. DMA加速发送 |
| 竞争条件:多端口同时访问继电器 | 低 | 高 | 1. 临界区保护2. 命令队列化3. 最小切换间隔保护(已有) |
| 内存碎片:频繁分配/释放 | 低 | 中 | 1. 全部使用静态缓冲区2. 避免动态malloc |
6.2 资源风险
| 风险 | 概率 | 影响 | 应对措施 |
|---|---|---|---|
| Flash不足:代码量超过64KB | 低 | 高 | 1. 启用-O2优化2. 精简日志字符串3. 评估裁剪非必要功能 |
| RAM不足:缓冲区+解析器上下文超支 | 低 | 高 | 1. 缓冲区实际需求计算: RX: 3×128=384B TX: 2×256=512B 解析器: 3×128B=384B 总计约1.3KB(余量充足) |
| 中断风暴:高频率字节接收导致系统假死 | 低 | 高 | 1. 配置合适的中断优先级2. 使用DMA分担CPU负载 |
6.3 应对措施优先级
P0(立即处理):
├─ 临界区保护实现(防止竞争条件)
└─ 环形缓冲区溢出处理(日志+计数)
P1(本阶段完成):
├─ 中断优先级配置
└─ 内存使用量测量验证
P2(集成测试阶段):
├─ 压力测试暴露潜在问题
└─ 性能优化调参
7. 附录
附录A:关键数据结构定义
/**
* @brief 接收环形缓冲区
*/
typedef struct {
uint8_t buffer[UART_RX_BUFFER_SIZE];
volatile uint16_t head; /**< 写入索引 */
volatile uint16_t tail; /**< 读取索引 */
volatile uint16_t count; /**< 有效数据计数 */
} uart_rx_ring_t;
/**
* @brief 发送环形缓冲区
*/
typedef struct {
uint8_t buffer[UART_TX_BUFFER_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
volatile uint16_t count;
volatile bool is_sending;
volatile uint16_t overflow_count;
} uart_tx_ring_t;
/**
* @brief 端口上下文
*/
typedef struct {
UART_HandleTypeDef *huart;
const char *name;
uart_rx_ring_t rx_ring;
uart_tx_ring_t tx_ring;
parser_context_t parser;
uint8_t rx_tmp;
uint32_t rx_count;
uint32_t error_count;
} uart_port_context_t;
/**
* @brief 带路由信息的指令帧
*/
typedef struct {
cmd_frame_t frame;
port_id_t source_port;
uint32_t recv_tick;
} routed_cmd_frame_t;
附录B:接口API文档
B.1 CmdRouter模块
| 函数 | 原型 | 说明 |
|---|---|---|
CmdRouter_Init |
void CmdRouter_Init(void) |
初始化所有端口上下文 |
CmdRouter_FeedByte |
void CmdRouter_FeedByte(port_id_t port, uint8_t byte, uint32_t tick) |
向指定端口喂入数据 |
CmdRouter_Task |
void CmdRouter_Task(void) |
主循环调用,处理就绪指令 |
CmdRouter_SendResponse |
void CmdRouter_SendResponse(port_id_t port, const uint8_t *data, uint16_t len) |
发送原始响应 |
CmdRouter_SendResponseFmt |
void CmdRouter_SendResponseFmt(port_id_t port, const char *fmt, ...) |
发送格式化响应 |
B.2 DebugLog模块
| 宏 | 说明 |
|---|---|
LOG_ERROR(fmt, ...) |
错误级别日志 |
LOG_WARN(fmt, ...) |
警告级别日志 |
LOG_INFO(fmt, ...) |
信息级别日志 |
LOG_DEBUG(fmt, ...) |
调试级别日志 |
LOG_HEXDUMP(data, len) |
十六进制内存 dump |
B.3 回调接口
/**
* @brief 指令执行完成回调(由CmdRouter内部调用)
* @param frame: 带路由信息的指令帧
* @retval 无
*/
void App_OnCommandReceived(const routed_cmd_frame_t *frame);
附录C:响应格式规范
成功响应:$OK[,content]*CS\r\n
- 示例:
$OK,RL,1*00\r\n(继电器控制成功) - 示例:
$OK,DI,1101*2B\r\n(四路DI状态)
错误响应:$ERR,err_code*CS\r\n
- err_code: PARAM(参数错误)、CS(校验和错误)、CMD(未知命令)、TIMEOUT(超时)
- 示例:
$ERR,CS*XX\r\n
事件上报:$DI_EVENT,channel,state*CS\r\n
- 示例:
$DI_EVENT,1,1*A5\r\n(通道1变为高电平)
文档版本历史
| 版本 | 日期 | 作者 | 变更说明 |
|---|---|---|---|
| 1.0 | 2026-03-27 | 架构师 | 初始版本创建 |
文档结束