嵌入式开发 气体传感器实战: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) |
|---|---|---|---|
| 连续加热 | 80mA | 400mW | 5小时 |
| 脉冲加热(150ms/5min) | 80mA | 0.4mW | 200+小时 |
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-2 | 3-5年 | 高浓度气体、高温 |
| MQ-7 |