十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32+MQ4气体传感器+OLED+蜂鸣器报警与串口调试完整实现

STM32+MQ4气体传感器+OLED+蜂鸣器报警与串口调试完整实现 简介STM32单片机与MQ4气体传感器、OLED显示屏、蜂鸣器结合的嵌入式气体检测报警项目源码面向电子爱好者、嵌入式初学者及物联网开发者。资源包含完整软件工程采用模块化设计覆盖初始化配置、MQ4数据读取与ADC转换、浓度阈值判断、串口发送、OLED实时显示及蜂鸣器报警等功能可直接在Keil环境下打开工程进行编译烧录适合学习STM32的GPIO、定时器、ADC、I2C和USART等外设开发。压缩包共232个文件大小约6.48MB以.c源文件、.h头文件、.o目标文件、.d依赖文件及.crf交叉引用文件为主同时包含Keil工程文件uvprojx、启动汇编.s及链接脚本.sct等能够反映完整的Cortex-M3工程构建与编译过程。目前已有588人浏览学习。对于正在做气体监测类课设或入门STM32的读者这套代码提供了可参考的硬件驱动、报警逻辑和串口调试范例有助于快速理解传感器数据采集与嵌入式系统联调流程。1. 这套组合能拼出一台什么样的气体报警装置MQ4 这类半导体气体传感器有个容易让人误判的特性它输出的模拟电压会随环境温度、湿度飘移刚上电那几分钟电压一直下降。如果代码里写死一个阈值等传感器热稳定后原本 1.2V 的“报警电压”可能已经掉到 0.9V蜂鸣器没响OLED 上显示的浓度也一路走低。这正是标题里“STM32单片机MQ4传感器OLED屏幕蜂鸣器报警MQ4数据发送到串口调试助手”这几样东西组合起来要解决的核心问题用 ADC 把 MQ4 的模拟电压量化在单片机内换算成浓度一边送 OLED 显示一边和报警阈值比较再把原始电压和结果从串口发出去供调试助手端观察和标定。适合做智能家居燃气泄漏演示、单片机毕业设计、环境监测课程项目的开发者新手能跟着引脚表和代码跑通全流程有经验的工程师则可以把注意力放在标定方法和报警迟滞这些容易被略过的细节上。2. 硬件连接从 STM32 最小系统到 MQ4、OLED、蜂鸣器的完整接线2.1 先理清 MQ4 模块上每根线的作用市面上常见的 MQ4 模块是 4 引脚VCC、GND、DO数字输出、AO模拟输出。DO 引脚内部已经接了比较器只能输出“超标/未超标”的高低电平无法得到连续浓度AO 引脚才是我们要接的模拟信号源。MQ4 的加热丝需要 5V 供电不能直接用 3.3V否则加热不足会导致灵敏度大幅下降。OLED 和 STM32 的 GPIO 用 3.3V 供电即可。外设引脚连接到 STM32说明MQ4 VCC5V系统板 5V 输出加热电流约 150mAUSB 供电注意压降MQ4 AOPA1ADC1_IN112 位 ADC 模拟输入OLED SDAPB7I2C1_SDA0.91 寸/0.96 寸 SSD1306OLED SCLPB6I2C1_SCLI2C 时钟线OLED VCC3.3V系统板 3.3V与模块共地蜂鸣器PB0GPIO 推挽输出经三极管驱动这里有几个容易踩的点第一MQ4 模块和 OLED 模块都要和 STM32 共地否则 ADC 采样电压会悬空漂移第二PA1 可以换成其他支持 ADC 的引脚但 CubeMX 里的通道号要跟着改第三DO 引脚如果悬空模块上的比较器输出状态不确定不用的引脚建议不接避免干扰。2.2 MQ4 的电压输出是怎么“换算”成浓度的MQ4 内部是一个加热电阻加二氧化锡气敏层当甲烷或天然气接触气敏层时其电导率发生变化模块上的分压电路把电导率变化转换成 AO 引脚的电压变化。典型特性是洁净空气中输出电压约为 0.1V 到 0.3V浓度升高时电压升高但电压和浓度不是线性关系而是在对数坐标上近似一条直线。很多例程里写“电压除以5再乘100”得到的是一个没有任何物理意义的百分数不是 PPM这个误区要避开。常见做法是选取数据手册灵敏度特性曲线上的两个点做对数坐标线性插值。下面这个函数把电压映射为估算浓度#include math.h // 把 MQ4 输出电压映射为估算浓度PPM // 标定点(x0, y0) 和 (x1, y1) 取自数据手册中的典型曲线 static float ppm_from_voltage(float v) { float x0 0.3f, y0 500.0f; // 0.3V 对应约 500ppm float x1 1.2f, y1 5000.0f; // 1.2V 对应约 5000ppm float k (logf(y1) - logf(y0)) / (x1 - x0); return expf(logf(y0) k * (v - x0)); }这段代码的逻辑是把纵轴浓度取自然对数这样原来的曲线在“电压-对数浓度”坐标系里近似直线两个标定点确定斜率 k任意电压 v 对应的对数浓度就是log(y0) k*(v-x0)最后用expf还原成 PPM。这个函数不是精确公式只是工程近似两个标定点的选取会直接决定输出值所以第 5 章会专门讲怎么在实机上标定这两个点。2.3 蜂鸣器为什么不能直接接 GPIO 输出有源蜂鸣器工作电流在 20~30mASTM32 GPIO 的灌电流和拉电流能力有限长时间直接驱动轻则电压跌落、触发掉电复位重则破坏引脚。稳妥的接法是用 NPN 三极管做开关GPIO 输出高电平时三极管导通蜂鸣器通电发声GPIO 输出低电平时三极管截止。注意选有源蜂鸣器自带振荡源给高电平就响无源蜂鸣器需要 PWM 方波驱动接到 GPIO 上是不会响的。基极串联一个 1kΩ 限流电阻我一般会在蜂鸣器两端反并联一个二极管防止关断瞬间的反向电动势损伤三极管。3. 用 STM32CubeMX 配好工程再用 HAL 库把 MQ4 电压读出来3.1 外设参数怎么填ADC1、I2C1、USART1 与 GPIO打开 STM32CubeMX选择 STM32F103C8T6 或对应型号按下表配置外设后生成代码。PA1 配置为 ADC1_IN1PB6/PB7 配置为 I2C1PA9/PA10 配置为 USART1PB0 配置为 GPIO 推挽输出、初始电平为低。外设配置项参数值原因ADC1IN1使能单通道MQ4 只需一路模拟输入ADC1Resolution12 bitsSTM32F1 默认分辨率ADC1Sampling Time55.5 Cycles信号变化慢采样时间越长越稳I2C1Speed100kHz Standard Mode杜邦线下 400kHz 波形易畸变USART1ModeAsynchronous串口调试助手标准模式USART1Baud Rate115200, 8N1常用默认够用且稳定GPIO PB0OutputPush-Pull, Low上电不触发蜂鸣器关于“stm32芯片包安装”CubeMX 里如果搜不到 STM32F103C8说明芯片支持包没装。在 Help - Manage Embedded Software Packages 里勾选 STM32Cube FW_F1下载完毕后重启 CubeMX 即可。如果下载时报错通常是网络问题可以检查代理设置或改用低版本包重试。3.2 读取 ADC 的 HAL 代码先会读再谈滤波生成工程后在 main.c 里添加以下代码。这里我采用“重新配置通道 - 启动 - 等待转换 - 读取 - 停止”的方式便于在单通道前提下保证每次采样状态一致// 读取 ADC1_IN1 的原始值返回 0~4095 uint16_t adc_read_raw(void) { ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_1; // 对应 PA1 sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; if (HAL_ADC_ConfigChannel(hadc1, sConfig) ! HAL_OK) { Error_Handler(); } HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); // 等待转换完成 uint16_t val HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return val; } // 原始 ADC 值换算为电压vref 是参考电压 float adc_to_voltage(uint16_t raw, float vref) { return (float)raw * vref / 4096.0f; }逻辑说明HAL_ADC_ConfigChannel每次读取前重新设置通道和采样时间避免其他代码修改配置后导致采样异常HAL_ADC_Start启动一次转换PollForConversion阻塞等待转换结束超时 100ms 足够HAL_ADC_Stop停止 ADC否则下次Start可能因状态机不处于 READY 而返回错误。参数说明vref是参考电压这里用 3.3f。如果实测板子 VREF 引脚是 3.28V 或 3.32V就改成实际值否则浓度整体会偏移几个百分点。MQ4 信号变化是秒级的这些阻塞延时不会影响主循环节奏。3.3 对采样值做去极值平均滤掉突发噪声MQ4 模块本身的输出有轻微抖动加上电磁干扰单次 ADC 读数可能偏离真实值。我一般做“10 次采样去掉最大最小再平均”float mq4_read_voltage(void) { uint16_t buf[10]; for (int i 0; i 10; i) { buf[i] adc_read_raw(); HAL_Delay(2); } // 简单冒泡排序丢弃最大和最小值 for (int i 0; i 10; i) { for (int j i 1; j 10; j) { if (buf[j] buf[i]) { uint16_t t buf[i]; buf[i] buf[j]; buf[j] t; } } } uint32_t sum 0; for (int i 2; i 8; i) sum buf[i]; // 取中间 6 个 return adc_to_voltage((uint16_t)(sum / 6), 3.3f); }这段代码先连续采样 10 次每次间隔 2ms再排序后删掉最高和最低值对剩下 6 个求平均。比起滑动平均滤波去极值可以让突发的尖峰脉冲完全不参与计算代价是排序消耗一点 CPU但对 72MHz 主频的 STM32 来说微不足道。如果后续还要做更平滑的显示可以在主循环里再叠加一阶低通滤波float filtered 0.0f; filtered filtered * 0.8f voltage * 0.2f;系数 0.8 越大曲线越平滑但响应越迟钝。对于报警用途平滑和响应速度要平衡0.7~0.8 是常见的经验范围。4. OLED 屏幕显示、蜂鸣器报警阈值与 MQ4 数据发送到串口调试助手4.1 用 HAL 库点亮 OLED 屏幕全流程初始化、清屏、显示字符串0.91 寸 OLED 模块是 128×32 分辨率SSD1306 驱动I2C 地址一般是 0x3C。下单时要注意0.96 寸是 128×64两者初始化代码几乎一样区别只在页面数量。下面是最小初始化代码#define OLED_ADDR 0x3C // I2C 7 位地址 // 向 SSD1306 发送命令 static void oled_write_cmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; // 第一字节 0x00 表示后续是指令 HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR 1, buf, 2, 100); } // OLED 初始化必须按顺序执行 void oled_init(void) { HAL_Delay(100); oled_write_cmd(0xAE); // 关闭显示 oled_write_cmd(0x20); oled_write_cmd(0x00); // 水平寻址模式 oled_write_cmd(0x8D); oled_write_cmd(0x14); // 开启电荷泵 oled_write_cmd(0xAF); // 开启显示 oled_clear(); }逻辑说明SSD1306 内部把像素分页管理I2C 每次传输时第一个字节必须是控制字节——0x00表示后续是命令0x40表示后续是显存数据。0x8D 0x14是开启内部电荷泵忘了这一步屏幕就会不亮。HAL_I2C_Master_Transmit的地址参数会自动左移一位所以这里传入OLED_ADDR 1。如果初始化后屏幕不亮先查 SDA/SCL 有没有接反再用逻辑分析仪或示波器看 I2C 时钟线有没有波形。OLED 的中文显示需要字库一般项目里直接用 ASCII 字符串足够中文就截一张 16×16 点阵图用取模软件生成数组。4.2 报警阈值逻辑用迟滞避免蜂鸣器反复进出报警只做一个阈值判断在浓度临界点附近会出现蜂鸣器频繁“哒哒哒”响的问题。更稳的写法是引入上下两个阈值形成滞回区间#define ALARM_ON_PPM 1000.0f // 浓度超过该值进入报警 #define ALARM_OFF_PPM 800.0f // 浓度低于该值才解除报警 static uint8_t alarm_state 0; // 0:正常 1:报警 void update_alarm(float ppm) { if (alarm_state 0 ppm ALARM_ON_PPM) { alarm_state 1; HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET); } else if (alarm_state 1 ppm ALARM_OFF_PPM) { alarm_state 0; HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET); } }逻辑说明正常状态下只有当浓度超过 1000ppm 才置位报警报警状态下必须回落到 800ppm 以下才清除。这样 800~1000ppm 之间是一个稳定区浓度在这个区间抖动时蜂鸣器状态不会翻转。阈值大小依据应用场景定甲烷的爆炸下限约为 4.4% 体积浓度即 44000ppm一般可燃气体报警器会在 10%LEL 左右报警折算约 4400ppm自己做演示项目用 1000ppm 更能直观看到报警触发。4.3 把 MQ4 数据发送到串口调试助手帧格式与串口参数串口发送本质是把数据变成字节流。最简单的做法是使用snprintf拼好一帧文本再交给HAL_UART_Transmit#include stdio.h char tx_buf[64]; void uart_send_mq4(float voltage, float ppm, uint8_t alarm) { int len snprintf(tx_buf, sizeof(tx_buf), VOL%.2fV, PPM%.1f, ALARM%d\r\n, voltage, ppm, alarm); HAL_UART_Transmit(huart1, (uint8_t*)tx_buf, len, 100); }这段代码把电压、浓度、报警状态拼成一行文本末尾带\r\n换行方便串口调试助手按行显示。使用snprintf处理浮点数需要在 Keil 的 C 库设置里勾选 MicroLIB否则浮点格式化函数体积很大。HAL_UART_Transmit是阻塞发送100ms 超时。在调试阶段可以接受阻塞正式做产品时建议改用 DMA 发送或中断发送避免发送过程中主循环被卡住。串口调试助手侧的参数要和代码一致参数设置值波特率115200数据位8停止位1校验位None换行方式勾选“发送新行”或收到\r\n自动换行SSCOM、正点原子串口助手、XCOM 这几类工具的用法一样差别只在界面和附加功能。重要的是打开助手时确认选择了正确的 COM 口波特率不要选错接收区出现乱码先看两边波特率是否一致。4.4 串口收不到数据时的排查顺序如果打开串口调试助手后什么都收不到按下面的顺序排查检查 TX/RX 是否交叉连接STM32 的 PA9(TX1) 接 USB 转 TTL 的 RXPA10(RX1) 接它的 TX两端必须共地。确认电脑识别到了串口设备。CH340/CP2102 芯片需要安装驱动插上后设备管理器里应该出现 COM 口没有出现就是驱动没装或线是坏的。如果识别到端口但显示类似“STM32 Virtual COM Port 感叹号”说明板载虚拟串口驱动有问题换一个外部 USB 转 TTL 模块接 PA9/PA10 最省事。代码里检查huart1是否已经初始化CubeMX 生成的MX_USART1_UART_Init有没有在main中被调用。串口助手先打开再复位开发板。有些 USB 转 TTL 模块在上电瞬间枚举失败复位一次就能恢复。5. 标定与长期运行该做的事5.1 两点标定把屏幕上的 PPM 显示到接近真实值整篇文章里最容易忽略、也最影响结果的就是标定。第 2 章给出的ppm_from_voltage只是用数据手册上的典型值做的映射不同批次的 MQ4 传感器个体差异很大同一款模块之间的零点电压可能差 0.1V。我做法是准备一瓶已知浓度的标气或者在通风良好的室外记录洁净空气电压作为零点再用打火机气体靠近传感器记录高浓度点电压。前提是整个过程用串口助手观察 VOL 值等它稳定后再记录。得到两个点后把第 2 章的ppm_from_voltage改成// 标定参数分别记录两个状态下串口打印的电压平均值 float cal_v0 0.20f; // 洁净空气电压读串口得到 float cal_v1 1.15f; // 靠近高浓度气体后的电压 float cal_ppm0 50.0f; // 洁净空气的估算值建议设 0 float cal_ppm1 3000.0f; // 对应气体的估算值用这两组数据算出 k再回填给ppm_from_voltage。没有标气时这个数不代表真实 PPM只是一个用于观察相对变化和触发阈值的指标。OLED 上如果显示“0ppm”但电压还在 0.2V 附近检查ppm_from_voltage里零点电压是否和你标定的洁净空气电压一致。5.2 用串口调试助手的数据保存功能做长期观察SSCOM 和 XCOM 都自带“保存接收数据”功能。把发送周期设为 1 秒观察半小时用保存下来的数据画曲线能看到 MQ4 的热漂移规律上电前几分钟电压下降速度很快30 分钟后才趋于平缓。这带出一个实际设计点软件里要加预热倒计时上电前 60 秒只显示浓度不触发报警否则冷启动时的电压下降可能让浓度计算值超过报警线造成不必要的蜂鸣器响。5.3 用独立看门狗给 7x24 小时运行兜底如果这套装置要长期通电加独立看门狗 IWDG 是很值得做的。CubeMX 中打开 IWDG设置分频和重载值生成代码后主循环里调用HAL_IWDG_Refresh(hiwdg);IWDG 一旦启动就不能被软件停止必须在超时前刷新否则 MCU 自动复位。这是避免单片机卡死在某个外设等待中的保底手段。主循环的执行时间要留 20% 余量像我前面用的HAL_ADC_PollForConversion超时 100ms配合 2ms 的采样间隔一个循环周期大约 30ms喂狗周期设 1 秒就能稳定运行如果某个版本的 OLED 驱动偶尔卡在 I2C 等待上看门狗会在超时后强制复位整个系统恢复比断电重启更快也更可靠。本文还有配套的精品资源点击获取
返回列表