尧图精选

STM32+MQ-7一氧化碳监测工程:ADC采样、PPM换算与报警联动详解

🕒 发布时间:2026/9/16 15:08:36 📁 来源:尧图网络
简介一套基于STM32单片机的CO浓度监测与报警工程面向嵌入式初学者与物联网应用开发者解决气体数据采集、本地显示、超限报警与上位机监控联动的问题。工程采用MQ-7一氧化碳传感器感知环境CO浓度由STM32完成数据读取与阈值判断同时驱动OLED屏幕实时刷新数值并在浓度超标时触发蜂鸣器报警最终通过串口把数据送往调试助手便于远程观察与记录。压缩包约6.34MB共229个文件包含35个.c与35个.h源码、46个.o编译输出、44个.crf调试信息以及uvprojx工程文件等目录结构完整Keil环境下可直接打开工程查看逻辑或二次修改。已有964人浏览学习覆盖传感器模拟量采集、ADC配置、OLED驱动、GPIO控制蜂鸣器以及UART串口通信等关键编程环节能帮助读者快速理解STM32外设协同工作的开发流程和代码组织方式。1. 为什么这个STM32一氧化碳监测工程值得拆开看把MQ-7接到STM32上并不稀奇但真正做过气体监测的人都知道问题从来不在“能不能读到电压”而在“读到的电压到底代表多少PPM、够不够触发报警、有没有被人为误判掉”。这套工程把传感器采集、OLED显示、蜂鸣器报警、串口上报串成了一条完整链路覆盖了嵌入式开发里最核心的四类外设ADC负责读模拟量TIM负责控制蜂鸣器和采样节奏I2C驱动OLED屏幕UART把浓度数据抛给PC端串口调试助手。适合正在做环境监测类毕设、智能家居项目或者想系统过一遍STM32标准外设库实际用法的开发者。工程里有stm32f10x_adc.c、stm32f10x_i2c.c、stm32f10x_tim.c和OLED.c说明作者走的是库函数开发路线不是寄存器硬怼。跟着拆一遍能少走不少弯路。2. STM32外设与MQ-7信号链路分压、ADC量程与蜂鸣器驱动2.1 MQ-7的输出特性为什么不能把模拟脚直接接ADCMQ-7是二氧化锡半导体气体传感器靠加热丝维持敏感层温度一氧化碳分子在敏感层表面发生氧化还原反应改变半导体载流子浓度进而改变等效电阻Rs。注意它内部有两组回路加热回路H-H之间和测量回路A-B之间前者需要一直供电后者才是我们采样的对象。市面上卖的MQ-7模块已经把测量回路做成串联分压结构传感器等效电阻Rs与负载电阻RL串联模块输出VOVC×RL/(RsRL)VC是模块供电电压。这里有个新手常踩的坑模块标称VC是5VVO在洁净空气中大约0.1V到0.3V但在高浓度CO下有可能逼近1V甚至更高。STM32的ADC参考电压是3.3V如果直接用杜邦线把模块AO脚接到PA1芯片不会坏但测量范围被压缩且模块上灵敏度电位器拧到底时输出可能超过3.3V有烧坏ADC输入级的风险。更稳妥的做法是分压后再进ADC或者直接用3.3V给模块的VC供电牺牲一点灵敏度换取安全。这个工程里ADC配置在stm32f10x_adc.c能跑通的方案通常是把VC接5VAO输出经过两个100k电阻分压到ADC输入。2.2 ADC采样参数与电压换算公式STM32F103的ADC是12位满量程4095电压计算公式为V_adc ADC_VALUE / 4095 * 3.3如果前端加了分压实际电压还要乘回分压比。分压电阻误差直接反映在浓度计算上所以这里不建议用1%以下的精密电阻温漂也得考虑。常见做法是R1R2100k分压比2则V_mq7 V_adc * 2ADC初始化关键参数如下表参数推荐值说明ADC 分辨率12位04095采样时间55.5或71.5周期信号源内阻较大采样时间要放宽触发方式软件触发配合定时器周期性采样校准上电自校准ADC_ResetCalibration 后启动采样时间如果设置太短内部采样电容充不满结果偏低且跳动大。MQ-7输出本身就有波动建议采样间隔不低于50ms代码里通常配合定时器中断或者主循环里做延时采样。2.3 蜂鸣器驱动电路GPIO直驱会翻车蜂鸣器分为有源和无源两种区别在于内部是否带振荡源。有源蜂鸣器给高电平就响控制代码最简单无源蜂鸣器需要给PWM方波或对GPIO做高频翻转才能发声音调还可以通过改变频率来调整。这个工程文件里出现了stm32f10x_tim.c一个合理的推断是作者用TIM做PWM输出驱动无源蜂鸣器发出不同频率的报警音同时也用定时器做ADC采样节拍。无论是哪种蜂鸣器直接拿GPIO推都会有电流问题STM32 GPIO最大灌电流大约25mA而蜂鸣器工作电流普遍在30mA以上单片机会发热甚至损坏引脚。正规做法是用NPN三极管做开关电路PB5 --1kΩ-- S8050基极 S8050发射极 -- GND 蜂鸣器正极 -- 5V 蜂鸣器负极 -- S8050集电极 蜂鸣器两端反向并联 1N4148 续流二极管蜂鸣器内部有线圈属于感性负载断电瞬间会产生反向电动势不加续流二极管的话能量会倒灌进三极管和GPIO。工程里如果要修改电路千万别省这颗二极管。2.4 OLED屏幕I2C地址与SSD1306兼容性OLED.c对应的是0.96寸128x64屏主控SSD1306市面上常见模块走I2C接口默认地址0x78或0x7A取决于模块上SA0引脚的电平。stm32f10x_i2c.c的存在说明用的STM32硬件I2C外设。F1系列的硬件I2C口碑一般很多人遇到I2C卡死在EV5等待事件上原因多半是I2C时钟没使能或者GPIO没配置成开漏输出并外接上拉电阻。代码里如果用的是标准库初始化顺序应该是开GPIOB时钟、开I2C1时钟、配置PB6/PB7为开漏复用输出、配置I2C频率为400k或100k。I2C_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Mode I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 0x30; I2C_InitStructure.I2C_Ack I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed 400000; I2C_Init(I2C1, I2C_InitStructure);注意I2C_ClockSpeed不要设成500k以上SSD1306的极限是400k超了会花屏或者偶发通信失败。OLED显示本身是异步的刷新率控制在20fps以内就够了毕竟它只是给人看浓度数字不是跑动画。3. 标准库工程结构ADC采集、PPM换算与OLED刷新逻辑3.1 工程文件功能和烧录前的改动点工程文件列表里有几个值得解释的项文件作用project.uvguix.86187Keil MDK工程界面布局文件stm32f10x_adc.c / rcc.c / tim.c / i2c.c / flash.cSTM32标准外设库驱动OLED.cSSD1306显示驱动keilkill.bat清理编译中间文件的批处理脚本拿到工程后第一件事不是烧录而是确认芯片型号。这个工程看起来是基于STM32F103系列如果板子是C8T664KB Flash而工程配置成RCT6256KB Flash烧进去大概率能跑但Flash地址分配可能超出物理范围导致HardFault。在Keil里打开Options for Target - Device确认芯片然后到Target标签页检查晶振频率是不是8MHz。板载晶振不一致串口波特率和系统时钟全都会漂。3.2 ADC单通道采集与滑动平均滤波MQ-7输出信号带有明显的噪声不做滤波直接显示的话OLED上的数字会跳得没法看。工程里常见的实现是连续采样N次去掉最大值和最小值剩下的取平均。下面给出标准库下的ADC采集代码uint16_t ADC_GetValue(uint8_t channel) { ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); ADC_ClearFlag(ADC1, ADC_FLAG_EOC); return ADC_GetConversionValue(ADC1); } uint16_t ADC_GetAverage(uint8_t channel, uint8_t times) { uint32_t sum 0; uint16_t max 0, min 4095; uint8_t i; for (i 0; i times; i) { uint16_t val ADC_GetValue(channel); sum val; if (val max) max val; if (val min) min val; } return (sum - max - min) / (times - 2); }每次转换前重新配置通道是为了兼容多通道切换场景。ADC_SampleTime_55Cycles5是55.5个ADC时钟周期在14MHz ADC时钟下约4微秒对高内阻的MQ-7输出正好。times取1020去极值平均能有效滤掉毛刺。如果项目对实时性要求高可以把滤波移入DMA完成ADC工作在连续转换模式DMA循环搬运到缓冲区CPU只在缓冲区更新时取一次均值。这只是架构差异不改变滤波本质。3.3 从电压到PPMR0标定与线性映射这是整个项目里最容易做错的一步。MQ-7手册给出的是Rs/R0与PPM的对数关系曲线并不是线性的。R0是传感器在洁净空气中的电阻值会随着老化发生漂移所以工程里必须做一件事上电后先预热几分钟然后记录当前洁净空气下的传感器电阻值作为R0。之后任意时刻的传感器电阻Rs与R0的比值再拿去查表或者拟合曲线才能得到可信的PPM值。工程级的近似做法是这样标定时记录洁净空气中ADC值 adc_baseline记录一个已知CO浓度的ADC值 adc_known比如把传感器放到打火机出气口短暂接触用两点线性映射float co_ppm (float)(adc_current - adc_baseline) * (known_ppm) / (adc_known - adc_baseline);这段代码的意思是假设浓度与ADC读数线性相关浓度从0变化到known_ppm时ADC从adc_baseline线性增长到adc_known于是当前值按压差比例换算。实际传感器的双对数曲线不是直线但用于报警阈值判断比如100ppm、200ppm足够。真正做计量级产品必须在标准气体下做多点标定然后拟合成曲线映射表。3.4 OLED显示与蜂鸣器联动逻辑OLED刷新逻辑不复杂核心是把浓度值变成字符串再调用OLED_ShowString刷新对应区域。常见布局是第1行显示“CO PPM”第2行显示浓度数字第3行显示状态NORMAL/ALARM。注意SSD1306的显存是分8页的每页8个像素高度跨页显示大数字时地址要算好。显示频率不要每次循环都刷整屏那会让屏幕闪烁也干扰I2C通信。一般做法是500ms刷新一次数字区域报警状态下才加快刷新频率。联动代码大致是这个结构if (co_ppm ALARM_THRESHOLD) { GPIO_SetBits(GPIOB, GPIO_Pin_5); // 有源蜂鸣器直接拉高 OLED_ShowString(1, 3, ALARM!); } else { GPIO_ResetBits(GPIOB, GPIO_Pin_5); OLED_ShowString(1, 3, NORMAL); }报警阈值放在头文件里用宏定义不要去代码里翻着改。阈值设多少要根据应用场景来后面第5章会专门讲标定问题。4. UART串口帧协议设计与SSCOM调试助手联调4.1 USART1初始化与printf重定向串口是这套系统里最容易被低估的部分。很多人以为开个UART发送数据就行了实际联调时经常遇到数据能发但乱码、数据对但丢帧、上位机收不到但示波器能看到。大部分问题出在两处一是波特率二是串口助手是否勾选了DTR/RTS。先看标准库的串口初始化void USART1_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate baudrate; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); }注意TX引脚必须配成GPIO_Mode_AF_PP复用推挽输出RX配成浮空输入或者带上拉。很多人串口不通就是忘了开RCC_APB2Periph_AFIO时钟。F103的USART1挂在APB2总线上APB2频率等于系统时钟72MHz所以115200波特率的误差非常小可以放心用。4.2 上报数据帧协议让串口助手能稳定解析如果只在循环里printf(CO:%d\r\n, ppm)人眼能看但上位机解析容易串。调试助手里数据是连续流如果某次串口丢了一个字节后面所有数据都错位。建议用定长帧 帧头 校验和的结构帧格式[0xAA][0x55][LEN][DATA0][DATA1][ALARM][SUM][0x0D][0x0A]其中DATA0为PPM高字节DATA1为低字节ALARM为报警状态SUM为前面所有字节累加和。收端先找0xAA 0x55再校验累加和能最大程度避免乱码导致的解析错误。代码实现如下uint8_t txBuf[9]; void CO_SendFrame(uint16_t ppm, uint8_t alarm) { uint8_t sum 0; txBuf[0] 0xAA; txBuf[1] 0x55; txBuf[2] 4; txBuf[3] (uint8_t)(ppm 8); txBuf[4] (uint8_t)(ppm 0xFF); txBuf[5] alarm; sum txBuf[0] txBuf[1] txBuf[2] txBuf[3] txBuf[4] txBuf[5]; txBuf[6] sum; txBuf[7] 0x0D; txBuf[8] 0x0A; for (uint8_t i 0; i 9; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, txBuf[i]); } }USART_FLAG_TXE表示发送寄存器为空可以写入下一个字节。这个循环是低效的阻塞发送但对115200波特率来说9个字节也就0.8ms主循环完全能接受。注意校验和只要8位溢出的高位直接丢弃收发两端算法一致即可。4.3 SSCOM和XCOM的参数设置与踩坑点工程配套的调试助手说两个最常用的SSCOM和正点原子XCOM。第一次打开先别急着点“打开串口”把波特率设成115200数据位8、停止位1、校验位None这个必须和STM32端完全一致。SSCOM里有“DTR”“RTS”两个复选框如果勾着打开串口的瞬间会拉低DTR信号而不少STM32板子的BOOT0或复位电路恰好和DTR/RTS有联动会导致芯片复位或进入ISP模式。表现为一开串口就开始乱码或者程序直接重新运行。处理方法是取消勾选DTR和RTS或者换用不带流控的调试线。验证链路是否通畅的步骤USB转TTL模块的TX接STM32的PA10RXRX接PA9TXGND共地打开XCOM选择对应COM口波特率设115200上电后观察是否持续输出以AA 55开头的9字节数据帧用打火机气体短暂接触MQ-7看到PPM值出现波动即链路正常。串口调试助手只能证明“数据发出来了”不能证明“数据是对的”。下一步校验浓度数值就需要回到第3章的R0基线做确认。5. MQ-7标定与现场验收的实战技巧5.1 三分钟逐级标定法MQ-7出厂时并没有统一的R0值同一批次不同芯片差异能到20%。不做标定直接显示绝对浓度数字只有参考意义。这里给出一套能在现场执行的精简标定方法上电后先预热10分钟让传感器内部温度场稳定。然后让模块处在洁净空气环境中记录ADC值连续1分钟的最小值作为baseline。再取标准气源或已知浓度的CO气体比如标准气袋记录稳定后的ADC值。两点映射得到一个线性系数写入Flash以备下次启动加载。这个方案不需要光谱仪精度够家用级报警但别指望它通过计量认证。5.2 报警阈值与防误报一氧化碳的安全阈值行业内常用NIOSH标准35ppm下连续暴露8小时为允许上限200ppm为立即危险浓度。家用报警器常见阈值是100ppm超过后蜂鸣器必须持续报警。工程里还要加一个防误报逻辑浓度超过阈值后持续3次采样都超标才触发报警避免传感器瞬时毛刺导致误报。采样周期500ms3次确认意味着1.5秒延迟这个延迟人耳是接受的。5.3 串口后门让标定系数可以在线调整最后一个技巧是把标定参数放到串口指令里不必每次改代码重新烧录。比如上位机发送CAL 120STM32收到后把当前ADC值视为120ppm计算新的线性系数并存入Flash发送TH 80则把报警阈值改成80ppm。这样在现场调试时不需要动代码就能反复校准。工程文件里的stm32f10x_flash.c正好可以用来保存标定参数Flash写入寿命在1万次左右标定动作不是高频操作完全够用。串口接收端只需在中断里做简易状态机判断帧头、指令类型、数值三个字段即可。这样做的好处是换一个传感器硬件烧录一次固件后续调试全部在调试助手里完成。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →