944 lines
40 KiB
Markdown
944 lines
40 KiB
Markdown
# 多通信接口统一指令处理系统开发计划
|
||
|
||
**项目名称**: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)纳入统一指令处理体系,使这三个接口能够:
|
||
|
||
1. **接收相同格式指令**并执行相同的业务操作
|
||
2. **向指令来源接口返回响应**(而非统一从UART2返回)
|
||
3. **所有收发数据在UART2上打印详细调试日志**
|
||
|
||
### 1.2 范围与边界
|
||
|
||
| 纳入范围 | 排除范围 |
|
||
| --------------------------- | ------------------- |
|
||
| UART1(RF433)、UART3(485)指令接收 | 现有RF433 TX/RX应用逻辑修改 |
|
||
| 多路响应路由机制 | 物理层驱动修改 |
|
||
| 全链路调试日志系统 | 协议层(protocol.h)重构 |
|
||
| 共享资源并发保护 | <br /> |
|
||
|
||
### 1.3 预期交付物
|
||
|
||
| 交付物 | 说明 |
|
||
| ------------------------- | ------------------- |
|
||
| `multi_uart_router.[c/h]` | 多UART统一路由核心模块 |
|
||
| `cmd_router.[c/h]` | 指令路由与响应分发模块 |
|
||
| `debug_log.[c/h]` | 增强型调试日志系统 |
|
||
| 修改后的 `cmd_parser.c` | 支持多实例解析器(可选)或响应路由接口 |
|
||
| 修改后的 `main.c` | 中断回调和任务调度整合 |
|
||
| 测试用例与验证方案 | <br /> |
|
||
|
||
***
|
||
|
||
## 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) │ │
|
||
│ └─────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**关键问题**:
|
||
|
||
1. `cmd_parser.c`中`send_response_ok()`和`send_response_err()`**硬编码**使用`UART2_Print_String()`
|
||
2. `IO_Monitor`中`send_di_event()`同样**硬编码**使用`UART2_Print_String()`
|
||
3. UART1和UART3的接收中断**未接入指令解析体系**
|
||
4. 缺乏统一的**响应路由**机制
|
||
|
||
### 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)**
|
||
|
||
```c
|
||
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已验证可行)
|
||
|
||
**环形缓冲区设计**:
|
||
|
||
```c
|
||
#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 模块接口设计(关键函数原型)
|
||
|
||
```c
|
||
/**
|
||
* @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 指令包结构定义
|
||
|
||
```c
|
||
/** 端口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`的集成方式
|
||
|
||
**方案:扩展而非修改**
|
||
|
||
1. **新增`CmdRouter`层**:封装现有`CmdParser`,添加端口追踪能力
|
||
2. **保持`CmdParser`独立**:不修改现有`cmd_parser.c`,通过回调钩子实现集成
|
||
|
||
**集成接口设计**:
|
||
|
||
```c
|
||
/**
|
||
* @brief 注册指令处理回调
|
||
* @note 当指令解析完成时调用,传入来源端口信息
|
||
* @param callback: 回调函数指针
|
||
* @retval 无
|
||
*/
|
||
void CmdParser_RegisterCallback(void (*callback)(const routed_cmd_frame_t *frame));
|
||
```
|
||
|
||
**替代方案(可选)**:
|
||
|
||
如果项目允许适度修改`cmd_parser.c`,推荐直接将`send_response_ok/err()`改为通过回调输出:
|
||
|
||
```c
|
||
// 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 响应路由表设计
|
||
|
||
```c
|
||
/**
|
||
* @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驱动发送
|
||
|
||
```c
|
||
/** 发送环形缓冲区(与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**:
|
||
|
||
```c
|
||
/**
|
||
* @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`宏):
|
||
|
||
```c
|
||
#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) | 中断 + 主循环 | 需要临界区保护 |
|
||
|
||
**解决方案:优先级继承 + 临界区**
|
||
|
||
```c
|
||
/**
|
||
* @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(); // 退出临界区
|
||
}
|
||
```
|
||
|
||
**中断优先级配置建议**:
|
||
|
||
```c
|
||
// 在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()`接口 | 函数实现 | 字节可正确路由到对应解析器 |
|
||
|
||
**关键里程碑代码片段**:
|
||
|
||
```c
|
||
// 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显示完整收发流程 |
|
||
|
||
**关键修改**:
|
||
|
||
```c
|
||
// 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发送继电器控制指令
|
||
|
||
**测试步骤**:
|
||
|
||
1. UART1发送:`$RL,1*2F`(期望Relay开启)
|
||
2. UART3发送:`$RL,0*2E`(期望Relay关闭)
|
||
3. 间隔50ms交替发送10轮
|
||
|
||
**验收标准**:
|
||
|
||
- 每次操作后继电器状态与最后一条指令一致
|
||
- 两个端口各自收到正确的响应帧
|
||
- UART2日志完整记录所有收发过程
|
||
|
||
#### 5.2.2 大数据量压力测试
|
||
|
||
**测试场景**:连续快速发送1000帧指令
|
||
|
||
**测试步骤**:
|
||
|
||
1. UART1以10ms间隔连续发送DI查询指令
|
||
2. 记录丢包率、错误率
|
||
3. 监测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:关键数据结构定义
|
||
|
||
```c
|
||
/**
|
||
* @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 回调接口
|
||
|
||
```c
|
||
/**
|
||
* @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 | 架构师 | 初始版本创建 |
|
||
|
||
***
|
||
|
||
**文档结束**
|