STM32从零到量产开发:四路继电器工业控制模块开发— 继电器模块(bsp_relay)设计说明

1. 模块概述

bsp_relay 模块是 STM32 工业控制板级支持包(BSP)中的核心组件,专为四路继电器输出端子设计。该模块对上层应用暴露统一的逻辑接口(1=吸合,0=断开),屏蔽了底层硬件“低电平有效”的极性差异。此外,模块内置了寿命管理机制,支持动作次数统计与超限告警,满足工业级设备运维监控需求。

核心特性

  • 基础控制:支持单路及批量设置、读取、翻转继电器通断状态。
  • 状态缓存:内部维护状态位图,避免频繁读取 GPIO 寄存器,提高运行效率。
  • 寿命管理:记录单路累计动作次数(仅在真实翻转时计数),并提供寿命超限告警位图。
  • 安全保护:所有接口均包含通道边界合法性校验。

2. 硬件引脚映射

本模块依赖 CubeMX 生成的 main.h 中的引脚宏定义。硬件映射关系如下:

通道号 逻辑状态 硬件电平 GPIO 端口 引脚宏
RELAY 1 吸合 (ON) 低电平 (RESET) GPIOB RELAY1_Pin (PB6)
RELAY 1 断开 (OFF) 高电平 (SET) GPIOB RELAY1_Pin (PB6)
RELAY 2 吸合 (ON) 低电平 (RESET) GPIOB RELAY2_Pin (PB7)
RELAY 2 断开 (OFF) 高电平 (SET) GPIOB RELAY2_Pin (PB7)
RELAY 3 吸合 (ON) 低电平 (RESET) GPIOB RELAY3_Pin (PB8)
RELAY 3 断开 (OFF) 高电平 (SET) GPIOB RELAY3_Pin (PB8)
RELAY 4 吸合 (ON) 低电平 (RESET) GPIOB RELAY4_Pin (PB9)
RELAY 4 断开 (OFF) 高电平 (SET) GPIOB RELAY4_Pin (PB9)

极性说明:GPIO 初始化为 SET(高电平)表示断开(OFF),写低电平表示吸合(ON)。本驱动对上层统一暴露逻辑值:1 = 吸合,0 = 断开。


3. API 接口详解

3.1 模块初始化

void BSP_Relay_Init(void);
  • 功能描述:初始化继电器模块,上电时将全部 4 路继电器置为断开状态。
  • 参数:无。
  • 返回值:无。
  • 实现细节:硬件 GPIO 初始化由 MX_GPIO_Init 完成。此函数通过 HAL_GPIO_WritePin 将 PB6~PB9 统一输出高电平,并将内部状态缓存 s_relay_state 清零。

3.2 单路状态设置

void BSP_Relay_Set(uint8_t ch, uint8_t state);
  • 功能描述:设置指定通道的继电器通断状态。
  • 参数
    • ch:通道号,取值范围 1~4。超出范围将触发边界保护直接返回。
    • state:目标状态,RELAY_ON (1) 吸合 / RELAY_OFF (0) 断开。
  • 返回值:无。
  • 实现细节
    1. 校验通道合法性。
    2. 对比目标状态与内部缓存状态,若状态无变化则直接返回,避免无效操作。
    3. 状态发生真实翻转时,更新硬件引脚电平,更新内部状态缓存,并将该通道的动作计数 s_relay_act_count 累加 1。

3.3 单路状态获取

uint8_t BSP_Relay_Get(uint8_t ch);
  • 功能描述:获取指定继电器当前的逻辑通断状态。
  • 参数ch:通道号 1~4
  • 返回值RELAY_ON (1) 吸合 / RELAY_OFF (0) 断开。非法通道返回 0
  • 实现细节:直接从内部状态缓存 s_relay_state 中提取对应 bit 位,无需读取硬件寄存器。

3.4 批量状态设置

void BSP_Relay_SetAll(uint8_t mask);
  • 功能描述:采用位图掩码批量设置所有继电器状态。
  • 参数
    • mask:4位有效掩码。bit0 对应 RELAY1,bit1 对应 RELAY2,以此类推。1 = 吸合,0 = 断开。
  • 返回值:无。
  • 实现细节:内部通过循环调用 BSP_Relay_Set 逐路完成设置,自动继承状态防抖与计数逻辑。

3.5 批量状态获取

uint8_t BSP_Relay_GetAll(void);
  • 功能描述:批量读取所有继电器当前状态。
  • 参数:无。
  • 返回值:4位有效掩码,bit0~bit3 对应通道 1~4,高 4 位强制清零。

3.6 单路状态翻转

void BSP_Relay_Toggle(uint8_t ch);
  • 功能描述:翻转单路继电器当前状态(吸合 ↔ 断开)。
  • 参数ch:通道号 1~4
  • 返回值:无。
  • 实现细节:读取当前状态后取反,调用 BSP_Relay_Set 执行。若状态发生实际翻转,会自动累加动作计数。

3.7 动作次数获取

uint32_t BSP_Relay_GetActionCount(uint8_t ch);
  • 功能描述:获取指定继电器上电后的累计动作次数,用于寿命统计。
  • 参数ch:通道号 1~4
  • 返回值:累计动作次数(uint32_t)。掉电不保存,仅状态真实翻转时计数 +1。非法通道返回 0

3.8 寿命告警获取

uint8_t BSP_Relay_GetLifeAlarmMask(void);
  • 功能描述:获取继电器寿命超限告警位图。
  • 参数:无。
  • 返回值:4位告警掩码。bit0 = RELAY1 … bit3 = RELAY4。bit=1 代表该通道动作次数超出寿命阈值。
  • 实现细节:遍历内部计数数组,当动作次数 >= RELAY_LIFE_THRESHOLD(默认 100,000 次)时置位对应标志。

4. 内部实现逻辑简述

4.1 状态缓存机制

模块内部定义了静态变量 s_relay_stateuint8_t 类型),使用低 4 位分别映射 4 路继电器的逻辑状态。所有的读取操作(Get / GetAll)均基于此缓存进行,极大降低了总线访问频率,提升了系统实时性。

4.2 硬件抽象层

通过 relay_pin_t 结构体与 s_relay_tbl 静态常量数组,将通道号(1~4)与底层硬件(GPIO_Port, Pin)解耦。后续若发生硬件引脚变更,仅需修改此映射表,无需改动业务逻辑代码。

4.3 寿命管理策略

  • 阈值定义:通过宏 RELAY_LIFE_THRESHOLD 定义寿命上限(当前设为 100,000 次)。
  • 精准计数:仅在 BSP_Relay_Set 中检测到目标状态与旧状态不一致时,才执行硬件写入与计数累加,杜绝了重复赋值导致的计数虚高问题。
  • 告警输出BSP_Relay_GetLifeAlarmMask 提供实时的位图查询,方便上层应用通过单一接口获取全局健康状态。

bsp_relay.h

/**
  ******************************************************************************
  * @file    bsp_relay.h
  * @brief   4通道继电器驱动 - 对应产品规格书 3.2 继电器输出端子
  *
  * 硬件映射 (来自 CubeMX main.h):
  *   RELAY1 -> PB6, RELAY2 -> PB7, RELAY3 -> PB8, RELAY4 -> PB9
  *
  * 极性说明:
  *   GPIO 初始化为 SET(高电平)= 断开(OFF)
  *   写低电平 = 吸合(ON)
  *   本驱动对上层暴露逻辑值: 1=吸合, 0=断开
  *
  * 接口说明:
  *   1. 基础控制:单路/批量设置、读取、翻转继电器通断状态
  *   2. 寿命管理:动作次数统计、寿命超限告警位图,用于设备运维监控(需求#3)
  ******************************************************************************
  */
#ifndef __BSP_RELAY_H
#define __BSP_RELAY_H

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

/* 通道数量 */
#define RELAY_CH_NUM        4u

/* 逻辑状态定义 (上层使用) */
#define RELAY_OFF           0u
#define RELAY_ON            1u

/**
  * @brief  初始化继电器 (全部置为断开RELAY_OFF)
  * @note   GPIO引脚硬件初始化由 MX_GPIO_Init 完成,本函数仅统一设置上电初始断开状态
  */
void BSP_Relay_Init(void);

/**
  * @brief  设置单路继电器通断状态
  * @param  ch: 通道号 1~4,超出范围会做边界保护
  * @param  state: RELAY_ON 吸合 / RELAY_OFF 断开
  */
void BSP_Relay_Set(uint8_t ch, uint8_t state);

/**
  * @brief  获取单路继电器当前逻辑通断状态
  * @param  ch: 通道号 1~4
  * @retval RELAY_ON 吸合 / RELAY_OFF 断开
  */
uint8_t BSP_Relay_Get(uint8_t ch);

/**
  * @brief  批量设置所有继电器状态,采用位图掩码控制
  * @param  mask: 4位有效掩码
  *         bit0 对应 RELAY1,bit1 对应 RELAY2,bit2 对应 RELAY3,bit3 对应 RELAY4
  *         bit位=1 对应通道吸合,bit位=0 对应通道断开
  */
void BSP_Relay_SetAll(uint8_t mask);

/**
  * @brief  批量读取所有继电器当前状态,返回位图掩码
  * @retval 4位有效掩码
  *         bit0 对应 RELAY1,bit1 对应 RELAY2,bit2 对应 RELAY3,bit3 对应 RELAY4
  *         bit位=1 对应通道当前吸合,bit位=0 对应通道断开
  */
uint8_t BSP_Relay_GetAll(void);

/**
  * @brief  翻转单路继电器当前状态:吸合→断开 / 断开→吸合
  * @param  ch: 通道号 1~4
  * @note   状态发生实际翻转时,会自动累加该通道动作计数
  */
void BSP_Relay_Toggle(uint8_t ch);

/**
  * @brief  获取单路继电器累计动作次数,用于寿命统计
  * @param  ch: 通道号 1~4
  * @retval 上电后累计动作次数,掉电不保存;仅状态真实翻转时计数+1
  */
uint32_t BSP_Relay_GetActionCount(uint8_t ch);

/**
  * @brief  获取继电器寿命超限告警位图
  * @retval 4位告警掩码
  *         bit0=RELAY1 ... bit3=RELAY4,bit=1 代表该通道动作次数超出寿命阈值(需求#3)
  */
uint8_t BSP_Relay_GetLifeAlarmMask(void);

#ifdef __cplusplus
}
#endif
#endif /* __BSP_RELAY_H */

bsp_relay.c

/**
  ******************************************************************************
  * @file    bsp_relay.c
  * @brief   4通道继电器驱动实现
  ******************************************************************************
  */
#include "bsp_relay.h"
#include "main.h"

/* ===================== 硬件与阈值宏定义 ===================== */
/* 寿命告警阈值:单路动作次数超过此值视为达到寿命,置位告警标志 */
#define RELAY_LIFE_THRESHOLD   100000u

/* ===================== 硬件引脚结构体 ===================== */
/* 继电器引脚映射结构体  */
typedef struct
{
    GPIO_TypeDef *port;
    uint16_t      pin;   //HAL库管脚类型uint16_t,与工程引脚宏匹配
} relay_pin_t;

/* 继电器通道硬件引脚映射表 ,(通道号 1~4 对应数组索引 0~3)*/
static const relay_pin_t s_relay_tbl[RELAY_CH_NUM] =
{
    { RELAY1_GPIO_Port, RELAY1_Pin },
    { RELAY2_GPIO_Port, RELAY2_Pin },
    { RELAY3_GPIO_Port, RELAY3_Pin },
    { RELAY4_GPIO_Port, RELAY4_Pin },
};

/* ===================== 模块内部静态变量 ===================== */
/* 4路继电器状态缓存:bit0=通道1、bit1=通道2、bit2=通道3、bit3=通道4,避免频繁读取GPIO */
static uint8_t s_relay_state = 0u;

/* 每路继电器累计动作次数,仅状态真实翻转时计数,用于继电器寿命管理 */
static uint32_t s_relay_act_count[RELAY_CH_NUM] = {0u, 0u, 0u, 0u};

/* ===================== 对外公共接口 ===================== */
/**
  * @brief  继电器模块初始化,上电全部断开
  * @note  继电器低电平吸合,初始化输出高电平断开所有通道
  */
void BSP_Relay_Init(void)
{
    HAL_GPIO_WritePin(GPIOB, RELAY1_Pin | RELAY2_Pin | RELAY3_Pin | RELAY4_Pin, GPIO_PIN_SET);
    s_relay_state = 0u;
}

/**
  * @brief  设置单路继电器通断
  * @param  ch: 通道号 1~4
  * @param  state: 0断开,非0吸合
  * @note   状态无变化时直接返回,仅真实翻转时更新硬件、状态缓存、动作计数
  */
void BSP_Relay_Set(uint8_t ch, uint8_t state)
{
    uint8_t idx;
    uint8_t old_state;

    /* 通道合法性校验 */
    if (ch < 1u || ch > RELAY_CH_NUM)
    {
        return;
    }
    idx = ch - 1u;
    old_state = (s_relay_state >> idx) & 0x01u;

    /* 标准化目标状态:0=断开,1=吸合 */
    uint8_t target = (state != 0u) ? 1u : 0u;
    if (target == old_state)
    {
        return;
    }

    if (target)
    {
        /* 吸合:低有效,输出RESET */
        HAL_GPIO_WritePin(s_relay_tbl[idx].port, s_relay_tbl[idx].pin, GPIO_PIN_RESET);
        s_relay_state |= (uint8_t)(1u << idx);	//位操作是int类型32位,整型提升后截断低8位
    }
    else
    {
        /* 断开:输出SET高电平 */
        HAL_GPIO_WritePin(s_relay_tbl[idx].port, s_relay_tbl[idx].pin, GPIO_PIN_SET);
        s_relay_state &= (uint8_t)~(1u << idx);
    }
    s_relay_act_count[idx]++;
}

/**
  * @brief  批量设置全部4路继电器
  * @param  mask: 状态掩码 bit0~bit3对应通道1~4,1吸合 0断开
  */
void BSP_Relay_SetAll(uint8_t mask)
{
    uint8_t i;
    for (i = 0u; i < RELAY_CH_NUM; i++)
    {
        BSP_Relay_Set(i + 1u, (uint8_t)((mask >> i) & 0x01u));
    }
}

/**
  * @brief  获取单路继电器当前状态
  * @param  ch: 通道1~4
  * @retval 0断开 / 1吸合,非法通道返回0
  */
uint8_t BSP_Relay_Get(uint8_t ch)
{
    if (ch < 1u || ch > RELAY_CH_NUM)
    {
        return 0u;
    }
    return (s_relay_state >> (ch - 1u)) & 0x01u;
}

/**
  * @brief  获取四路继电器整体状态掩码
  * @retval 低4位有效,bit0~bit3对应1~4通道,高4位清零屏蔽无效位
  */
uint8_t BSP_Relay_GetAll(void)
{
    return s_relay_state & 0x0Fu;
}

/**
  * @brief  翻转单路继电器当前状态
  * @param  ch: 通道1~4
  */
void BSP_Relay_Toggle(uint8_t ch)
{
    if (ch < 1u || ch > RELAY_CH_NUM)
    {
        return;
    }
    BSP_Relay_Set(ch, (uint8_t)(BSP_Relay_Get(ch) ? 0u : 1u));
}

/**
  * @brief  获取单路继电器累计动作次数
  * @param  ch: 通道1~4
  * @retval 动作总次数,非法通道返回0
  */
uint32_t BSP_Relay_GetActionCount(uint8_t ch)
{
    if (ch < 1u || ch > RELAY_CH_NUM)
    {
        return 0u;
    }
    return s_relay_act_count[ch - 1u];
}

/**
  * @brief  获取继电器寿命告警位图
  * @retval bit0~bit3对应1~4通道,bit=1表示该路动作次数超寿命阈值
  */
uint8_t BSP_Relay_GetLifeAlarmMask(void)
{
    uint8_t i;
    uint8_t mask = 0u;
    for (i = 0u; i < RELAY_CH_NUM; i++)
    {
        if (s_relay_act_count[i] >= RELAY_LIFE_THRESHOLD)
        {
            mask |= (uint8_t)(1u << i);
        }
    }
    return mask;
}

Logo

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

更多推荐