Arduino MQ-2 烟雾燃气报警器:预热、基线校准与 ppm 到底能信几分

Cartoon of a large MQ-2 gas sensor with a silver wire-mesh canister on a royal blue board with a blue trimmer, grey smoke wisps curling around it and a small black buzzer sounding beside it

MQ-2 模块能又快又便宜地告诉你:它周围的空气变了 —— 但它给不了你一个可信的 ppm 数字。所以就照实做:先让传感器预热,让 Arduino 记下洁净空气读到的数值,任何高过这条基线的爬升就报警。它是厨房燃气泄漏的早期预警,或者一份学校专题,而不是一台通过认证的探测器。

做一个 MQ-2 燃气烟雾报警器要准备什么?

MQ-2 模块只需要三条连接:5 V、地,以及一条进 Arduino 的模拟信号线。清单上其余的东西,全都是为了让这三条线不动烙铁也接得起来。

Parts list

ItemPriceQty
Relay Module 3.3V 5V 12V 1/2/4/8 Ways Optocoupler Trigger Relay Module 1 2 4 8 Channel Relay Module - 5V RELAY MODULE(1WAY)Relay Module 3.3V 5V 12V 1/2/4/8 Ways Optocoupler Trigger Relay Module 1 2 4 8 Channel Relay Module - 5V RELAY MODULE(1WAY)1WRELAYRM3.15
DC Cooling Fan 5V/12V Brushless Small Fan for 3D Printer DIY Projects Electronics Cooling - 6010 FAN (5V)DC Cooling Fan 5V/12V Brushless Small Fan for 3D Printer DIY Projects Electronics Cooling - 6010 FAN (5V)F601005RM8.95

加上这两样,警报一响就让排气扇转起来。继电器是开关,不是电源 —— 风扇由它自己的 5V 电源供电,被切换的那一侧始终维持低压直流。

MQ-2 模块MH-FMD 有源蜂鸣器露出来的都是弯角针,所以要用公对母杜邦线的母头那一端去夹住它们;那些线的公头则落进面包板,而面包板的电源轨正好把 Uno 那一个 5 V 插孔分成两路,再由公对公杜邦线把电源轨接回 Uno。这块 Uno 是用全尺寸 USB-B 烧录的,所以还需要那条 A 转 B 数据线

MQ-2 到底是怎么“闻”到气体的?

MQ-2 的感应元件,是镀在一支小陶瓷管上的一层二氧化锡(SnO2)薄膜,管中穿着一根镍铬加热丝 —— 这是 Hanwei 规格书写明的结构。加热丝把这层膜维持在几百摄氏度,在这个温度下,空气里的氧会吸附到薄膜表面,并且把二氧化锡里的电子俘获住。被俘走的电子留下一层电流很难穿过的耗尽层 —— 所以一颗洁净而炽热的传感器,电阻其实相当

可燃气体会打破这个安排。LPG、丁烷、甲烷、酒精蒸气,以及烟里那些没烧完的碳氢化合物,会跟吸附在表面的氧起反应,把它扣住的电子放回来;耗尽层随之变薄,电阻下降,模块上那颗固定电阻再把这个下降变成 AO 脚上升的电压。整条链路里没有任何一环是针对某一种气体的 —— 这正是同一颗元件对 LPG、烟和酒精都有反应的原因,也是这个家族还有像 MQ-135 空气质量传感器这样的兄弟型号的原因,那颗正是我们那篇烟霾监测器教程用的主角。

那层银色的网是不锈钢防爆网,作用是不让里面炽热发光的元件点着它正在嗅探的气体。绝对不要拆掉它,也绝对不要拿火靠近它。至于直接侦测火焰本身而不是烟,那是红外线的活 —— 请看我们的火焰传感器火灾警报器教程

MQ-2 传感器的卡通剖面图:银色网状外壳,米白色陶瓷管里有一圈发出橙光的加热丝,青色气体分子从外面进来,橙色的氧原子点从表面脱离
网罩里面:加热丝把二氧化锡薄膜维持在高温,氧吸附上去并俘获电子,而到来的气体把这些氧撞掉 —— 于是电阻下降。

为什么 MQ-2 要先预热,读数才可信?

MQ-2 在每一次上电之后的头几分钟都会读得偏高,并且一路往下漂 —— 这是化学,不是故障:加热丝必须先把陶瓷管带到工作温度,氧才会重新吸附回薄膜上,并稳定在那个定义了这颗传感器“洁净空气”的覆盖程度上。规格书要求超过 24 小时的预热,它公布的灵敏度数据才成立 —— 这种预烧,一颗新传感器做一次就够;之后每次上电,只要几分钟。

下面这份程序会等三分钟,把 64 次采样平均成一条基线,再以高出这条基线的百分比作为报警条件,所以传感器老化了、或者厨房本底本来就高一些,基线和阈值会一起跟着挪。

卡通折线图:读数从很高的位置衰减下来、进入一段平坦的高原,高原上标着一条青色虚线基线,上方另有一条橙色虚线报警线,一个由烟造成的尖峰越过了这条线并让蜂鸣器发声
读数随着加热丝稳定而下降、变平。那一段平坦就是基线,报警线设在高出基线一个固定百分比的位置,只有真正的上升才越得过去。

MQ-2 和蜂鸣器要怎么接到 Uno 上?

MQ-2 板子把四个引脚的标示印在排针旁边的背面,因为银色金属罐从上面正好把那一头挡住了。上电之前先翻过来看清楚 —— VCC 和 GND 接反,等于把 5 V 反着加在加热丝上。想分清两个输出:你凑近网罩呼气时,AO 会以几十个计数为单位缓缓变化;DO 则只会硬生生地待在 5 V 或 0 V。

接到 为什么
Uno 5VGND 面包板 + 电源轨 电源轨把一个 5 V 插孔分给两个模块用
MQ-2 VCC / GND + 轨 / − 轨 这里的负载是加热丝,不该挂在 Arduino 的引脚上
MQ-2 AO Uno A0 一个随气体平滑上升的电压 —— 程序读的就是它
MQ-2 DO 空着不接 一个比较器输出,越过蓝色电位器设定的阈值时变。那颗电位器只挪得动这个阈值 —— 它对模拟值完全没有影响
蜂鸣器 VCC / GND + 轨 / − 轨 模块自带驱动三极管
蜂鸣器 I/O Uno D8 这支脚被拉时才发声 —— 也就是板上印的 低电平触发

MQ-2 的加热丝标称 33 Ω,所以在 5 V 下电流是 5 ÷ 33 ≈ 150 mA,功耗 5 × 0.15 ≈ 0.75 W —— 正是规格书那句“小于 800 mW”从另一个方向算回来的结果。加上 Uno 和蜂鸣器,整台报警器大约在四分之一安培上下,落在 USB 输入那颗 500 mA 自恢复保险丝的范围之内。这根加热丝是持续通电的,这既是模块摸起来温温的原因,也是这个作品不适合用电池跑的原因。

亲眼看一次烟被扇到网罩上的样子会更清楚:

MQ-2 燃气传感器的 Arduino 代码到底做了什么?

MQ-2 不需要任何库 —— AO 就是一个普通的模拟电压,蜂鸣器也只占一支数字脚。开发板选 Arduino Uno,上传,然后马上把串口监视器开在 9600 baud —— 打开串口会经由一颗电容脉冲触发 Uno 的复位线,所以每开一次那个窗口,三分钟的预热就会悄悄地从头再来一遍。这份程序编译出来 3,970 字节,大约是 Uno 闪存的 12%;而那几行继电器的代码,即使你没有加购选配件也完全无害。

// MQ-2 厨房燃气 + 烟雾报警器:先预热,学会洁净空气,再对“上升”报警。
// 不需要任何库 - AO 就是普通的模拟电压,蜂鸣器只占一支数字脚。

const uint8_t MQ2_AO_PIN  = A0;  // MQ-2 模拟输出,0-5 V 进 10 位 ADC
const uint8_t BUZZER_PIN  = 8;   // MH-FMD 有源蜂鸣器 I/O - 低电平时发声
const uint8_t RELAY_PIN   = 7;   // 选配继电器 IN,跳线拨到 H = 高电平触发

const unsigned long WARMUP_MS   = 180000UL;  // 3 分钟:让加热丝和薄膜稳定下来
const uint8_t       BASE_SAMPLE = 64;        // 取平均后成为基线的采样次数
const unsigned long BASE_GAP_MS = 60UL;      // 两次采样之间的间隔(合计约 4 秒)

const float    RISE_FACTOR  = 1.35;  // 高出洁净空气基线 35% 就报警
const float    CLEAR_FACTOR = 1.15;  // 要掉回基线附近才解除
const uint16_t RISE_FLOOR   = 40;    // ……但升幅至少要有 40 个 ADC 计数

const unsigned long ALARM_MIN_MS = 5000UL;  // 最短报警时长,免得一阵轻烟就让它抖个不停
const unsigned long BEEP_MS      = 250UL;   // 断续鸣叫的开/关周期

uint16_t baseline    = 0;
uint16_t alarmLevel  = 0;
uint16_t clearLevel  = 0;

bool alarmOn = false;
unsigned long alarmStartedMs = 0;
unsigned long beepToggledMs  = 0;
bool beepAudible = false;

// 这个蜂鸣器模块是低电平触发:把 I/O 拉低,它才发声。
void buzzer(bool on) {
  digitalWrite(BUZZER_PIN, on ? LOW : HIGH);
}

uint16_t averageOfSamples() {
  uint32_t total = 0;
  for (uint8_t i = 0; i < BASE_SAMPLE; i++) {
    total += analogRead(MQ2_AO_PIN);
    delay(BASE_GAP_MS);
  }
  return (uint16_t)(total / BASE_SAMPLE);
}

void setup() {
  Serial.begin(9600);

  // 在每一支脚变成输出“之前”先把空闲电平设好。刚设定的输出脚起点是
  // LOW,而 LOW 正是让这个蜂鸣器尖叫的电平 - 所以要先写 HIGH。
  // 继电器跳线拨在 H 时刚好相反:LOW 才是它的关闭状态。
  digitalWrite(BUZZER_PIN, HIGH);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  pinMode(RELAY_PIN, OUTPUT);

  Serial.println(F("Warming up - keep the air around the sensor clean."));
  while (millis() < WARMUP_MS) {
    delay(1000);
  }

  baseline   = averageOfSamples();
  uint16_t rise = (uint16_t)(baseline * (RISE_FACTOR - 1.0));
  if (rise < RISE_FLOOR) {
    rise = RISE_FLOOR;
  }
  alarmLevel = baseline + rise;
  clearLevel = baseline + (uint16_t)(rise * (CLEAR_FACTOR - 1.0) / (RISE_FACTOR - 1.0));

  Serial.print(F("Clean-air baseline: "));
  Serial.print(baseline);
  Serial.print(F("  alarm at: "));
  Serial.print(alarmLevel);
  Serial.print(F("  clears below: "));
  Serial.println(clearLevel);
}

void loop() {
  unsigned long now = millis();
  uint16_t reading = analogRead(MQ2_AO_PIN);

  if (!alarmOn && reading >= alarmLevel) {
    alarmOn = true;
    alarmStartedMs = now;
    beepToggledMs = now;
    beepAudible = true;
    buzzer(true);
    digitalWrite(RELAY_PIN, HIGH);
    Serial.print(F("ALARM - reading "));
    Serial.println(reading);
  }

  if (alarmOn) {
    // 让声音断断续续,听起来才像警报,而不是一颗卡住的蜂鸣器。
    if (now - beepToggledMs >= BEEP_MS) {
      beepToggledMs = now;
      beepAudible = !beepAudible;
      buzzer(beepAudible);
    }

    // 只有这两件事同时成立才准停:空气已经恢复洁净,而且报警已经
    // 响满最短时长。无符号相减在 millis() 溢出回绕时依然正确。
    if (reading < clearLevel && (now - alarmStartedMs >= ALARM_MIN_MS)) {
      alarmOn = false;
      buzzer(false);
      digitalWrite(RELAY_PIN, LOW);
      Serial.println(F("Clear"));
    }
  }

  // 每秒打印一行,方便你在测试时盯着数字变化。
  static unsigned long lastLogMs = 0;
  if (now - lastLogMs >= 1000UL) {
    lastLogMs = now;
    Serial.print(F("raw "));
    Serial.print(reading);
    Serial.print(F("  baseline "));
    Serial.println(baseline);
  }

  delay(20);
}

有一行很容易看漏:digitalWrite() 写在 pinMode(OUTPUT) 之前。先写电平,动到的是同一个端口寄存器位,而这时脚位还是输入 —— 所以脚位一变成输出的那一瞬间,电平就已经是对的了。

程序打印出来的那条基线,直接决定报警到底触不触发得了。ADC 到 1023 个计数就到顶,所以基线一旦高过大约 758,35% 的触发点就被推到满量程之外,再多的烟也够不着 —— 这说明传感器还在预烧,让它继续通着电就好,别去改程序。灵敏度也住在这里:不在那颗蓝色电位器上(它只挪得动 DO 的阈值),而在 RISE_FACTORRISE_FLOOR 里。厨房的水蒸气会误触发就把它们调高,划一根火柴都叫不响就调低。

这块板子给的 ppm,到底能信几分?

MQ-2 的规格书把灵敏度写成一个比值,而不是一个读数:Rs/R0 —— 而 R0 并不是它在洁净空气里的读数。规格书把 R0 定义为那一颗传感器在洁净空气中1000 ppm 氢气下的电阻,所以连这个比值的分母,都必须在一种已知气体里测出来。所有公布的曲线 —— LPG、甲烷、丙烷、烟、酒精、氢气、一氧化碳 —— 都是画在这个比值上的,而且斜率各不相同,所以要把一个读数换算成 ppm,同时需要三样东西:在你自己那颗传感器上测出来的 R0、对应的那条曲线,以及现场究竟是哪一种气体。一个原始 ADC 计数,这三样一样都给不了。

这正是这个作品选择“对上升报警”的原因。规格书写的探测范围 —— LPG 和丙烷 200–5000 ppm,甲烷 5000–20000 ppm —— 说的是这颗元件在实验室里会响应的范围,而不是一块未经校准的板子能报出来的数值。当作早期预警,它完全对得起自己的位置:钢瓶开始嘶嘶漏气时,它会比隔壁房间的人早很多注意到。当作生命安全设备,它替代不了一台通过认证的探测器。

要怎么安全地测试它?

MQ-2 对一根吹熄的火柴、或者半米外的一支线香反应很好:烟里那些没烧完的碳氢化合物,正是薄膜会起反应的东西,原始数值一两秒内就跳起来了。这才是安全的测法,也是学校演示该用的测法。

千万不要为了让它“测得准一点”而去打开燃气钢瓶或者炉头的阀门 —— 那等于为了测一个警报器,在房间里造出一个引燃风险,而漏出来的 LPG 旁边划一根火柴,正是这个警报器存在的意义所要防止的那场意外。在通风的房间里,把一支丁烷打火机不点火地按住阀门一秒钟,就已经是这个作品需要的、最接近 LPG 的测试了。

卡通对比图:一只手拿着已经吹熄的火柴,灰色的烟飘向银色网罩的 MQ-2 传感器并打上绿色对勾;旁边是一支正在漏气的红色 LPG 钢瓶和一根点着的火柴,整幅被红色禁止符号划掉
用已经吹熄的火柴测,或者用线香。绝对不要为了证明警报有效而去打开燃气钢瓶,也绝对不要把火带到传感器旁边。

在马来西亚的厨房里,传感器该装在哪?

Arduino 燃气泄漏侦测器只在气体真的会到达的地方才帮得上忙,而 LPG 和烟的走向刚好相反。钢瓶里的丙烷和丁烷比空气重,所以漏出来会往下沉,积在地面附近;烟是热的,往上走。一颗 MQ-2 没办法同时照顾两边,所以针对马来西亚厨房流理台底下那支钢瓶,把它装低 —— 离地大约 30 cm,并且在调压器一米之内。同时也要让它离蒸气远一点:MQ-2 对酒精蒸气有反应,对一锅滚水冒出来的湿气也有反应,所以装在炉子上方或者电饭锅旁边,它就会不停地狼来了;而油烟里的油雾还会慢慢糊住元件,让它变钝。

怎么让警报一响,排气扇就转起来?

5 V 继电器模块能把一支 Arduino 引脚,变成一个带得动风扇的开关。控制侧是一个三位螺丝端子,标着 IN、DC−、DC+ —— 没有排针,所以杜邦线裸露的公头直接夹进螺丝里。DC+ 和 DC− 取自面包板的电源轨,IN 接到 D7,线圈电流大约 71 mA —— 也就是它那颗 0.36 W、70 Ω 的线圈在 5 V 下的表现。

上传程序之前,先把那颗小小的 L/H 触发跳线拨到 H。它决定的是 IN 脚上给高电平还是低电平才让线圈吸合,而这份程序是把引脚拉高来触发的。留在 L 上,风扇会从一上电就开始转,反而在警报响起时停掉 —— 完全反了,而这正是那种被当成“继电器坏了”的经典工单,实际上只是一颗两针的跳线。

被切换的那一侧是干接点:COM 和 NO 只是一对裸露的金属触片,跟 Arduino 之间没有任何电气通路,所以那颗 60 mm 5 V 风扇要用它自己的 5 V 电源。电源正极进 COMNO 出来接风扇的红线,风扇黑线直接回到电源的负极 —— 继电器做的只是把这个回路中的一条腿断开或接通。要是图省事把风扇挂到 Uno 的电源轨上,加热丝、线圈和风扇就一起挤那颗 500 mA 自恢复保险丝,而这还没算上风扇启动时的涌流。

风扇那个白色的两针插头不必剪掉:一根公头杜邦线正好能插进它的每一个孔位,风扇因此还留得住、还能拿去别处用,另外两端则夹进负载侧的螺丝端子。这一侧请始终维持低压直流 —— 触点上印的额定值确实涵盖市电,但市电切换属于我们那篇继电器模块安全使用教程的范围。

我们从真实客户那里常看到的错误

只买了传感器本身。有位客户问我们:“can you show me the product that i need to buy from your shop to make this smart smoke detectore” —— 光是这个模块当不了那台侦测器:它伸出来的是公针,而杜邦线那一端需要的是母座。上面那份材料清单,才是完整的答案。

在开机第一分钟就给传感器下判断。刚上电时的数字是加热丝在稳定,不是气体。这份程序只在上电时学一次基线,之后再也不学 —— 所以你要是把报警器搬到另一个房间,或者传感器已经老化,按一下复位,让它重新学习它现在所在的这团空气。

把蜂鸣器当成高电平触发来接线。它会从开机开始一直尖叫、停不下来,看起来就像模块坏了。HIGH 才是安静 —— 跟我们那篇 PIR 人体感应灯教程里的蜂鸣器是同一个坑。

以为不用写代码,或者以为火柴一定要点着。另一位客户用马来文问:“perlu coding lagi? dan saya perlukan mancis kah untuk buat prototype ini berkesan?” —— 要写代码,就是上面那份 sketch;而火柴的效果,恰恰是在你把它吹熄之后最好。

常见问题

MQ-2 要预热多久?

每次上电都要几分钟;如果你想要规格书那套灵敏度数据成立,一颗新传感器还要先做一次 24 小时以上的预烧。这份程序会等三分钟,然后自己量一条基线,所以两种情况下它都保持诚实。

MQ-2 能给我一个 ppm 数字吗?

不能,除非你先在一种已知浓度的已知气体里校准那一颗传感器 —— 规格书要求的是 1000 ppm 的 LPG 或异丁烷。它的曲线全都是相对于 R0 的比值,而 R0 是一个在 1000 ppm 氢气下定义的参考电阻,加上每一种气体的斜率都不一样,所以一个原始读数只能算是一个相对信号。

MQ-2 能不能当我家的烟雾或燃气探测器?

不能。把它当成早期预警或者教学作品,生命安全请交给一台通过认证的 LPG 或烟雾探测器。一块未经校准的模块,既没有经过测试的报警阈值,也没有对自身故障的监督。

MQ-2 能接在 ESP32 或 Raspberry Pi Pico 上吗?

加热丝仍然要 5 V,而 AO 的摆幅可以接近 5 V —— 这两块板子的模拟输入上限都是 3.3 V,已经超了。先用两颗电阻把 AO 分压下来,或者干脆让传感器留在 Uno 上。另一个常见的下一步是加一块显示屏,做法见我们的 LCD1602 教程

最后更新:2026年8月。有问题?WhatsApp 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *