2026-03-27 10:09:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
* @file io_monitor.c
|
|
|
|
|
|
* @brief IO状态监控模块实现
|
|
|
|
|
|
* @author Application Layer
|
2026-03-27 16:21:00 +08:00
|
|
|
|
* @version 1.2
|
2026-03-27 10:09:13 +08:00
|
|
|
|
******************************************************************************
|
|
|
|
|
|
* @attention
|
|
|
|
|
|
* 本模块实现四路数字输入的状态监控
|
|
|
|
|
|
* 关键特性:
|
|
|
|
|
|
* 1. 10ms定时扫描,平衡响应速度和CPU占用
|
|
|
|
|
|
* 2. 软件去抖,连续3次相同状态才确认变化
|
2026-03-27 16:21:00 +08:00
|
|
|
|
* 3. 状态变化时通过回调函数上报,支持多端口路由
|
|
|
|
|
|
*
|
2026-03-27 10:09:13 +08:00
|
|
|
|
* 修订历史:
|
2026-03-27 16:21:00 +08:00
|
|
|
|
* v1.2 - 增加事件回调机制,支持多端口路由
|
2026-03-27 10:09:13 +08:00
|
|
|
|
* v1.1 - 修复审查报告中危-5:去抖计数器初始化优化
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "io_monitor.h"
|
|
|
|
|
|
#include "uart2_print.h"
|
2026-03-27 16:29:37 +08:00
|
|
|
|
#include "multi_uart_router.h"
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#include "main.h"
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 调试宏定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/* DEBUG_IO_MONITOR: 调试日志开关,置1时启用调试输出,置0时禁用 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#define DEBUG_IO_MONITOR 1
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG_IO_MONITOR
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 调试日志宏,带模块前缀"[IO]"方便在串口调试终端中过滤日志 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[IO] " fmt "\r\n", ##__VA_ARGS__)
|
|
|
|
|
|
#else
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 禁用调试日志时,将宏定义为空,避免生成无用代码 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#define DEBUG_LOG(fmt, ...)
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 数据结构定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief IO通道数据结构
|
|
|
|
|
|
* @note 用于描述一个数字输入通道的完整状态信息
|
|
|
|
|
|
*
|
|
|
|
|
|
* 设计目的:
|
|
|
|
|
|
* 该结构体封装了监控单个数字输入(DI)通道所需的全部状态信息,包括
|
|
|
|
|
|
* 硬件连接参数(GPIO端口和引脚)、当前稳定状态、去抖计数器、原始采样状态
|
|
|
|
|
|
* 以及状态变化统计。这使得多通道扫描逻辑能够以统一的结构处理每个通道。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 字段说明:
|
|
|
|
|
|
* - port: GPIO端口指针,指向硬件寄存器(如GPIOB)
|
|
|
|
|
|
* - pin: GPIO引脚编号(如GPIO_PIN_4),用于HAL库读取函数
|
|
|
|
|
|
* - current_state: 经过去抖处理后的稳定状态,0=低电平,1=高电平
|
|
|
|
|
|
* - debounce_counter: 去抖计数器,累加相同采样值的次数,达到阈值才确认状态变化
|
|
|
|
|
|
* - last_raw_state: 上一次采样的原始电平状态,用于检测电平变化
|
|
|
|
|
|
* - change_count: 状态变化总次数统计,用于诊断和监控
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
typedef struct {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
GPIO_TypeDef *port; /**< GPIO端口指针,指向外设寄存器基地址 */
|
|
|
|
|
|
uint16_t pin; /**< GPIO引脚编号,对应HAL库的引脚定义 */
|
|
|
|
|
|
uint8_t current_state; /**< 经去抖处理后的稳定状态,0=低电平,1=高电平 */
|
|
|
|
|
|
uint8_t debounce_counter; /**< 去抖计数器,连续采样到相同值的次数 */
|
|
|
|
|
|
uint8_t last_raw_state; /**< 上一次采样的原始电平,用于变化检测 */
|
|
|
|
|
|
uint32_t change_count; /**< 状态变化累计次数,用于性能监控 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
} io_channel_t;
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 全局变量定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 四路数字输入通道配置表
|
|
|
|
|
|
* @note 静态初始化表,定义四个监控通道的GPIO硬件连接
|
|
|
|
|
|
*
|
|
|
|
|
|
* 各通道映射关系:
|
|
|
|
|
|
* - 通道0: GPIOB, GPIO_PIN_4
|
|
|
|
|
|
* - 通道1: GPIOB, GPIO_PIN_5
|
|
|
|
|
|
* - 通道2: GPIOB, GPIO_PIN_6
|
|
|
|
|
|
* - 通道3: GPIOB, GPIO_PIN_7
|
|
|
|
|
|
*
|
|
|
|
|
|
* 初始化值说明:所有状态和计数器均初始化为0,表示上电初始状态未知
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
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}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 上一次扫描时刻的时间戳
|
|
|
|
|
|
* @note 用于实现10ms固定间隔扫描,避免每次都执行扫描操作
|
|
|
|
|
|
* HAL_GetTick()返回系统运行毫秒数
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
static uint32_t last_scan_tick = 0;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 事件上报使能标志
|
|
|
|
|
|
* @note 置true时允许状态变化自动上报,置false时禁止上报
|
|
|
|
|
|
* 可用于批量操作时临时抑制不必要的事件通知
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
static bool report_enabled = true;
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief IO事件回调函数指针
|
|
|
|
|
|
* @note 设置后,IO状态变化将通过回调函数上报
|
|
|
|
|
|
* 为NULL时使用默认的UART2_Print_String输出
|
|
|
|
|
|
*/
|
|
|
|
|
|
static io_event_callback_t g_event_callback = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 内部静态函数声明
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 计算异或校验和
|
|
|
|
|
|
* @note 对输入数据的每个字节执行异或运算,生成校验码
|
|
|
|
|
|
* 用于DI_EVENT消息的校验和计算
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param data: 待计算校验和的数据缓冲区(输入)
|
|
|
|
|
|
* @param len: 数据长度,以字节为单位(输入)
|
|
|
|
|
|
* @return 校验和: uint8_t类型的异或结果
|
|
|
|
|
|
*
|
|
|
|
|
|
* 算法原理:遍历数据缓冲区,将每个字节与累加器进行异或操作
|
|
|
|
|
|
* 初始值为0,最终结果为所有字节的异或和
|
|
|
|
|
|
*/
|
|
|
|
|
|
static uint8_t calc_checksum(const char *data, uint8_t len);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送DI状态变化事件
|
|
|
|
|
|
* @note 构造并发送ASCII格式的状态变化消息至UART2
|
|
|
|
|
|
* 消息格式: $DI_EVENT,<channel>,<state>*<checksum>\r\n
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param channel: 通道编号,从0开始计数(输入)
|
|
|
|
|
|
* @param state: 通道状态,0=低电平,1=高电平(输入)
|
|
|
|
|
|
* @return 无返回值
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调用说明:
|
|
|
|
|
|
* - 此函数在状态变化被确认后调用,用于通知上位机或日志系统
|
|
|
|
|
|
* - 内部会调用calc_checksum计算校验和
|
|
|
|
|
|
* - 通过UART2_Print_String发送原始字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
static void send_di_event(uint8_t channel, uint8_t state);
|
|
|
|
|
|
|
|
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 内部静态函数实现
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 计算异或校验和
|
|
|
|
|
|
* @note 对输入数据的每个字节执行异或运算,生成校验码
|
|
|
|
|
|
* 用于DI_EVENT消息的校验和计算
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param data: 待计算校验和的数据缓冲区(输入)
|
|
|
|
|
|
* @param len: 数据长度,以字节为单位(输入)
|
|
|
|
|
|
* @return 校验和: uint8_t类型的异或结果
|
|
|
|
|
|
*
|
|
|
|
|
|
* 算法原理:遍历数据缓冲区,将每个字节与累加器进行异或操作
|
|
|
|
|
|
* 初始值为0,最终结果为所有字节的异或和
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送DI状态变化事件
|
|
|
|
|
|
* @note 构造并发送ASCII格式的状态变化消息
|
|
|
|
|
|
* 消息格式: $DI_EVENT,<channel>,<state>*<checksum>\r\n
|
|
|
|
|
|
* 如果设置了回调函数,则通过回调发送;否则发送到UART2
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param channel: 通道编号,从0开始计数(输入)
|
|
|
|
|
|
* @param state: 通道状态,0=低电平,1=高电平(输入)
|
|
|
|
|
|
* @return 无返回值
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调用说明:
|
|
|
|
|
|
* - 此函数在状态变化被确认后调用,用于通知上位机或日志系统
|
|
|
|
|
|
* - 内部会调用calc_checksum计算校验和
|
|
|
|
|
|
* - 如果设置了回调函数,则通过回调发送;否则通过UART2_Print_String发送
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
static void send_di_event(uint8_t channel, uint8_t state)
|
|
|
|
|
|
{
|
|
|
|
|
|
char msg[32];
|
|
|
|
|
|
uint8_t cs;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 构造消息主体,channel+1将0-base转换为1-base的用户可见编号 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
int len = snprintf(msg, sizeof(msg), "$DI_EVENT,%d,%d*", channel + 1, state);
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 计算异或校验和,跳过'$'符号只对正文部分计算 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
cs = calc_checksum(msg + 1, len - 1);
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 将校验和追加到消息末尾,格式为两位十六进制数 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs);
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
2026-03-27 16:29:37 +08:00
|
|
|
|
/* 输出调试日志到UART2,记录状态变化 */
|
|
|
|
|
|
DEBUG_LOG("CH%d -> %s", channel + 1, state ? "HIGH" : "LOW");
|
|
|
|
|
|
|
|
|
|
|
|
/* 向UART1(RF433)发送状态变化事件 */
|
|
|
|
|
|
MultiUART_SendString(PORT_UART1, msg);
|
|
|
|
|
|
|
|
|
|
|
|
/* 输出完整消息到UART2,方便调试查看 */
|
|
|
|
|
|
DEBUG_LOG("RF433 TX: \"%s\"", msg);
|
|
|
|
|
|
|
|
|
|
|
|
/* 如果设置了回调函数,也通过回调发送 */
|
2026-03-27 16:21:00 +08:00
|
|
|
|
if (g_event_callback != NULL) {
|
|
|
|
|
|
g_event_callback(channel, state, msg);
|
|
|
|
|
|
}
|
2026-03-27 10:09:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 公共函数实现
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief IO监控模块初始化
|
|
|
|
|
|
* @note 在系统启动时调用,初始化所有数字输入通道的默认状态
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能说明:
|
|
|
|
|
|
* 1. 读取各通道GPIO引脚的当前电平状态
|
|
|
|
|
|
* 2. 初始化去抖计数器为1(已稳定采样一次)
|
|
|
|
|
|
* 3. 重置变化计数器和扫描时间戳
|
|
|
|
|
|
* 4. 使能事件上报功能
|
|
|
|
|
|
*
|
|
|
|
|
|
* 初始化策略:
|
|
|
|
|
|
* - 去抖计数器初始化为1而非0,这样首次采样时只需再连续采样2次
|
|
|
|
|
|
* 即可确认状态,相比初始化为0可加快首次状态确认速度
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void IO_Monitor_Init(void)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 遍历所有IO通道进行初始化 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
for (int i = 0; i < IO_CHANNEL_COUNT; i++) {
|
|
|
|
|
|
io_channel_t *ch = &di_channels[i];
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 读取GPIO当前电平,HAL_GPIO_ReadPin返回GPIO_PinState类型 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->current_state = HAL_GPIO_ReadPin(ch->port, ch->pin) ? 1 : 0;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 记录原始状态作为上次采样值 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->last_raw_state = ch->current_state;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 去抖计数器初始化为1,已完成首次稳定采样 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->debounce_counter = 1;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 变化计数清零 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->change_count = 0;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 初始化扫描时间戳为0,确保首次扫描立即执行 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
last_scan_tick = 0;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 使能自动上报功能 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
report_enabled = true;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 输出初始化完成日志,显示初始各通道状态掩码 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
DEBUG_LOG("Init OK, initial states: 0x%02X", IO_Monitor_GetAllStates());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief IO监控定时任务
|
|
|
|
|
|
* @note 应在主循环或定时器中断中周期性调用,执行扫描和去抖处理
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 算法说明 - 软件去抖三段式状态机:
|
|
|
|
|
|
* 阶段1: 采样值与上次相同 -> 计数器累加
|
|
|
|
|
|
* 阶段2: 采样值与上次不同 -> 重置计数器并更新上次值
|
|
|
|
|
|
* 阶段3: 计数器达到阈值(IO_DEBOUNCE_COUNT)且与当前稳定状态不同
|
|
|
|
|
|
* -> 确认状态变化,更新current_state,触发事件上报
|
|
|
|
|
|
*
|
|
|
|
|
|
* 去抖阈值说明:
|
|
|
|
|
|
* - IO_DEBOUNCE_COUNT通常定义为3,表示连续3次采样相同才确认
|
|
|
|
|
|
* - 结合IO_SCAN_PERIOD_MS(10ms),去抖确认时间为20-30ms
|
|
|
|
|
|
* - 可有效滤除机械开关的抖动噪声
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void IO_Monitor_Task(void)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 获取当前系统时间戳(毫秒) */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint32_t current_tick = HAL_GetTick();
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 扫描周期控制
|
|
|
|
|
|
* 仅当距上次扫描超过IO_SCAN_PERIOD_MS时才执行新扫描
|
|
|
|
|
|
* 这种节流机制可避免高频调用时CPU资源浪费
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (current_tick - last_scan_tick < IO_SCAN_PERIOD_MS) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 更新扫描时间戳 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
last_scan_tick = current_tick;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 遍历所有通道执行去抖扫描
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
for (int i = 0; i < IO_CHANNEL_COUNT; i++) {
|
|
|
|
|
|
io_channel_t *ch = &di_channels[i];
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 读取GPIO引脚当前电平,转换为0/1表示 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint8_t raw_state = HAL_GPIO_ReadPin(ch->port, ch->pin) ? 1 : 0;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 去抖逻辑分支
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (raw_state != ch->last_raw_state) {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 分支1: 采样值发生变化 -> 重置去抖计数器 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->debounce_counter = 0;
|
|
|
|
|
|
ch->last_raw_state = raw_state;
|
|
|
|
|
|
} else {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 分支2: 采样值与上次相同 -> 累加去抖计数器 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (ch->debounce_counter < IO_DEBOUNCE_COUNT) {
|
|
|
|
|
|
ch->debounce_counter++;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
/* 分支3: 计数器达到阈值且与稳定状态不一致 -> 确认状态变化 */
|
|
|
|
|
|
else if (ch->current_state != raw_state) {
|
|
|
|
|
|
/* 更新为新确认的稳定状态 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->current_state = raw_state;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 增加变化统计计数 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
ch->change_count++;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 事件上报(已使能情况下) */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (report_enabled) {
|
|
|
|
|
|
send_di_event(i, raw_state);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取指定通道的当前状态
|
|
|
|
|
|
* @note 返回经过去抖处理后的稳定状态值
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param channel: 通道编号,从0开始计数(输入)
|
|
|
|
|
|
* @return 通道状态: 0=低电平,1=高电平,通道无效时返回0
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用说明:
|
|
|
|
|
|
* - 通道编号超出有效范围(0-3)时静默返回0,不报错
|
|
|
|
|
|
* - 返回的是已去抖的稳定状态,非原始采样值
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint8_t IO_Monitor_GetState(uint8_t channel)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 参数边界检查,超出范围的通道返回0 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (channel >= IO_CHANNEL_COUNT) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return di_channels[channel].current_state;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取所有通道状态的组合掩码
|
|
|
|
|
|
* @note 将四路通道状态打包为一个字节返回,方便批量读取和显示
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 通道状态掩码: uint8_t类型,每位对应一个通道状态
|
|
|
|
|
|
*
|
|
|
|
|
|
* 位域定义:
|
|
|
|
|
|
* - bit0: 通道0状态
|
|
|
|
|
|
* - bit1: 通道1状态
|
|
|
|
|
|
* - bit2: 通道2状态
|
|
|
|
|
|
* - bit3: 通道3状态
|
|
|
|
|
|
*
|
|
|
|
|
|
* 示例:返回0x05(0101b)表示通道0=高、通道1=低、通道2=高、通道3=低
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint8_t IO_Monitor_GetAllStates(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint8_t states = 0;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 遍历所有通道,将各通道状态按位组合成掩码 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
for (int i = 0; i < IO_CHANNEL_COUNT; i++) {
|
|
|
|
|
|
if (di_channels[i].current_state) {
|
|
|
|
|
|
states |= (1 << i);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return states;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置事件上报使能状态
|
|
|
|
|
|
* @note 可用于在批量操作或初始化过程中临时抑制事件上报
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param enable: true=使能上报,false=禁用上报(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* - 系统初始化期间不希望产生大量事件,可先禁用再恢复
|
|
|
|
|
|
* - 批量设置多个通道状态时,可先禁用避免中间状态触发事件
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void IO_Monitor_EnableReport(bool enable)
|
|
|
|
|
|
{
|
|
|
|
|
|
report_enabled = enable;
|
|
|
|
|
|
DEBUG_LOG("Report %s", enable ? "enabled" : "disabled");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取指定通道的状态变化次数
|
|
|
|
|
|
* @note 用于诊断和性能监控,统计各通道状态变化频率
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param channel: 通道编号,从0开始计数(输入)
|
|
|
|
|
|
* @return 变化次数: uint32_t类型,通道无效时返回0
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用说明:
|
|
|
|
|
|
* - 变化次数从模块初始化后开始累计
|
|
|
|
|
|
* - 可用于检测某通道是否频繁触发或存在故障(如开关抖动)
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint32_t IO_Monitor_GetChangeCount(uint8_t channel)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 参数边界检查,超出范围返回0 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (channel >= IO_CHANNEL_COUNT) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return di_channels[channel].change_count;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置IO事件回调函数
|
|
|
|
|
|
* @note 设置后,IO状态变化将通过回调函数上报
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param callback: 回调函数指针,NULL则使用默认UART2输出(输入)
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用说明:
|
|
|
|
|
|
* - 设置回调后,send_di_event()将通过回调发送事件
|
|
|
|
|
|
* - 传入NULL恢复默认UART2输出方式
|
|
|
|
|
|
* - 回调函数原型: void callback(uint8_t channel, uint8_t state, const char *event_msg)
|
|
|
|
|
|
*/
|
|
|
|
|
|
void IO_Monitor_SetEventCallback(io_event_callback_t callback)
|
|
|
|
|
|
{
|
|
|
|
|
|
g_event_callback = callback;
|
|
|
|
|
|
DEBUG_LOG("Event callback %s", callback ? "set" : "cleared");
|
|
|
|
|
|
}
|