
手头的Arduino Uno套件里十有八九躺着一块蓝色的小模块丝印写着MQ-135。你问它有什么用网上资料一大堆但绝大多数教程只是让你读一个串口数值看灯亮灯灭看完等于没看。我从最早按教程接线的第一次到后来把它塞进一个长期跑数据的小项目前前后后踩了不少坑也总算把这颗十几块钱的传感器脾气摸得比较透。这篇实验笔记正好是我这套Uno实验里的第15个就把Arduino Uno和MQ-135这组经典组合从头到尾捋一遍它到底能测什么、内部怎么工作、怎么接线、怎么写稳定的读值代码、怎么做校准、怎么把电阻比换算成PPM浓度最后一节是实测里真正遇到的故障与对策。你完全可以照着步骤复现也可以直接跳到对应的坑位对照排查。1. MQ-135到底能测什么先别急着接线1.1 一份气体清单和“广谱”的真正含义MQ-135的说明书写得很热闹能检测氨气、氮氧化物、酒精、苯系物、烟雾、二氧化碳。但这里有一个必须先讲清楚的概念——它属于广谱型气敏元件不是单一气体传感器。也就是说它对上面这些气体都有响应但没有选择性。如果房间里同时存在酒精和氨气你拿到的读数是一个混合后的综合结果没办法拆开说是哪种气体贡献了多少。这一点决定了它的真实定位是“空气污染程度指示器”而不是“某某气体浓度计”。你可以拿它判断“当前空气比刚才差了”或者“某个污染源靠近了”但别指望它像实验室仪器一样告诉你精确到个位的ppm浓度。1.2 最常见的误用场景很多初学者把它当成CO2传感器。严格来说CO2只是它“沾边”的目标之一而且它对CO2的灵敏度远不如对氨气、酒精那么高。用它做室内通风提醒看一个粗粒度趋势判断“该不该开窗”问题不大但要写入论文或者对接精密环境监测系统就得慎重了。它的经典应用场景其实是氨气检测。农业养殖场里鸡鸭棚舍粪便发酵会产生大量氨气用MQ-135做通风联动是社区里非常成熟的做法。其次是烟雾报警的辅助判断——注意是“辅助”真正做火警系统还是要靠光电式烟雾传感器。下表是我结合datasheet整理的常用目标气体参考范围目标气体典型检测范围响应灵敏度典型用途氨气 NH310 ~ 300 ppm高养殖场、冷库氮氧化物 NOx10 ~ 1000 ppm高尾气环境监测酒精 C2H5OH10 ~ 300 ppm中高酒精泄漏/气体实验苯系物10 ~ 1000 ppm中装修污染粗测二氧化碳 CO2350 ~ 10000 ppm低通风趋势参考烟雾-中烟雾预警辅助1.3 为什么“能力边界”必须先讲因为后边所有的校准和公式换算都依附于同一个前提你想测的到底是哪一种气体。选错了假设公式算出来的浓度能差出一个数量级这不是精度问题是方法论问题。我在一开始就花了整整一段说边界就是希望你先确定项目目标再决定用哪种校准方法和换算公式。2. 二氧化锡敏感层与电压分压读懂模块的“内功”2.1 核心原理表面吸附反应改变电阻MQ-135的心脏是一块二氧化锡SnO2厚膜。它被加热到两三百度以后表面会吸附空气中的氧气分子氧分子从半导体材料夺取自由电子形成氧负离子吸附态这时候敏感层表面出现一个势垒整体电阻处于一个较高的水平。当还原性气体氨气、酒精、一氧化碳这类凑过来它们会和吸附的氧发生反应把电子“还”回去势垒降低电阻随之下降。气体浓度越高参与反应的分子越多电阻降得越狠。一句话概括气体分子扮演了电子搬运工间接改变了半导体电阻。这个电阻变化量就是整个传感器输出的原始信号。2.2 加热器、传感器电阻和负载电阻SnO2敏感层必须在高温下才灵敏所以传感器内部封装了一根镍铬合金加热丝通电后发热。这就是为什么模块一上电就会微微发烫——那真不是故障是工作状态。手册给的数据是加热电阻约31.7Ω±3.3Ω5V供电时加热功率小于0.8W电流大概150mA左右。这组数字在你以后做低功耗项目时非常重要它意味着MQ-135不适合电池长期供电老老实实插电使用就好。传感器本体在电路上表现为一个可变电阻记作Rs。模块上还有一个固定电阻叫负载电阻RL常见1kΩ或10kΩ不同厂家板子不一样也有设计成20k~47kΩ的。Rs和RL串联电源从上往下分压中间抽头接到模拟输出口AO。气体来了Rs变小RL两端分到的电压就变大AO输出的电压也跟着变大。整个过程就是一个再简单不过的电阻分压电路。2.3 模块上LM393比较器和DO口的真相再仔细看模块除了传感器还有一个LM393双比较器。它的作用是把AO的模拟电压和一个阈值电压做比较输出一个数字电平。阈值由板上的蓝色电位器调节。所以你手里的模块其实同时给了两路信号AO是模拟量用来精确读值DO是数字量用来做简单报警。这里顺便纠正一个流传很广的误解有人说“MQ-135模块只能读数字没法读模拟”这是不对的。模块上标着AO的焊盘一直都在只是很多教程从头到尾只用DO没提AO而已。2.4 电压方向别记反分压公式是Vout VCC × RL / (Rs RL)。气体浓度升高 → Rs下降 → Vout上升 → Arduino模拟口读到的ADC数值变大。反过来干净空气里Rs大Vout就小ADC值也小。电阻越大 → 电压越小 → ADC值越小这个方向关系建议拿记号笔写在模块背面省得调试时绕晕。3. 接线与模块选型模拟口、数字口、电位器一次说清3.1 四根线的标准接线MQ-135成品模块通常是四针VCC、GND、AO、DO部分板子还把A、B焊盘引出来。和Arduino Uno的接线如下MQ-135引脚Arduino Uno引脚说明VCC5V给加热丝和比较器供电不要接3.3VGNDGND和Uno共地AOA0模拟输出接模拟输入DOD8数字输出可选接数字输入接线顺序建议先接GND再接VCC最后接信号线。拆的时候反过来先拔信号线。养成这个习惯能少烧很多传感器。3.2 供电问题别让USB口硬扛前面算过MQ-135加热电流约150mA加上Uno板子本身和可能的显示屏、WiFi扩展板USB口的500mA就会变得非常紧张。这种情况下读数会莫名其妙地跳动甚至系统重启。建议直接给Uno插9V/12V适配器或者用一个5V/2A的稳压电源从5V引脚供电。做任何测量项目电源余量永远要往宽里算。3.3 裸传感器和成品模块怎么选市面上有两种形态一种是六针的裸传感器只有加热丝引脚H和敏感层引脚A/B需要自己焊负载电阻另一种是带LM393的成品模块插上就能用。新手建议买成品模块省掉焊接和分压计算的麻烦。如果以后做产品用裸传感器自己设计分压电路反而更可控因为板载RL固定了想改灵敏度没得选。裸传感器接线有个细节要特别注意加热丝引脚和敏感层引脚不能接反。H脚接5V和GNDA端接VCCB端经过RL到GND从B端取样。接反轻则数值不对重则直接烧掉敏感层。3.4 电位器到底拧哪里怎么验证DO口接的是LM393比较器电位器设定阈值。拧的方向会影响触发灵敏度把一个酒精棉球靠近传感器用万用表测DO对地电压正常会看到电平翻转。如果是高电平报警还是低电平报警取决于模块上LED接法不同板子可能相反以实测为准。这里也分享一个我在课堂上常用的验证方法不点火只用打火机出气口在传感器旁边按一下或者拿酒精棉球晃两下观察DO和LED有没有反应。这比点明火安全得多也足够验证传感器是否存活。测试完别忘了开窗通风。4. 串口读值实验从最小程序到稳定滑动平均4.1 最小读值程序接线完成后先把下面的代码烧进去。这是整个实验的起点看原始数据。const int gasPin A0; // 传感器AO接A0 void setup() { Serial.begin(9600); } void loop() { int raw analogRead(gasPin); // 返回0~1023 float voltage raw * (5.0F / 1023.0F); // 换算为电压 Serial.print(raw ); Serial.print(raw); Serial.print( , voltage ); Serial.print(voltage, 3); Serial.println( V); delay(500); }打开串口监视器波特率选9600你会看到一行一行刷新。analogRead返回的是0到1023的整数对应0V到5V。如果你用万用表实测过5V引脚发现实际只有4.8V可以把代码里的5.0F改成实际值精度会更好一点。4.2 为什么刚开机读数会“飘”刚上电的前几分钟数值经常忽高忽低甚至一路爬升或者一路下跌。这非常正常加热丝要把敏感层从室温加热到工作温度表面吸附反应要慢慢建立平衡这个过程少说也要几分钟。刚通电5分钟内的读数不要采信。如果传感器在仓库里放了一两个月第一次上电的稳定时间可能长达数小时需要有点耐心。我有一次做长时间记录开机头十分钟的曲线简直像过山车差点以为是接线错误。后来在项目里加了一个“预热倒计时”逻辑上电前5分钟只显示预热状态不参与任何报警判断这个问题就再没困扰过我。4.3 用滑动平均压住抖动就算预热完成模拟口读数还是会有小幅抖动因为传感器本身就是个化学反应过程不是理想的线性电阻。最简单的处理办法是滑动平均维护一个固定长度的窗口每次采样把最老的值踢出去把新值加进来再求平均。const int gasPin A0; const int WINDOW 10; // 窗口长度数值越大越平滑响应越慢 int buffer[WINDOW]; int index 0; long sum 0; void setup() { Serial.begin(9600); memset(buffer, 0, sizeof(buffer)); } void loop() { sum - buffer[index]; buffer[index] analogRead(gasPin); sum buffer[index]; index (index 1) % WINDOW; int avg sum / WINDOW; Serial.println(avg); delay(100); }注意窗口长度是个折中窗口越长越平滑但响应越迟钝。做报警项目的时候如果窗口设成50传感器已经冲到报警值了你的系统可能还要好几秒才反应过来。我的经验是10到20这个区间比较舒服。4.4 串口绘图器比监视器好用得多Arduino IDE从1.6.7版本开始内置了一个串口绘图器Serial Plotter位置在“工具”菜单里。它会把一行一个的数字画成实时曲线比盯着串口监视器里的数字跳来跳去直观一百倍。测试传感器有没有反应最直接的步骤先把窗口打开曲线稳定成一条水平线然后拿一个酒精棉球从20厘米外缓缓靠近传感器你会看到曲线迅速往下掉电阻变小ADC数值变小——注意这个方向和你想的可能相反拿开棉球曲线慢慢爬回原来的水平线。能画出这个“下跌再回升”的过程就说明传感器和接线都没问题。5. R0校准决定所有换算准确度的关键一步5.1 为什么不能抄别人代码里的R0网上很多MQ-135教程最后都有PPM换算公式里面必备一个被称为“校准基准”的R0。有些教程直接把这个数写死在代码里告诉你“照着抄就行”。千万别照抄。原因很简单每一颗MQ-135传感器的制造一致性都很差同批次不同器件在相同干净空气中的Rs可以差出好几倍。抄一个别人的R0换算出来的浓度能偏到姥姥家。校准这一步是每一个使用MQ-135的项目绕不开的宿命。5.2 校准环境的准备所谓校准简单说就是测量传感器在干净空气中的基准电阻R0。关键在于“干净”两个字我的做法是新房装修期间不要校准甲醛、苯系物会污染基准。挑一个有风的天气把窗户全打开通风半小时以上。或者把传感器放进一个干净的密封袋扎口前用力灌几口户外新鲜空气别在汽车尾气多的地方取样。校准前还有个前置条件新传感器连续通电48小时行话叫“烧机”或老化。这一步让敏感层表面状态稳定下来之后测到的R0才有意义。第一次用就急着校准的话隔两天你会发现R0自己变了。5.3 手算R0一个能直接用的校准程序先复习一下分压公式变形Rs RL × (VCC - Vout) / Vout。在干净空气中测到的Rs就是我们要找的R0。下面的程序会连续采样50次求平均然后把R0直接打印出来。const int gasPin A0; const float VCC 5.0F; const float RL 10.0F; // 改成你模块上实际的负载电阻单位kΩ void setup() { Serial.begin(9600); delay(3000); // 等串口稳定 } void loop() { float sum 0; for (int i 0; i 50; i) { sum analogRead(gasPin); delay(100); } float avg sum / 50.0F; float vout avg * VCC / 1023.0F; float rs RL * (VCC - vout) / vout; // 单位kΩ Serial.print(Vout ); Serial.print(vout, 3); Serial.print( V, Rs ); Serial.print(rs, 3); Serial.println( kΩ); Serial.print(建议写入代码的R0 ); Serial.print(rs, 1); Serial.println( kΩ); delay(5000); }RL值一定要按你模块的实际电阻改。很多模块板载电压标签旁边有丝印标着1K或10K没有丝印就拿万用表量AO到GND之间的电阻。用错的RLR0会连带着成倍出错。校准完成后我会把R0和校准日期用马克笔写在模块背面方便过几个月重新校准。5.4 把R0存进EEPROM免得每次改代码手算的R0是一次性的。做长期项目时更合理的做法是把它存进EEPROM并且在代码里留一个“校准模式”按住按钮上电系统自动进校准流程并更新R0。下面是这个思路的完整实现。#include EEPROM.h const int gasPin A0; const int calButton 2; // 按钮一端接这个引脚另一端接GND const float VCC 5.0F; const float RL 10.0F; // 单位kΩ按模块实际改 float R0 30.0F; // 默认值第一次使用建议先跑上一节的校准程序 void setup() { Serial.begin(9600); pinMode(calButton, INPUT_PULLUP); EEPROM.get(0, R0); if (R0 0.0F || R0 200.0F) R0 30.0F; // 防呆 Serial.print(当前R0 ); Serial.println(R0); } void loop() { if (digitalRead(calButton) LOW) { Serial.println(开始校准请确保传感器处于新鲜空气中...); float sum 0; for (int i 0; i 100; i) { sum analogRead(gasPin); delay(100); } float avg sum / 100.0F; float vout avg * VCC / 1023.0F; float rs RL * (VCC - vout) / vout; R0 rs; EEPROM.put(0, R0); Serial.print(校准完成新R0 ); Serial.println(R0, 2); delay(3000); } }这里用到了EEPROM.put和EEPROM.get可以直接存float比手动拆字节方便太多。AVR平台上这个库是内置的不需要额外安装。5.5 R0会漂移定期校准是常态校准不是一劳永逸的。敏感层长期暴露在空气中会慢慢吸附油污、灰尘甚至被某些气体“毒化”导致R0漂移。我的习惯是每1到3个月重新校准一次。那些说“我去年校准过今年还在用”的代码换算出来的浓度可信度真的不高。对精度有要求的场景还可以在设备外壳上贴一个“下次校准日期”标签。6. 从Rs/R0到PPM数据手册曲线的拟合与实测对比6.1 数据手册里那张对数坐标图MQ-135的datasheet里有一张最关键的图叫“灵敏度特性曲线”横轴是气体浓度ppm纵轴是Rs/R0两个坐标轴都是对数刻度。每种气体画成一条近似直线。对数坐标下的直线意味着背后的数学关系是幂函数Rs/R0 a × PPM^b或者反过来写成 PPM A × (Rs/R0)^B。这就是所有PPM换算公式的来源。你不需要搞懂高深数学只要知道只要从图上读出两个点就能算出A和B。6.2 手算两个点拟合出你自己的公式以氨气那条线为例我从自己手头datasheet的图上读出两个近似点(10 ppm, 0.5)和(100 ppm, 0.13)。不同批次印刷可能有差异这里只看方法。取对数后用直线方程 log10(Rs/R0) m × log10(PPM) bm (lg0.13 - lg0.5) / (lg100 - lg10) (-0.886 - (-0.301)) / (2 - 1) -0.585 b lg0.5 - m × lg10 -0.301 0.585 0.284于是转换公式为PPM 10^((log10(Rs/R0) - 0.284) / (-0.585))这段在代码里实现就是先把Rs/R0取对数代入算完再pow(10, 结果)。整个过程Arduino的float运算毫无压力。6.3 网上流传的“现成公式”能不能用社区里流传很广的MQ-135 CO2公式是ppm 116.6 × pow(Rs/R0, -2.769)氨气公式是ppm 102.2 × pow(Rs/R0, -2.473)。这些都是爱好者拟合出来的拿来做个趋势参考没问题但你得知道它的几个坑第一在干净空气下CO2公式用校准后的R0算出来大约只有一百多ppm而真实大气CO2浓度约420ppm左右偏低明显。想在绝对数值上更接近得做一个基线修正。第二广谱交叉灵敏度会让换算结果被酒精、氨气等干扰气体带偏。第三这些拟合参数来自某一批手册图纸换成别的厂家的模块误差会更大。6.4 一个完整的计算实例假设你模块的负载电阻RL10kΩVCC5.0V校准得到的R030kΩ。某一时刻Arduino读到模拟值ADC639。我们一步步算电压Vout 639 × 5.0 / 1023 ≈ 3.125V传感器电阻Rs 10 × (5.0 - 3.125) / 3.125 ≈ 6kΩ比例Rs/R0 6 / 30 0.2代入上面拟合的氨气公式 log10(Rs/R0) lg0.2 -0.699 log10(PPM) (-0.699 - 0.284) / (-0.585) ≈ 1.68 PPM ≈ 47.9也就是说这个读值对应的氨气浓度大约是48ppm。这个量级在养殖场通风场景里已经属于“需要开风机”的区间。如果不小心用了别人的R0比如用了30kΩ的两倍60kΩ算出来会变成两倍多判断阈值就全乱了。6.5 不追求绝对浓度时的“比值法”说实话对大多数DIY项目来说绝对ppm浓度的价值没那么大。更鲁棒的做法是直接观测Rs/R0比值的变化。校准完成后干净空气下Rs/R0约等于1污染来了比值往下掉。你可以在代码里写比值小于0.6就报警。这个方法完全绕开了公式拟合误差只用“相对变化”做判断实测非常稳定。我后来做的空气盒子就是用这个思路一年多下来误报率很低。7. 故障排查与温湿度干扰实测中真正会遇到的坑7.1 一张故障排查表把这几年在课程群和项目里看到的高频问题整理成一张表按“现象 → 原因 → 处理”对照查现象可能原因处理方法ADC一直为0AO没接对、没共地检查接线确认GND连在一起ADC一直接近1023VCC接错、传感器烧了检查供电替换传感器对比数值剧烈跳动预热未完成、供电纹波大、旁边有风预热10分钟以上换适配器加滑动平均DO的LED一直亮电位器阈值设得太低调整电位器用酒精棉球验证翻转DO的LED一直不亮阈值太高或传感器坏了反向调电位器测AO电压判断传感器输出数值几乎不变敏感层被油污覆盖或老化钝化通电长时间加热尝试恢复不行就换数值随时间缓慢上涨环境逐渐污染或传感器漂移重新校准R0排查污染源遇到DO口异常时先测AO口的电压。AO有正常电压变化而DO不正常问题在比较器电路或电位器AO都没反应问题在传感器本体或供电。7.2 温湿度干扰湿度天读数会“假报警”SnO2敏感层对温度和湿度都有响应这是材料本身的物理特性。梅雨季节空气湿度高传感器的基线会整体偏移有时候明明空气质量不错报警却莫名其妙触发。温度的影响相对小但也存在。应对方法有三种按性价比排序校准环境和使用环境尽量一致冬天校准的R0到夏天可能偏尽量在接近实际使用的温湿度条件下校准。配合DHT22做粗补偿记录温湿度用经验公式对读数做线性修正。精度一般但趋势会被拉正。接受它是“相对指标”做趋势监控和阈值报警不追求跨季节的绝对浓度一致性。我的实际建议是第三种省钱省心。如果项目真的需要精确的CO2、氨气浓度直接换NDIR原理的传感器比如MH-Z19价格高一个数量级精度也高一个数量级。用MQ-135硬扛绝对精度是性价比很低的一件事。7.3 长期存放后的“唤醒”仪式传感器在抽屉里吃灰半年后重新上电数值往往特别离谱。这是因为存储期间敏感层表面吸附了水汽、灰尘和有机物。别急着扔先通电让它“烧”一个晚上到一天很多情况下数值会慢慢恢复到正常范围。我有一块模块放了半年刚上电时读值比新件高了快一倍通电一晚上之后就回到正常水平了。7.4 电源纹波和接地是隐形杀手如果你发现数值在供电不稳的场景下乱跳先怀疑电源而不是传感器。降压模块输出纹波大的时候ADC参考电压和分压基准一起抖读数自然跟着抖。我做空气质量盒子时曾经被这个问题折磨了一整天最后换了一个纹波更小的5V稳压模块立竿见影。另外一个容易被忽略的点是Uno的AREF和内部参考电压。如果项目里同时接了其他传感器占了A1、A2确保没有给AREF引脚接不合适的电压。A0和其他模拟口的参考是同一个任何干扰都会交叉影响。7.5 一点收尾的心里话这个实验做到最后我最大的体会不是“MQ-135怎么算ppm”而是知道什么时候该停止打磨一颗便宜的传感器。它适合做趋势判断、适合做报警触发、适合做入门学习但如果你在它身上反复纠结“为什么测出来的CO2不是420ppm”那大概率是选错工具了。接受它的性格把它放到它能发光的位置——这本身也是硬件工程里很重要的一课。