MQ-7一氧化碳检测仪设计:STC12 ADC采样、加热时序与R0标定
2026/9/18 17:07:07 网站建设 项目流程

简介:围绕AT89C52单片机的一氧化碳检测仪设计文档,面向电子信息、自动化等专业的课程设计、毕设与单片机入门实践者。内容完整覆盖硬件与软件两条主线:MCU最小系统、CITY 7E/F型CO传感器数据采集、AD0832模数转换、数码管显示、电源与三极管驱动单音频报警电路,以及模数转换程序、数据处理算法与报警阈值编程设定,并含信号补偿降低测量误差的思路,附硬件主电路图、实物图与程序附录。资源包为单个PDF文件,约1.16MB,正文含前言、总体设计、硬件设计、软件设计、总结与参考文献、程序附录等章节,目录结构清晰,可直接打印或分章查阅。已有218人学习下载,适合需要完整方案参考、接口电路与代码实现细节的读者对照使用。

1. 一氧化碳检测仪为什么卡在传感器时序和标定,而不是单片机选型

不少人拿到「基于单片机的一氧化碳检测仪设计」这个题目,第一反应是先定单片机:89C52 还是 STC12,要不要上 STM32。等板子焊出来、程序烧进去,卡住的几乎都不是单片机,而是传感器那头——MQ-7 的读数在 20 ppm 和 300 ppm 之间来回跳,加热几十秒后数值开始往上爬,标定一次管不了三天。

一氧化碳检测仪的成本大头在传感器,但决定这台仪器能不能用的,是加热时序、负载电阻取值和 R0 标定这三件横跨软硬件的事。这篇按选型、实现、联调、长期运行的顺序,把 MQ-7 加 STC12 这条最常见的课程设计路线讲清楚:定时器怎么排 60 秒高温和 90 秒低温,采样窗口卡在哪个时刻,ADC 码值怎么换算成 Rs 和 ppm,报警阈值和滞回怎么定,以及连续通电时读数为什么会慢慢飘、怎么把它压回去。适合正在做这个题目的学生,也适合第一次碰电化学或半导体气敏传感器的嵌入式工程师。

2. MQ-7、电化学与 NDIR:一氧化碳传感器选型和 ADC 匹配

2.1 四类一氧化碳检测方案的成本与适用场景

先看传感器。一氧化碳检测的常见路线有四条,选错方向,后面软件写得再好也救不回来。

| 方案 | 典型器件 | 检测范围 | 一致性 | 外围复杂度 | 成本 | 适合场景 | | 加热式半导体 | MQ-7 | 20~2000 ppm | 差,需常标定 | 中,必须做加热时序 | 低 | 课程设计、教学演示、粗略报警 | | 半导体复合型 | MQ-9 | 10~1000 ppm | 差,交叉敏感 | 中 | 低 | 多功能报警,CO 与可燃气混测 | | 电化学 | 三电极电化学 CO 传感器 | 0~1000 ppm | 好,线性度好 | 低,但需 I-V 转换运放 | 中高 | 便携式报警器、手持仪表 | | NDIR 红外 | 红外 CO 检测模块 | 0~5000 ppm | 好,寿命长 | 高,涉及光路和调制 | 高 | 工业在线监测 |

MQ-7 之所以在课程设计里出现频率最高,是因为它便宜、直插、输出是模拟电压,接上 ADC 就能读数。代价是它对温湿度敏感、离散性大、需要周期加热,而且每颗的 R0 都不一样——同一批买回来的五颗,标定值可能差 30%。

电化学传感器线性好、几乎没有加热功耗,但输出是微安级电流,必须加一级跨阻放大,对初学者来说运放选型和布线容易翻车。如果题目只要求做一台能报警的演示装置,MQ-7 够了;如果要求精度或者要求长期在线,电化学是更合理的选择。

2.2 为什么 STC12C5A60S2 的内置 ADC 比 89C52 外挂 ADC0832 更省事

常见的 51 单片机(STC89C52RC 这一档)没有 ADC,读 MQ-7 得外挂一颗。可选的有 ADC0832(8 位、串行、双通道)和 TLC549(8 位串行)。8 位在 5V 基准下分辨率是 19.5 mV,而 MQ-7 在 100 ppm 附近输出往往只有几百毫伏,有效码值可能只用到二三十个,换算成浓度误差会很大。

STC12C5A60S2 这类增强型 51 自带 8 通道 10 位 ADC,P1 口复用为模拟输入,分辨率 4.88 mV,代码量还少一大截。这就是它在这个题目里被大量采用的原因:一颗芯片同时干了主控和采样的活,PCB 上少一条 SPI 走线,也就少一个干扰源。如果手上是 STM32,12 位 ADC 折合 1.22 mV,更宽裕,但要注意 STM32 的 ADC 输入阻抗和采样时间配置,源阻抗大时要拉长采样周期。

选型结论:题目限定 51 体系,优先选带 ADC 的 STC12 系列;只能用 89C52 时,外挂 ADC 尽量选 10 位以上,别用 0832 硬撑。

2.3 负载电阻 RL 和分压网络的取值怎么定

MQ-7 的等效电路是一个随浓度变化的电阻 Rs,和负载电阻 RL 串联在回路电压 Vc 上,输出电压 Vout 从 RL 两端取。关系式是:

Rs = (Vc / Vout - 1) × RL

RL 的取值直接决定 ADC 能用上多少量程。RL 太小,Vout 整体偏低,高浓度段几乎贴地;RL 太大,Vout 在洁净空气里就逼近 Vc,进入饱和区,低浓度分辨率反而变差。

我一般的做法是让目标浓度区间(家用报警关心的 20~500 ppm)对应的 Vout 落在 0.5~3.5 V 之间。MQ-7 在洁净空气中的 Rs 通常是几十千欧量级,在 100 ppm 时降到几 kΩ。取 RL = 10 kΩ 是个折中的起点,实测后再调。板上把 RL 做成可调电阻或者预留两个焊盘,标定时改起来方便。

提示:RL 上并联的滤波电容不要贪大。0.1 μF 配 10 kΩ 时间常数是 1 ms,足够了;用 10 μF 会把响应拖慢到几百毫秒,还会让加热切换瞬间的尖峰被平均进采样值。

2.4 加热回路的供电和 IO 驱动

MQ-7 要求两个加热档位:5.0 V 维持 60 秒,然后降到 1.4 V 维持 90 秒。5 V 除以加热丝电阻约 31 Ω,瞬时电流约 160 mA,瞬时功率接近 0.8 W;按 60 秒加 90 秒的占空比折算,平均功耗约 350 mW,这正是手册标称值的来历。

这意味着两件事。第一,5 V 电源轨要能扛住 200 mA 以上的脉冲负载,用 USB 供电时最好加 220 μF 以上的储能电容,否则加热开启瞬间 MCU 可能复位。第二,加热丝不能直接用 IO 驱动,必须经 NPN 三极管或者小 MOS 管,IO 只出控制信号。

1.4 V 这一档有两种做法:用基准源加分压,或者用 PWM 等效。PWM 更省事,占空比取 1.4/5 = 28%,频率设在 2~5 kHz,靠加热丝自身的热惯性平滑。要注意 PWM 频率不能太低,否则会看到采样值跟着开关节奏抖。

3. 用定时器排 MQ-7 高低温循环:采样、Rs 换算与 ppm 拟合

3.1 加热时序状态机与 5 ms 定时器节拍

整个仪器的节奏由定时器定。STC12 是 1T 内核,11.0592 MHz 下一次计数就是一个时钟,5 ms 需要 55296 个计数,用 Timer0 模式 1 装初值 0x2800 即可。

// STC12C5A60S2 @ 11.0592MHz, Timer0 5ms 中断驱动加热状态机 #include <stc12c5a60s2.h> sbit HEAT_HI = P2^0; // 经 NPN 驱动加热丝,1 = 5V 档 sbit HEAT_LO = P2^1; // 1 = 切换到 1.4V 档(PWM 通道亦可) volatile bit sample_flag = 0; volatile unsigned int tick5ms = 0; void Timer0_Init(void) { TMOD &= 0xF0; TMOD |= 0x01; // Timer0 模式1,16 位不自动重装 TH0 = 0x28; TL0 = 0x00; // 5ms @11.0592MHz ET0 = 1; EA = 1; TR0 = 1; } void Timer0_ISR(void) interrupt 1 { static unsigned int t = 0; static unsigned char st = 0; TH0 = 0x28; TL0 = 0x00; // 手动重装 tick5ms++; t++; if (st == 0) { // 高温清洗段:60s HEAT_HI = 1; HEAT_LO = 0; if (t >= 12000) { t = 0; st = 1; } } else { // 低温检测段:90s HEAT_HI = 0; HEAT_LO = 1; // 实际用 PWM 等效 1.4V if (t >= 17000 && t < 17800) sample_flag = 1; // 末段采样窗口 if (t >= 18000) { t = 0; st = 0; } } }

逻辑上有三个点要抠。t以 5 ms 为单位计数,12000 对应 60 秒,18000 对应 90 秒,和手册的高低温比例一致。采样窗口开在低温段的末尾(第 85 秒到第 89 秒),因为低温阶段传感器才对 CO 敏感,而且要等温度落下来读数才收敛。sample_flag只置位不清零,交给主循环处理,中断里不碰 ADC 寄存器,避免采样过程被更长的中断打断。

3.2 STC12 内置 ADC 的寄存器配置与采样窗口

STC12 的 ADC 用三个寄存器:P1ASF 设定哪些 P1 口当模拟输入,ADC_CONTR 控制电源、通道和启动,ADC_RES/ADC_RESL 存结果。10 位结果跨两个字节,高 8 位在 ADC_RES,低 2 位在 ADC_RESL 的低两位。

unsigned int ADC_Read(unsigned char ch) { unsigned int val; P1ASF = 1 << ch; // 例如 ch=0,P1.0 作模拟输入 ADC_CONTR = 0x80 | ch | 0x08; // 开电源 | 选通道 | 启动转换 _nop_(); _nop_(); _nop_(); _nop_(); // 给采样保持留几个周期 while (!(ADC_CONTR & 0x10)); // 等 ADC_FLAG 置位 ADC_CONTR &= ~0x10; // 清标志 val = ((unsigned int)ADC_RES << 2) | (ADC_RESL & 0x03); return val; // 0 ~ 1023 }

参数说明:0x80是 ADC_POWER 位,转换前必须置 1,转换完可以清掉省电;0x08是 ADC_START;0x10是 ADC_FLAG。ADC 时钟由 CLK_DIV 的低三位分频,默认 SYSclk/2/1,11.0592 MHz 下约 5.5 MHz,超出了器件建议范围,所以实际工程里要把 CLK_DIV 设成 0x20(SYSclk/2/2)甚至更低,否则低位码值会抖得厉害。这是很容易被忽略的一处配置。

如果主循环在跑 LCD 刷新,采样那一次读完后要连读 3 次丢弃第一次,取后两次平均,因为切换通道后的第一次转换常带残留电荷。

3.3 从 ADC 码值到 Rs,再到 Rs/R0

把 ADC 码值还原成电压,再按分压公式算 Rs。

#define VREF 5.0f // ADC 参考电压 #define RL_KOHM 10.0f // 负载电阻,实测后修改 float Rs_Kohm(unsigned int adc) { float vout = adc * VREF / 1023.0f; if (vout < 0.01f) vout = 0.01f; // 防除零 if (vout > 4.95f) vout = 4.95f; // 防饱和,饱和说明 RL 偏大 return RL_KOHM * (VREF - vout) / vout; }

注意这里的 VREF 用的是回路电压 Vc,不是 MCU 的供电。如果加热丝和传感器共用 5 V 轨,大电流会让 Vc 掉几十毫伏,标定前最好用万用表实测 Vc 写进去,别直接填 5.0。饱和判断那两行不是凑数:读数长期贴 4.95 V,说明 RL 选大了或者传感器没在低温档,这时候算出来的 Rs 是假的。

3.4 两点标定求 a、b,以及为什么不能照抄经验公式

MQ-7 的 ppm 与 Rs/R0 在双对数坐标下近似直线,关系写成:

ppm = a × (Rs / R0)^b

网上流传的ppm = 100 * (Rs/R0)^(-1.53)只是某一条典型曲线的拟合结果,换一颗传感器就不准。正确做法是拿两个浓度点实测,反解 a 和 b。

#include <math.h> /* r1/r2 为两个标定点测得的 Rs/R0,p1/p2 为标准气浓度 */ void Fit_AB(float r1, float p1, float r2, float p2, float *a, float *b) { *b = log(p2 / p1) / log(r2 / r1); // 两点确定斜率 *a = p1 / pow(r1, *b); // 回代求系数 }

标定步骤按这个顺序走:板子通电老化 24 小时,放在通风无 CO 的环境里,记录低温段末的 Rs 作为 R0;然后通 100 ppm 和 400 ppm 标准气(或者用一台校准过的手持仪器对照),各记一组 Rs/R0,代入上面函数求出 a、b,写回固件。

两点里有一点必须说清楚:R0 是每颗传感器自己的值,换传感器就要重标,不能沿用上一颗的。同一批产品可以在产线上一颗一颗标,这在毕业设计里做不了,那就至少做到用同一颗传感器完成全部实验数据。

提示:Keil C51 上的powlog是双精度浮点,一次运算几百个机器周期。主循环里每秒算一次没问题,放进高频中断会拖垮整个系统。真要在中断里算,改成定点查表。

3.5 温湿度漂移的补偿与采样滤波

MQ-7 的灵敏度随相对湿度上升明显下降,湿度从 33% RH 升到 85% RH,Rs/R0 可能变化三成以上。室内环境下这个量级足以让报警点漂掉,所以加一颗 DHT11 或 DHT22 做湿度参考是值得的。

补偿方式不用搞复杂,做一维查表就够:在 20%、40%、60%、80% RH 四个点各标一次 R0,存进固件,运行时按当前湿度线性插值出修正系数。温度影响相对小,可以在 0~40 ℃ 范围内再叠一个线性因子。

采样滤波用中位值平均法,比单纯滑动平均抗脉冲噪声。取 9 个样本排序,去掉最大最小各两个,剩下 5 个求平均,能同时压掉工频干扰和加热切换的偶发尖峰。

4. 报警阈值、LCD1602 显示与整机联调排查表

4.1 分级阈值与滞回区间的设定

报警阈值不能只设一个点,否则浓度在阈值附近抖动时蜂鸣器会疯狂开关。我一般分四档,同时加 10 ppm 的滞回。

| 状态 | 浓度区间(ppm) | 显示 | 蜂鸣器 | 继电器 | | 正常 | < 30 | 数值 + NORMAL | 关 | 断开 | | 预警 | 30 ~ 60 | 数值 + WARN | 1 Hz 间歇 | 断开 | | 报警 | 60 ~ 200 | 数值 + ALARM | 连续 | 吸合 | | 危险 | > 200 | 数值闪烁 + EVAC | 连续 | 吸合 |

滞回的实现方式是:报警触发点 35 ppm,解除点 25 ppm;二级报警触发点 70 ppm,解除点 55 ppm。用两个独立阈值判断上升沿和下降沿,比在单个阈值上做延时可靠得多。30 ppm 这个量级是家用报警器常见的预警起点,如果做的是工业场合,阈值按现场规程另外定。

4.2 LCD1602 刷新节奏与报警状态机

LCD1602 走 4 位并口要占 6 根 IO(RS、RW、EN 加 D4~D7),别用固定延时等指令执行完,忙标志 BF 检测才是正路。刷新频率控制在 5 Hz 左右,也就是 200 ms 一次,太快会闪,太慢在浓度快速上升时看不出变化。

状态机这样写:每 200 ms 读一次当前 ppm,先跟滞回阈值比出新的状态,只有状态或显示值发生变化时才写 LCD。数值部分单独占一行右对齐,报警状态放第二行左侧,这样局部刷新比整屏重写省时间,也避免闪屏。

4.3 万用表加示波器的四步定位法

调试顺序固定下来,能省很多时间。

第一步,用万用表量加热丝两端。低温档应该是 1.4 V 左右,高温档 5 V 左右,两个档位各持续 60 秒和 90 秒。这一步不对,后面全是白费。

第二步,量负载电阻上的 Vout。洁净空气里应该在几百毫伏到 1 V 之间,对着打火机放气时应该明显上升。Vout 不动,先怀疑传感器或者 RL 焊接。

第三步,用示波器看 P2.0 的波形,确认 60 秒和 90 秒的比例,以及 PWM 的 1.4 V 等效电压。加热时序错位是最常见的隐蔽问题——程序逻辑看着对,实际定时器初值算错了。

第四步才是看串口打印的 ADC 码值和算出的 ppm。前面对了,这一步基本就顺了。

4.4 常见故障与对应处理

| 现象 | 可能原因 | 处理 | | 读数恒为 1023 | P1ASF 没设、RL 开路、ADC 通道选错 | 查 P1ASF 赋值,量 RL 两端电压 | | 读数恒为 0 | 传感器未加热、加热丝断 | 量加热丝电阻,正常约 31 Ω | | 数值整体偏高 | R0 标定值偏小或用了别人的 R0 | 重新做洁净空气标定 | | 数值跳动 ±30 ppm | ADC 时钟过快、没滤波 | 调 CLK_DIV,加中位值平均 | | 加热开启时 MCU 复位 | 电源带不动脉冲负载 | 加大储能电容,分离模拟供电 | | 标定后两天又漂 | 温湿度未补偿、传感器未老化 | 老化 24 h,加湿度修正 |

有一处特别容易被忽略:ADC 采样应该避开加热档位切换的瞬间。切换时电流突变几百毫安,地线上会出现几十毫伏的弹跳,正好落在采样窗口里就是一组假数据。把采样窗口放在低温段末尾、离切换点至少几秒,问题自然消失。

5. 让它连续通电一周:R0 老化跟踪、断丝自检与 CSV 记录

真正把检测仪挂在墙上,最难的不是标定,而是三个月后读数还准不准。半导体传感器会老化,R0 会缓慢上移,同一台仪器今天的 30 ppm 可能对应三个月前的 20 ppm。

应对办法是在固件里做 R0 跟踪。设定一个“洁净窗口”,比如每天凌晨的某一小时,把这段时间内低温段末的 Rs 最小值当作准 R0,和出厂标定值做加权平滑更新:

#define R0_ALPHA 0.05f // 每天只挪动 5% float R0_track(float r0_old, float r0_now) { if (r0_now > r0_old * 1.5f) return r0_old; // 明显异常,丢弃 if (r0_now < r0_old * 0.5f) return r0_old; return r0_old * (1 - R0_ALPHA) + r0_now * R0_ALPHA; }

这个上限下限的判断不能省,否则一次断电恢复后的错误读数会把基准带偏,之后所有浓度都跟着错。同时把 R0 相对出厂值的偏移量记下来,超过 ±20% 就在屏幕上提示传感器到期更换——这比等它彻底报错再换要主动。

断丝自检靠加热回路本身就能做。在加热管的低端串一个 1 Ω 的采样电阻,ADC 读它两端的压降,正常加热时应有几十毫伏,读到接近零就说明加热丝断了或者驱动管坏了。这一步不额外增加元件,只多占一个 ADC 通道。

数据记录方面,最省事的做法是通过串口每 5 秒输出一行文本:

# 串口输出格式,直接重定向存成 .csv # 2024-05-11 09:00:05,12.4,3.18,0.0,28.6,NORMAL

导入表格软件就能画曲线,也能直接交给指导老师看过程数据。如果要脱机运行,加一个 SD 卡模块,按小时建文件、每行追加,掉电最多丢当前行。注意写卡要点:一次写满 512 字节再落盘,别每行都调用一次同步,否则写入次数很快耗尽卡寿命。

最后补一个实际经验:整个系统上电后头两分钟读数一定不要采信,加热丝还没到工作温度,这段时间的 ppm 直接显示“预热中”,等走过一个完整的高低温周期再进入正常显示。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询