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);
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐