Files
433_STM32/docs/application_development_plan.md
zhongxuanzhen 71027ebc46 3.27_433:添加UART2调试打印、IO监控、指令解析和继电器控制模块。
能够接收UART2指令控制继电器开关,或向UART2发送四路IO输入状态,并使用轮询方式检测IO状态进行及时反馈。
2026-03-27 10:09:13 +08:00

995 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 模块,验证继电器控制