尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP32+MQ-2烟雾传感器:从原理到应用,零基础自制物联网报警装置

ESP32+MQ-2烟雾传感器:从原理到应用,零基础自制物联网报警装置 想给厨房装个能联网报警的烟雾检测装置又不想直接买成品于是把目光落在了ESP32开发板和MQ-2烟雾传感器上。这两个组合可以说是零基础入门物联网的经典开局MQ-2不涉及复杂的I2C、SPI通信协议只输出一路模拟电压接上就能读数ESP32自带WiFi和蓝牙把报警消息推送到手机也是顺手的事。我最初以为怎么也得折腾一两天结果从接线到第一版报警程序跑通也就花了不到两个小时。这篇就当作一份完整的实操记录来写从MQ-2内部到底怎么“闻”到烟雾到ESP32的接线、开发环境搭建、第一段读码再到浓度换算、滑动平均滤波、报警通知和远程推送最后是我实际踩过的一堆坑。零基础跟着做完全能跑通有点基础的朋友可以直接跳到浓度算法和踩坑部分那里有不少常规教程不会写的细节。1. MQ-2的“嗅觉原理”一块加热的半导体如何闻到烟味1.1 气敏材料与内部结构MQ-2的核心是一个以二氧化锡SnO2为主体的气敏元件。在洁净空气中这个元件的电阻很高几乎不导通一旦接触到可燃气体或者烟雾中的还原性成分比如甲烷、丙烷、丁烷、氢气、酒精蒸汽气体分子就会吸附在材料表面引起载流子浓度变化元件的电导率随之明显上升。用一个生活化的类比来理解把气敏层想象成一张干燥的海绵平时不吸水所以电阻很大而当周围充满“气味分子”海绵就开始吸水吸得越多导电性越好。MQ-2就是这样一张“会吸味的海绵”它吸的是气体分子吸得越多阻值变化越明显。但这个过程不是瞬间的它强烈依赖温度。传感器内部藏了一根加热丝工作电压约5V电流在150到180毫安之间任务是把气敏材料加热到合适的工作温度。正因为有加热丝MQ-2才有“上电预热”这个说法元件温度没稳定之前表面反应速度很慢输出值会乱飘。新传感器甚至建议先通电老化24小时以上再标定使用。这里要先摆正一个心态MQ-2在手册里主要针对天然气、液化石油气、城市煤气等可燃气体同时也对烟雾敏感。也就是说它不是“只认烟雾”的传感器酒精、油烟、炒菜的味道它都可能响应。它的准确定位是“环境气味异常监测”而不是“精确烟雾浓度分析仪”。带着这个认知去设计功能后面的路会顺畅很多。1.2 模拟量与数字量AO和DO必须分清楚市面上常见的MQ-2模块板上其实已经集成了整套外围电路裸传感器、一个负载电阻RL、一个LM393电压比较器、一个可调电位器以及AO、DO两个输出引脚。AO是模拟电压输出。浓度越高传感器电阻Rs越小负载电阻上的分压就越高所以AO电压随烟雾浓度升高而上升。DO是数字开关输出LM393把AO电压和电位器设定的参考电压做比较一旦AO电压超过这个参考值DO就翻转电平。很多人第一次用MQ-2以为DO也输出浓度信息结果读出来只有0和1满脸疑惑。这里提前说清楚DO只告诉你“超没超过我设定的阈值”它不告诉你“浓度是多少”。想读浓度请认准AO。1.3 为什么要串联一个负载电阻如果直接买裸传感器会发现它有6个引脚两个加热丝引脚H和四个测量极引脚A、B。裸传感器的测量极不能直接接到电源两端量电压因为传感器本身就是一个阻值会变的电阻你必须把它和另一个固定电阻串联用分压的方式把“阻值变化”翻译成“电压变化”。这个固定电阻就是RL。分压公式是Vout VCC × RL / (Rs RL)给定了VCC和RLRs越小Vout越大反之亦然。RL的取值决定了信号落在哪个区间一般取4.7kΩ到10kΩ之间模块板上默认焊了一个5kΩ附近的电阻直接用AO口读数就行不需要自己再算分压。如果自己搭电路选RL时有个实用的思路让传感器在洁净空气下的AO电压落在0.2V到0.5V附近明显烟雾时升到1.5V以上这样正好落在ESP32的ADC线性度最好的区间。2. 硬件连接ESP32与MQ-2的接线图与供电细节2.1 四根线的接法MQ-2模块一般引出4个引脚VCC、GND、DO、AO。接线不复杂但有一个细节值得注意VCC最好接5V而不是3.3V。原因是模块上的加热丝和比较器在5V下工作更稳定而且烟雾浓度升高时AO电压会接近VCC如果VCC只有3.3V高浓度场景下输出容易顶到ESP32的ADC满量程读数饱和就没区分度了。接5V的话AO电压范围更大数据更好看。MQ-2模块引脚接到ESP32位置说明VCC5V / VIN给加热丝和比较器供电推荐5VGNDGND必须共地信号才有参考基准DO可暂不接后面需要开关量报警再接AOGPIO34或32/33/35/36/39模拟电压输入必须接ADC1引脚如果你手上的ESP32开发板USB供电能力足够5V直接从板子的VIN或5V引脚引出即可。这里要重点强调共地传感器和ESP32必须共用同一个GND否则AO电压的参考点不一致读数会乱跳而且很难排查。2.2 电流需求与预热MQ-2不是一个低功耗外设加热丝工作电流就有150到180毫安启动瞬间可能更高。ESP32开发板在USB供电下5V引脚一般能提供几百毫安带一个MQ-2没问题但如果板上还接了蜂鸣器、OLED、舵机这些负载就要注意电流预算了。我的实测经验是USB直连电脑时模块、蜂鸣器、OLED三件套一起跑没压力但换上劣质USB线或插在个别笔记本USB口上偶尔会出现电压跌落传感器读数整体偏低。这时候换一根粗短线或者改用5V/2A的适配器供电问题立解。还有一个容易被忽略的环节上电预热。刚上电的MQ-2输出曲线非常难看我见过AO电压从接近满量程慢慢掉到正常值的情况也见过反向慢慢爬上去的。总之预热完成前的读数不具备参考价值。所以不管是标定还是调试先上电等5到10分钟让传感器内部温度稳定下来再说话。2.3 引脚选择ADC1还是ADC2ESP32的ADC分ADC1和ADC2两组。ADC1包含GPIO32到39ADC2包含GPIO0、2、4、12到15、25到27。关键问题在于ADC2和WiFi的硬件存在共用关系一旦WiFi处于工作状态ADC2引脚的模拟读取就会失效读数变成乱跳或者恒定的满量程值。烟雾报警项目几乎都要开WiFi做远程通知所以AO必须接在ADC1上。在ADC1里GPIO36、GPIO37、GPIO38、GPIO39是“仅输入”引脚没有其他复用功能用来读模拟量最干净。GPIO32到35也属于ADC1但通常还复用其他功能用起来也完全没问题。我个人习惯把AO接GPIO34它在很多开发板上比较靠边好走线代码里也好记。如果接线完成发现读数不对第一步先确认是不是接到了ADC2引脚这个坑我踩过不止一次。3. 从零搭建Arduino IDE开发环境并跑通第一段代码3.1 Arduino IDE安装与ESP32支持包获取Arduino IDE是零基础学ESP32最顺滑的入口。去官网下载安装版装好后还需要给IDE添加ESP32开发板的支持包。在线安装的方式是在“文件-首选项-附加开发板管理器网址”里填入ESP32的JSON地址然后到“工具-开发板-开发板管理器”里搜索esp32安装。这个方式看着简单但核心包下载经常卡住很多人在进度条面前等了半小时毫无反应。我的建议是直接用离线安装包从可靠的渠道下载espressif的ESP32包解压后把整个文件夹放到Arduino的hardware目录下。Windows里通常是“我的文档\Arduino\hardware”macOS是“~/Documents/Arduino/hardware”。离线包装完后开发板管理器里照样能搜到ESP32不影响后续更新。这个方法在大学校园网、公司内网这类受限环境下特别好用建议收藏备用。3.2 开发板选择与烧录参数装好支持包后在“工具-开发板”里选择“ESP32 Dev Module”这对应大部分通用开发板。如果你手头是带OLED的特定型号也可以选对应型号但大多数情况下通用选项就能正常编译烧录。接着在“端口”里选择开发板对应的串口Windows下是COMxmacOS下是/dev/cu.usbmodem或/dev/tty.usbserial开头。烧录时最容易碰到的问题是上传失败报错“Failed to connect to ESP32”或者“Serial data stream disconnected”。这个错误九成是开发板没进入下载模式。解法很经典按住板上的BOOT键点上传看到日志出现“Connecting...”的瞬间松开BOOT键基本都能成功。如果Windows下设备管理器里看不到COM口大概率是CP2102或CH340驱动没装好装完重启IDE即可。3.3 第一段代码串口打印原始值与电压环境就绪后烧第一段代码。目的只有一个把MQ-2的AO电压读出来在串口监视器上打印。#define MQ2_AO 34 void setup() { Serial.begin(115200); analogReadResolution(12); analogSetAttenuation(ADC_ATTEN_DB_11); delay(2000); } void loop() { int raw analogRead(MQ2_AO); float voltage raw * 3.3f / 4095.0f; Serial.printf(raw%d, voltage%.3f V\n, raw, voltage); delay(500); }第一行定义引脚为GPIO34方便后面改。analogReadResolution(12)把ADC设置为12位分辨率读数是0到4095而不是默认的10位0到1023。analogSetAttenuation(ADC_ATTEN_DB_11)设置衰减为11dB对应满量程大约3.3V这样3.3V以内的信号都能读。如果不设置衰减ESP32的ADC在电压超过约1.1V时就会饱和烟雾一浓读数就卡在4095不动很多人以为传感器坏了其实是这里没配置。串口监视器里如果能看到raw值和voltage值跟随打火机气体或者烟雾变化说明整个链路已经通了。接下来所有的算法、报警、联网都是在这个基础上叠加。4. 浓度算法进阶从电压值到ppm以及滑动平均滤波的工程实践4.1 读懂特性曲线对数坐标系里的“直线”要把电压变成ppm浓度先得理解MQ-2的数据手册。手册里有一张关键图横轴是气体浓度ppm纵轴是Rs/R0传感器当前电阻和洁净空气中电阻的比值两个轴都是对数刻度。在双对数坐标系下Rs/R0和ppm的关系在常用浓度范围内近似为一条直线不同的气体对应不同的直线。ppm是百万分之一的浓度单位比如200ppm意味着空气中每百万份里有两百份是目标气体。为什么用对数坐标因为传感器的电阻变化跨了好几个数量级线性坐标下低浓度区会挤成一团只有对数坐标能同时看清低浓度和高浓度的变化趋势。这也给了算法上的启发可以用幂函数来拟合这条线形式是Rs/R0 a × ppm^b两边取对数正好是一条直线。所以网上很多MQ-2算法的本质就是对这个幂函数求反解ppm A × (Rs/R0)^B其中A、B是由曲线拟合得到的常数。4.2 R0校准在新鲜空气里确立基线公式里有个R0它是传感器在洁净空气中的电阻值。R0不是固定的每颗传感器都不一样而且会随使用时间漂移。正确流程是把传感器放在通风良好的洁净环境通电预热10分钟以上采集一段时间AO电压计算Rs取稳定后的值作为R0然后把这个R0写进代码或存进EEPROM。Rs的计算公式由分压电路反推Rs (VCC - Vout) × RL / Vout其中VCC是模块供电电压如果VCC接5V这里填5.0Vout是AO读到的电压RL是模块上的负载电阻大多数模块在5kΩ左右看模块背面的丝印确认。代码实现大概是float voltage raw * 3.3f / 4095.0f; float vcc 5.0f; float rl 5.0f; // kΩ根据模块实际值修改 float rs (vcc - voltage) * rl / voltage;注意一个容易翻车的细节AO电压最终进入ESP32的ADC但ADC的满量程是3.3V如果传感器输出电压接近或超过3.3V读到的voltage会失真算出的Rs自然也不对。所以前面强调VCC接5V且RL选择适中目的就是让正常工作范围内的AO电压不要顶到3.3V。如果实测发现AO电压频繁超过3.2V可以用两个电阻把AO信号先分压衰减一半再接GPIO代码里把voltage乘2还原这样高浓度区间的读数精度反而更好。4.3 浓度换算经验公式与实现拿到R0之后每次读取就可以计算当前Rs/R0比值再套用拟合公式转成ppm。一个在社区里流传很广的经验公式长这样float ratio rs / r0; float ppm 613.9f * pow(ratio, -2.074f);这个公式是从手册某条典型曲线拟合出来的对很多模块在家庭环境下都能用数值量级“大概对”。但必须说明三点局限第一它不区分气体种类MQ-2对不同气体的响应曲线差别很大同一个ppm的甲烷和酒精读出来的电压不同一个公式硬套结果只能算“等效烟雾浓度”。第二传感器制造批次差异和老化会让系数偏移。第三温湿度直接影响气敏材料响应冬天和夏天的读数会差不少。我的建议是在家庭报警和趋势监测场景把这个ppm当成“指示值”别当科学测量数据。想让它更准可以做双点校准在已知浓度的标准气体环境下采集两个点的Rs/R0在对数坐标下线性插值得到专属于自己这颗传感器的拟合线。但这对设备要求太高普通爱好者没必要折腾。用上面的经验公式配合合理的报警阈值已经能解决“厨房油烟大引发报警”“燃气泄漏触发告警”这类核心需求。4.4 滑动平均滤波让读数稳下来ADC原始读数跳动是常态传感器微幅波动、环境气流扰动、电源纹波都会体现在数值上。最简单有效的平滑方式是滑动平均滤波思路是维护一个固定长度的数组每次新读数进来把最旧的数据挤掉加进新的然后求平均。#define FILTER_N 20 float samples[FILTER_N]; uint8_t idx 0; float sum 0; float addSample(float v) { sum - samples[idx]; // 移除最旧值 samples[idx] v; // 写入新值 sum v; // 累加 idx (idx 1) % FILTER_N; return sum / FILTER_N; }使用上把每次算出来的voltage或ppm丢进addSample用返回值做后续判断。窗口N的选择有个权衡N太小滤波效果不明显N太大响应变慢我试下来20一拍比较均衡。还有一点要提醒不要在analogRead之后加delay凑时间做“连续采样”那会阻塞整个主循环导致报警响应、WiFi保活全部卡顿。滑动平均是每读一次更新一次主循环继续跑是嵌入式里更优雅的做法。5. 报警系统落地本地声光提示与远程通知5.1 不要用“单次超阈值”直接报警浓度算出来之后下一个问题是“什么条件才算报警”。最简单的写法是if (ppm 200) 报警但实际测试会发现读数在阈值附近抖动时蜂鸣器会疯狂反复触发非常烦人。工程上有两个经典手段我建议都用上。第一个叫迟滞比较。报警阈值和解除阈值不设成同一个值比如报警阈值为200ppm解除阈值设为150ppm。浓度从低往上突破200才报警从高往下跌回150以下才解除报警中间这50ppm是缓冲带能滤掉临界抖动。第二个叫持续确认连续N次读数超过阈值才触发报警比如每500毫秒读一次连续5次超过阈值相当于2.5秒才真正报警滤掉瞬时扰动。#define ALARM_ON 200.0f #define ALARM_OFF 150.0f bool alarmState false; int overCount 0; void checkAlarm(float ppm) { if (!alarmState) { if (ppm ALARM_ON) { overCount; if (overCount 5) { alarmState true; overCount 0; } } else { overCount 0; } } else { if (ppm ALARM_OFF) { alarmState false; } } }这个逻辑虽然琐碎但非常实用直接抄走就行。5.2 蜂鸣器与LED声光报警的驱动细节报警状态确定后驱动蜂鸣器和LED。蜂鸣器建议用有源蜂鸣器通电就响不需要PWM频率控制对新手友好。GPIO随便选一个数字引脚比如GPIO26或GPIO27这些引脚做数字输出不受WiFi影响。LED可以用普通发光二极管串一个220Ω电阻接GPIO25或者直接用开发板自带的LED大多数是GPIO2。报警提示不建议一直响间歇式更合理比如响0.2秒停0.2秒配合LED闪烁既醒目又不吵。实现上用millis()非阻塞计时避免delay阻塞主循环。void alarmLoop() { static unsigned long lastToggle 0; static bool beepOn false; if (millis() - lastToggle 200) { lastToggle millis(); beepOn !beepOn; digitalWrite(BUZZER_PIN, beepOn ? HIGH : LOW); digitalWrite(LED_PIN, beepOn ? HIGH : LOW); } }把这段放进loop()在报警状态时调用即可。平时不报警时记得把蜂鸣器引脚拉低不然可能一直微响。5.3 WiFi远程通知把报警推送到手机用了ESP32不联网就太浪费了。报警时把消息推到手机才算得上真正的“智能报警”。最简单的联网方案是让ESP32连接家里WiFi再通过HTTP请求调用一个通知服务的URL手机端就能收到消息。这类服务的原理基本都是“URL触发推送”ESP32这边只是发一个HTTP GET或POST代码量很小。#include WiFi.h #include HTTPClient.h const char* ssid your-ssid; const char* password your-password; const char* notifyUrl http://your-notify-service/alert?msgsmoke_alert; void setup() { WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); } } void sendAlert() { HTTPClient http; http.begin(notifyUrl); int code http.GET(); http.end(); }在报警触发的那一瞬间调用sendAlert()同时加一个标志位让同一个报警周期只发一次通知浓度回落后再复位避免重复轰炸。如果希望手机实时看浓度趋势还可以在ESP32上跑一个轻量Web服务器把当前ppm放在网页里手机浏览器直接打开开发板IP就能看。更进阶一点是接MQTT把数据推给订阅端但本质上都是同一套逻辑先在本地把浓度算稳再通过WiFi送出去。我还试过用蓝牙App来控制ESP32的报警布防和撤防。蓝牙BLE的GATT服务和WiFi互不冲突手机上写个小程序发送开关指令ESP32收到后决定要不要进入报警监听。这样家里有人时撤防没人时布防整体体验会好很多。这属于进阶玩法等基础链路跑通再加不迟。6. 踩坑实录让“嗅觉”稳定的完整排查链路6.1 上电就爆表先过预热期再说我第一次上电烧完程序串口监视器里AO电压显示2.9V接近满量程第一反应是传感器坏了。冷静下来查了查发现这是预热期的正常现象加热丝温度没稳定气敏材料表面状态混乱电导率波动剧烈AO电压会从一个异常值慢慢回到正常范围整个过程可能持续几分钟甚至十几分钟。前面代码里的delay(2000)只是给串口稳定用的跟传感器预热没有关系。要真正规避预热期干扰可以在程序里加一个“预热倒计时”上电后前120秒只采集和打印不更新校准基线、不做报警判断。这样即使预热期读数爆炸也不会误报。6.2 ADC非线性与量程选择ESP32的ADC精度说好听点是“够用”说难听点是“有脾气”。我实测过多个开发板低电压端0到0.1V和高电压端2.8V以上的非线性都很明显误差能到5%以上中间段相对靠谱。如果发现MQ-2在纯净空气中读出的电压比万用表量到的低很多大概率是ADC低端非线性在捣鬼。对策有三条。第一用analogSetAttenuation(ADC_ATTEN_DB_11)把量程拉满别让它在默认的1.1V小量程下工作。第二把正常工作电压区间设计在0.5V到2.5V中段。第三如果项目对精度有硬性要求可以用基于eFuse校准值的API做线性校正或者外接ADS1115这类高精度ADC芯片一条I2C总线搞定代价是复杂度和成本都上去了。6.3 DO输出不理人先查电位器有个朋友反馈烟雾很大DO引脚电平就是不变。远程看了半天最后发现是模块上的蓝色电位器被拧到最大比较器的参考电压设得太高传感器即使处于高浓度也达不到那个阈值。DO只是个开关通不通全靠电位器定的阈值说了算。如果要用DO做简单的开关量报警电位器逆时针多拧几圈把阈值降低然后用打火机气体不点火只放气或烟雾去触发测试微调直到“正常环境不触发、有烟雾就触发”的平衡点。但既然都要接ESP32了AO口的浓度数据这么宝贵没必要退回到DO。DO更适合那些不想写代码、只想继电器直接联动排风扇的场景。6.4 WiFi一开ADC就乱跳这是我踩过最隐蔽的坑之一。AO原本接在GPIO25上单独跑模拟量一切正常一旦在代码里加了WiFi.begin()读数直接变成满量程或者乱跳完全没法用。原因就是GPIO25属于ADC2通道ADC2和WiFi模块共用硬件资源WiFi开启后ADC2的模拟采集会被切走。解法很简单AO改接ADC1通道GPIO32到39其中GPIO36、37、38、39最干净是纯粹的输入引脚。这也是我在接线部分反复叮嘱引脚选择的原因这里算是把根因彻底交代了。6.5 传感器老化和温湿度漂移的日常维护MQ-2用久了R0会漂。我做过一个测试同一个传感器在同一个厨房环境三周后R0涨了约15%导致相同浓度下计算出的ppm明显偏高。原因包括气敏材料老化、表面吸附物残留、环境温湿度变化。对策就是定期校准每隔一个月左右把传感器放到通风洁净处通电稳定后重新测一次R0更新代码里的基线值。如果传感器曾经长时间接触高浓度气体比如直接对着丁烷喷气敏材料可能“中毒”响应变得迟钝甚至失效。这种基本救不回来直接换新的。好在MQ-2模块不贵几块钱到十几块钱一个备两三个库存不心疼。另外提示一下长期高湿环境下读数会偏高因为水蒸气也会影响电导率要追求精确就得配温湿度传感器做补偿但在家庭烟雾报警这个级别做趋势判断和阈值报警完全不用纠结这一点。最后分享一个多次实测总结出来的小技巧不要在代码里写死一个绝对报警阈值而是让ESP32每次开机后自动记录前几十秒的“基线值”后续判断时以当前值相对于基线的增量为准。这样做的好处非常实际——换了房间、换了传感器甚至季节变化导致R0漂移程序都能自动适应不用重新烧录改参数。我现在的烟雾检测节点基本都是这个逻辑跑起来省心很多。再往后走这个节点完全可以并入一个更大的环境监测系统比如用LoRa把烟雾、温湿度、光照数据汇总到网关或者通过蓝牙App远程查看实时曲线。不过那些都是后话核心还是今天讲的这条链路理解MQ-2原理、接对线、读稳数据、算准浓度、处理好报警。把这步走扎实ESP32这双“鼻子”就算真正长在你手里了。
返回列表