Files
433_STM32/docs/application_development_plan.md

995 lines
30 KiB
Markdown
Raw Normal View History

# E32-433TBH-SC 应用层软件开发计划
## 项目概述
| 项目属性 | 描述 |
| --------- | -------------------------------- |
| **目标MCU** | STM32F103C8T6 (Cortex-M3, 72MHz) |
| **开发环境** | Keil MDK-ARM V5.32 |
| **架构模式** | 裸机轮询 + 中断驱动 |
| **当前状态** | HAL层就绪基础通信验证通过 |
***
## 一、整体架构设计
### 1.1 硬件资源映射
```
┌─────────────────────────────────────────────────────────────────┐
│ 硬件连接关系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ UART1 (PA9/PA10) ◄────► RF433无线模块 (9600bps) │
│ │
│ UART2 (PA2/PA3) ◄────► 调试串口/上位机 (115200bps) │
│ │
│ UART3 (PB10/PB11) ◄────► 预留串口 (9600bps) │
│ │
│ MCU_DI1 (PB4) ◄────► 数字输入1 │
│ MCU_DI2 (PB5) ◄────► 数字输入2 │
│ MCU_DI3 (PB6) ◄────► 数字输入3 │
│ MCU_DI4 (PB7) ◄────► 数字输入4 │
│ │
│ RL_Control (PA15) ◄────► 继电器控制输出 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 软件分层架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ UART2_Print │ │ 指令解析器 │ │ IO状态监控 │ │ 继电器控制 │ │
│ │ (调试输出) │ │ (ASCII指令) │ │ (DI1-4) │ │ (RL_Control)│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ 服务层 (Service) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ RingBuffer │ │ CmdParser │ │ Debounce │ │
│ │ (环形缓冲区) │ │ (指令解析) │ │ (软件去抖) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ 驱动层 (Driver) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ UART Driver │ │ GPIO Driver │ │ RF433 Driver │ │
│ │ (usart.c) │ │ (gpio.c) │ │ (rf433.c) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ 硬件抽象层 (HAL - STM32) │
│ STM32F1xx_HAL_Driver / CMSIS │
└─────────────────────────────────────────────────────────────────────┘
```
### 1.3 核心模块交互关系
```
┌─────────────────┐
│ UART2_Print │
│ (调试输出) │
└────────▲────────┘
│ 日志/响应输出
┌──────────────┐ ┌────────┴────────┐ ┌──────────────┐
│ UART1 │◄──►│ 指令解析器 │◄──►│ UART2 │
│ (RF433) │ │ (CmdParser) │ │ (调试/上位机) │
└──────────────┘ └────────┬────────┘ └──────────────┘
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ IO状态监控 │ │ 继电器控制 │ │ RF433通信 │
│ (DI1-4) │ │ (RL_Control)│ │ (透传模式) │
└─────────────┘ └─────────────┘ └─────────────┘
```
### 1.4 任务调度模型
基于现有裸机架构,采用**时间片轮询**模型:
```c
void main(void)
{
system_init();
while(1)
{
UART2_Print_Task(); // 发送缓冲区处理
IO_Monitor_Task(); // IO扫描与去抖
CmdParser_Task(); // 指令解析处理
__WFI(); // 等待中断,降低功耗
}
}
```
**调度周期设计**
| 任务 | 执行周期 | 说明 |
| ----------------- | ---- | ----------------- |
| `UART2_Print_Task` | 1ms | 发送缓冲区数据处理 |
| `IO_Monitor_Task` | 10ms | IO扫描与去抖足够采样间隔 |
| `CmdParser_Task` | 每轮 | 指令解析,响应无需微秒级 |
***
## 二、模块详细设计
### 2.1 UART2_Print 模块
#### 2.1.1 模块职责
- 提供调试信息输出接口
- 支持 `printf` 风格的格式化输出
- 支持中断安全调用(通过环形缓冲区)
#### 2.1.2 头文件设计 (`uart2_print.h`)
```c
#ifndef __UART2_PRINT_H
#define __UART2_PRINT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdarg.h>
#include <stdbool.h>
#define UART2_TX_BUFFER_SIZE 256
void UART2_Print_Init(void);
void UART2_Print_Send(const uint8_t *data, uint16_t len);
void UART2_Print_String(const char *str);
void UART2_Print_Printf(const char *fmt, ...);
void UART2_Print_Task(void);
bool UART2_Print_IsBusy(void);
#ifdef __cplusplus
}
#endif
#endif
```
#### 2.1.3 环形缓冲区实现
```c
#include "uart2_print.h"
#include "usart.h"
typedef struct {
uint8_t buffer[UART2_TX_BUFFER_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
volatile bool is_sending;
} ring_buffer_t;
static ring_buffer_t tx_ring = {0};
void UART2_Print_Init(void)
{
tx_ring.head = 0;
tx_ring.tail = 0;
tx_ring.is_sending = false;
}
void UART2_Print_Send(const uint8_t *data, uint16_t len)
{
if (len == 0) return;
__disable_irq();
for (uint16_t i = 0; i < len; i++) {
uint16_t next = (tx_ring.head + 1) % UART2_TX_BUFFER_SIZE;
if (next == tx_ring.tail) {
break;
}
tx_ring.buffer[tx_ring.head] = data[i];
tx_ring.head = next;
}
__enable_irq();
if (!tx_ring.is_sending) {
UART2_Print_Task();
}
}
void UART2_Print_String(const char *str)
{
UART2_Print_Send((const uint8_t *)str, strlen(str));
}
void UART2_Print_Task(void)
{
if (tx_ring.is_sending || tx_ring.tail == tx_ring.head) {
return;
}
uint8_t byte = tx_ring.buffer[tx_ring.tail];
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE;
tx_ring.is_sending = true;
HAL_UART_Transmit_IT(&huart2, &byte, 1);
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
tx_ring.is_sending = false;
if (tx_ring.tail != tx_ring.head) {
UART2_Print_Task();
}
}
}
```
#### 2.1.4 printf 重定向
```c
#include <stdio.h>
#if defined(__CC_ARM) || defined(__ARMCC_VERSION)
int fputc(int ch, FILE *f)
{
UART2_Print_Send((uint8_t *)&ch, 1);
return ch;
}
#endif
```
***
### 2.2 ASCII文本指令协议
#### 2.2.1 指令格式设计
**设计原则**
- 人类可读,方便调试
- 格式简单,易于解析
- 兼容常见串口调试工具
- **调试友好**校验和FF为特权后门跳过校验
**指令格式**
```
$CMD,param1,param2*CS\r\n
$ - 帧起始符
CMD - 指令名称(2-4字符)
, - 参数分隔符
param - 参数(可选)
* - 校验和起始符
CS - 校验和(2位十六进制从$到*之间所有字符的异或值)
特权后门: CS=FF 时强制跳过校验,用于人工调试
\r\n - 帧结束符
```
#### 2.2.2 指令定义
| 指令 | 参数1 | 参数2 | 功能说明 | 示例 |
| ----- | ------------ | --------- | -------------- | ----------------------- |
| `RL` | 继电器号(1) | 0/1 | 继电器控制 | `$RL,1,1*FF\r\n` |
| `DI` | 通道号(0-4) | (无) | 查询IO状态 | `$DI,1*FF\r\n` |
| `FWD` | 目标端口(1) | 数据 | 数据转发 | `$FWD,1,Hello*FF\r\n` |
| `ECHO`| (无) | (无) | 心跳测试 | `$ECHO*FF\r\n` |
**参数说明**
- `RL` 指令参数2为 `0`=关闭,`1`=打开
- `DI` 指令参数1为 `0`=查询所有,`1-4`=查询指定通道
#### 2.2.3 完整指令示例
**示例1控制继电器1打开**
```
指令: $RL,1,1*FF\r\n
解析:
- RL: 继电器控制指令
- 参数1=1: 继电器编号1
- 参数2=1: 打开
- CS=FF: 跳过校验(调试模式)
```
**示例2控制继电器1关闭**
```
指令: $RL,1,0*FF\r\n
解析:
- 参数2=0: 关闭
```
**示例3查询DI1状态**
```
指令: $DI,1*FF\r\n
响应: $OK,DI,1,1*XX\r\n (DI1=HIGH)
或: $OK,DI,1,0*XX\r\n (DI1=LOW)
```
**示例4查询所有DI状态**
```
指令: $DI,0*FF\r\n
响应: $OK,DI,1010*XX\r\n (DI1=1, DI2=0, DI3=1, DI4=0)
```
**示例5转发数据到RF433**
```
指令: $FWD,1,Hello RF433*FF\r\n
说明: 将"Hello RF433"转发到UART1(RF433模块)
```
**示例6带校验和的正式指令**
```
指令: $RL,1,1*4C\r\n
校验和计算: '$' ^ 'R' ^ 'L' ^ ',' ^ '1' ^ ',' ^ '1'
= 0x24 ^ 0x52 ^ 0x4C ^ 0x2C ^ 0x31 ^ 0x2C ^ 0x31
= 0x4C
```
#### 2.2.4 响应格式
**成功响应**
```
$OK,RL,1,1*XX\r\n (继电器1已打开)
$OK,DI,1,1*XX\r\n (DI1当前为HIGH)
$OK,DI,1010*XX\r\n (所有DI状态)
```
**失败响应**
```
$ERR,CMD*XX\r\n (未知指令)
$ERR,CS*XX\r\n (校验和错误)
$ERR,PARAM*XX\r\n (参数错误)
```
**IO状态上报主动上报**
```
$DI_EVENT,1,1*XX\r\n (DI1变为HIGH)
$DI_EVENT,3,0*XX\r\n (DI3变为LOW)
```
#### 2.2.5 安全防护设计
**防护目标**防止畸形数据导致单片机死机HardFault
**防护措施**
| 风险场景 | 防护措施 | 实现方式 |
| -------- | -------- | -------- |
| 缓冲区溢出 | 边界检查 | 写入前检查索引是否越界 |
| 超长数据帧 | 超时重置 | 长时间未收到完整帧则重置状态机 |
| 非法字符 | 字符过滤 | 只接受合法ASCII字符 |
| 内存越界 | 固定长度 | 使用固定大小缓冲区,拒绝超长数据 |
#### 2.2.6 指令解析器实现(带安全防护)
```c
#include <string.h>
#include <ctype.h>
#define CMD_BUFFER_SIZE 64
#define CMD_MAX_LEN 8
#define PARAM_MAX_LEN 32
#define PARSE_TIMEOUT_MS 1000
typedef enum {
PARSE_IDLE,
PARSE_CMD,
PARSE_PARAM1,
PARSE_PARAM2,
PARSE_CHECKSUM,
PARSE_COMPLETE
} parse_state_t;
typedef struct {
char cmd[CMD_MAX_LEN];
char param1[PARAM_MAX_LEN];
char param2[PARAM_MAX_LEN];
uint8_t received_cs;
uint8_t calculated_cs;
bool valid;
bool skip_checksum;
} cmd_frame_t;
static parse_state_t parse_state = PARSE_IDLE;
static cmd_frame_t current_frame;
static uint8_t field_index = 0;
static uint8_t checksum_acc = 0;
static uint32_t last_rx_tick = 0;
static void reset_parser(void)
{
parse_state = PARSE_IDLE;
field_index = 0;
checksum_acc = 0;
memset(&current_frame, 0, sizeof(current_frame));
}
static bool is_valid_cmd_char(char c)
{
return isupper(c) || isdigit(c);
}
static bool is_valid_param_char(char c)
{
return isprint(c) && c != '*' && c != '\r' && c != '\n';
}
static uint8_t hex_to_byte(char high, char low)
{
uint8_t val = 0;
if (high >= '0' && high <= '9') val = (high - '0') << 4;
else if (high >= 'A' && high <= 'F') val = (high - 'A' + 10) << 4;
else if (high >= 'a' && high <= 'f') val = (high - 'a' + 10) << 4;
if (low >= '0' && low <= '9') val |= (low - '0');
else if (low >= 'A' && low <= 'F') val |= (low - 'A' + 10);
else if (low >= 'a' && low <= 'f') val |= (low - 'a' + 10);
return val;
}
void CmdParser_FeedByte(uint8_t byte, uint32_t current_tick)
{
if (parse_state != PARSE_IDLE) {
if (current_tick - last_rx_tick > PARSE_TIMEOUT_MS) {
reset_parser();
return;
}
}
last_rx_tick = current_tick;
switch (parse_state) {
case PARSE_IDLE:
if (byte == '$') {
reset_parser();
parse_state = PARSE_CMD;
}
break;
case PARSE_CMD:
if (byte == ',') {
current_frame.cmd[field_index] = '\0';
parse_state = PARSE_PARAM1;
field_index = 0;
} else if (byte == '*') {
current_frame.cmd[field_index] = '\0';
parse_state = PARSE_CHECKSUM;
field_index = 0;
} else if (is_valid_cmd_char(byte)) {
if (field_index < CMD_MAX_LEN - 1) {
current_frame.cmd[field_index++] = byte;
checksum_acc ^= byte;
} else {
reset_parser();
}
} else {
reset_parser();
}
break;
case PARSE_PARAM1:
if (byte == ',') {
current_frame.param1[field_index] = '\0';
parse_state = PARSE_PARAM2;
field_index = 0;
} else if (byte == '*') {
current_frame.param1[field_index] = '\0';
parse_state = PARSE_CHECKSUM;
field_index = 0;
} else if (is_valid_param_char(byte)) {
if (field_index < PARAM_MAX_LEN - 1) {
current_frame.param1[field_index++] = byte;
checksum_acc ^= byte;
} else {
reset_parser();
}
} else {
reset_parser();
}
break;
case PARSE_PARAM2:
if (byte == '*') {
current_frame.param2[field_index] = '\0';
parse_state = PARSE_CHECKSUM;
field_index = 0;
} else if (is_valid_param_char(byte)) {
if (field_index < PARAM_MAX_LEN - 1) {
current_frame.param2[field_index++] = byte;
checksum_acc ^= byte;
} else {
reset_parser();
}
} else {
reset_parser();
}
break;
case PARSE_CHECKSUM:
if (byte == '\n') {
current_frame.received_cs = hex_to_byte(
current_frame.cmd[0],
current_frame.cmd[1]
);
current_frame.calculated_cs = checksum_acc;
current_frame.skip_checksum = (current_frame.received_cs == 0xFF);
if (current_frame.skip_checksum ||
current_frame.received_cs == current_frame.calculated_cs) {
current_frame.valid = true;
parse_state = PARSE_COMPLETE;
} else {
reset_parser();
}
} else if (byte != '\r') {
if (field_index < 2) {
current_frame.cmd[field_index++] = byte;
}
}
break;
case PARSE_COMPLETE:
reset_parser();
if (byte == '$') {
parse_state = PARSE_CMD;
}
break;
}
}
bool CmdParser_HasCompleteFrame(cmd_frame_t *frame)
{
if (parse_state == PARSE_COMPLETE && current_frame.valid) {
if (frame) {
memcpy(frame, &current_frame, sizeof(cmd_frame_t));
}
return true;
}
return false;
}
void CmdParser_Acknowledge(void)
{
reset_parser();
}
```
**安全防护要点说明**
1. **缓冲区边界检查**:每次写入前检查 `field_index < MAX_LEN`
2. **超时重置**超过1秒未收到完整帧自动重置状态机
3. **字符过滤**:只接受合法字符,非法字符触发重置
4. **固定长度缓冲区**:不使用动态内存分配,避免内存碎片
5. **校验和特权**`CS=FF` 时跳过校验,方便人工调试
***
### 2.3 IO状态监控模块
#### 2.3.1 检测方式
**推荐方案:定时扫描 + 软件去抖**
| 方案 | 优点 | 缺点 | 适用场景 |
| ---- | --------- | --------------- | ------ |
| 外部中断 | 响应快 | 需要额外中断资源,硬件去抖复杂 | 低频事件 |
| 定时扫描 | 简单可靠,统一管理 | 有扫描延迟 | 多路IO监控 |
#### 2.3.2 软件去抖实现
```c
#define DEBOUNCE_COUNT 3
#define IO_CHANNEL_COUNT 4
typedef struct {
GPIO_TypeDef *port;
uint16_t pin;
uint8_t current_state;
uint8_t debounce_counter;
uint8_t last_raw_state;
} io_channel_t;
static io_channel_t di_channels[IO_CHANNEL_COUNT] = {
{GPIOB, GPIO_PIN_4, 0, 0, 0},
{GPIOB, GPIO_PIN_5, 0, 0, 0},
{GPIOB, GPIO_PIN_6, 0, 0, 0},
{GPIOB, GPIO_PIN_7, 0, 0, 0}
};
static uint32_t last_scan_tick = 0;
void IO_Monitor_Task(void)
{
if (HAL_GetTick() - last_scan_tick < 10) {
return;
}
last_scan_tick = HAL_GetTick();
for (int i = 0; i < IO_CHANNEL_COUNT; i++) {
io_channel_t *ch = &di_channels[i];
uint8_t raw_state = HAL_GPIO_ReadPin(ch->port, ch->pin) ? 1 : 0;
if (raw_state != ch->last_raw_state) {
ch->debounce_counter = 0;
ch->last_raw_state = raw_state;
} else {
if (ch->debounce_counter < DEBOUNCE_COUNT) {
ch->debounce_counter++;
} else if (ch->current_state != raw_state) {
ch->current_state = raw_state;
IO_StateChangeHandler(i, raw_state);
}
}
}
}
void IO_StateChangeHandler(uint8_t channel, uint8_t state)
{
UART2_Print_Printf("$DI_EVENT,%d,%d*XX\r\n",
channel + 1,
state);
}
uint8_t IO_Monitor_GetAllStates(void)
{
uint8_t states = 0;
for (int i = 0; i < IO_CHANNEL_COUNT; i++) {
if (di_channels[i].current_state) {
states |= (1 << i);
}
}
return states;
}
```
#### 2.3.3 IO监控模块接口
```c
#ifndef __IO_MONITOR_H
#define __IO_MONITOR_H
#include <stdint.h>
void IO_Monitor_Init(void);
void IO_Monitor_Task(void);
uint8_t IO_Monitor_GetState(uint8_t channel);
uint8_t IO_Monitor_GetAllStates(void);
#endif
```
***
### 2.4 继电器控制模块
#### 2.4.1 模块接口
```c
#ifndef __RELAY_CONTROL_H
#define __RELAY_CONTROL_H
#include <stdint.h>
#include <stdbool.h>
void Relay_Init(void);
void Relay_SetState(uint8_t relay_id, bool state);
bool Relay_GetState(uint8_t relay_id);
void Relay_Toggle(uint8_t relay_id);
#endif
```
#### 2.4.2 实现代码
```c
#include "relay_control.h"
#include "main.h"
void Relay_Init(void)
{
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin, GPIO_PIN_RESET);
}
void Relay_SetState(uint8_t relay_id, bool state)
{
if (relay_id == 1) {
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin,
state ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
}
bool Relay_GetState(uint8_t relay_id)
{
if (relay_id == 1) {
return HAL_GPIO_ReadPin(RL_Control_GPIO_Port, RL_Control_Pin) ? true : false;
}
return false;
}
void Relay_Toggle(uint8_t relay_id)
{
if (relay_id == 1) {
HAL_GPIO_TogglePin(RL_Control_GPIO_Port, RL_Control_Pin);
}
}
```
***
## 三、关键数据结构与接口
### 3.1 简化的数据结构
```c
#ifndef __APP_TYPES_H
#define __APP_TYPES_H
#include <stdint.h>
#include <stdbool.h>
#define IO_CHANNEL_COUNT 4
typedef enum {
UART_PORT_1 = 1,
UART_PORT_2 = 2,
UART_PORT_3 = 3
} uart_port_t;
typedef struct {
char cmd[8];
char param1[16];
char param2[32];
uart_port_t source;
bool valid;
} cmd_frame_t;
#endif
```
### 3.2 设计说明
| 数据结构 | 用途 | 设计理由 |
| -------- | ---- | -------- |
| `uart_port_t` | 标识串口 | 简单枚举UART1=RF433UART2=调试口UART3=预留 |
| `cmd_frame_t` | 存储解析后的指令 | 固定长度字符数组,避免动态内存分配 |
**为什么不需要复杂的数据结构?**
1. **ASCII指令格式**:人类可读,直接用字符串处理即可
2. **裸机轮询架构**:不需要消息队列,直接在主循环处理
3. **单一职责**:每个模块功能单一,不需要复杂的抽象
***
## 四、实施路线图
### 4.1 分阶段开发计划
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 开发阶段时间线 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 第一阶段 (基础框架) 第二阶段 (核心功能) 第三阶段 (扩展) │
│ ├─ UART2_Print ├─ ASCII指令解析 ├─ RF433透传 │
│ ├─ IO监控模块 ├─ 继电器控制 ├─ UART3扩展 │
│ └─ 基础日志输出 └─ 指令响应 └─ 配置存储 │
│ │
│ 验证点: 验证点: 验证点: │
│ · 串口输出正常 · 指令响应正确 · 无线通信正常 │
│ · IO状态上报 · 继电器动作 · 长时间稳定性 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 4.2 第一阶段:基础框架搭建
**目标**实现调试输出能力和IO状态监控
**任务清单**
| 序号 | 任务 | 预计工作量 | 依赖 |
| --- | --- | --- | --- |
| 1.1 | 实现 `UART2_Print` 模块 | 1.5h | 无 |
| 1.2 | 实现 `printf` 重定向 | 0.5h | 1.1 |
| 1.3 | 实现 `IO_Monitor` 模块 | 1.5h | 无 |
| 1.4 | 集成到 `main.c` 主循环 | 0.5h | 1.1, 1.3 |
| 1.5 | 编写测试代码验证 | 0.5h | 1.4 |
**验证方法**
```c
void phase1_test(void)
{
UART2_Print_Init();
IO_Monitor_Init();
printf("Phase 1 Test Start\r\n");
printf("System Clock: %d MHz\r\n", SystemCoreClock / 1000000);
while (1) {
IO_Monitor_Task();
UART2_Print_Task();
static uint32_t last_tick = 0;
if (HAL_GetTick() - last_tick > 5000) {
last_tick = HAL_GetTick();
uint8_t states = IO_Monitor_GetAllStates();
printf("DI States: 0x%02X\r\n", states);
}
}
}
```
**验收标准**
- [ ] UART2能正常输出调试信息
- [ ] `printf` 函数可用
- [ ] DI1-DI4状态变化能实时上报
- [ ] 软件去抖功能正常
***
### 4.3 第二阶段:指令解析与继电器控制
**目标**实现ASCII指令解析和继电器控制
**任务清单**
| 序号 | 任务 | 预计工作量 | 依赖 |
| --- | --- | --- | --- |
| 2.1 | 实现ASCII指令解析器 | 2h | 第一阶段 |
| 2.2 | 实现继电器控制模块 | 0.5h | 无 |
| 2.3 | 实现指令响应机制 | 1h | 2.1, 2.2 |
| 2.4 | UART2接收中断集成 | 0.5h | 2.1 |
| 2.5 | 端到端测试 | 0.5h | 2.4 |
**验证方法**
```c
void phase2_test(void)
{
UART2_Print_Init();
IO_Monitor_Init();
CmdParser_Init();
Relay_Init();
printf("Phase 2 Test Start\r\n");
while (1) {
IO_Monitor_Task();
UART2_Print_Task();
CmdParser_Task();
}
// 手动测试:通过串口发送指令
// 打开继电器1: $RL,1,1*FF\r\n
// 关闭继电器1: $RL,1,0*FF\r\n
// 查询DI1状态: $DI,1*FF\r\n
// 查询所有DI: $DI,0*FF\r\n
}
```
**验收标准**
- [ ] 能正确解析ASCII指令
- [ ] 校验和验证正常
- [ ] 继电器能按指令开关
- [ ] 指令执行后有响应输出
- [ ] 错误指令能被识别并返回错误信息
***
### 4.4 第三阶段RF433透传与扩展
**目标**实现RF433无线透传功能
**任务清单**
| 序号 | 任务 | 预计工作量 | 依赖 |
| --- | --- | --- | --- |
| 3.1 | 实现FWD转发指令处理 | 1h | 第二阶段 |
| 3.2 | 集成RF433模块驱动 | 1h | 3.1 |
| 3.3 | 实现双向透传 | 1h | 3.2 |
| 3.4 | 压力测试与优化 | 1h | 3.3 |
**验证方法**
```c
// 测试场景:
// 1. UART2发送: $FWD,1,Hello RF433*FF\r\n
// 验证UART1(RF433)是否收到 "Hello RF433"
//
// 2. RF433接收数据后自动转发到UART2
// 验证UART2是否收到RF433的数据
```
**验收标准**
- [ ] UART2→RF433转发正常
- [ ] RF433→UART2转发正常
- [ ] 连续转发100次无丢包
- [ ] 长时间运行稳定(>30分钟
***
### 4.5 文件组织
```
Core/
├── Inc/
│ ├── uart2_print.h # 调试打印模块
│ ├── io_monitor.h # IO监控模块
│ ├── cmd_parser.h # 指令解析模块
│ ├── relay_control.h # 继电器控制模块
│ └── app_types.h # 公共类型定义
├── Src/
│ ├── main.c # 主程序(修改)
│ ├── uart2_print.c # 调试打印实现
│ ├── io_monitor.c # IO监控实现
│ ├── cmd_parser.c # 指令解析实现
│ └── relay_control.c # 继电器控制实现
```
***
### 4.6 风险与缓解措施
| 风险 | 影响 | 缓解措施 |
| --- | --- | --- |
| 环形缓冲区溢出 | 数据丢失 | 增加缓冲区大小,添加溢出检测 |
| 指令解析错误 | 功能异常 | 完善状态机,添加超时重置机制 |
| 继电器控制时序 | 硬件损坏 | 添加最小间隔限制 |
***
## 五、总结
### 架构设计要点
1. **简化分层**:应用层 → 服务层 → 驱动层,职责清晰
2. **ASCII指令**:人类可读,方便调试,易于扩展
3. **时间片轮询**基于现有裸机架构无需RTOS
### 协议设计亮点
1. **ASCII文本格式**`$CMD,param1,param2*CS\r\n`,简单直观
2. **异或校验**:计算简单,适合嵌入式系统
3. **统一响应格式**`$OK,...``$ERR,...`
### 关键技术决策
| 决策点 | 选择 | 理由 |
| --- | --- | --- |
| 指令格式 | ASCII文本 | 人类可读,方便调试,兼容串口工具 |
| IO检测方式 | 定时扫描 | 4路统一管理软件去抖灵活 |
| 打印缓冲 | 环形缓冲区 | ISR安全非阻塞发送 |
### 下一步行动
建议按照以下顺序开始编码:
1. 实现 `UART2_Print` 模块,验证调试输出
2. 实现 `IO_Monitor` 模块验证IO监控
3. 实现 `CmdParser` 模块,验证指令解析
4. 实现 `Relay_Control` 模块,验证继电器控制