STM32从零到量产开发:四路继电器工业控制模块开发— 数字输入(bsp_di)设计说明
·
STM32从零到量产开发:四路继电器工业控制模块开发— 数字输入(bsp_di)设计说明
1. 模块概述
本模块实现了 STM32 四路继电器工业控制板上的 4通道光耦隔离数字输入(DI) 驱动。该驱动封装了底层 GPIO 读取逻辑,提供统一的逻辑电平接口,并内置软件消抖机制,确保在工业现场环境下获取稳定可靠的输入状态。
核心特性
- 硬件抽象:统一封装 GPIO 端口与引脚,上层业务无需关心具体引脚映射。
- 逻辑统一:自动处理光耦“低电平有效”特性,对外统一暴露
1=有输入,0=无输入。 - 软件消抖:基于系统时基
HAL_GetTick()实现非阻塞式定时消抖,有效滤除工业现场干扰。 - 多模式读取:支持单通道读取、全通道位图读取及原始电平调试读取。
2. 硬件配置与映射
2.1 引脚定义
驱动依赖 CubeMX 生成的 main.h 中的宏定义,具体映射关系如下:
| 通道编号 | GPIO 端口 | 引脚号 | 备注 |
|---|---|---|---|
| DI1 | DI1_GPIO_Port |
DI1_Pin (PB0) |
光耦隔离输入 |
| DI2 | DI2_GPIO_Port |
DI2_Pin (PB1) |
光耦隔离输入 |
| DI3 | DI3_GPIO_Port |
DI3_Pin (PB10) |
光耦隔离输入 |
| DI4 | DI4_GPIO_Port |
DI4_Pin (PB11) |
光耦隔离输入 |
2.2 电气特性与极性
- 输入模式:GPIO 配置为 上拉输入。
- 有效电平:低电平有效(Active Low)。
- 外部信号接入 $
ightarrow$ 光耦导通 $
ightarrow$ 引脚被拉低 (GPIO_PIN_RESET)。 - 外部信号断开 $
ightarrow$ 光耦截止 $
ightarrow$ 引脚被上拉至高电平 (GPIO_PIN_SET)。
- 外部信号接入 $
- 逻辑转换:驱动内部自动将物理低电平转换为逻辑
1。
3. 核心数据结构
3.1 硬件引脚映射表
用于解耦具体引脚配置,便于移植:
typedef struct {
GPIO_TypeDef *port; // GPIO 端口指针
uint16_t pin; // GPIO 引脚掩码
} di_pin_t;
static const di_pin_t s_di_tbl[DI_CH_NUM] = {
{ DI1_GPIO_Port, DI1_Pin },
{ DI2_GPIO_Port, DI2_Pin },
{ DI3_GPIO_Port, DI3_Pin },
{ DI4_GPIO_Port, DI4_Pin },
};
3.2 消抖状态管理结构
每个通道独立维护消抖状态,避免通道间相互影响:
typedef struct {
uint8_t raw; // 实时原始引脚电平(仅底层扫描使用)
uint8_t stable; // 消抖完成后的稳定状态(业务/Modbus读取)
uint32_t last_change; // 电平跳变时间戳 (ms),消抖计时基准
} di_debounce_t;
static di_debounce_t s_di[DI_CH_NUM]; // 4路独立状态缓存
4. 软件消抖机制
4.1 设计原理
采用 状态机 + 时间戳 的非阻塞消抖算法,不占用 CPU 资源,适合在主循环中周期调用。
4.2 工作流程
- 采样:读取当前物理电平
cur。 - 跳变检测:
- 若
cur != raw:更新raw,重置last_change = HAL_GetTick()。 - 若
cur == raw:保持当前状态,继续计时。
- 若
- 稳定确认:
- 条件:
cur != stable且(HAL_GetTick() - last_change) >= DI_DEBOUNCE_MS。 - 动作:更新
stable = cur,通知上层状态变更。
- 条件:
4.3 参数配置
#define DI_DEBOUNCE_MS 10u // 消抖确认时长 10ms
- 建议:工业场景下光耦输入建议 5~10ms;干接点/传感器输入建议 3~5ms。
- 扫描周期:推荐主循环调用
BSP_DI_Scan()周期为 1~5ms。
5. API 接口详解
5.1 初始化
void BSP_DI_Init(void);
- 功能:初始化消抖状态缓存,读取当前电平作为初始稳定值。
- 注意:GPIO 硬件模式需先由
MX_GPIO_Init()配置完成。
5.2 周期扫描
void BSP_DI_Scan(void);
- 功能:执行 4 路通道的消抖状态机更新。
- 调用要求:必须在主循环中 周期性调用,依赖
HAL_GetTick()时基。
5.3 获取单路状态
uint8_t BSP_DI_Get(uint8_t ch);
- 参数:
ch通道编号 (1~4)。 - 返回:
1=有输入,0=无输入。非法通道返回0。 - 用途:业务逻辑判断、Modbus 寄存器映射。
5.4 获取全通道状态
uint8_t BSP_DI_GetAll(void);
- 返回:4位有效掩码。
bit0$
ightarrow$ DI1bit1$
ightarrow$ DI2bit2$
ightarrow$ DI3bit3$
ightarrow$ DI4
- 用途:批量状态上报、快速位运算判断。
5.5 获取原始电平(调试用)
uint8_t BSP_DI_GetRaw(uint8_t ch);
- 参数:
ch通道编号 (1~4)。 - 返回:
1=低电平(有输入),0=高电平(无输入)。 - 用途:硬件排查、消抖参数调试、光耦故障诊断。
6. 使用示例
// 1. 系统初始化阶段
BSP_DI_Init();
// 2. 主循环 (Main Loop)
while (1) {
BSP_DI_Scan(); // 必须周期调用,建议 1-5ms
// 业务逻辑
if (BSP_DI_Get(1)) {
// DI1 触发,执行相应动作
Relay_Control(RELAY_1, ON);
}
// 批量检查
uint8_t all_di = BSP_DI_GetAll();
if (all_di == 0x0F) {
// 4路全通
}
HAL_Delay(1); // 简易延时,实际项目建议使用定时器中断或 RTOS 任务
}
7. 注意事项
- 时基依赖:
BSP_DI_Scan强依赖HAL_GetTick(),确保 SysTick 中断正常运行且未被长时间阻塞。 - 扫描频率:扫描周期应小于消抖时间(
Scan_Period < DI_DEBOUNCE_MS),否则可能导致消抖失效或响应延迟。 - 线程安全:当前实现为裸机单线程模型。若移植至 RTOS,需对
s_di数组加互斥锁保护。 - 硬件一致性:修改引脚映射时,务必同步更新
s_di_tbl和main.h中的定义。
bsp_di.h代码实现
/**
******************************************************************************
* @file bsp_di.h
* @brief 4通道光耦隔离数字输入驱动 - 对应产品规格书 3.3 数字输入端子
*
* 硬件映射 (来自 CubeMX main.h):
* DI1 -> PB0, DI2 -> PB1, DI3 -> PB10, DI4 -> PB11
*
* 极性说明:
* GPIO 配置为上拉输入, 光耦导通时引脚被拉低
* 规格书 3.3: "输入为低有效" -> 读到低电平 = 外部有输入信号
* 本驱动对上层统一暴露逻辑值: 1=有输入, 0=无输入
*
* 消抖机制说明:
* 软件定时消抖,多次采样电平保持 DEBOUNCE_CONFIRM_MS 毫秒一致,才更新稳定状态
* 扫描依赖系统毫秒时基 HAL_GetTick()
******************************************************************************
*/
#ifndef __BSP_DI_H
#define __BSP_DI_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
/* 数字输入总通道数量 */
#define DI_CH_NUM 4u
/* 软件消抖确认时长配置说明 */
/* 如果按键输入硬件已经有消抖电容,工业场景软件消抖取5~10ms */
/* 如果是无光耦隔离/外部干接点/传感器输入,软件消抖不能省,建议设 3~5ms */
#define DI_DEBOUNCE_MS 10u
/**
* @brief 初始化4路数字输入GPIO,初始化消抖缓存状态
* @note GPIO硬件模式由MX_GPIO_Init完成配置,本函数仅初始化内部消抖变量
*/
void BSP_DI_Init(void);
/**
* @brief DI通道消抖定时扫描函数
* @note 必须在主循环固定周期调用,推荐扫描周期 1~5ms;依赖 HAL_GetTick() 获取系统毫秒时基
*/
void BSP_DI_Scan(void);
/**
* @brief 获取单路DI经过软件消抖后的稳定逻辑状态(业务正常读取接口)
* @param ch: 通道编号 1~4
* @retval 1=外部有输入信号,0=无输入信号
*/
uint8_t BSP_DI_Get(uint8_t ch);
/**
* @brief 批量获取全部4路DI消抖后稳定状态,返回位图掩码
* @retval 4位有效掩码
* bit0对应DI1、bit1对应DI2、bit2对应DI3、bit3对应DI4;bit=1代表该通道有输入
*/
uint8_t BSP_DI_GetAll(void);
/**
* @brief 获取单路DI当前实时原始电平(未经过软件消抖,仅调试排查硬件时使用)
* @param ch: 通道编号 1~4
* @retval 1=硬件检测到输入,0=无输入
*/
uint8_t BSP_DI_GetRaw(uint8_t ch);
#ifdef __cplusplus
}
#endif
#endif /* __BSP_DI_H */
bsp_di.c代码实现
/**
******************************************************************************
* @file bsp_di.c
* @brief 4通道数字输入驱动实现 (含软件消抖)
******************************************************************************
*/
#include "bsp_di.h"
#include "main.h"
/* ===================== 硬件引脚定义 ===================== */
/* DI 引脚映射结构体 (通道号 1~4 对应数组索引 0~3) */
typedef struct
{
GPIO_TypeDef *port;
uint16_t pin;
} di_pin_t;
/* 硬件引脚映射表 */
static const di_pin_t s_di_tbl[DI_CH_NUM] =
{
{ DI1_GPIO_Port, DI1_Pin },
{ DI2_GPIO_Port, DI2_Pin },
{ DI3_GPIO_Port, DI3_Pin },
{ DI4_GPIO_Port, DI4_Pin },
};
/* ===================== 消抖状态结构定义 ===================== */
/* 单通道消抖状态结构体 */
typedef struct
{
uint8_t raw; /* 实时原始引脚电平,仅底层扫描使用,上层禁止读取 */
uint8_t stable; /* 消抖完成稳定状态,业务/Modbus读取 */
uint32_t last_change; /* 电平跳变时间戳(ms),消抖计时基准 */
} di_debounce_t;
/* 四路独立消抖状态缓存 */
static di_debounce_t s_di[DI_CH_NUM];
/* ===================== 内部底层私有函数 ===================== */
/**
* @brief 底层读取单通道原始引脚电平
* @param idx 内部数组下标 0~3
* @retval 1 = 低电平有输入,0 = 无输入
* @note 硬件电平适配层,仅此处修改高低有效逻辑,上层全部无感
*/
static uint8_t DI_ReadRawLevel(uint8_t idx)
{
return (HAL_GPIO_ReadPin(s_di_tbl[idx].port, s_di_tbl[idx].pin) == GPIO_PIN_RESET) ? 1u : 0u;
}
/* ===================== 对外公共接口 ===================== */
/**
* @brief DI模块初始化,初始化所有通道消抖状态
*/
void BSP_DI_Init(void)
{
uint8_t i;
uint32_t now = HAL_GetTick();
for (i = 0u; i < DI_CH_NUM; i++)
{
s_di[i].raw = DI_ReadRawLevel(i);
s_di[i].stable = s_di[i].raw;
s_di[i].last_change = now;
}
}
/**
* @brief 周期消抖扫描函数,主循环定时调用
* @note 检测电平跳变重置计时,稳定达到消抖时长后更新可靠状态
*/
void BSP_DI_Scan(void)
{
uint8_t i;
uint8_t cur;
uint32_t now = HAL_GetTick();
for (i = 0u; i < DI_CH_NUM; i++)
{
cur = DI_ReadRawLevel(i);
if (cur != s_di[i].raw)
{
s_di[i].raw = cur;
s_di[i].last_change = now;
}
else
{
if ((cur != s_di[i].stable) && ((now - s_di[i].last_change) >= DI_DEBOUNCE_MS))
{
s_di[i].stable = cur;
}
}
}
}
/**
* @brief 获取单路消抖完成后的DI状态
* @param ch 通道号 1~4
* @retval 0无输入 / 1有输入,非法通道返回0
*/
uint8_t BSP_DI_Get(uint8_t ch)
{
if (ch < 1u || ch > DI_CH_NUM)
{
return 0u;
}
return s_di[ch - 1u].stable;
}
/**
* @brief 获取四路DI整体状态掩码
* @retval bit0~bit3 对应 DI1~DI4,bit置1代表有输入
*/
uint8_t BSP_DI_GetAll(void)
{
uint8_t i;
uint8_t mask = 0u;
for (i = 0u; i < DI_CH_NUM; i++)
{
if (s_di[i].stable)
{
mask |= (uint8_t)(1u << i);
}
}
return mask;
}
/**
* @brief 获取单路未消抖原始电平,仅调试诊断使用
* @param ch 通道号 1~4
* @retval 1低电平有输入 / 0无输入,非法通道返回0
*/
uint8_t BSP_DI_GetRaw(uint8_t ch)
{
if (ch < 1u || ch > DI_CH_NUM)
{
return 0u;
}
return DI_ReadRawLevel(ch - 1u);
}
更多推荐



所有评论(0)