STM32 继电器驱动——双触发模式的通用控制接口
问题背景继电器模块的触发方式有两种高电平触发常见于低电平驱动型继电器模组和低电平触发常见于光耦隔离型模组。如果业务代码直接操作 GPIO 而不区分触发模式换一个模块就要改代码。本文给出一个简洁的继电器驱动通过active_mode字段封装两种触发模式的差异提供初始化、开关、翻转等标准接口。驱动设计核心思路驱动使用relay_active_t枚举区分触发模式typedef enum { RELAY_ACTIVE_HIGH 0, // 高电平触发输出 SET 吸合 RELAY_ACTIVE_LOW // 低电平触发输出 RESET 吸合 } relay_active_t;所有操作函数relay_on、relay_off都根据active_mode计算实际物理电平调用方只需关心开/关逻辑。toggle 实现relay_toggle通过HAL_GPIO_ReadPin读取当前引脚电平结合active_mode判断继电器是否已吸合然后翻转。这种读-判断-写的方式比维护内部状态变量更可靠状态始终与硬件一致。API 参考函数说明relay_init(cb, gpio_x, gpio_pin, active_mode, set_pin)初始化并关闭继电器relay_on(cb)吸合继电器relay_off(cb)释放继电器relay_set(cb, val)设置状态0关, 非0开relay_toggle(cb)翻转状态使用示例#include driver_relay.h ​ relay_control_t relay1; ​ int main(void) { // 初始化高电平触发GPIOB_PIN_12 relay_init(relay1, (uint32_t)GPIOB, GPIO_PIN_12, RELAY_ACTIVE_HIGH, (relay_gpio_set_func_t)HAL_GPIO_WritePin); ​ relay_on(relay1); // 吸合 HAL_Delay(1000); relay_off(relay1); // 释放 HAL_Delay(1000); relay_toggle(relay1); // 再次吸合 HAL_Delay(1000); relay_set(relay1, 0); // 通过变量控制释放 ​ while (1); }注意事项gpio_x参数为uint32_t类型传入 GPIO 端口时需要强转set_pin函数指针不能为 NULL否则relay_init返回RELAY_ERRORrelay_toggle内部调用HAL_GPIO_ReadPin需要该引脚已在 CubeMX 中正确配置初始化后继电器默认处于关闭释放状态总结本驱动仅 ~150 行代码核心价值在于用active_mode字段消除了不同继电器模块在触发方式上的差异让上层代码完全透明。完整源码driver_relay.h/** * file driver_relay.h * author Jiang Ying * brief 继电器驱动头文件 * version V1.0.0 * date 2026.03.28 * note 该文件包含继电器驱动函数声明提供初始化、开启、关闭、 * 设置和切换功能。用户可以通过调用这些函数来控制继电器的状态。 */ ​ #ifndef _DRIVER_RELAY_H #define _DRIVER_RELAY_H ​ /* Includes ------------------------------------------------------------------*/ #include stm32f1xx_hal.h ​ #include stdint.h ​ /** * brief 继电器状态枚举 * note 定义继电器的两种状态关闭和开启 */ typedef enum { RELAY_ERROR 0, RELAY_OK !RELAY_ERROR }relay_state_t; ​ /** * brief 继电器状态枚举 * note 定义继电器的两种状态关闭和开启 */ typedef enum { RELAY_RESET 0, RELAY_SET }relay_set_t; ​ /** * brief 继电器触发模式枚举 * note 定义继电器的不同触发模式 */ typedef enum { RELAY_ACTIVE_HIGH 0, /* 高电平触发模式GPIO输出高电平时继电器吸合 */ RELAY_ACTIVE_LOW /* 低电平触发模式GPIO输出低电平时继电器吸合 */ }relay_active_t; ​ /** * brief GPIO设置函数指针类型 * note 用于设置GPIO引脚状态的函数指针类型 */ typedef relay_state_t (*relay_gpio_set_func_t)(uint32_t, uint32_t, uint32_t); ​ /** * brief 继电器控制结构体 * note 包含继电器的GPIO配置、触发模式和控制函数 */ typedef struct { uint32_t gpio_x; /* GPIO端口 */ uint32_t gpio_pin; /* GPIO引脚 */ relay_active_t active_mode; /* 继电器触发模式 */ relay_gpio_set_func_t set_pin; /* GPIO设置函数指针 */ }relay_control_t; ​ ​ relay_state_t relay_init(relay_control_t *cb, uint32_t gpio_x, uint32_t gpio_pin, relay_active_t active_mode, relay_gpio_set_func_t set_pin); relay_state_t relay_on(relay_control_t *cb); relay_state_t relay_off(relay_control_t *cb); relay_state_t relay_set(relay_control_t *cb, uint32_t val); relay_state_t relay_toggle(relay_control_t *cb); ​ ​ #endif /* _DRIVER_RELAY_H */ ​driver_relay.c/** * file driver_relay.c * author Jiang Ying * brief 继电器驱动源文件 * version V1.0.0 * date 2026.03.28 */ /* Includes ------------------------------------------------------------------*/ #include driver_relay.h ​ /** * brief 打开继电器 * param cb 继电器控制结构体指针 * return 继电器操作状态 * note 根据继电器的触发模式决定输出高电平还是低电平 */ relay_state_t relay_on(relay_control_t *cb) { relay_state_t state RELAY_ERROR; uint32_t pin_state; ​ /* 根据触发模式决定实际输出的电平 */ if (cb-active_mode RELAY_ACTIVE_HIGH) { pin_state RELAY_SET; /* 高电平触发模式输出高电平吸合继电器 */ } else { pin_state RELAY_RESET; /* 低电平触发模式输出低电平吸合继电器 */ } ​ state cb-set_pin(cb-gpio_x, cb-gpio_pin, pin_state); ​ return state; } ​ /** * brief 关闭继电器 * param cb 继电器控制结构体指针 * return 继电器操作状态 * note 根据继电器的触发模式决定输出高电平还是低电平 */ relay_state_t relay_off(relay_control_t *cb) { relay_state_t state RELAY_ERROR; uint32_t pin_state; ​ /* 根据触发模式决定实际输出的电平 */ if (cb-active_mode RELAY_ACTIVE_HIGH) { pin_state RELAY_RESET; /* 高电平触发模式输出低电平释放继电器 */ } else { pin_state RELAY_SET; /* 低电平触发模式输出高电平释放继电器 */ } ​ state cb-set_pin(cb-gpio_x, cb-gpio_pin, pin_state); ​ return state; } ​ /** * brief 设置继电器状态 * param cb 继电器控制结构体指针 * param val 继电器状态值0表示关闭非0表示打开 * return 继电器操作状态 * note 根据继电器的触发模式和状态值决定输出高电平还是低电平 */ relay_state_t relay_set(relay_control_t *cb, uint32_t val) { if (val) { return relay_on(cb); /* 打开继电器 */ } else { return relay_off(cb); /* 关闭继电器 */ } } ​ /** * brief 初始化继电器控制结构体 * param cb 继电器控制结构体指针 * param gpio_x GPIO端口 * param gpio_pin GPIO引脚 * param active_mode 继电器触发模式 * param set_pin GPIO设置函数指针 * return 继电器操作状态 * note 配置继电器控制结构体并初始化继电器为关闭状态 */ relay_state_t relay_init(relay_control_t *cb, uint32_t gpio_x, uint32_t gpio_pin, relay_active_t active_mode, relay_gpio_set_func_t set_pin) { relay_state_t state RELAY_ERROR; ​ if (cb NULL || set_pin NULL) { return RELAY_ERROR; } ​ /* 配置继电器控制结构体 */ cb-gpio_x gpio_x; cb-gpio_pin gpio_pin; cb-active_mode active_mode; cb-set_pin set_pin; ​ /* 初始化继电器为关闭状态 */ state relay_off(cb); ​ return state; } ​ /** * brief 切换继电器状态 * param cb 继电器控制结构体指针 * return 继电器操作状态 * note 如果继电器当前是打开状态则关闭如果是关闭状态则打开 */ relay_state_t relay_toggle(relay_control_t *cb) { relay_state_t state RELAY_ERROR; uint32_t current_state; uint32_t new_state; ​ /* 获取当前引脚状态 */ if (cb-active_mode RELAY_ACTIVE_HIGH) { /* 高电平触发模式下引脚为高表示继电器打开 */ current_state (HAL_GPIO_ReadPin((GPIO_TypeDef*)cb-gpio_x, cb-gpio_pin) GPIO_PIN_SET) ? 1 : 0; } else { /* 低电平触发模式下引脚为低表示继电器打开 */ current_state (HAL_GPIO_ReadPin((GPIO_TypeDef*)cb-gpio_x, cb-gpio_pin) GPIO_PIN_RESET) ? 1 : 0; } ​ /* 切换状态 */ new_state current_state ? 0 : 1; ​ /* 设置新状态 */ state relay_set(cb, new_state); ​ return state; } ​

相关新闻

连山区口腔机构排名,专业的竟然是这些!

连山区口腔机构排名,专业的竟然是这些!

引言在连山区,口腔健康备受关注,众多口腔机构林立。究竟哪些机构专业度较高呢?让我们一同探寻。专业口腔机构剖析连山区兴工路周雷口腔诊所:这是一家备受赞誉的口腔机构。诊所环境整洁舒适,诊疗设备定期消毒&#xff0…

2026/10/12 19:56:58 阅读更多 →
BRSS 大版本更新:网易云也能当播客,部署更简单,Bug 全修

BRSS 大版本更新:网易云也能当播客,部署更简单,Bug 全修

上次文章 项目地址:Github、Gitee 🔗 新版本仓库:Master分支 📦 老版本归档: Old-version 分支 一、这次都更新了什么(新功能) 🎵 1. 网易云音乐电台 → 播客 这是呼声最高的功…

2026/10/12 13:24:45 阅读更多 →
搭建同城外卖系统:商家端商品管理、多规格SKU与库存同步解决方案

搭建同城外卖系统:商家端商品管理、多规格SKU与库存同步解决方案

在搭建同城外卖系统时,很多开发工作会围绕用户下单、骑手配送展开,而真正决定商家运营效率的,往往是商家端的商品管理能力。尤其是商品规格越来越丰富后,如何设计SKU模型、保证库存准确、降低维护成本,是开发同城外卖A…

2026/10/12 19:59:39 阅读更多 →

最新新闻

GDI绘图函数详解:MoveToEx、LineTo、Polyline、Arc与贝塞尔曲线

GDI绘图函数详解:MoveToEx、LineTo、Polyline、Arc与贝塞尔曲线

1. 从一组绘图API说起:为什么这些函数值得单独拎出来讲如果你写过Windows平台下的图形程序,或者维护过一些年代稍早的桌面软件,大概率会在某个WM_PAINT消息处理里见过MoveToEx、LineTo、Polyline这一串函数。它们不像Direct2D、OpenGL那样光鲜…

2026/10/12 19:59:16 阅读更多 →
ESP32应用商店实战:模块化OTA与动态加载如何落地

ESP32应用商店实战:模块化OTA与动态加载如何落地

“在ESP32上做应用商店”这句话,我每次讲给同行听,收到的都是同一个表情:眉头一皱。ESP32那点Flash和RAM,跑一个轻量操作系统都紧绷绷,还要做“应用商店”?这不是往奥拓车里塞航空母舰吗?说实话…

2026/10/12 19:59:16 阅读更多 →
直线扫描转换算法详解:DDA、中点与Bresenham从原理到实现

直线扫描转换算法详解:DDA、中点与Bresenham从原理到实现

直线扫描转换算法(DDA、中点、Bresenham)实现——从原理到代码一次讲透想先从自己画的第一条直线说起。当年刚接触图形学,满脑子都是"画一条竖线不是很简单吗",直到亲手打开一个像素点阵画布才意识到:真实显…

2026/10/12 19:59:16 阅读更多 →
基于COMSOL的输变电工程工频电磁场仿真与环评应用实践

基于COMSOL的输变电工程工频电磁场仿真与环评应用实践

最近在做某输变电工程的环评支撑时,正好碰到一段三相高压电缆要从居住区地下穿过。评审专家和住户最关心的就一件事:电缆埋在地下,地面的人站久了到底有没有影响?这个问题落到技术层面,其实是两个量:电缆运…

2026/10/12 19:58:15 阅读更多 →
大模型能力爆发:界面生成、合规监管与端侧部署的实战解析

大模型能力爆发:界面生成、合规监管与端侧部署的实战解析

10月的AI圈像是被按了快进键。我早上还在刷一组产品更新,下午就看到三条消息前后脚占据了讨论区:新一代(第六代)旗舰大模型全面开放,还突然“长出了界面”;某头部AI公司在一个海外市场公开道歉,…

2026/10/12 19:58:15 阅读更多 →
Qt触摸屏滑动卡死:QScroller与事件冲突的排查与优化

Qt触摸屏滑动卡死:QScroller与事件冲突的排查与优化

1. 触摸屏滑动卡死:一个让Qt桌面端开发者头疼的经典问题如果你做过带触摸屏的Qt桌面项目,尤其是那种跑在工控机、自助终端、医疗设备或者车载中控上的应用,大概率遇到过这个场景:手指在QTableView、QTableWidget或者QListWidget上…

2026/10/12 19:58:15 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →