项目概述
本项目基于 零知派ESP32 和 MQ135 气体传感器,构建了一套智能烟雾检测与自动排烟报警系统。当检测到烟雾浓度超过阈值时,系统自动驱动蜂鸣器报警、启动风扇排烟,并通过舵机控制风扇扫风方向以扩大排烟范围。系统配备 ST7789(240*240) TFT彩色屏幕,实时显示烟雾浓度和各项设备状态;同时通过 MQTT 协议 接入物联网,支持远程监控与手动控制。
系统功能一览
| 功能 | 说明 |
|---|---|
| 烟雾检测 | MQ135 传感器实时采集烟雾浓度(0~100%) |
| 声光报警 | 无源蜂鸣器发出 2kHz 方波警报音 |
| 自动排烟 | 烟雾超标后 1 秒自动启动风扇,延迟启动防止误报 |
| 扫风控制 | 舵机带动风扇在 20°~160° 往复摆动,扩大排烟覆盖范围 |
| 本地显示 | ST7789 彩屏显示烟雾浓度数值、进度条、设备状态 |
| 远程监控 | 通过 WiFi + MQTT 上报数据,接收远程控制指令 |
| 手动/自动模式 | 自动模式下由阈值触发控制;手动模式下由远程指令接管 |
项目亮点
- 本地+远程双通道监控 — 既可通过 TFT 屏幕本地查看,也可通过 Web 仪表盘或手机 APP 远程监控
- 非阻塞架构设计 — 全部使用时间戳轮询(
millis()),无delay()阻塞,保证系统实时响应 - 硬件 PWM 驱动 — 蜂鸣器、风扇、舵机全部使用 ESP32 LEDC 硬件 PWM,CPU 零开销维持输出
- 自动+手动双模式 — 支持自动阈值控制与远程手动控制无缝切换
- 滞回控制(Hysteresis) — 触发阈值 15%,恢复阈值 12%(80%),防止烟雾在临界值波动导致频繁启停
- 增量刷新屏幕 — 烟雾值与状态栏分离更新,仅在变化时重绘,减少 SPI 通信开销
项目难点及解决方案
难点 1:GPIO19 引脚复用冲突
现象:加入 TFT 屏幕后,蜂鸣器不再发声,但屏幕显示"BUZZER: ON"。
原因:ESP32 的 VSPI 总线默认将 **GPIO19 作为 MISO(主入从出)**引脚。TFT_eSPI 库初始化 SPI 总线时,会将 GPIO19 从 LEDC PWM 输出模式重新配置为 SPI 输入模式,导致蜂鸣器 PWM 信号无法输出。
解决:在 display.begin() 之后,强制重新绑定蜂鸣器引脚到 LEDC 通道:
display.begin(); // TFT 初始化(会误配 GPIO19)
ledcAttachChannel(BUZZER_PIN, ..., BUZZER_CHANNEL); // 重新绑定蜂鸣器
难点 2:多个外设共用 PWM 通道
现象:风扇、蜂鸣器、舵机三个设备都需要 PWM 信号,但 ESP32 LEDC 通道有限。
解决:LEDC 通道独立分配,避免冲突:
- 通道 0 → 风扇 INA
- 通道 1 → 风扇 INB
- 通道 2 → 蜂鸣器
- 通道 3 → 舵机
难点 3:MQ135 传感器精度与预热
现象:新传感器读数不稳定,与实际烟雾浓度偏差大。
解决:在 readSmokeLevel() 中不做复杂校准,采用电压与满量程的比例归一化为 0~100% 的百分比值,降低对绝对精度的依赖。同时建议首次使用预热 24~48 小时。
目录
一、硬件系统部分
1.1 硬件清单
| 组件 | 数量 | 型号/规格 | 作用 |
|---|---|---|---|
| 主控板 | 1 | 零知ESP32 | 系统主控制器 |
| 扩展板 | 1 | 零知派ESP32扩展板 | 方便接线 |
| TFT 屏幕 | 1 | ST7789 240x240 SPI | 本地状态显示 |
| 烟雾传感器 | 1 | MQ135 | 检测烟雾/有害气体浓度 |
| 风扇驱动模块 | 1 | L9110 | 驱动直流风扇(PWM调速) |
| 蜂鸣器 | 1 | 无源蜂鸣器 | 声音报警 |
| 舵机 | 1 | MG90S | 控制风扇扫风方向 |
| USB转TTL模块 | 1 | 零知派USB转TTL模块 | 给风扇和舵机供电 |
| 面包板 | 1 | 面包板 | 搭建电路 |
| 杜邦线 | 若干 | 公对公 + 公对母 | 接线 |
本系统必须使用零知派USB转TTL模块来接外部 5V/2A 电源供电,仅靠 USB 口供电会导致以下问题:
- 风扇无法启动或转速不足 — 直流风扇启动电流可达 300~500mA,USB 口通常只能提供 500mA,无法同时满足 ESP32(~200mA)+ 风扇(~300mA)+ 舵机(~200mA)的同时工作需求
- 舵机抖动或卡死 — 舵机堵转时电流可高达 700mA,USB 供电电压会被拉低至 4.5V 以下,导致 ESP32 反复重启
- ESP32 WiFi 断连 — 电压不足时 ESP32 的 WiFi 射频模块工作不稳定,表现为频繁断线重连
- 屏幕显示异常 — SPI 通信在欠压情况下会出现数据错误,屏幕花屏、闪烁
- 系统反复重启 — 电压过低会触发 ESP32 的欠压复位(Brownout Detector),系统陷入「启动→供电不足→复位→再启动」的死循环
正确做法:使用 5V/2A 电源适配器通过 VIN 引脚(或 5V 引脚)为 ESP32 供电,USB 口仅用于程序烧录和串口调试。
1.2 接线方案
| MQ135 引脚 | 接 ESP32 |
|---|---|
| VCC | 5V / VIN |
| GND | GND |
| AO(模拟输出) | GPIO34 |
| DO(数字输出) | 不接 |
| L9110S | 接 ESP32 | 接风扇 |
|---|---|---|
| VCC | 5V / VIN | - |
| GND | GND | - |
| INA | GPIO25(PWM) | - |
| INB | GPIO26(PWM) | - |
| 蜂鸣器 | 接 ESP32 |
|---|---|
| 引脚 | GPIO19(PWM) |
| 引脚 | GND |
| 舵机线色 | 接 ESP32 |
|---|---|
| 红色(VCC) | 5V / VIN |
| 棕色(GND) | GND |
| 橙色(信号) | GPIO14(PWM) |
1.3 硬件连接图

1.4 实物连接图

二、软件架构设计
2.1 系统初始化
setup() 的执行顺序:
初始化开始
│
├── Serial.begin(115200) ← 串口调试
├── smokeSensor.begin() ← MQ135 ADC 配置
├── fanControl.begin() ← 风扇 LEDC 通道 0/1
├── buzzerAlarm.begin() ← 蜂鸣器 LEDC 通道 2
├── servo.begin() ← 舵机 LEDC 通道 3,预设 90°
├── display.begin() ← TFT 屏幕初始化
├── ledcAttachChannel(buzzer) ← 修复 SPI 与蜂鸣器引脚冲突
└── mqttClient.begin() ← WiFi 连接 + MQTT 连接 + 订阅主题
关键设计原则:
- 先初始化模块再初始化通信 — 确保传感器和执行器就绪后,再连接 MQTT
- 先 display.begin() 再重绑蜂鸣器 — 解决 GPIO19 引脚被 SPI 占用的问题
2.2 主循环逻辑
loop() 的主循环流程:
loop() 每一次迭代
│
├── [每1秒] 读取烟雾浓度 + 自动控制逻辑
│ │
│ ├── 读取 MQ135 电压 → 计算烟雾浓度百分比
│ ├── 串口输出传感器数据
│ │
│ ├── 如果处于 自动模式 (fanManualMode == false):
│ │ │
│ │ ├── 烟雾 ≥ 15% → 启动蜂鸣器 → 延迟1秒 → 启动风扇+舵机扫风
│ │ └── 烟雾 < 12% → 停止蜂鸣器 → 停止风扇+舵机扫风
│ │ └── 12%~15% → 保持当前状态(滞回区间)
│ │
│ └── 更新 TFT 屏幕显示(增量刷新)
│
├── buzzerAlarm.update() ← 蜂鸣器状态更新(扩展预留)
├── servo.update() ← 舵机扫风步进(50Hz PWM 维持 + 角度步进)
├── mqttClient.update() ← WiFi/MQTT 维持 + 收发消息
│
├── [每10秒] 串口输出 WiFi/MQTT 连接状态
│
└── [每5秒] 发布传感器数据到 MQTT 服务器
三、代码拆分讲解
3.1 文件结构
smoke_detector/
├── smoke_detector.ino 主程序入口(全局对象、setup、loop、MQTT回调)
├── smoke_detector.h 配置文件 + 6个类的声明
├── smoke_detector.cpp 6个类的完整实现
├── dashboard.html Web端MQTT监控面板(纯前端,直接浏览器打开)
├── mqtt_test.py Python MQTT测试脚本
└── sketch.yaml 项目配置
3.2 SmokeSensor(烟雾传感器)
class SmokeSensor {
void begin(); // 配置ADC:12位分辨率、11dB衰减
float readVoltage(); // 读取 MQ135 模拟电压(0~3.3V)
float readSmokeLevel(); // 计算烟雾浓度百分比(0~100%)
};
核心公式:
电压(V) = ADC原始值 / 4095 × 3.3
烟雾浓度(%) = 电压 / 3.3 × 100
3.3 FanControl(风扇控制)
class FanControl {
void begin(); // 绑定 INA → 通道0,INB → 通道1
void start(int speed = 255); // INA = PWM, INB = 0 → 正转
void stop(); // INA = 0, INB = 0 → 制动停止
void setSpeed(int speed); // 运行时调速
};
L9110S 驱动逻辑:
| INA | INB | 风扇状态 |
|---|---|---|
| PWM | 0 | 正转(PWM占空比调速) |
| 0 | 0 | 停止(电机制动) |
3.4 Alarm(蜂鸣器报警)
class Alarm {
void begin(); // 绑定 GPIO19 → LEDC 通道2,2kHz
void startAlarm(); // 输出 50% 占空比方波 → 最大音量
void stopAlarm(); // 输出 0 → 静音
};
采用 硬件 PWM 驱动无源蜂鸣器:
- 频率 2kHz(人耳最敏感的频率区间)
- 50% 占空比(最大音量输出)
- 一经配置由 LEDC 硬件持续输出,CPU 无需干预
3.5 ServoControl(舵机扫风)
class ServoControl {
void begin(); // 绑定 GPIO14 → LEDC 通道3,50Hz
void write(int deg); // 设置角度(0~180°)
void startSweep(); // 在 20°~160° 之间往复摆动
void stopSweep(); // 回到 90° 居中
void update(); // 每30ms步进2°,实现连续扫风
};
舵机 PWM 信号计算:
50Hz 周期 = 20000μs
14位分辨率 = 0~16383
占空比 = (脉宽μs / 20000) × 16383
0° → 1000μs → duty = 819
180° → 2000μs → duty = 1638
3.6 MQTTClient(远程通信)
class MQTTClient {
void begin(); // 设置MQTT服务器 → 连接WiFi → 连接MQTT
void update(); // 维护连接 + client.loop()
void publishSensorData(...); // 发布传感器 JSON 数据
void publishStatus(...); // 发布在线/离线状态
void setCallback(...); // 注册控制指令回调函数
};
数据上报格式(每5秒):
{
"smoke_level": 25.50,
"voltage": 0.84,
"alarm": false,
"fan": true,
"fan_speed": 255,
"servo": 90,
"mode": "auto"
}
3.7 DisplayManager(TFT屏幕显示)
class DisplayManager {
void begin(); // 初始化 ST7789,绘制静态元素
void update(...); // 增量刷新烟雾值和设备状态
};
增量刷新策略(提高性能、减少闪烁):
- 仅当烟雾浓度变化 ≥ 0.5% 时,重绘烟雾数值区域和进度条
- 仅当报警/风扇/舵机/蜂鸣器状态改变时,重绘状态区域
- 两部分互不影响,各自独立更新
屏幕布局(240×240,旋转方向 3):
┌──────────────────────────────┐
│ SMOKE DETECTOR │ ← 标题,白色
├──────────────────────────────┤
│ Smoke: │ ← 标签
│ 45.2% │ ← 大号数值(绿/黄/红)
│ ┌────────────────────────┐ │ ← 进度条(210px宽,居中)
│ │ ████████████░░░░░░░░░ │ │
├──────────────────────────────┤
│ ALARM: ACTIVE! │ ← 红/绿色
│ FAN: ON Spd:255 │ ← 青/绿色
│ SERVO: 45 deg │ ← 青色
│ BUZZER: ON │ ← 红/绿色
└──────────────────────────────┘
烟雾浓度颜色分级:
- 绿色:< 30%(安全)
- 黄色:30%~50%(注意)
- 红色:≥ 50%(危险)
四、操作过程及数据展示
4.1 操作步骤
步骤 1:搭建硬件电路
按 1.2 节接线方案连接所有模块,务必使用外部 5V/2A 电源供电。
步骤 2:配置 WiFi并编译上传
编辑 smoke_detector.h,修改 WiFi 信息:
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PASSWORD "你的WiFi密码"
选择ESP32开发板-验证代码-连接端口-上传代码
步骤 3:打开串口监视器
波特率 115200,观察启动日志:
ESP32 Smoke Detector System
Connecting to WiFi: zaixinjian
....
WiFi connected!
IP address: 192.168.1.100
Smoke: 12.3% Voltage: 0.41V Safe
WiFi: Connected | MQTT: Connected | IP: 192.168.1.100
Published: {"smoke_level":12.30,"voltage":0.41,"alarm":false,...}
步骤 4:Web 远程监控
直接用浏览器打开 dashboard.html(无需 web 服务器),它会通过 WebSocket 连接 MQTT 服务器,实时显示烟雾浓度仪表盘和趋势图。
如果 ESP32 成功连接 MQTT,dashboard.html 里会自动看到数据更新。
步骤 5:测试报警功能和远程控制
用打火机气体(不点火)或香烟靠近 MQ135 传感器,观察:
- TFT 屏幕烟雾数值上升,进度条变黄→变红
- 蜂鸣器发出 2kHz 尖锐报警音
- 约 1 秒后风扇启动,舵机开始扫风
- Web 端仪表盘同步更新
在 dashboard.html 中点按控制按钮,或通过 MQTT 客户端发送命令:
{"fan":"on"} // 启动风扇
{"fan":"off"} // 停止风扇
{"alarm":"off"} // 关闭报警
{"speed":128} // 设置风扇速度
{"mode":"auto"} // 返回自动模式
4.2 演示视频
零知派ESP32--智能烟雾报警排烟系统
五、技术原理
5.1 工作原理
整个系统的工作流程分为三个层次:
感知层:MQ135 传感器检测空气中的烟雾/有害气体浓度,输出模拟电压 → ESP32 ADC 采样 → 转换为 0~100% 的浓度百分比。
决策层(ESP32 固件逻辑):
烟雾浓度 → 阈值比较 → 自动模式判断 → 执行器控制
│ │
15% 阈值 MQTT 远程指令可
12% 恢复阈值 切换为手动模式
执行层:
| 执行器 | 控制方式 | 响应时间 |
|---|---|---|
| 蜂鸣器 | LEDC PWM 2kHz | 即时响应 |
| 风扇 | L9110S H桥 PWM | 延迟 1s 启动(防误报) |
| 舵机 | LEDC PWM 50Hz | 30ms 步进 2° |
5.2 工作模式配置
自动模式(默认)
当 fanManualMode == false:
| 烟雾浓度 | 蜂鸣器 | 风扇 | 舵机 |
|---|---|---|---|
| < 12% | 关闭 | 关闭 | 居中 90° |
| 12% ~ 15% | 保持之前状态 | 保持之前状态 | 保持之前状态 |
| ≥ 15% | 开启 | 1秒后开启(全速) | 90° 开始扫风 |
滞回(Hysteresis)机制:
触发阈值:15%(开启)
恢复阈值:12%(关闭)= 15% × 0.8
避免烟雾浓度在阈值附近波动导致设备频繁启停。
手动模式
通过 MQTT 发送任意控制指令后自动进入。手动模式下:
- 自动阈值判断暂停
- 所有执行器由远程指令控制
- 发送
{"mode":"auto"}切回自动模式
六、常见问题指引
Q1:蜂鸣器不响,屏幕上显示 ON
原因:GPIO19 被 SPI 总线占用(MISO 引脚冲突)。
解决:已固件层面修复。如果是自编译代码,确保 display.begin() 之后调用:
ledcAttachChannel(BUZZER_PIN, BUZZER_PWM_FREQ, BUZZER_PWM_RESOLUTION, BUZZER_CHANNEL);
Q2:风扇不转或转速慢
可能原因:
- 供电不足(最常见!USB 口电流不够)
- L9110S 接线错误(INA/INB 接反)
- 风扇堵塞或损坏
解决:使用外部 5V/2A 电源适配器供电。
Q3:MQTT 连接失败
串口输出:MQTT connection failed, rc=...
| 返回码 | 含义 | 解决 |
|---|---|---|
| -4 | 连接超时 | 检查 WiFi 是否在线 |
| -3 | 网络断开 | 检查 WiFi 信号 |
| -2 | 网络不可达 | 检查 MQTT 服务器地址 |
| -1 | 协议错误 | 检查 MQTT 版本 |
| 1 | 连接拒绝 | 服务器可能限制连接 |
| 2 | ClientID 冲突 | 修改 MQTT_CLIENT_ID |
| 3 | 服务器不可用 | 稍后重试 |
| 4 | 用户名/密码错误 | 检查认证信息 |
| 5 | 未授权 | 检查 ACL 权限 |
Q4:烟雾浓度读数不准
原因:MQ135 需要充分预热,且受温湿度影响较大。
解决:
- 首次使用预热 24~48 小时
- 在
smoke_detector.h中调整SMOKE_THRESHOLD阈值 - 本项目使用相对浓度(0~100%),适合判断"有烟/无烟"的场景
Q5:WiFi 频繁断连
可能原因:
- ESP32 供电不足
- 2.4GHz 信道干扰
- WiFi 信号弱
解决:使用外部电源供电,确保 ESP32 在 WiFi 信号覆盖范围内。
Q6:舵机抖动厉害
原因:供电不足导致舵机无法获得足够电流。
解决:55 舵机堵转电流可达 700mA,务必使用外部 5V/2A 电源,不能仅靠 USB。
Q7:如何修改烟雾报警阈值?
编辑 smoke_detector.h :
#define SMOKE_THRESHOLD 15.0 // 报警阈值(百分比)
恢复阈值自动为阈值的 80%(12.0),如需调整也修改对应代码逻辑。
2万+

被折叠的 条评论
为什么被折叠?



