单片机一氧化碳检测仪设计:MQ-7选型、ADC采样与报警标定 简介面向电子信息、自动化及嵌入式方向的学生与单片机课程设计人员围绕AT89C52单片机与CITY 7E/F一氧化碳传感器展开解决室内及公共场所CO浓度实时监测与超限报警问题。压缩包约1.16MB仅含1个PDF文件完整收录摘要、目录、系统总体设计、硬件电路、软件流程、总结、致谢、参考文献与附录便于直接对照撰写课程设计或毕业设计。硬件部分涵盖单片机最小系统、数据采集、AD0832模数转换、数码管显示、电源及三极管驱动的单音频报警电路软件部分采用结构化设计给出模数转换模块程序、报警上限编程设置方法并通过信号补偿降低测量误差。目前已有218人学习适合需要从原理、电路到程序完整梳理一氧化碳检测仪方案、快速搭建实验与排错思路的读者参考。1. 从一氧化碳检测仪的核心指标反推单片机选型很多基于单片机的一氧化碳检测仪设计第一版都能在面包板上跑通传感器一接ADC 一动串口打印一个数蜂鸣器按阈值响。真正装进外壳后读数开始漂做饭蒸汽、酒精、温湿度变化、MQ-7 加热周期切换都会让 ppm 乱跳。问题于是从“能不能测”变成“怎么把原始 ADC 变成可信 ppm并且让单片机知道什么时候该报故障”。这个标题适合课程设计、毕业设计和工控入门用 51 单片机、STC 单片机或 STM32 都能做但选型取决于要不要 12 位 ADC、TF 卡表格记录、串口升级和低功耗。先把测量范围、报警分档、标定方式定下来再回推单片机和传感器后面才不会反复改板。2. 一氧化碳传感器与信号调理电路从 MQ-7 到电化学模组的方案对比2.1 MQ-7/MQ-9 半导体传感器的工作周期MQ-7 是课程设计里最常见的一氧化碳传感器便宜、外围少但它不是“上电就线性输出 ppm”的器件。它内部有加热丝常见驱动方式是高电平加热一段时间再切到低电平测量循环往复。加热周期不按器件特性走读数会跟着加热状态起伏后面再滤波也救不回来。下面是一段 STC89C52RC 定时器产生 MQ-7 加热周期的示例。实际板子上高电平通常通过 MOS 管给加热丝 5V低电平测量阶段用另一路基准或分压给到约 1.4V不能简单理解为 0V。// STC89C52RC 定时器0 产生 MQ-7 加热周期 // 假设 12MHz 晶振定时器0 模式1约 1ms 中断一次 #include reg52.h sbit MQ7_HEAT P1^0; // 1: 5V 加热0: 1.4V 测量 unsigned int tick 0; void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器0模式116位 TH0 0xFC; // 1ms 初值按实际晶振重算 TL0 0x18; ET0 1; EA 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { TH0 0xFC; TL0 0x18; tick; if (tick 60000) { MQ7_HEAT 1; // 前 60s 高电平加热 } else { MQ7_HEAT 0; // 后 90s 低电平测量 } if (tick 150000) { tick 0; // 150s 一个完整周期 } }这段代码的逻辑很直白定时器每 1ms 累加一次60s 对应 60000 个 tick90s 对应 90000 个 tick总共 150s 一轮。参数不是固定真理MQ-7 不同批次、不同厂家手册会有差异常见做法是先按 60s/90s 跑再用标气看响应速度和恢复时间。测量阶段切换后的前 10s 数据我一般直接丢弃等加热丝热平衡再采样否则滑动平均会把切换尖峰带进 ppm。2.2 电化学一氧化碳模组的 UART/模拟输出如果不想折腾加热周期电化学一氧化碳模组更省心。常见模组有模拟电压输出也有 UART 输出。模拟输出一般接 ADCUART 输出则直接给单片机串口。它的线性度通常比 MQ-7 好交叉干扰也小代价是成本更高、寿命到期需要更换。以一类常见 UART 电化学模组为例帧长 9 字节帧头 0xFF浓度在固定字节里最后带校验。下面代码只做解析框架字节位置和校验方式必须按你手上模组的手册改不能照抄。// 电化学 CO 模组 UART 解析示例帧头 0xFF第2/3字节为浓度和校验 #include stdint.h #define FRAME_LEN 9 int parse_co_frame(uint8_t *b) { if (b[0] ! 0xFF) { return -1; // 帧头不对丢弃 } uint8_t sum 0; for (int i 1; i 7; i) { sum b[i]; } sum 0xFF - sum 1; // 常见校验取反加一 if (sum ! b[7]) { return -1; // 校验失败 } return (b[2] 8) | b[3]; // 返回 ppm具体字节按手册 }逻辑说明先判断帧头再做校验最后拼浓度。参数上重点是波特率、帧长、校验字节位置和单位。有些模组返回的是 ppm有些返回 0.1ppm有些还要乘量程系数。接 STM32 时可以把 UART 接收放进空闲中断加 DMA接 51 单片机时用接收中断逐字节入环形缓冲别在中断里做浮点运算。2.3 信号调理分压、运放、参考电压怎么定MQ-7 的敏感体电阻 Rs 随 CO 浓度变化通常要和一个负载电阻 RL 串联测 RL 两端电压。RL 常见取 10kΩ 左右太小灵敏度低太大 ADC 输入阻抗影响明显。假设 ADC 参考电压是 3.3V传感器模块输出 0 到 3.3V那么 ADC 码值换算电压就是float adc_to_voltage(uint16_t adc, float vref) { return (float)adc * vref / 4095.0f; // 12 位 ADC }如果 ADC 是 10 位分母改成 1023如果参考电压是 5Vvref 传 5.0。MQ-7 的 ppm 不是直接从电压线性得到常见做法是先算 Rs/R0再用幂函数或分段表拟合。形式通常写成 ppm a * (Rs/R0)^ba 和 b 来自标定或手册曲线。运放可以用 LM358 做电压跟随降低输出阻抗但要注意 LM358 在 3.3V 下共模范围有限单电源 5V 供电时更合适。参考电压要干净ADC 的 VREF 脚加 100nF 和 10uF 去耦别和加热丝共用一条细地线。2.4 传感器选型对比表与接线步骤方案输出优点缺点适合场景MQ-7模拟电阻变化成本低、资料多需加热周期、非线性、易受温湿度影响51 单片机课程设计、毕业设计MQ-9模拟电阻变化可测 CO 和可燃气体交叉干扰更明显低成本多气体演示电化学模组模拟或 UART线性好、功耗低单价高、寿命有限便携巡检、STM32 低功耗设备接线步骤可以按这个顺序先只给传感器供电不接 ADC测加热丝电压是否按周期切换再把 AO 接到 ADC 输入串口打印原始码值然后在干净空气里记录零点用酒精或标气看趋势最后才接蜂鸣器和继电器。注意 MQ-7 首次使用通常需要长时间老化常见做法是通电预热 24 到 48 小时再标定否则前几天的读数没有参考价值。3. 单片机最小系统与 ADC/串口采样用 STC/STM32 读取 CO 浓度的最小工程3.1 STC89C52RC 与 STM32F103 的一氧化碳检测仪选型边界51 单片机和 STM32 都能做一氧化碳检测仪但边界很清楚。STC89C52RC 这类 51 单片机适合按键、数码管、LCD1602、蜂鸣器、继电器这种逻辑控制做蓝桥杯单片机或课程设计很顺手。它本身没有 12 位 ADC常外接 PCF8591、ADC0832 或带模拟输出的传感器模块。STM32F103 自带 12 位 ADC、DMA、多路定时器和串口做 TF 卡表格记录、串口升级、上位机 CSV 输出更省事。对比项STC89C52RCSTM32F103ADC通常外扩 8 位内置 12 位多通道串口1 个 UART多 UART支持 DMA低功耗有掉电模式外设简单Stop/Standby 模式外设多开发工具Keil C51、STC-ISPKeil MDK、CubeMX适合课程设计、数码管显示巡检仪、数据记录、串口升级如果题目要求“基于单片机的一氧化碳检测仪设计”又没有指定平台我一般会建议只要求报警和显示用 STC89C52RC要求 12 位 ADC、历史曲线、TF 卡 CSV、串口在线升级用 STM32F103 或 STC32G 系列。C51 和 ARM 的功耗对比不能只看内核要看加热丝和显示背光它们往往才是耗电大头。3.2 最小系统晶振、复位、电源和基准51 最小系统STC89C52RC 常用 11.0592MHz 晶振串口波特率更准复位是高电平复位RC 复位加按键。STM32F103 常用 8MHz 晶振经 PLL 到 72MHz复位是低电平复位NRST 接 100nF 到地。电源部分MQ-7 加热丝瞬间电流不小5V 走线要粗模拟地和数字地单点汇合。ADC 基准直接用 VDDA 时VDDA 要加磁珠和电容滤波别把继电器线圈的噪声带进 ADC。3.3 ADC 采样轮询、DMA 与定时器触发STM32 读取 CO 传感器时如果只用轮询主循环里还要刷屏、扫按键采样间隔会抖。更常见的做法是定时器触发 ADCDMA 搬 64 个点主循环只读平均值。下面是一段初始化骨架具体寄存器按 HAL 库和芯片型号调整。// STM32F103 ADC1_IN0 定时器触发 DMA 采样 CO 传感器 #include stm32f1xx_hal.h ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; uint16_t adc_buf[64]; void CO_ADC_Init(void) { __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE(); hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 64); }逻辑说明定时器 2 的 TRGO 事件触发一次 ADCDMA 把结果写入 adc_buf采满 64 个点后可以产生中断或由主循环处理。参数上采样时间建议设长一点比如 55.5 周期或 71.5 周期因为传感器输出阻抗不低采样保持电容需要时间充电。参考电压 3.3V12 位满量程 4095算电压用 adc * 3.3 / 4095。如果读到的值一直接近 0 或 4095先查传感器供电和地线再查 ADC 通道配置。3.4 串口输出 CSV把浓度送到上位机/TF卡调试阶段最省事的办法是让单片机按固定格式发串口比如“adc,voltage,ppm\n”。上位机用 Python 直接存 CSV后面用表格软件画曲线。下面脚本按 9600 波特率读取实际波特率要和单片机一致。import serial import csv import time ser serial.Serial(COM3, 9600, timeout1) with open(co_log.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([timestamp, adc, voltage, ppm]) while True: line ser.readline().decode(ascii, errorsignore).strip() if not line: continue parts line.split(,) if len(parts) 3: adc, voltage, ppm parts writer.writerow([time.time(), adc, voltage, ppm])这段脚本的逻辑是逐行读取、跳过空行、按逗号拆分只有三个字段齐全才写入。参数上COM3 要换成实际串口timeout 设为 1 秒避免卡死。如果单片机端还要写 TF 卡格式可以保持一致用 FatFs 的 f_printf 按行追加后面第 5 章会给出代码。4. 报警阈值、标定与滤波一氧化碳检测仪的参数设定和代码实现4.1 报警阈值分档30ppm/50ppm/100ppm 怎么落地一氧化碳检测仪的报警阈值不能只写一个数。常见做法是分三档低报、中报、高报每一档对应不同声音节奏和继电器动作。阈值本身要结合传感器量程和场景下面这个表是很多巡检设备会采用的分档思路。档位浓度阈值确认时间动作低报30ppm连续 3 次采样黄灯、慢鸣中报50ppm连续 2 次采样红灯、快鸣高报100ppm立即红灯、长鸣、继电器断开故障ADC 超范围立即故障灯、蜂鸣器间歇确认时间很重要。传感器在加热切换或蒸汽经过时会出现瞬时尖峰如果一次超阈值就报警设备会误报。用连续采样确认配合回差比如高报 100ppm 触发降到 90ppm 以下才恢复可以避免在阈值附近反复跳。4.2 零点标定与跨度标定干净空气和标气MQ-7 的标定分两步零点标定和跨度标定。零点在干净空气里做记录 ADC 和 Rs算出 R0。跨度标定用已知浓度标气比如 100ppm调整幂函数系数。没有标气时至少要做零点标定并把系数存在 EEPROM 里。下面是一段 STC 内部 EEPROM 保存标定值的示例地址和写函数按具体芯片的例程替换。// STC 内部 EEPROM 保存 R0 与标定系数 #include reg52.h #include eeprom.h #define R0_ADDR 0x0000 void save_cal(float r0, float k) { unsigned char *p (unsigned char *)r0; for (int i 0; i 4; i) { EEPROM_Write(R0_ADDR i, p[i]); } p (unsigned char *)k; for (int i 0; i 4; i) { EEPROM_Write(R0_ADDR 4 i, p[i]); } }逻辑说明把 float 按字节写入 EEPROMR0 占 0x0000 到 0x0003k 占 0x0004 到 0x0007。参数上要注意字节序读写必须一致写之前确认该扇区已擦除很多 EEPROM 例程要求先擦后写。没有标气时不要随便改 k只保存 R0 就能明显改善零点一致性。4.3 滑动平均与一阶滤波抑制加热周期干扰传感器原始值会抖滑动平均和一阶低通是最常用的两种滤波。滑动平均适合周期性噪声一阶低通适合缓慢漂移。两者可以串起来用先滑动平均再一阶低通。#define N 16 float ma_buf[N]; int ma_idx 0; float moving_average(float x) { ma_buf[ma_idx] x; ma_idx (ma_idx 1) % N; float sum 0; for (int i 0; i N; i) { sum ma_buf[i]; } return sum / N; } float low_pass(float x, float prev, float alpha) { return alpha * x (1.0f - alpha) * prev; }逻辑说明N 是窗口长度16 个点适合 ADC 几十毫秒到几百毫秒采一次的场景N 越大曲线越平滑但报警响应越慢。alpha 一般取 0.1 到 0.3越小越平滑越大越跟手。加热周期切换后的前 10s 数据建议丢弃不进入滤波窗口。如果发现 ppm 一直滞后先把 N 降到 8再看报警确认次数是否要增加。4.4 报警逻辑蜂鸣器、继电器和故障自检报警逻辑最好写成状态机不要堆 if-else。下面代码把正常、低报、中报、高报、故障分开输入是 ppm 和 ADC 电压输出是状态。typedef enum { CO_NORMAL, CO_LOW, CO_MID, CO_HIGH, CO_FAULT } co_state_t; co_state_t co_update(float ppm, float v_adc) { if (v_adc 0.1f || v_adc 3.2f) { return CO_FAULT; // 传感器开路或短路 } if (ppm 100.0f) { return CO_HIGH; } if (ppm 50.0f) { return CO_MID; } if (ppm 30.0f) { return CO_LOW; } return CO_NORMAL; }逻辑说明电压范围判断可以抓传感器断线、ADC 基准异常。参数上0.1V 和 3.2V 要按实际电路分压比改不能直接套用。蜂鸣器接 GPIO 时加三极管驱动继电器线圈加续流二极管。故障状态要和报警状态区分故障是设备自身不可信报警是环境浓度超标两者声音节奏不要一样。5. 进阶低功耗、自检与串口升级的一氧化碳检测仪落地技巧5.1 低功耗巡检间歇加热与休眠电流便携式一氧化碳检测仪如果一直给 MQ-7 加热电池撑不了多久。常见做法是间歇巡检单片机定时唤醒给传感器加热一段时间采样算完 ppm再进休眠。STM32 可以用 RTC 唤醒 Stop 模式STC 单片机可以用掉电模式加外部中断或看门狗唤醒。下表是量级参考具体数值以实测为准。模式主要耗电说明连续加热加热丝 单片机运行响应快功耗高间歇加热占空比决定平均电流适合巡检响应变慢休眠单片机 Stop/掉电保留 RAM关外设显示关闭背光、数码管背光往往比单片机大低功耗不是把单片机休眠就完事加热丝和显示背光才是大头。我一般先测加热丝平均电流再决定巡检周期。如果要求 30 秒内出读数间歇加热的占空比不能太低否则传感器还没热起来。5.2 传感器失效自检与看门狗一氧化碳检测仪最怕“坏了还显示 0ppm”。自检可以抓三类异常ADC 电压长期贴地或贴满量程、加热丝电流异常、读数长时间不变化。前两类用阈值判断第三类可以用变化率判断。看门狗要开主循环喂狗ADC 中断和串口中断不要喂狗否则主循环卡死看门狗也复位不了。串口升级对现场设备很有用。c51 单片机串口升级架构常见做法是Bootloader 占一小段 FlashApp 占另一段上电先判断升级标志收到升级包后写暂存区校验通过再跳转。STM32 可以用双区加 CRCSTC 单片机可以借助 STC-ISP 的串口下载做产线烧录现场升级则要自己写 Bootloader。5.3 串口升级和 TF 卡表格记录把浓度记录到 TF 卡并以表格形式存储最直接的是 CSV。用 FatFs 打开追加模式每次写一行写完立刻关闭掉电最多丢最后一行。下面代码按 CSV 表格追加字段是时间、电压、ppm。// FatFs 记录 CSV 表格每次追加一行 #include ff.h FATFS fs; FIL fil; FRESULT fr; UINT bw; void log_csv(float ppm, float v) { fr f_open(fil, co_log.csv, FA_OPEN_APPEND | FA_WRITE); if (fr FR_OK) { f_printf(fil, %lu,%.2f,%.1f\n, HAL_GetTick(), v, ppm); f_close(fil); } }逻辑说明FA_OPEN_APPEND 表示存在就追加不存在就新建f_printf 写一行 CSV时间用毫秒计数电压保留两位ppm 保留一位。参数上TF 卡要先格式化为 FAT32SPI 模式接线尽量短CS 脚加上拉。如果一次巡检要记几个月按天分文件比如 co_20250101.csv避免单个文件过大。写入后立刻 f_close掉电最多丢最后一行这比攒够 512 字节再写更适合巡检仪。本文还有配套的精品资源点击获取