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

30 KiB
Raw Blame 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 任务调度模型

基于现有裸机架构,采用时间片轮询模型:

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)

#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 环形缓冲区实现

#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 重定向

#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 指令解析器实现(带安全防护)

#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 软件去抖实现

#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监控模块接口

#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 模块接口

#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 实现代码

#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 简化的数据结构

#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

验证方法

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

验证方法

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

验证方法

// 测试场景:
// 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 模块,验证继电器控制