STM32从零到量产开发:四路继电器工业控制模块开发-四路 LED 指示灯底层驱动模块(bsp_led)设计说明
·
STM32从零到量产开发:四路继电器工业控制模块开发-四路 LED 指示灯底层驱动模块(bsp_led)设计说明
1. 模块概述
本模块为面向工业量产级别的 STM32 四路 LED 指示灯底层驱动,涵盖以下四路独立指示灯的控制逻辑:
| 指示灯 | 标识 | 功能说明 |
|---|---|---|
| RUN | 运行指示 | 系统正常运行心跳 |
| COMM | 通信指示 | RS485 收发活动 |
| FAULT | 故障告警 | 故障状态高频闪烁 |
| RS232 | 串口交互 | RS232 收发活动 |
核心设计亮点:完全摒弃阻塞式延时函数(如 HAL_Delay),依托 HAL_GetTick() 毫秒时基,采用非阻塞状态机架构实现复杂灯效控制。
2. 核心架构与硬件适配
2.1 非阻塞状态机
通过在主循环中高频(5~10ms)调用 BSP_LED_Refresh() 函数,利用时间戳差值计算来驱动灯效状态翻转,确保系统实时性不受影响。
2.2 硬件底层抽象
封装私有函数 LED_Write(),统一处理硬件低电平有效的逻辑:
GPIO_PIN_RESET→ 点亮GPIO_PIN_SET→ 熄灭
实现业务逻辑与底层硬件引脚的单点维护。
2.3 时序参数宏定义
将闪烁周期、脉冲宽度等参数统一提取为宏,便于后期根据产品规格书灵活调整:
#define LED_SLOW_PERIOD 1000U // RUN 灯慢闪周期(ms)
#define LED_FAST_PERIOD 100U // FAULT 灯快闪周期(ms)
#define LED_PULSE_MS 50U // COMM/RS232 脉冲宽度(ms)
#define LED_SELFTEST_MS 1500U // 自检全亮持续时间(ms)
3. 四类灯效的业务逻辑
3.1 RUN 运行灯(常驻慢闪)
- 频率:1Hz(1000ms 周期,500ms 亮 / 500ms 灭)
- 用途:系统正常运行的心跳指示
- 优先级:受自检逻辑控制,自检期间暂停
3.2 FAULT 故障灯(条件快闪)
- 接口:
BSP_LED_SetFault(bool active) - 触发时:以 10Hz(100ms 周期)高频闪烁告警
- 清除时:立即熄灭
- 防抖机制:加入状态判断(
if (active && !s_fault_active)),避免主循环高频重复调用时计时基准不断重置
3.3 COMM / RS232 通信灯(瞬时脉冲)
- 触发时:立即点亮,记录 50ms 后的超时时间戳
- 刷新时:状态机检测超时并自动熄灭
- 高频触发:若持续高频触发,呈现常亮效果
3.4 SelfTest 硬件自检(最高优先级)
- 接口:
BSP_LED_SelfTest() - 行为:四灯同步全亮 1.5s
- 优先级:自检期间强制屏蔽所有常规闪烁与脉冲逻辑
- 用途:产线硬件接线校验
- 结束后:自动恢复常规灯效
4. 工程化设计细节
4.1 变量封装
所有计时基准和状态标志均声明为 static 私有变量,严格限制作用域,防止外部模块意外篡改:
static uint32_t s_run_last; // RUN 灯翻转时间戳
static uint32_t s_fault_last; // FAULT 灯翻转时间戳
static bool s_fault_active; // 故障激活标志
static uint32_t s_comm_off_at; // COMM 灯熄灭时间戳
static uint32_t s_rs232_off_at; // RS232 灯熄灭时间戳
static uint32_t s_test_off_at; // 自检结束时间戳
4.2 溢出安全处理
在判断时间戳是否到期时,采用有符号差值比较:
if ((int32_t)(now - s_test_off_at) >= 0)
{
// 自检时间到,退出自检模式
}
完美兼容 HAL_GetTick() 在系统运行 49.7 天后发生的 32 位无符号整数溢出归零场景。
4.3 防抖与状态锁定
故障设置接口中加入状态判断,避免在主循环中被高频重复调用时导致的计时基准不断重置问题:
void BSP_LED_SetFault(bool active)
{
if (active && !s_fault_active)
{
s_fault_active = true;
s_fault_last = HAL_GetTick(); // 仅在状态变化时重置基准
}
else if (!active && s_fault_active)
{
s_fault_active = false;
LED_Write(PIN_FAULT, false); // 故障清除,立即熄灭
}
}
5. 设计总结
| 设计维度 | 具体措施 |
|---|---|
| 实时性 | 非阻塞状态机,无 HAL_Delay |
| 可维护性 | 硬件抽象 + 宏定义参数化 |
| 安全性 | static 封装 + 溢出安全比较 |
| 可靠性 | 防抖锁定 + 自检优先级屏蔽 |
| 可测试性 | 产线自检模式,四灯全亮校验接线 |
该模块结构清晰、注释详尽,展现了工业级嵌入式代码素养,兼顾功能完整性、系统稳定性与后期可维护性。
/**
******************************************************************************
* @file bsp_led.h
* @brief 四路LED指示灯底层驱动对外接口
* @note RUN/COMM/FAULT/RS232四路独立指示灯,非阻塞毫秒时基状态机
******************************************************************************
*/
#ifndef __BSP_LED_H
#define __BSP_LED_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
/**
* @brief LED模块初始化
* @note 初始化全部计时静态变量,上电熄灭所有LED
*/
void BSP_LED_Init(void);
/**
* @brief LED状态机刷新函数,主循环周期调用(5~10ms)
* @note 处理RUN慢闪、FAULT快闪、COMM/RS232脉冲超时熄灭、自检计时
*/
void BSP_LED_Refresh(void);
/**
* @brief Modbus RS485总线通信脉冲触发,收发数据点亮50ms
*/
void BSP_CommPulse(void);
/**
* @brief RS232上位机交互脉冲触发,收到串口指令点亮50ms
*/
void BSP_RS232Pulse(void);
/**
* @brief 设置故障告警灯开关
* @param active 1=开启FAULT快闪 0=关闭故障灯
*/
void BSP_LED_SetFault(uint8_t active);
/**
* @brief 四路LED整机自检,全部点亮1.5s后自动恢复正常灯效
*/
void BSP_LED_SelfTest(void);
#ifdef __cplusplus
}
#endif
#endif
/**
******************************************************************************
* @file bsp_led.c
* @brief 4路独立LED指示灯驱动实现(非阻塞状态机 + 通信瞬时脉冲)(#LED redesign)
* @details 依托HAL_GetTick毫秒时基实现无阻塞计时,区分常驻闪烁、瞬时脉冲、全局自检三类灯效逻辑
* 硬件统一低电平有效:GPIO_PIN_RESET=点亮,GPIO_PIN_SET=熄灭
******************************************************************************
*/
#include "bsp_led.h"
#include "main.h"
/* ===================== 1. 顶层时序配置宏(统一管理灯效时长参数) ===================== */
/* 闪烁周期单位ms,匹配产品规格书时序要求
* RUN运行灯 1Hz 慢闪;FAULT故障灯 10Hz 快闪 */
#define LED_SLOW_PERIOD 1000u /* RUN完整闪烁周期 1000ms */
#define LED_SLOW_ON_TIME 500u /* RUN单次点亮持续时长 */
#define LED_FAST_PERIOD 100u /* FAULT完整闪烁周期 100ms */
#define LED_FAST_ON_TIME 50u /* FAULT单次点亮持续时长 */
#define LED_PULSE_MS 50u /* RS485/RS232通信脉冲点亮宽度 */
#define LED_TEST_MS 1500u /* 四路LED硬件自检总持续时长 */
/* ===================== 2. 模块静态私有状态变量(仅本文件内部读写) ===================== */
static uint32_t s_run_last = 0u; /* RUN运行灯慢闪计时基准时间戳
* 读写位置:仅BSP_LED_Refresh内部读写 */
static uint8_t s_fault_active = 0u; /* 故障告警总开关 1=开启FAULT快闪,0=关闭告警
* 赋值入口:BSP_LED_SetFault();读取判断:BSP_LED_Refresh故障分支 */
static uint32_t s_fault_last = 0u; /* FAULT故障灯快闪计时基准时间戳
* 赋值入口:BSP_LED_SetFault();读取判断:BSP_LED_Refresh故障分支 */
static uint32_t s_comm_off_at = 0u; /* RS485通信脉冲熄灭截止时间戳,0代表无有效脉冲
* 赋值入口:BSP_LED_CommPulse(),赋值为当前滴答+LED_PULSE_MS;读取清零:BSP_LED_Refresh脉冲分支 */
static uint32_t s_rs232_off_at = 0u; /* RS232串口脉冲熄灭截止时间戳,0代表无有效脉冲
* 赋值入口:BSP_RS232Pulse(),赋值为当前滴答+LED_PULSE_MS;读取清零:BSP_LED_Refresh脉冲分支 */
static uint32_t s_test_off_at = 0u; /* LED全局自检模式截止时间戳,0代表无自检任务
* 赋值入口:BSP_LED_SelfTest(),赋值为当前滴答+LED_TEST_MS;读取清零:BSP_LED_Refresh自检分支 */
/* ===================== 3. 底层私有硬件IO适配函数(全文件统一调用,硬件逻辑单点维护) ===================== */
/**
* @brief LED底层引脚输出统一适配接口
* @param port: LED对应GPIO端口
* @param pin: LED对应引脚编号
* @param on: 逻辑状态 1=点亮,0=熄灭
* @note 硬件低电平有效,on=1输出GPIO_PIN_RESET,on=0输出GPIO_PIN_SET
*/
static void LED_Write(GPIO_TypeDef *port, uint16_t pin, uint8_t on)
{
HAL_GPIO_WritePin(port, pin, on ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
/* ===================== 4. 对外公共业务接口(按程序运行时序:初始化→核心刷新→状态控制→自检) ===================== */
/**
* @brief LED模块初始化,复位全部计时、状态缓存,四路LED上电统一熄灭
*/
void BSP_LED_Init(void)
{
s_run_last = HAL_GetTick();
s_fault_last = HAL_GetTick();
s_fault_active = 0u;
s_comm_off_at = 0u;
s_rs232_off_at = 0u;
s_test_off_at = 0u;
/* 四路LED初始全部熄灭 */
LED_Write(STATUS_LED_GPIO_Port, STATUS_LED_Pin, 0u);
LED_Write(COMM_LED_GPIO_Port, COMM_LED_Pin, 0u);
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 0u);
LED_Write(RS232_LED_GPIO_Port, RS232_LED_Pin, 0u);
}
/**
* @brief LED全局状态机定时刷新主函数,主循环轮询调用
* @note 推荐调用周期 5~10ms;依赖HAL_GetTick()系统毫秒时基
* 执行优先级:自检逻辑 > RUN常驻闪烁 > FAULT故障闪烁 > 通信脉冲超时熄灭
*/
void BSP_LED_Refresh(void)
{
uint32_t now = HAL_GetTick();
uint32_t elapsed;
/* 【双向关联指引】
* 变量 s_test_off_at:定义在文件顶部【模块静态私有状态变量】区块
* 变量赋值位置:文件末尾函数 BSP_LED_SelfTest(),调用时赋值为 HAL_GetTick() + LED_TEST_MS(1500ms)
* 变量非0代表自检任务正在执行;自检优先级最高,直接跳过所有常规灯效逻辑 */
if (s_test_off_at != 0u)
{
/* 有符号差值判断,兼容uwTick 49.7天溢出归零场景 */
if (((int32_t)(now - s_test_off_at)) >= 0)
{
s_test_off_at = 0u;
s_run_last = now; /* 复位RUN计时基准,避免自检结束闪烁相位偏移 */
/* 自检到期熄灭全部通信、故障灯 */
LED_Write(COMM_LED_GPIO_Port, COMM_LED_Pin, 0u);
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 0u);
LED_Write(RS232_LED_GPIO_Port, RS232_LED_Pin, 0u);
}
else
{
return;
}
}
/* RUN运行灯1Hz常驻慢闪逻辑,不受故障、通信状态干扰
* 计时变量 s_run_last:定义在顶部静态变量区,仅本分支读写 */
elapsed = now - s_run_last;
if (elapsed >= LED_SLOW_ON_TIME && elapsed < LED_SLOW_PERIOD)
{
LED_Write(STATUS_LED_GPIO_Port, STATUS_LED_Pin, 0u);
}
else if (elapsed >= LED_SLOW_PERIOD)
{
LED_Write(STATUS_LED_GPIO_Port, STATUS_LED_Pin, 1u);
s_run_last = now;
}
else
{
LED_Write(STATUS_LED_GPIO_Port, STATUS_LED_Pin, 1u);
}
/* FAULT故障灯10Hz快闪逻辑,仅故障激活时执行
* 关联变量:s_fault_active(总开关)、s_fault_last(计时戳)
* 变量定义:文件顶部静态变量;赋值入口:BSP_LED_SetFault() */
if (s_fault_active)
{
elapsed = now - s_fault_last;
if (elapsed >= LED_FAST_ON_TIME && elapsed < LED_FAST_PERIOD)
{
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 0u);
}
else if (elapsed >= LED_FAST_PERIOD)
{
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 1u);
s_fault_last = now;
}
else
{
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 1u);
}
}
/* RS485通信脉冲计时标志 s_comm_off_at:定义在文件顶部【模块静态私有状态变量】区块
* 赋值入口:BSP_LED_CommPulse(),赋值为 HAL_GetTick() + LED_PULSE_MS(50ms)
* 变量非0代表存在未结束通信脉冲,时间戳超时后熄灭LED并清零标记 */
if (s_comm_off_at != 0u && ((int32_t)(now - s_comm_off_at)) >= 0)
{
LED_Write(COMM_LED_GPIO_Port, COMM_LED_Pin, 0u);
s_comm_off_at = 0u;
}
/* RS232串口通信脉冲计时标志 s_rs232_off_at:定义在文件顶部【模块静态私有状态变量】区块
* 赋值入口:BSP_RS232Pulse(),赋值为 HAL_GetTick() + LED_PULSE_MS(50ms)
* 变量非0代表存在未结束串口脉冲,时间戳超时后熄灭LED并清零标记 */
if (s_rs232_off_at != 0u && ((int32_t)(now - s_rs232_off_at)) >= 0)
{
LED_Write(RS232_LED_GPIO_Port, RS232_LED_Pin, 0u);
s_rs232_off_at = 0u;
}
}
/**
* @brief 故障告警状态开关控制
* @param active: 1=触发故障,FAULT灯10Hz快闪;0=清除故障,FAULT灯直接熄灭
* @note 【关联指引】本函数为 s_fault_active / s_fault_last 唯一赋值入口
* 两个变量在BSP_LED_Refresh()故障闪烁分支做读取判断,控制FAULT灯周期翻转
*/
void BSP_LED_SetFault(uint8_t active)
{
//上层要开启故障,且当前原本无故障
if (active && !s_fault_active)
{
s_fault_active = 1u; // 打开故障总开关
s_fault_last = HAL_GetTick(); // 记录故障触发时刻,作为闪烁计时起点
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 1u);
}
//上层要清除故障,且当前正处于故障状态
else if (!active && s_fault_active)
{
s_fault_active = 0u; // 关闭故障总开关
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 0u);
}
}
/**
* @brief Modbus RS485总线通信脉冲触发
* @note 【关联指引】本函数为 s_comm_off_at 唯一赋值入口
* s_comm_off_at = HAL_GetTick() + LED_PULSE_MS,赋值后在BSP_LED_Refresh()脉冲分支做超时判断熄灭
* 在Modbus有效帧收发位置调用,单次触发50ms点亮脉冲;频繁收发会持续刷新超时,保持常亮
*/
void BSP_CommPulse(void)
{
s_comm_off_at = HAL_GetTick() + LED_PULSE_MS; //记录LED_PULSE_MS后要熄灭的截止时间戳
LED_Write(COMM_LED_GPIO_Port, COMM_LED_Pin, 1u); //立刻点亮通信指示灯,BSP_LED_Refresh()没有亮灯的动作,要在这里亮灯
}
/**
* @brief RS232串口通信活动脉冲触发
* @note 【关联指引】本函数为 s_rs232_off_at 唯一赋值入口
* s_rs232_off_at = HAL_GetTick() + LED_PULSE_MS,赋值后在BSP_LED_Refresh()脉冲分支做超时判断熄灭
* 在RS232命令解析、数据收发位置调用,单次触发50ms点亮脉冲
*/
void BSP_RS232Pulse(void)
{
s_rs232_off_at = HAL_GetTick() + LED_PULSE_MS;
LED_Write(RS232_LED_GPIO_Port, RS232_LED_Pin, 1u);
}
/**
* @brief 启动四路LED硬件自检,全部同步点亮1.5s
* @note 【关联指引】本函数为 s_test_off_at 唯一赋值入口
* s_test_off_at = HAL_GetTick() + LED_TEST_MS,赋值后在BSP_LED_Refresh()头部做自检优先级判断
* 自检优先级最高,运行期间屏蔽所有闪烁、脉冲逻辑;非阻塞,1.5s后自动恢复常规灯效,用于产线硬件接线校验
*/
void BSP_LED_SelfTest(void)
{
s_test_off_at = HAL_GetTick() + LED_TEST_MS;
LED_Write(STATUS_LED_GPIO_Port, STATUS_LED_Pin, 1u);
LED_Write(COMM_LED_GPIO_Port, COMM_LED_Pin, 1u);
LED_Write(FAULT_LED_GPIO_Port, FAULT_LED_Pin, 1u);
LED_Write(RS232_LED_GPIO_Port, RS232_LED_Pin, 1u);
}
更多推荐

所有评论(0)