3.27_433:添加UART2调试打印、IO监控、指令解析和继电器控制模块。
能够接收UART2指令控制继电器开关,或向UART2发送四路IO输入状态,并使用轮询方式检测IO状态进行及时反馈。
This commit is contained in:
109
Core/Inc/cmd_parser.h
Normal file
109
Core/Inc/cmd_parser.h
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file cmd_parser.h
|
||||
* @brief ASCII指令解析模块头文件
|
||||
* @author Application Layer
|
||||
* @version 1.0
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块实现ASCII文本指令的解析和处理
|
||||
* 指令格式: $CMD,param1,param2*CS\r\n
|
||||
* 支持异或校验,FF为调试特权后门
|
||||
* 包含完善的安全防护机制
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef __CMD_PARSER_H
|
||||
#define __CMD_PARSER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define CMD_MAX_LEN 8
|
||||
#define PARAM_MAX_LEN 32
|
||||
#define PARSE_TIMEOUT_MS 1000
|
||||
|
||||
typedef enum {
|
||||
CMD_CODE_UNKNOWN = 0,
|
||||
CMD_CODE_RL = 1,
|
||||
CMD_CODE_DI = 2,
|
||||
CMD_CODE_ECHO = 3,
|
||||
CMD_CODE_FWD = 4
|
||||
} cmd_code_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;
|
||||
|
||||
/**
|
||||
* @brief 初始化指令解析模块
|
||||
* @note 重置解析状态机,清空缓冲区
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void CmdParser_Init(void);
|
||||
|
||||
/**
|
||||
* @brief 指令解析任务处理函数
|
||||
* @note 在主循环中调用,处理已接收的完整指令帧
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void CmdParser_Task(void);
|
||||
|
||||
/**
|
||||
* @brief 喂入单字节数据到解析器
|
||||
* @note 通常在UART接收中断中调用
|
||||
* @param byte: 接收到的字节数据
|
||||
* @param current_tick: 当前系统tick(用于超时检测)
|
||||
* @retval 无
|
||||
*/
|
||||
void CmdParser_FeedByte(uint8_t byte, uint32_t current_tick);
|
||||
|
||||
/**
|
||||
* @brief 检查是否有完整的指令帧
|
||||
* @note 用于查询解析状态
|
||||
* @param frame: 输出参数,存储解析结果
|
||||
* @retval true: 有完整帧, false: 无
|
||||
*/
|
||||
bool CmdParser_HasCompleteFrame(cmd_frame_t *frame);
|
||||
|
||||
/**
|
||||
* @brief 确认指令已处理
|
||||
* @note 处理完指令后调用,重置解析器
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void CmdParser_Acknowledge(void);
|
||||
|
||||
/**
|
||||
* @brief 获取解析错误计数
|
||||
* @note 用于调试和诊断
|
||||
* @param 无
|
||||
* @retval 错误计数
|
||||
*/
|
||||
uint32_t CmdParser_GetErrorCount(void);
|
||||
|
||||
/**
|
||||
* @brief 获取接收到的有效指令计数
|
||||
* @note 用于调试和诊断
|
||||
* @param 无
|
||||
* @retval 有效指令计数
|
||||
*/
|
||||
uint32_t CmdParser_GetValidCount(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
82
Core/Inc/io_monitor.h
Normal file
82
Core/Inc/io_monitor.h
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file io_monitor.h
|
||||
* @brief IO状态监控模块头文件
|
||||
* @author Application Layer
|
||||
* @version 1.0
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块实现四路数字输入(DI1-DI4)的状态监控
|
||||
* 采用定时扫描+软件去抖方式检测IO状态变化
|
||||
* 状态变化时通过UART2_Print上报
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef __IO_MONITOR_H
|
||||
#define __IO_MONITOR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define IO_CHANNEL_COUNT 4
|
||||
#define IO_SCAN_PERIOD_MS 10
|
||||
#define IO_DEBOUNCE_COUNT 3
|
||||
|
||||
/**
|
||||
* @brief 初始化IO监控模块
|
||||
* @note 初始化各通道状态,读取初始IO电平
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void IO_Monitor_Init(void);
|
||||
|
||||
/**
|
||||
* @brief IO监控任务处理函数
|
||||
* @note 在主循环中调用,每10ms扫描一次IO状态
|
||||
* 检测到状态变化时自动上报
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void IO_Monitor_Task(void);
|
||||
|
||||
/**
|
||||
* @brief 获取指定通道的IO状态
|
||||
* @note 返回经过去抖处理后的稳定状态
|
||||
* @param channel: 通道号(0-3对应DI1-DI4)
|
||||
* @retval 0: LOW, 1: HIGH
|
||||
*/
|
||||
uint8_t IO_Monitor_GetState(uint8_t channel);
|
||||
|
||||
/**
|
||||
* @brief 获取所有IO通道状态
|
||||
* @note 返回4位状态值,每位对应一个通道
|
||||
* @param 无
|
||||
* @retval 状态值 (bit0=DI1, bit1=DI2, bit2=DI3, bit3=DI4)
|
||||
*/
|
||||
uint8_t IO_Monitor_GetAllStates(void);
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用IO状态变化上报
|
||||
* @note 禁用后状态变化不会触发上报,但状态仍会更新
|
||||
* @param enable: true=启用, false=禁用
|
||||
* @retval 无
|
||||
*/
|
||||
void IO_Monitor_EnableReport(bool enable);
|
||||
|
||||
/**
|
||||
* @brief 获取IO状态变化次数统计
|
||||
* @note 用于调试和诊断
|
||||
* @param channel: 通道号(0-3)
|
||||
* @retval 该通道的状态变化次数
|
||||
*/
|
||||
uint32_t IO_Monitor_GetChangeCount(uint8_t channel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
117
Core/Inc/protocol.h
Normal file
117
Core/Inc/protocol.h
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file protocol.h
|
||||
* @brief 简化的ASCII文本指令协议
|
||||
*
|
||||
* 协议格式: $SN,CMD,param1,param2*CS\r\n
|
||||
* - SN: 序列号(0-255),用于防丢包和重复
|
||||
* - CMD: 指令名称
|
||||
* - CS: CRC8校验(从$到*之间所有字符的CRC8值)
|
||||
*
|
||||
* 抗干扰/防丢失机制:
|
||||
* 1. CRC8校验 - 比XOR更强的检错能力
|
||||
* 2. 序列号 - 接收方可检测丢包和重复
|
||||
* 3. 应答机制 - 重要指令需要确认
|
||||
*/
|
||||
|
||||
#ifndef __PROTOCOL_H
|
||||
#define __PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 协议配置 */
|
||||
#define PROTOCOL_MAX_CMD_LEN 8
|
||||
#define PROTOCOL_MAX_PARAM_LEN 32
|
||||
#define PROTOCOL_MAX_PARAMS 4
|
||||
#define PROTOCOL_FRAME_TIMEOUT 1000 /* 帧超时/ms */
|
||||
|
||||
typedef struct {
|
||||
uint8_t sn; /* 序列号 */
|
||||
char cmd[PROTOCOL_MAX_CMD_LEN]; /* 指令名 */
|
||||
char params[PROTOCOL_MAX_PARAMS][PROTOCOL_MAX_PARAM_LEN];
|
||||
uint8_t param_count; /* 参数数量 */
|
||||
uint8_t checksum; /* CRC8校验和 */
|
||||
bool valid; /* 解析是否成功 */
|
||||
} protocol_frame_t;
|
||||
|
||||
typedef enum {
|
||||
PROTOCOL_OK = 0,
|
||||
PROTOCOL_ERROR_INVALID_FRAME,
|
||||
PROTOCOL_ERROR_CHECKSUM_FAIL,
|
||||
PROTOCOL_ERROR_TIMEOUT,
|
||||
PROTOCOL_ERROR_UNKNOWN_CMD,
|
||||
PROTOCOL_ERROR_PARAM_COUNT,
|
||||
PROTOCOL_ERROR_BUFFER_FULL
|
||||
} protocol_error_t;
|
||||
|
||||
typedef enum {
|
||||
PROTOCOL_CMD_RL, /* 继电器控制: RL,id,ON/OFF */
|
||||
PROTOCOL_CMD_DI, /* 查询IO: DI,id */
|
||||
PROTOCOL_CMD_DIA, /* 查询所有IO: DIA */
|
||||
PROTOCOL_CMD_FWD, /* 数据转发: FWD,port,data */
|
||||
PROTOCOL_CMD_ECHO, /* 心跳: ECHO */
|
||||
PROTOCOL_CMD_ACK, /* 应答: ACK,sn,result */
|
||||
PROTOCOL_CMD_STATUS, /* 状态查询: STATUS */
|
||||
PROTOCOL_CMD_UNKNOWN
|
||||
} protocol_cmd_t;
|
||||
|
||||
typedef struct {
|
||||
protocol_cmd_t cmd;
|
||||
uint8_t sn;
|
||||
bool need_ack; /* 是否需要应答 */
|
||||
uint32_t send_time; /* 发送时间 */
|
||||
uint8_t retry_count; /* 重试次数 */
|
||||
} protocol_pending_t;
|
||||
|
||||
#define PROTOCOL_MAX_PENDING 4
|
||||
|
||||
typedef struct {
|
||||
protocol_pending_t pending[PROTOCOL_MAX_PENDING];
|
||||
uint8_t pending_count;
|
||||
uint8_t next_sn;
|
||||
uint8_t last_rx_sn;
|
||||
} protocol_context_t;
|
||||
|
||||
/* 协议初始化 */
|
||||
void protocol_init(void);
|
||||
|
||||
/* 解析收到的指令 */
|
||||
protocol_error_t protocol_parse(const char *data, uint16_t length, protocol_frame_t *frame);
|
||||
|
||||
/* 构造发送帧 */
|
||||
protocol_error_t protocol_build(char *buffer, uint16_t buffer_size, uint16_t *out_length,
|
||||
protocol_cmd_t cmd, uint8_t sn,
|
||||
const char *params[], uint8_t param_count);
|
||||
|
||||
/* 构造应答帧 */
|
||||
protocol_error_t protocol_build_ack(char *buffer, uint16_t buffer_size, uint16_t *out_length,
|
||||
uint8_t sn, bool success, const char *msg);
|
||||
|
||||
/* 发送命令并等待应答 */
|
||||
protocol_error_t protocol_send_with_ack(protocol_cmd_t cmd, const char *params[], uint8_t param_count,
|
||||
uint32_t timeout_ms, uint8_t max_retries);
|
||||
|
||||
/* 协议任务处理(处理超时和重试) */
|
||||
void protocol_task(void);
|
||||
|
||||
/* CRC8计算 */
|
||||
uint8_t protocol_crc8(const uint8_t *data, uint16_t length);
|
||||
|
||||
/* 指令名转枚举 */
|
||||
protocol_cmd_t protocol_cmd_from_string(const char *cmd_str);
|
||||
|
||||
/* 枚举转指令名 */
|
||||
const char* protocol_cmd_to_string(protocol_cmd_t cmd);
|
||||
|
||||
/* 获取上一个错误 */
|
||||
protocol_error_t protocol_get_last_error(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __PROTOCOL_H */
|
||||
72
Core/Inc/relay_control.h
Normal file
72
Core/Inc/relay_control.h
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file relay_control.h
|
||||
* @brief 继电器控制模块头文件
|
||||
* @author Application Layer
|
||||
* @version 1.0
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块提供继电器的安全控制接口
|
||||
* 当前硬件配置:PA15连接继电器控制端
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef __RELAY_CONTROL_H
|
||||
#define __RELAY_CONTROL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RELAY_COUNT 1
|
||||
#define RELAY_MIN_INTERVAL 100
|
||||
|
||||
/**
|
||||
* @brief 初始化继电器控制模块
|
||||
* @note 将继电器初始状态设为关闭
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void Relay_Init(void);
|
||||
|
||||
/**
|
||||
* @brief 设置继电器状态
|
||||
* @note 带最小间隔保护,防止频繁切换损坏继电器
|
||||
* @param relay_id: 继电器编号(1-4),当前硬件只有1
|
||||
* @param state: true=打开, false=关闭
|
||||
* @retval 无
|
||||
*/
|
||||
void Relay_SetState(uint8_t relay_id, bool state);
|
||||
|
||||
/**
|
||||
* @brief 获取继电器当前状态
|
||||
* @note 读取GPIO输出状态
|
||||
* @param relay_id: 继电器编号(1-4)
|
||||
* @retval true=打开, false=关闭
|
||||
*/
|
||||
bool Relay_GetState(uint8_t relay_id);
|
||||
|
||||
/**
|
||||
* @brief 翻转继电器状态
|
||||
* @note 带最小间隔保护
|
||||
* @param relay_id: 继电器编号(1-4)
|
||||
* @retval 无
|
||||
*/
|
||||
void Relay_Toggle(uint8_t relay_id);
|
||||
|
||||
/**
|
||||
* @brief 获取继电器切换次数
|
||||
* @note 用于调试和诊断
|
||||
* @param 无
|
||||
* @retval 切换次数
|
||||
*/
|
||||
uint32_t Relay_GetToggleCount(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
99
Core/Inc/uart2_print.h
Normal file
99
Core/Inc/uart2_print.h
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file uart2_print.h
|
||||
* @brief UART2调试打印模块头文件
|
||||
* @author Application Layer
|
||||
* @version 1.0
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块提供基于环形缓冲区的非阻塞调试信息输出功能
|
||||
* 支持printf风格的格式化输出
|
||||
* 支持中断安全调用
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* @brief 初始化UART2打印模块
|
||||
* @note 清空发送缓冲区,重置状态标志
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void UART2_Print_Init(void);
|
||||
|
||||
/**
|
||||
* @brief 发送原始数据到UART2
|
||||
* @note 数据写入环形缓冲区,非阻塞返回
|
||||
* @param data: 待发送的数据指针
|
||||
* @param len: 数据长度
|
||||
* @retval 无
|
||||
*/
|
||||
void UART2_Print_Send(const uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief 发送字符串到UART2
|
||||
* @note 自动计算字符串长度
|
||||
* @param str: 待发送的字符串指针
|
||||
* @retval 无
|
||||
*/
|
||||
void UART2_Print_String(const char *str);
|
||||
|
||||
/**
|
||||
* @brief 格式化打印到UART2
|
||||
* @note 支持printf风格的格式化输出
|
||||
* @param fmt: 格式化字符串
|
||||
* @param ...: 可变参数
|
||||
* @retval 无
|
||||
*/
|
||||
void UART2_Print_Printf(const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief UART2打印任务处理函数
|
||||
* @note 在主循环中调用,处理发送缓冲区中的数据
|
||||
* 每次调用发送一个字节(中断方式)
|
||||
* @param 无
|
||||
* @retval 无
|
||||
*/
|
||||
void UART2_Print_Task(void);
|
||||
|
||||
/**
|
||||
* @brief 检查UART2是否正在发送
|
||||
* @note 用于查询当前发送状态
|
||||
* @param 无
|
||||
* @retval true: 正在发送, false: 空闲
|
||||
*/
|
||||
bool UART2_Print_IsBusy(void);
|
||||
|
||||
/**
|
||||
* @brief 获取发送缓冲区剩余空间
|
||||
* @note 用于检测缓冲区是否即将溢出
|
||||
* @param 无
|
||||
* @retval 剩余空间字节数
|
||||
*/
|
||||
uint16_t UART2_Print_Available(void);
|
||||
|
||||
/**
|
||||
* @brief UART2发送完成中断回调
|
||||
* @note 由HAL库调用,不应由用户直接调用
|
||||
* @param huart: UART句柄指针
|
||||
* @retval 无
|
||||
*/
|
||||
void UART2_Print_TxCpltCallback(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
378
Core/Src/cmd_parser.c
Normal file
378
Core/Src/cmd_parser.c
Normal file
@ -0,0 +1,378 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file cmd_parser.c
|
||||
* @brief ASCII指令解析模块实现
|
||||
* @author Application Layer
|
||||
* @version 1.1
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块实现ASCII文本指令的解析和处理
|
||||
* 关键特性:
|
||||
* 1. 状态机解析,健壮可靠
|
||||
* 2. 完善的安全防护(缓冲区边界检查、超时重置、字符过滤)
|
||||
* 3. 异或校验,FF特权后门
|
||||
* 4. 支持RL、DI、ECHO指令
|
||||
*
|
||||
* 修订历史:
|
||||
* v1.1 - 修复审查报告高危-6、中危-7/8
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "cmd_parser.h"
|
||||
#include "uart2_print.h"
|
||||
#include "io_monitor.h"
|
||||
#include "relay_control.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h> // snprintf
|
||||
#include <stdlib.h> // atoi
|
||||
|
||||
#define DEBUG_CMD_PARSER 1
|
||||
|
||||
#if DEBUG_CMD_PARSER
|
||||
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[CMD] " fmt "\r\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_LOG(fmt, ...)
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PARSE_IDLE,
|
||||
PARSE_CMD,
|
||||
PARSE_PARAM1,
|
||||
PARSE_PARAM2,
|
||||
PARSE_CHECKSUM,
|
||||
PARSE_COMPLETE
|
||||
} parse_state_t;
|
||||
|
||||
typedef struct {
|
||||
parse_state_t state;
|
||||
cmd_frame_t frame;
|
||||
uint8_t field_index;
|
||||
uint8_t checksum_acc;
|
||||
uint8_t cs_buffer[2];
|
||||
uint8_t cs_index;
|
||||
uint32_t last_rx_tick;
|
||||
uint32_t error_count;
|
||||
uint32_t valid_count;
|
||||
} parser_context_t;
|
||||
|
||||
static parser_context_t ctx;
|
||||
|
||||
static void reset_parser(void)
|
||||
{
|
||||
ctx.state = PARSE_IDLE;
|
||||
ctx.field_index = 0;
|
||||
ctx.checksum_acc = 0;
|
||||
ctx.cs_index = 0;
|
||||
memset(&ctx.frame, 0, sizeof(ctx.frame));
|
||||
}
|
||||
|
||||
static bool is_valid_cmd_char(char c)
|
||||
{
|
||||
return isupper((unsigned char)c) || isdigit((unsigned char)c);
|
||||
}
|
||||
|
||||
static bool is_valid_param_char(char c)
|
||||
{
|
||||
return isprint((unsigned char)c) && c != '*' && c != '\r' && c != '\n';
|
||||
}
|
||||
|
||||
static uint8_t hex_char_to_val(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t hex_to_byte(char high, char low)
|
||||
{
|
||||
return (hex_char_to_val(high) << 4) | hex_char_to_val(low);
|
||||
}
|
||||
|
||||
static uint8_t calc_checksum(const char *data, uint8_t len)
|
||||
{
|
||||
uint8_t cs = 0;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
cs ^= (uint8_t)data[i];
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
static void send_response_ok(const char *content)
|
||||
{
|
||||
char msg[64];
|
||||
uint8_t cs;
|
||||
|
||||
int len = snprintf(msg, sizeof(msg), "$OK,%s*", content);
|
||||
cs = calc_checksum(msg + 1, len - 1);
|
||||
snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs);
|
||||
|
||||
UART2_Print_String(msg);
|
||||
}
|
||||
|
||||
static void send_response_err(const char *err_code)
|
||||
{
|
||||
char msg[32];
|
||||
uint8_t cs;
|
||||
|
||||
int len = snprintf(msg, sizeof(msg), "$ERR,%s*", err_code);
|
||||
cs = calc_checksum(msg + 1, len - 1);
|
||||
snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs);
|
||||
|
||||
UART2_Print_String(msg);
|
||||
}
|
||||
|
||||
static bool is_str_empty(const char *str)
|
||||
{
|
||||
return (str == NULL || str[0] == '\0');
|
||||
}
|
||||
|
||||
static bool is_str_numeric(const char *str)
|
||||
{
|
||||
if (is_str_empty(str)) {
|
||||
return false;
|
||||
}
|
||||
while (*str) {
|
||||
if (!isdigit((unsigned char)*str)) {
|
||||
return false;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void process_cmd_frame(const cmd_frame_t *frame)
|
||||
{
|
||||
DEBUG_LOG("CMD=%s P1=%s P2=%s CS=%02X/%02X %s",
|
||||
frame->cmd, frame->param1, frame->param2,
|
||||
frame->received_cs, frame->calculated_cs,
|
||||
frame->skip_checksum ? "(skip)" : "");
|
||||
|
||||
if (strcmp(frame->cmd, "RL") == 0) {
|
||||
if (!is_str_numeric(frame->param1) || !is_str_numeric(frame->param2)) {
|
||||
send_response_err("PARAM");
|
||||
DEBUG_LOG("Invalid RL params: not numeric");
|
||||
return;
|
||||
}
|
||||
|
||||
int relay_id = atoi(frame->param1);
|
||||
int state = atoi(frame->param2);
|
||||
|
||||
if (relay_id >= 1 && relay_id <= 4 && (state == 0 || state == 1)) {
|
||||
Relay_SetState(relay_id, state ? true : false);
|
||||
|
||||
char resp[32];
|
||||
snprintf(resp, sizeof(resp), "RL,%d,%d", relay_id, state);
|
||||
send_response_ok(resp);
|
||||
|
||||
DEBUG_LOG("Relay %d -> %s", relay_id, state ? "ON" : "OFF");
|
||||
} else {
|
||||
send_response_err("PARAM");
|
||||
DEBUG_LOG("Invalid RL params: id=%d state=%d", relay_id, state);
|
||||
}
|
||||
}
|
||||
else if (strcmp(frame->cmd, "DI") == 0) {
|
||||
if (is_str_empty(frame->param1) || strcmp(frame->param1, "0") == 0) {
|
||||
uint8_t states = IO_Monitor_GetAllStates();
|
||||
char resp[32];
|
||||
snprintf(resp, sizeof(resp), "DI,%d%d%d%d",
|
||||
(states >> 0) & 1, (states >> 1) & 1,
|
||||
(states >> 2) & 1, (states >> 3) & 1);
|
||||
send_response_ok(resp);
|
||||
DEBUG_LOG("DI all states: 0x%02X", states);
|
||||
}
|
||||
else if (is_str_numeric(frame->param1)) {
|
||||
int channel = atoi(frame->param1);
|
||||
if (channel >= 1 && channel <= 4) {
|
||||
uint8_t state = IO_Monitor_GetState(channel - 1);
|
||||
char resp[32];
|
||||
snprintf(resp, sizeof(resp), "DI,%d,%d", channel, state);
|
||||
send_response_ok(resp);
|
||||
DEBUG_LOG("DI%d = %d", channel, state);
|
||||
} else {
|
||||
send_response_err("PARAM");
|
||||
DEBUG_LOG("Invalid DI channel: %d", channel);
|
||||
}
|
||||
}
|
||||
else {
|
||||
send_response_err("PARAM");
|
||||
DEBUG_LOG("Invalid DI param: not numeric");
|
||||
}
|
||||
}
|
||||
else if (strcmp(frame->cmd, "ECHO") == 0) {
|
||||
send_response_ok("ECHO");
|
||||
DEBUG_LOG("ECHO response sent");
|
||||
}
|
||||
else {
|
||||
send_response_err("CMD");
|
||||
DEBUG_LOG("Unknown command: %s", frame->cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void CmdParser_Init(void)
|
||||
{
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.state = PARSE_IDLE;
|
||||
|
||||
DEBUG_LOG("Init OK");
|
||||
}
|
||||
|
||||
void CmdParser_FeedByte(uint8_t byte, uint32_t current_tick)
|
||||
{
|
||||
if (ctx.state != PARSE_IDLE && ctx.state != PARSE_COMPLETE) {
|
||||
if (current_tick - ctx.last_rx_tick >= PARSE_TIMEOUT_MS) {
|
||||
ctx.error_count++;
|
||||
DEBUG_LOG("Timeout, reset parser");
|
||||
reset_parser();
|
||||
if (byte == '$') {
|
||||
ctx.state = PARSE_CMD;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctx.last_rx_tick = current_tick;
|
||||
|
||||
switch (ctx.state) {
|
||||
case PARSE_IDLE:
|
||||
if (byte == '$') {
|
||||
reset_parser();
|
||||
ctx.state = PARSE_CMD;
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSE_CMD:
|
||||
if (byte == ',') {
|
||||
ctx.frame.cmd[ctx.field_index] = '\0';
|
||||
ctx.state = PARSE_PARAM1;
|
||||
ctx.field_index = 0;
|
||||
} else if (byte == '*') {
|
||||
ctx.frame.cmd[ctx.field_index] = '\0';
|
||||
ctx.state = PARSE_CHECKSUM;
|
||||
ctx.field_index = 0;
|
||||
ctx.cs_index = 0;
|
||||
} else if (is_valid_cmd_char(byte)) {
|
||||
if (ctx.field_index < CMD_MAX_LEN - 1) {
|
||||
ctx.frame.cmd[ctx.field_index++] = byte;
|
||||
ctx.checksum_acc ^= byte;
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
reset_parser();
|
||||
}
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
reset_parser();
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSE_PARAM1:
|
||||
if (byte == ',') {
|
||||
ctx.frame.param1[ctx.field_index] = '\0';
|
||||
ctx.state = PARSE_PARAM2;
|
||||
ctx.field_index = 0;
|
||||
} else if (byte == '*') {
|
||||
ctx.frame.param1[ctx.field_index] = '\0';
|
||||
ctx.state = PARSE_CHECKSUM;
|
||||
ctx.field_index = 0;
|
||||
ctx.cs_index = 0;
|
||||
} else if (is_valid_param_char(byte)) {
|
||||
if (ctx.field_index < PARAM_MAX_LEN - 1) {
|
||||
ctx.frame.param1[ctx.field_index++] = byte;
|
||||
ctx.checksum_acc ^= byte;
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
reset_parser();
|
||||
}
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
reset_parser();
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSE_PARAM2:
|
||||
if (byte == '*') {
|
||||
ctx.frame.param2[ctx.field_index] = '\0';
|
||||
ctx.state = PARSE_CHECKSUM;
|
||||
ctx.field_index = 0;
|
||||
ctx.cs_index = 0;
|
||||
} else if (is_valid_param_char(byte)) {
|
||||
if (ctx.field_index < PARAM_MAX_LEN - 1) {
|
||||
ctx.frame.param2[ctx.field_index++] = byte;
|
||||
ctx.checksum_acc ^= byte;
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
reset_parser();
|
||||
}
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
reset_parser();
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSE_CHECKSUM:
|
||||
if (byte == '\n') {
|
||||
ctx.frame.received_cs = hex_to_byte(ctx.cs_buffer[0], ctx.cs_buffer[1]);
|
||||
ctx.frame.calculated_cs = ctx.checksum_acc;
|
||||
ctx.frame.skip_checksum = (ctx.frame.received_cs == 0xFF);
|
||||
|
||||
if (ctx.frame.skip_checksum ||
|
||||
ctx.frame.received_cs == ctx.frame.calculated_cs) {
|
||||
ctx.frame.valid = true;
|
||||
ctx.state = PARSE_COMPLETE;
|
||||
ctx.valid_count++;
|
||||
} else {
|
||||
ctx.error_count++;
|
||||
DEBUG_LOG("Checksum error: recv=%02X calc=%02X",
|
||||
ctx.frame.received_cs, ctx.frame.calculated_cs);
|
||||
send_response_err("CS");
|
||||
reset_parser();
|
||||
}
|
||||
} else if (byte != '\r') {
|
||||
if (ctx.cs_index < 2) {
|
||||
ctx.cs_buffer[ctx.cs_index++] = byte;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSE_COMPLETE:
|
||||
reset_parser();
|
||||
if (byte == '$') {
|
||||
ctx.state = PARSE_CMD;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CmdParser_Task(void)
|
||||
{
|
||||
if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) {
|
||||
process_cmd_frame(&ctx.frame);
|
||||
reset_parser();
|
||||
}
|
||||
}
|
||||
|
||||
bool CmdParser_HasCompleteFrame(cmd_frame_t *frame)
|
||||
{
|
||||
if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) {
|
||||
if (frame) {
|
||||
memcpy(frame, &ctx.frame, sizeof(cmd_frame_t));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CmdParser_Acknowledge(void)
|
||||
{
|
||||
reset_parser();
|
||||
}
|
||||
|
||||
uint32_t CmdParser_GetErrorCount(void)
|
||||
{
|
||||
return ctx.error_count;
|
||||
}
|
||||
|
||||
uint32_t CmdParser_GetValidCount(void)
|
||||
{
|
||||
return ctx.valid_count;
|
||||
}
|
||||
158
Core/Src/io_monitor.c
Normal file
158
Core/Src/io_monitor.c
Normal file
@ -0,0 +1,158 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file io_monitor.c
|
||||
* @brief IO状态监控模块实现
|
||||
* @author Application Layer
|
||||
* @version 1.1
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块实现四路数字输入的状态监控
|
||||
* 关键特性:
|
||||
* 1. 10ms定时扫描,平衡响应速度和CPU占用
|
||||
* 2. 软件去抖,连续3次相同状态才确认变化
|
||||
* 3. 状态变化时自动上报ASCII格式消息
|
||||
*
|
||||
* 修订历史:
|
||||
* v1.1 - 修复审查报告中危-5:去抖计数器初始化优化
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "io_monitor.h"
|
||||
#include "uart2_print.h"
|
||||
#include "main.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DEBUG_IO_MONITOR 1
|
||||
|
||||
#if DEBUG_IO_MONITOR
|
||||
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[IO] " fmt "\r\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_LOG(fmt, ...)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
GPIO_TypeDef *port;
|
||||
uint16_t pin;
|
||||
uint8_t current_state;
|
||||
uint8_t debounce_counter;
|
||||
uint8_t last_raw_state;
|
||||
uint32_t change_count;
|
||||
} io_channel_t;
|
||||
|
||||
static io_channel_t di_channels[IO_CHANNEL_COUNT] = {
|
||||
{GPIOB, GPIO_PIN_4, 0, 0, 0, 0},
|
||||
{GPIOB, GPIO_PIN_5, 0, 0, 0, 0},
|
||||
{GPIOB, GPIO_PIN_6, 0, 0, 0, 0},
|
||||
{GPIOB, GPIO_PIN_7, 0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static uint32_t last_scan_tick = 0;
|
||||
static bool report_enabled = true;
|
||||
|
||||
static uint8_t calc_checksum(const char *data, uint8_t len)
|
||||
{
|
||||
uint8_t cs = 0;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
cs ^= (uint8_t)data[i];
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
static void send_di_event(uint8_t channel, uint8_t state)
|
||||
{
|
||||
char msg[32];
|
||||
uint8_t cs;
|
||||
|
||||
int len = snprintf(msg, sizeof(msg), "$DI_EVENT,%d,%d*", channel + 1, state);
|
||||
|
||||
cs = calc_checksum(msg + 1, len - 1);
|
||||
|
||||
snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs);
|
||||
|
||||
UART2_Print_String(msg);
|
||||
|
||||
DEBUG_LOG("CH%d -> %s", channel + 1, state ? "HIGH" : "LOW");
|
||||
}
|
||||
|
||||
void IO_Monitor_Init(void)
|
||||
{
|
||||
for (int i = 0; i < IO_CHANNEL_COUNT; i++) {
|
||||
io_channel_t *ch = &di_channels[i];
|
||||
|
||||
ch->current_state = HAL_GPIO_ReadPin(ch->port, ch->pin) ? 1 : 0;
|
||||
ch->last_raw_state = ch->current_state;
|
||||
ch->debounce_counter = 1;
|
||||
ch->change_count = 0;
|
||||
}
|
||||
|
||||
last_scan_tick = 0;
|
||||
report_enabled = true;
|
||||
|
||||
DEBUG_LOG("Init OK, initial states: 0x%02X", IO_Monitor_GetAllStates());
|
||||
}
|
||||
|
||||
void IO_Monitor_Task(void)
|
||||
{
|
||||
uint32_t current_tick = HAL_GetTick();
|
||||
|
||||
if (current_tick - last_scan_tick < IO_SCAN_PERIOD_MS) {
|
||||
return;
|
||||
}
|
||||
last_scan_tick = current_tick;
|
||||
|
||||
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 < IO_DEBOUNCE_COUNT) {
|
||||
ch->debounce_counter++;
|
||||
} else if (ch->current_state != raw_state) {
|
||||
ch->current_state = raw_state;
|
||||
ch->change_count++;
|
||||
|
||||
if (report_enabled) {
|
||||
send_di_event(i, raw_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IO_Monitor_GetState(uint8_t channel)
|
||||
{
|
||||
if (channel >= IO_CHANNEL_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
return di_channels[channel].current_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;
|
||||
}
|
||||
|
||||
void IO_Monitor_EnableReport(bool enable)
|
||||
{
|
||||
report_enabled = enable;
|
||||
DEBUG_LOG("Report %s", enable ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
uint32_t IO_Monitor_GetChangeCount(uint8_t channel)
|
||||
{
|
||||
if (channel >= IO_CHANNEL_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
return di_channels[channel].change_count;
|
||||
}
|
||||
@ -28,6 +28,12 @@
|
||||
#include "rf433_config.h"
|
||||
#include "rf433_hal.h"
|
||||
|
||||
/* 应用层模块头文件 */
|
||||
#include "uart2_print.h"
|
||||
#include "io_monitor.h"
|
||||
#include "cmd_parser.h"
|
||||
#include "relay_control.h"
|
||||
|
||||
#if (RF433_MODE == RF433_MODE_TX) || (RF433_MODE == RF433_MODE_BOTH)
|
||||
#include "rf433_tx_app.h"
|
||||
#endif
|
||||
@ -55,7 +61,7 @@
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
|
||||
/* USER CODE BEGIN PV */
|
||||
|
||||
static uint8_t uart2_rx_byte = 0;
|
||||
/* USER CODE END PV */
|
||||
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
@ -100,11 +106,20 @@ int main(void)
|
||||
MX_USART3_UART_Init();
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
||||
/* 初始化RF433模块 - 使用默认配置 */
|
||||
rf433_init(NULL);
|
||||
/* 初始化应用层模块 */
|
||||
UART2_Print_Init();
|
||||
IO_Monitor_Init();
|
||||
CmdParser_Init();
|
||||
Relay_Init();
|
||||
|
||||
/* 启动UART2接收中断 */
|
||||
HAL_UART_Receive_IT(&huart2, &uart2_rx_byte, 1);
|
||||
|
||||
/* 初始化RF433模块 - 使用默认配置 */
|
||||
rf433_init(NULL);
|
||||
|
||||
/* 启动UART接收 - 使用rf433_hal中的临时变量 */
|
||||
HAL_UART_Receive_IT(&huart1, &rf433_uart_rx_tmp, 1);
|
||||
/* 启动UART1接收 - 使用rf433_hal中的临时变量 */
|
||||
HAL_UART_Receive_IT(&huart1, &rf433_uart_rx_tmp, 1);
|
||||
|
||||
/* 根据配置模式初始化TX/RX应用层 */
|
||||
#if (RF433_MODE == RF433_MODE_TX) || (RF433_MODE == RF433_MODE_BOTH)
|
||||
@ -119,7 +134,13 @@ int main(void)
|
||||
rf433_rx_app_start();
|
||||
#endif
|
||||
|
||||
/* USER CODE END 2 */
|
||||
/* 打印启动信息 */
|
||||
printf("\r\n========================================\r\n");
|
||||
printf("E32-433TBH-SC Application Started\r\n");
|
||||
printf("System Clock: %d MHz\r\n", SystemCoreClock / 1000000);
|
||||
printf("========================================\r\n");
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
/* USER CODE BEGIN WHILE */
|
||||
@ -129,6 +150,11 @@ int main(void)
|
||||
|
||||
/* USER CODE BEGIN 3 */
|
||||
|
||||
/* 应用层任务处理 */
|
||||
UART2_Print_Task();
|
||||
IO_Monitor_Task();
|
||||
CmdParser_Task();
|
||||
|
||||
#if (RF433_MODE == RF433_MODE_TX) || (RF433_MODE == RF433_MODE_BOTH)
|
||||
/* TX任务 */
|
||||
rf433_tx_app_task();
|
||||
@ -139,8 +165,6 @@ int main(void)
|
||||
rf433_rx_app_task();
|
||||
#endif
|
||||
|
||||
/* 短延时,避免CPU占用过高 */
|
||||
HAL_Delay(1);
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
}
|
||||
@ -186,6 +210,43 @@ void SystemClock_Config(void)
|
||||
|
||||
/* USER CODE BEGIN 4 */
|
||||
|
||||
/**
|
||||
* @brief UART接收完成中断回调函数
|
||||
* @note 处理UART1(RF433)和UART2(调试口)的接收数据
|
||||
* @param huart: UART句柄指针
|
||||
* @retval 无
|
||||
*/
|
||||
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
|
||||
{
|
||||
if (huart->Instance == USART1)
|
||||
{
|
||||
/* 调用RF433模块的UART接收回调 */
|
||||
rf433_hal_uart_rxcplt_callback();
|
||||
}
|
||||
else if (huart->Instance == USART2)
|
||||
{
|
||||
/* 喂入指令解析器 */
|
||||
CmdParser_FeedByte(uart2_rx_byte, HAL_GetTick());
|
||||
/* 重新启动接收 */
|
||||
HAL_UART_Receive_IT(&huart2, &uart2_rx_byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART发送完成中断回调函数
|
||||
* @note 处理UART2发送完成,触发下一次发送
|
||||
* @param huart: UART句柄指针
|
||||
* @retval 无
|
||||
*/
|
||||
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
|
||||
{
|
||||
if (huart->Instance == USART2)
|
||||
{
|
||||
/* 调用UART2打印模块的发送完成回调 */
|
||||
UART2_Print_TxCpltCallback();
|
||||
}
|
||||
}
|
||||
|
||||
/* USER CODE END 4 */
|
||||
|
||||
/**
|
||||
|
||||
102
Core/Src/relay_control.c
Normal file
102
Core/Src/relay_control.c
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file relay_control.c
|
||||
* @brief 继电器控制模块实现
|
||||
* @author Application Layer
|
||||
* @version 1.1
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块实现继电器的安全控制
|
||||
* 关键特性:
|
||||
* 1. 最小切换间隔保护,防止频繁切换损坏继电器
|
||||
* 2. 状态记录,支持诊断
|
||||
* 3. 调试日志输出
|
||||
*
|
||||
* 修订历史:
|
||||
* v1.1 - 修复审查报告中危-9/10:对齐RELAY_COUNT与参数校验逻辑
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "relay_control.h"
|
||||
#include "uart2_print.h"
|
||||
#include "main.h"
|
||||
|
||||
#define DEBUG_RELAY 1
|
||||
|
||||
#if DEBUG_RELAY
|
||||
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[RELAY] " fmt "\r\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_LOG(fmt, ...)
|
||||
#endif
|
||||
|
||||
#define MAX_RELAY_ID 4
|
||||
|
||||
static bool current_states[MAX_RELAY_ID] = {false, false, false, false};
|
||||
static uint32_t last_toggle_tick = 0;
|
||||
static uint32_t toggle_count = 0;
|
||||
|
||||
void Relay_Init(void)
|
||||
{
|
||||
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin, GPIO_PIN_RESET);
|
||||
|
||||
for (int i = 0; i < MAX_RELAY_ID; i++) {
|
||||
current_states[i] = false;
|
||||
}
|
||||
last_toggle_tick = 0;
|
||||
toggle_count = 0;
|
||||
|
||||
DEBUG_LOG("Init OK, state=OFF");
|
||||
}
|
||||
|
||||
void Relay_SetState(uint8_t relay_id, bool state)
|
||||
{
|
||||
if (relay_id < 1 || relay_id > MAX_RELAY_ID) {
|
||||
DEBUG_LOG("Invalid relay ID: %d", relay_id);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t current_tick = HAL_GetTick();
|
||||
if (current_tick - last_toggle_tick < RELAY_MIN_INTERVAL) {
|
||||
DEBUG_LOG("Toggle too fast, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t idx = relay_id - 1;
|
||||
|
||||
if (current_states[idx] == state) {
|
||||
DEBUG_LOG("State unchanged: %s", state ? "ON" : "OFF");
|
||||
return;
|
||||
}
|
||||
|
||||
if (relay_id == 1) {
|
||||
HAL_GPIO_WritePin(RL_Control_GPIO_Port, RL_Control_Pin,
|
||||
state ? GPIO_PIN_SET : GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
current_states[idx] = state;
|
||||
last_toggle_tick = current_tick;
|
||||
toggle_count++;
|
||||
|
||||
DEBUG_LOG("Relay %d -> %s (count=%lu)", relay_id, state ? "ON" : "OFF", toggle_count);
|
||||
}
|
||||
|
||||
bool Relay_GetState(uint8_t relay_id)
|
||||
{
|
||||
if (relay_id < 1 || relay_id > MAX_RELAY_ID) {
|
||||
return false;
|
||||
}
|
||||
return current_states[relay_id - 1];
|
||||
}
|
||||
|
||||
void Relay_Toggle(uint8_t relay_id)
|
||||
{
|
||||
if (relay_id < 1 || relay_id > MAX_RELAY_ID) {
|
||||
return;
|
||||
}
|
||||
Relay_SetState(relay_id, !current_states[relay_id - 1]);
|
||||
}
|
||||
|
||||
uint32_t Relay_GetToggleCount(void)
|
||||
{
|
||||
return toggle_count;
|
||||
}
|
||||
@ -14,7 +14,13 @@
|
||||
#include "main.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "uart2_print.h"
|
||||
#define DEBUG_CMD_PARSER 1
|
||||
#if DEBUG_CMD_PARSER
|
||||
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[CMD] " fmt "\r\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_LOG(fmt, ...)
|
||||
#endif
|
||||
/* ============================================================================
|
||||
* 私有变量
|
||||
* ============================================================================ */
|
||||
|
||||
224
Core/Src/uart2_print.c
Normal file
224
Core/Src/uart2_print.c
Normal file
@ -0,0 +1,224 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file uart2_print.c
|
||||
* @brief UART2调试打印模块实现
|
||||
* @author Application Layer
|
||||
* @version 1.1
|
||||
******************************************************************************
|
||||
* @attention
|
||||
* 本模块实现基于环形缓冲区的非阻塞调试信息输出
|
||||
* 关键特性:
|
||||
* 1. 环形缓冲区避免数据丢失
|
||||
* 2. 中断安全,支持ISR中调用
|
||||
* 3. 非阻塞发送,不影响实时性
|
||||
*
|
||||
* 修订历史:
|
||||
* v1.1 - 修复审查报告高危-1/2/3,中危-4
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "uart2_print.h"
|
||||
#include "usart.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define DEBUG_PRINT_ENABLED 1
|
||||
|
||||
#if DEBUG_PRINT_ENABLED
|
||||
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[UART2] " fmt "\r\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_LOG(fmt, ...)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t buffer[UART2_TX_BUFFER_SIZE];
|
||||
volatile uint16_t head;
|
||||
volatile uint16_t tail;
|
||||
volatile uint16_t count;
|
||||
volatile bool is_sending;
|
||||
volatile uint16_t overflow_count;
|
||||
} 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.count = 0;
|
||||
tx_ring.is_sending = false;
|
||||
tx_ring.overflow_count = 0;
|
||||
|
||||
DEBUG_LOG("Init OK, buffer size: %d", UART2_TX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void UART2_Print_Send(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (len == 0 || data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t written = 0;
|
||||
bool needs_kickoff = false;
|
||||
|
||||
__disable_irq();
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
if (tx_ring.count >= UART2_TX_BUFFER_SIZE) {
|
||||
tx_ring.overflow_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
tx_ring.buffer[tx_ring.head] = data[i];
|
||||
tx_ring.head = (tx_ring.head + 1) % UART2_TX_BUFFER_SIZE;
|
||||
tx_ring.count++;
|
||||
written++;
|
||||
}
|
||||
|
||||
if (written > 0 && !tx_ring.is_sending) {
|
||||
tx_ring.is_sending = true;
|
||||
needs_kickoff = true;
|
||||
}
|
||||
__enable_irq();
|
||||
|
||||
if (needs_kickoff) {
|
||||
uint8_t byte;
|
||||
__disable_irq();
|
||||
byte = tx_ring.buffer[tx_ring.tail];
|
||||
__enable_irq();
|
||||
HAL_UART_Transmit_IT(&huart2, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void UART2_Print_String(const char *str)
|
||||
{
|
||||
if (str == NULL) {
|
||||
return;
|
||||
}
|
||||
UART2_Print_Send((const uint8_t *)str, strlen(str));
|
||||
}
|
||||
|
||||
void UART2_Print_Printf(const char *fmt, ...)
|
||||
{
|
||||
if (fmt == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[128];
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (len >= 0) {
|
||||
if (len >= (int)sizeof(buffer)) {
|
||||
len = sizeof(buffer) - 1;
|
||||
}
|
||||
UART2_Print_Send((const uint8_t *)buffer, len);
|
||||
}
|
||||
}
|
||||
|
||||
void UART2_Print_Task(void)
|
||||
{
|
||||
uint8_t byte;
|
||||
uint16_t current_tail;
|
||||
|
||||
__disable_irq();
|
||||
if (tx_ring.is_sending || tx_ring.count == 0) {
|
||||
__enable_irq();
|
||||
return;
|
||||
}
|
||||
|
||||
current_tail = tx_ring.tail;
|
||||
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE;
|
||||
tx_ring.count--;
|
||||
tx_ring.is_sending = true;
|
||||
byte = tx_ring.buffer[current_tail];
|
||||
__enable_irq();
|
||||
|
||||
HAL_UART_Transmit_IT(&huart2, &byte, 1);
|
||||
}
|
||||
|
||||
void UART2_Print_TxCpltCallback(void)
|
||||
{
|
||||
uint8_t byte;
|
||||
uint16_t current_tail;
|
||||
bool has_more = false;
|
||||
|
||||
__disable_irq();
|
||||
tx_ring.is_sending = false;
|
||||
|
||||
if (tx_ring.count > 0) {
|
||||
current_tail = tx_ring.tail;
|
||||
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE;
|
||||
tx_ring.count--;
|
||||
tx_ring.is_sending = true;
|
||||
byte = tx_ring.buffer[current_tail];
|
||||
has_more = true;
|
||||
}
|
||||
__enable_irq();
|
||||
|
||||
if (has_more) {
|
||||
HAL_UART_Transmit_IT(&huart2, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool UART2_Print_IsBusy(void)
|
||||
{
|
||||
bool busy;
|
||||
__disable_irq();
|
||||
busy = tx_ring.is_sending || (tx_ring.count > 0);
|
||||
__enable_irq();
|
||||
return busy;
|
||||
}
|
||||
|
||||
uint16_t UART2_Print_Available(void)
|
||||
{
|
||||
uint16_t available;
|
||||
__disable_irq();
|
||||
available = UART2_TX_BUFFER_SIZE - tx_ring.count;
|
||||
__enable_irq();
|
||||
return available;
|
||||
}
|
||||
|
||||
uint16_t UART2_Print_GetOverflowCount(void)
|
||||
{
|
||||
return tx_ring.overflow_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief printf重定向函数 (Keil MDK)
|
||||
* @note 重定向标准库printf到UART2
|
||||
* @param ch: 待发送字符
|
||||
* @param f: 文件指针(未使用)
|
||||
* @retval 发送的字符
|
||||
*/
|
||||
#if defined(__CC_ARM) || defined(__ARMCC_VERSION)
|
||||
int fputc(int ch, FILE *f)
|
||||
{
|
||||
(void)f;
|
||||
UART2_Print_Send((uint8_t *)&ch, 1);
|
||||
return ch;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief printf重定向函数 (GCC)
|
||||
* @note 重定向标准库printf到UART2
|
||||
* @param ch: 待发送字符
|
||||
* @retval 发送的字符
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
int __io_putchar(int ch)
|
||||
{
|
||||
UART2_Print_Send((uint8_t *)&ch, 1);
|
||||
return ch;
|
||||
}
|
||||
|
||||
int _write(int file, char *ptr, int len)
|
||||
{
|
||||
(void)file;
|
||||
UART2_Print_Send((uint8_t *)ptr, len);
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
@ -279,30 +279,28 @@ void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
|
||||
}
|
||||
|
||||
/* USER CODE BEGIN 1 */
|
||||
void uart1_reconfig( uint32_t rate )
|
||||
/**
|
||||
* @brief UART1波特率重配置函数
|
||||
* @note 用于RF433模块波特率动态调整
|
||||
* @param rate: 目标波特率
|
||||
* @retval 无
|
||||
*/
|
||||
void uart1_reconfig(uint32_t rate)
|
||||
{
|
||||
/* 原串口1的初始化 */
|
||||
huart1.Instance = USART1;
|
||||
huart1.Init.BaudRate = rate;
|
||||
huart1.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
huart1.Init.StopBits = UART_STOPBITS_1;
|
||||
huart1.Init.Parity = UART_PARITY_NONE;
|
||||
huart1.Init.Mode = UART_MODE_TX_RX;
|
||||
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
||||
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
|
||||
if (HAL_UART_Init(&huart1) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
}
|
||||
huart1.Instance = USART1;
|
||||
huart1.Init.BaudRate = rate;
|
||||
huart1.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
huart1.Init.StopBits = UART_STOPBITS_1;
|
||||
huart1.Init.Parity = UART_PARITY_NONE;
|
||||
huart1.Init.Mode = UART_MODE_TX_RX;
|
||||
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
||||
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
|
||||
if (HAL_UART_Init(&huart1) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
|
||||
{
|
||||
if (huart->Instance == USART1)
|
||||
{
|
||||
/* 调用RF433模块的UART接收回调 */
|
||||
rf433_hal_uart_rxcplt_callback();
|
||||
}
|
||||
}
|
||||
/* UART回调函数已移至main.c统一管理 */
|
||||
|
||||
/* USER CODE END 1 */
|
||||
|
||||
Reference in New Issue
Block a user