气体传感器实战:MQ 系列检测酒精烟雾一氧化碳,安全监控 DIY 方案

气体传感器实战:MQ 系列检测酒精烟雾一氧化碳,安全监控 DIY 方案

为什么 MQ 系列传感器还在用?

MQ 系列气体传感器诞生已经二十多年了,便宜、好买、容易上手。虽然精度比不上工业级设备,但做个家庭烟雾报警、酒精检测、一氧化碳监控,完全够用。

今天我用 Arduino 把 MQ-2(烟雾/可燃气体)、MQ-3(酒精)、MQ-7(一氧化碳)三个最常用的传感器跑一遍,从接线到校准到报警系统,一次讲清楚。

硬件清单

模块型号价格(约)说明
气体传感器MQ-2¥8-15烟雾、液化气、甲烷检测
气体传感器MQ-3¥6-12酒精/乙醇检测
气体传感器MQ-7¥10-18一氧化碳检测
主控板Arduino Uno R3¥15-25也可用 ESP32
面包板830 孔¥5-10接线用
蜂鸣器有源 5V¥2-5报警声音
LED红/黄/绿各 1¥0.5状态指示
电阻220Ω + 10kΩ¥1限流和分压
杜邦线公对公/公对母¥3连接线

总成本:约 ¥50-90,比买一个成品烟雾报警器还便宜。

传感器原理速讲

MQ 系列的核心是一个 SnO₂(二氧化锡)敏感材料,加热后电阻会随周围气体浓度变化:

  • 清洁空气中,SnO₂ 表面吸附氧气,电阻较高

  • 遇到还原性气体(烟雾、酒精、CO),氧气被消耗,电阻下降

  • 气体浓度越高,电阻越低,输出电压越高

关键参数:加热时间。 每个 MQ 传感器上电后需要预热 24-48 小时 才能达到稳定状态(有些型号可以缩短到几分钟用于快速测试,但精度会打折扣)。实际项目中建议至少预热 30 分钟再开始读数。

接线方案

三个传感器的接线方式基本一样,都是 4 个引脚:VCC、GND、AOUT(模拟输出)、DOUT(数字输出,部分模块带)。

Arduino Uno
├── 5V ────→ 传感器 VCC
├── GND ────→ 传感器 GND
├── A0  ────→ 传感器 AOUT(模拟信号)
├── D2  ────→ 蜂鸣器正极(通过 220Ω 电阻)
└── GND ────→ 蜂鸣器负极

如果你用的是带比较器的模块(淘宝上大部分都带),还会多一个 DOUT 引脚,可以通过板载电位器调节数字阈值,直接输出高/低电平。不过我建议用 AOUT 模拟输出,精度更高,可以在代码里灵活调整阈值。

完整代码

// MQ 系列气体传感器 - 多通道检测 + 报警系统
// 支持 MQ-2(烟雾)、MQ-3(酒精)、MQ-7(一氧化碳)

#define MQ2_PIN A0      // MQ-2 烟雾传感器
#define MQ3_PIN A1      // MQ-3 酒精传感器
#define MQ7_PIN A2      // MQ-7 一氧化碳传感器
#define BUZZER_PIN 2    // 蜂鸣器
#define LED_RED 3       // 红色 LED - 危险
#define LED_YELLOW 4    // 黄色 LED - 警告
#define LED_GREEN 5     // 绿色 LED - 正常

// 报警阈值(需要根据实际校准调整)
#define MQ2_SMOKE_THRESHOLD 300    // MQ-2 烟雾报警值
#define MQ3_ALCOHOL_THRESHOLD 250  // MQ-3 酒精报警值
#define MQ7_CO_THRESHOLD 200       // MQ-7 CO 报警值

// 预热时间(毫秒)- 传感器需要时间稳定
#define WARMUP_TIME 180000  // 3 分钟最小预热

unsigned long warmupStart;
bool sensorReady = false;

void setup() {
  Serial.begin(115200);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_YELLOW, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);

  // 所有 LED 关闭
  digitalWrite(LED_RED, LOW);
  digitalWrite(LED_YELLOW, LOW);
  digitalWrite(LED_GREEN, LOW);
  digitalWrite(BUZZER_PIN, LOW);

  warmupStart = millis();
  Serial.println("=== MQ 气体传感器系统启动 ===");
  Serial.print("预热中...预计 ");
  Serial.print(WARMUP_TIME / 60000);
  Serial.println(" 分钟");
}

void loop() {
  // 预热检查
  if (!sensorReady) {
    unsigned long elapsed = millis() - warmupStart;
    if (elapsed >= WARMUP_TIME) {
      sensorReady = true;
      Serial.println("✅ 预热完成,传感器就绪!");
      digitalWrite(LED_GREEN, HIGH);
    } else {
      Serial.print("预热进度: ");
      Serial.print(elapsed / 1000);
      Serial.print(" / ");
      Serial.print(WARMUP_TIME / 1000);
      Serial.println(" 秒");
      delay(5000);
      return;
    }
  }

  // 读取传感器值
  int mq2Value = analogRead(MQ2_PIN);
  int mq3Value = analogRead(MQ3_PIN);
  int mq7Value = analogRead(MQ7_PIN);

  // 打印数据
  Serial.print("MQ-2(烟雾): ");
  Serial.print(mq2Value);
  Serial.print(" | MQ-3(酒精): ");
  Serial.print(mq3Value);
  Serial.print(" | MQ-7(CO): ");
  Serial.println(mq7Value);

  // 判断状态并控制输出
  bool alarm = false;
  bool warning = false;

  // 烟雾检测
  if (mq2Value > MQ2_SMOKE_THRESHOLD) {
    Serial.println("⚠️ 检测到烟雾!");
    alarm = true;
  } else if (mq2Value > MQ2_SMOKE_THRESHOLD * 0.7) {
    warning = true;
  }

  // 酒精检测
  if (mq3Value > MQ3_ALCOHOL_THRESHOLD) {
    Serial.println("⚠️ 检测到酒精蒸气!");
    alarm = true;
  } else if (mq3Value > MQ3_ALCOHOL_THRESHOLD * 0.7) {
    warning = true;
  }

  // 一氧化碳检测
  if (mq7Value > MQ7_CO_THRESHOLD) {
    Serial.println("🚨 检测到一氧化碳!危险!");
    alarm = true;
  } else if (mq7Value > MQ7_CO_THRESHOLD * 0.7) {
    warning = true;
  }

  // 控制 LED 和蜂鸣器
  if (alarm) {
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_YELLOW, LOW);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(BUZZER_PIN, HIGH);  // 蜂鸣器响
  } else if (warning) {
    digitalWrite(LED_RED, LOW);
    digitalWrite(LED_YELLOW, HIGH);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(BUZZER_PIN, LOW);
  } else {
    digitalWrite(LED_RED, LOW);
    digitalWrite(LED_YELLOW, LOW);
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(BUZZER_PIN, LOW);
  }

  delay(2000);  // 每 2 秒读取一次
}

校准方法:这一步不能省

MQ 传感器的数值不是绝对的,受温度、湿度、传感器个体差异影响很大。校准是必须的。

方法一:清洁空气基线法(推荐入门)

// 在已知清洁空气中读取 100 次,取平均值作为基线
float getBaseline(int pin) {
  long sum = 0;
  for (int i = 0; i 
#include 

// WiFi 和 MQTT 配置
const char* wifi_ssid = "YOUR_WIFI";
const char* mqtt_server = "192.168.1.100";

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {
  WiFi.begin(wifi_ssid, "YOUR_PASSWORD");
  while (WiFi.status() != WL_CONNECTED) delay(500);

  client.setServer(mqtt_server, 1883);
  client.connect("gas-sensor");
}

void loop() {
  if (client.connected()) {
    int mq2Value = analogRead(MQ2_PIN);
    client.publish("sensor/gas/mq2", String(mq2Value).c_str());
  }
  delay(5000);
}

在 Home Assistant 的 configuration.yaml 中添加:

sensor:
  - platform: mqtt
    name: "厨房烟雾传感器"
    state_topic: "sensor/gas/mq2"
    unit_of_measurement: "ppm"
    device_class: "gas"

总结

MQ 系列传感器虽然古老,但胜在便宜好用。做家庭安全监控、实验室气体检测、或者简单的 DIY 项目,完全够用。

核心要点回顾:

  • 上电先预热,至少 30 分钟

  • 一定要校准,清洁空气基线是最简单的方法

  • 电源要稳定,加热丝耗电不小

  • 阈值要根据实际环境调整,不要照搬数据手册

  • 安全相关的应用(尤其 CO 检测),建议配合商业设备双重保障

希望这篇博客文章对您有所帮助!


MQ系列传感器高级应用

1. 多传感器阵列

构建气体传感器阵列,实现多种气体同时检测:

// 多传感器阵列配置
#define MQ2_PIN A0    // 可燃气体
#define MQ7_PIN A1    // CO
#define MQ135_PIN A2  // 空气质量
#define MQ131_PIN A3  // O3

struct GasReading {
    float ppm;
    String gas_type;
    String level;
};

class GasSensorArray {
private:
    int pins[4];
    float R0_values[4];
    
public:
    void begin() {
        pins[0] = MQ2_PIN;
        pins[1] = MQ7_PIN;
        pins[2] = MQ135_PIN;
        pins[3] = MQ131_PIN;
        
        // 从EEPROM加载R0值
        for (int i = 0; i < 4; i++) {
            R0_values[i] = EEPROM.readFloat(i * sizeof(float));
        }
    }
    
    GasReading readSensor(int sensor_index) {
        GasReading reading;
        int analog_value = analogRead(pins[sensor_index]);
        float Rs = ((5.0 * 1000) / analog_value) - 1000;
        float ratio = Rs / R0_values[sensor_index];
        
        switch(sensor_index) {
            case 0:  // MQ-2
                reading.gas_type = "Combustible";
                reading.ppm = 1000 * pow(ratio, -2.0);
                break;
            case 1:  // MQ-7
                reading.gas_type = "CO";
                reading.ppm = 100 * pow(ratio, -2.5);
                break;
            case 2:  // MQ-135
                reading.gas_type = "Air Quality";
                reading.ppm = 26 * pow(ratio, -2.2);
                break;
            case 3:  // MQ-131
                reading.gas_type = "O3";
                reading.ppm = 50 * pow(ratio, -1.8);
                break;
        }
        
        // 判断危险级别
        if (reading.ppm < 50) reading.level = "Safe";
        else if (reading.ppm < 200) reading.level = "Warning";
        else reading.level = "Danger";
        
        return reading;
    }
    
    void readAll() {
        for (int i = 0; i < 4; i++) {
            GasReading reading = readSensor(i);
            Serial.print(reading.gas_type);
            Serial.print(": ");
            Serial.print(reading.ppm);
            Serial.print(" ppm (");
            Serial.print(reading.level);
            Serial.println(")");
        }
    }
};

2. 数据融合算法

使用多个传感器数据提高检测准确性:

class SensorFusion {
private:
    float kalman_gain;
    float estimated_ppm;
    float error_estimate;
    float last_estimate;
    
public:
    SensorFusion() {
        kalman_gain = 0.5;
        estimated_ppm = 0;
        error_estimate = 100;
        last_estimate = 0;
    }
    
    float update(float measurement) {
        // 卡尔曼滤波
        kalman_gain = error_estimate / (error_estimate + 25);  // 25是测量噪声
        estimated_ppm = last_estimate + kalman_gain * (measurement - last_estimate);
        error_estimate = (1.0 - kalman_gain) * error_estimate + 
                        abs(last_estimate - estimated_ppm) * 0.5;
        last_estimate = estimated_ppm;
        
        return estimated_ppm;
    }
    
    void reset() {
        estimated_ppm = 0;
        error_estimate = 100;
        last_estimate = 0;
    }
};

// 使用示例
SensorFusion mq7_fusion;

void loop() {
    float raw_ppm = readMQ7();
    float filtered_ppm = mq7_fusion.update(raw_ppm);
    
    Serial.print("Raw: ");
    Serial.print(raw_ppm);
    Serial.print(" ppm, Filtered: ");
    Serial.print(filtered_ppm);
    Serial.println(" ppm");
    
    delay(1000);
}

3. 温度补偿

MQ传感器对温度敏感,需要补偿:

class TemperatureCompensatedMQ {
private:
    int sensor_pin;
    float R0;
    float reference_temp;  // 校准时的温度(20°C)
    
public:
    TemperatureCompensatedMQ(int pin, float r0) {
        sensor_pin = pin;
        R0 = r0;
        reference_temp = 20.0;
    }
    
    float readWithTempCompensation(float current_temp) {
        // 读取传感器
        int analog_value = analogRead(sensor_pin);
        float Rs = ((5.0 * 1000) / analog_value) - 1000;
        float ratio = Rs / R0;
        
        // 温度补偿系数
        // 根据数据手册,每升高1°C,灵敏度变化约0.5%
        float temp_coeff = 1.0 + (current_temp - reference_temp) * 0.005;
        
        // 补偿后的ratio
        float compensated_ratio = ratio / temp_coeff;
        
        // 计算PPM
        float ppm = 100 * pow(compensated_ratio, -2.5);
        
        return ppm;
    }
};

// 使用示例
#include <DallasTemperature.h>

OneWire oneWire(2);
DallasTemperature temp_sensor(&oneWire);
TemperatureCompensatedMQ mq7(A0, 9.8);

void setup() {
    temp_sensor.begin();
}

void loop() {
    temp_sensor.requestTemperatures();
    float current_temp = temp_sensor.getTempCByIndex(0);
    
    float ppm = mq7.readWithTempCompensation(current_temp);
    
    Serial.print("Temperature: ");
    Serial.print(current_temp);
    Serial.print("°C, CO: ");
    Serial.print(ppm);
    Serial.println(" ppm");
    
    delay(2000);
}

4. 低功耗设计

对于电池供电的应用,需要优化功耗:

class LowPowerMQSensor {
private:
    int heater_pin;
    int sensor_pin;
    unsigned long last_measurement;
    unsigned long measurement_interval;
    
public:
    LowPowerMQSensor(int heater, int sensor) {
        heater_pin = heater;
        sensor_pin = sensor;
        measurement_interval = 300000;  // 5分钟
        pinMode(heater_pin, OUTPUT);
    }
    
    float readPulsed() {
        // 脉冲加热模式
        // 加热150ms,读取传感器
        digitalWrite(heater_pin, HIGH);
        delay(150);
        
        int analog_value = analogRead(sensor_pin);
        
        // 立即关闭加热
        digitalWrite(heater_pin, LOW);
        
        // 计算Rs
        float Rs = ((5.0 * 1000) / analog_value) - 1000;
        
        return Rs;
    }
    
    void loop() {
        unsigned long current_time = millis();
        
        if (current_time - last_measurement >= measurement_interval) {
            float Rs = readPulsed();
            float ratio = Rs / 9.8;  // R0 = 9.8
            float ppm = 100 * pow(ratio, -2.5);
            
            Serial.print("CO: ");
            Serial.print(ppm);
            Serial.println(" ppm");
            
            last_measurement = current_time;
        }
        
        // 进入睡眠模式
        // 使用Arduino的睡眠功能
    }
};

功耗对比

模式加热电流平均功耗电池寿命(2000mAh)
连续加热80mA400mW5小时
脉冲加热(150ms/5min)80mA0.4mW200+小时

5. 无线传输方案

将传感器数据通过WiFi或LoRa传输:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "your_ssid";
const char* password = "your_password";
const char* mqtt_server = "broker.emqx.io";

WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
    }
}

void reconnect() {
    while (!client.connected()) {
        if (client.connect("MQ_Sensor_Node")) {
            Serial.println("MQTT connected");
        } else {
            delay(5000);
        }
    }
}

void setup() {
    setup_wifi();
    client.setServer(mqtt_server, 1883);
}

void loop() {
    if (!client.connected()) {
        reconnect();
    }
    client.loop();
    
    // 读取传感器
    float ppm = readMQ7();
    
    // 发布到MQTT
    char topic[] = "home/sensor/co";
    char payload[10];
    sprintf(payload, "%.2f", ppm);
    client.publish(topic, payload);
    
    delay(5000);
}

6. 数据可视化

使用Web界面实时显示传感器数据:

#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

void handleRoot() {
    String html = "<!DOCTYPE html><html><head>";
    html += "<meta charset='UTF-8'>";
    html += "<title>MQ Sensor Monitor</title>";
    html += "<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>";
    html += "</head><body>";
    html += "<h1>Gas Sensor Monitor</h1>";
    html += "<canvas id='chart' width='400' height='200'></canvas>";
    html += "<script>";
    html += "var ctx = document.getElementById('chart').getContext('2d');";
    html += "var chart = new Chart(ctx, {";
    html += "  type: 'line',";
    html += "  data: {";
    html += "    labels: [],";
    html += "    datasets: [{label: 'CO (ppm)', data: [], borderColor: 'red'}]";
    html += "  }";
    html += "});";
    html += "setInterval(function() {";
    html += "  fetch('/data').then(r => r.json()).then(data => {";
    html += "    chart.data.labels.push(new Date().toLocaleTimeString());";
    html += "    chart.data.datasets[0].data.push(data.ppm);";
    html += "    if (chart.data.labels.length > 20) {";
    html += "      chart.data.labels.shift();";
    html += "      chart.data.datasets[0].data.shift();";
    html += "    }";
    html += "    chart.update();";
    html += "  });";
    html += "}, 2000);";
    html += "</script></body></html>";
    
    server.send(200, "text/html", html);
}

void handleData() {
    float ppm = readMQ7();
    String json = "{\"ppm\": " + String(ppm) + "}";
    server.send(200, "application/json", json);
}

void setup() {
    setup_wifi();
    server.on("/", handleRoot);
    server.on("/data", handleData);
    server.begin();
}

void loop() {
    server.handleClient();
}

传感器寿命和维护

1. 寿命预期

传感器预期寿命影响因素
MQ-23-5年高浓度气体、高温
MQ-7