
简介围绕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 得外挂一颗。可选的有 ADC08328 位、串行、双通道和 TLC5498 位串行。8 位在 5V 基准下分辨率是 19.5 mV而 MQ-7 在 100 ppm 附近输出往往只有几百毫伏有效码值可能只用到二三十个换算成浓度误差会很大。STC12C5A60S2 这类增强型 51 自带 8 通道 10 位 ADCP1 口复用为模拟输入分辨率 4.88 mV代码量还少一大截。这就是它在这个题目里被大量采用的原因一颗芯片同时干了主控和采样的活PCB 上少一条 SPI 走线也就少一个干扰源。如果手上是 STM3212 位 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) × RLRL 的取值直接决定 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 模式116 位不自动重装 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; // 例如 ch0P1.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_START0x10是 ADC_FLAG。ADC 时钟由 CLK_DIV 的低三位分频默认 SYSclk/2/111.0592 MHz 下约 5.5 MHz超出了器件建议范围所以实际工程里要把 CLK_DIV 设成 0x20SYSclk/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/R0p1/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 上的pow和log是双精度浮点一次运算几百个机器周期。主循环里每秒算一次没问题放进高频中断会拖垮整个系统。真要在中断里算改成定点查表。3.5 温湿度漂移的补偿与采样滤波MQ-7 的灵敏度随相对湿度上升明显下降湿度从 33% RH 升到 85% RHRs/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 根 IORS、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 直接显示“预热中”等走过一个完整的高低温周期再进入正常显示。本文还有配套的精品资源点击获取