项目概述本项目基于零知派ESP32和MQ135气体传感器构建了一套智能烟雾检测与自动排烟报警系统。当检测到烟雾浓度超过阈值时系统自动驱动蜂鸣器报警、启动风扇排烟并通过舵机控制风扇扫风方向以扩大排烟范围。系统配备ST7789240*240 TFT彩色屏幕实时显示烟雾浓度和各项设备状态同时通过MQTT 协议接入物联网支持远程监控与手动控制。系统功能一览功能说明烟雾检测MQ135 传感器实时采集烟雾浓度0~100%声光报警无源蜂鸣器发出 2kHz 方波警报音自动排烟烟雾超标后 1 秒自动启动风扇延迟启动防止误报扫风控制舵机带动风扇在 20°~160° 往复摆动扩大排烟覆盖范围本地显示ST7789 彩屏显示烟雾浓度数值、进度条、设备状态远程监控通过 WiFi MQTT 上报数据接收远程控制指令手动/自动模式自动模式下由阈值触发控制手动模式下由远程指令接管项目亮点本地远程双通道监控— 既可通过 TFT 屏幕本地查看也可通过 Web 仪表盘或手机 APP 远程监控非阻塞架构设计— 全部使用时间戳轮询millis()无delay()阻塞保证系统实时响应硬件 PWM 驱动— 蜂鸣器、风扇、舵机全部使用 ESP32 LEDC 硬件 PWMCPU 零开销维持输出自动手动双模式— 支持自动阈值控制与远程手动控制无缝切换滞回控制Hysteresis— 触发阈值 15%恢复阈值 12%80%防止烟雾在临界值波动导致频繁启停增量刷新屏幕— 烟雾值与状态栏分离更新仅在变化时重绘减少 SPI 通信开销项目难点及解决方案难点 1GPIO19 引脚复用冲突现象加入 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 → 舵机难点 3MQ135 传感器精度与预热现象新传感器读数不稳定与实际烟雾浓度偏差大。解决在readSmokeLevel()中不做复杂校准采用电压与满量程的比例归一化为 0~100% 的百分比值降低对绝对精度的依赖。同时建议首次使用预热 24~48 小时。目录一、硬件系统部分1.1 硬件清单1.2 接线方案1.3 硬件连接图1.4 实物连接图二、软件架构设计2.1 系统初始化2.2 主循环逻辑三、代码拆分讲解3.1 文件结构3.2 SmokeSensor烟雾传感器3.3 FanControl风扇控制3.4 Alarm蜂鸣器报警3.5 ServoControl舵机扫风3.6 MQTTClient远程通信3.7 DisplayManagerTFT屏幕显示四、操作过程及数据展示4.1 操作步骤4.2 演示视频五、技术原理5.1 工作原理5.2 工作模式配置六、常见问题指引Q1蜂鸣器不响屏幕上显示 ONQ2风扇不转或转速慢Q3MQTT 连接失败Q4烟雾浓度读数不准Q5WiFi 频繁断连Q6舵机抖动厉害Q7如何修改烟雾报警阈值一、硬件系统部分1.1 硬件清单组件数量型号/规格作用主控板1零知ESP32系统主控制器扩展板1零知派ESP32扩展板方便接线TFT 屏幕1ST7789 240x240 SPI本地状态显示烟雾传感器1MQ135检测烟雾/有害气体浓度风扇驱动模块1L9110驱动直流风扇PWM调速蜂鸣器1无源蜂鸣器声音报警舵机1MG90S控制风扇扫风方向USB转TTL模块1零知派USB转TTL模块给风扇和舵机供电面包板1面包板搭建电路杜邦线若干公对公 公对母接线本系统必须使用零知派USB转TTL模块来接外部 5V/2A 电源供电仅靠 USB 口供电会导致以下问题风扇无法启动或转速不足— 直流风扇启动电流可达 300~500mAUSB 口通常只能提供 500mA无法同时满足 ESP32~200mA 风扇~300mA 舵机~200mA的同时工作需求舵机抖动或卡死— 舵机堵转时电流可高达 700mAUSB 供电电压会被拉低至 4.5V 以下导致 ESP32 反复重启ESP32 WiFi 断连— 电压不足时 ESP32 的 WiFi 射频模块工作不稳定表现为频繁断线重连屏幕显示异常— SPI 通信在欠压情况下会出现数据错误屏幕花屏、闪烁系统反复重启— 电压过低会触发 ESP32 的欠压复位Brownout Detector系统陷入「启动→供电不足→复位→再启动」的死循环正确做法使用 5V/2A 电源适配器通过VIN 引脚或 5V 引脚为 ESP32 供电USB 口仅用于程序烧录和串口调试。1.2 接线方案MQ135 烟雾传感器MQ135 引脚接 ESP32VCC5V / VINGNDGNDAO模拟输出GPIO34DO数字输出不接L9110 风扇驱动模块L9110S接 ESP32接风扇VCC5V / VIN-GNDGND-INAGPIO25PWM-INBGPIO26PWM-无源蜂鸣器蜂鸣器接 ESP32引脚GPIO19PWM引脚GNDMG90S舵机舵机线色接 ESP32红色VCC5V / VIN棕色GNDGND橙色信号GPIO14PWM1.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(); // 配置ADC12位分辨率、11dB衰减 float readVoltage(); // 读取 MQ135 模拟电压0~3.3V float readSmokeLevel(); // 计算烟雾浓度百分比0~100% };核心公式电压(V) ADC原始值 / 4095 × 3.3 烟雾浓度(%) 电压 / 3.3 × 1003.3 FanControl风扇控制class FanControl { void begin(); // 绑定 INA → 通道0INB → 通道1 void start(int speed 255); // INA PWM, INB 0 → 正转 void stop(); // INA 0, INB 0 → 制动停止 void setSpeed(int speed); // 运行时调速 };L9110S 驱动逻辑INAINB风扇状态PWM0正转PWM占空比调速00停止电机制动3.4 Alarm蜂鸣器报警class Alarm { void begin(); // 绑定 GPIO19 → LEDC 通道22kHz void startAlarm(); // 输出 50% 占空比方波 → 最大音量 void stopAlarm(); // 输出 0 → 静音 };采用硬件 PWM驱动无源蜂鸣器频率 2kHz人耳最敏感的频率区间50% 占空比最大音量输出一经配置由 LEDC 硬件持续输出CPU 无需干预3.5 ServoControl舵机扫风class ServoControl { void begin(); // 绑定 GPIO14 → LEDC 通道350Hz 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 16383.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 DisplayManagerTFT屏幕显示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,...}步骤 4Web 远程监控直接用浏览器打开dashboard.html无需 web 服务器它会通过 WebSocket 连接 MQTT 服务器实时显示烟雾浓度仪表盘和趋势图。如果 ESP32 成功连接 MQTTdashboard.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 50Hz30ms 步进 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 电源适配器供电。Q3MQTT 连接失败串口输出MQTT connection failed, rc...返回码含义解决-4连接超时检查 WiFi 是否在线-3网络断开检查 WiFi 信号-2网络不可达检查 MQTT 服务器地址-1协议错误检查 MQTT 版本1连接拒绝服务器可能限制连接2ClientID 冲突修改MQTT_CLIENT_ID3服务器不可用稍后重试4用户名/密码错误检查认证信息5未授权检查 ACL 权限Q4烟雾浓度读数不准原因MQ135 需要充分预热且受温湿度影响较大。解决首次使用预热 24~48 小时在smoke_detector.h中调整SMOKE_THRESHOLD阈值本项目使用相对浓度0~100%适合判断有烟/无烟的场景Q5WiFi 频繁断连可能原因ESP32 供电不足2.4GHz 信道干扰WiFi 信号弱解决使用外部电源供电确保 ESP32 在 WiFi 信号覆盖范围内。Q6舵机抖动厉害原因供电不足导致舵机无法获得足够电流。解决55 舵机堵转电流可达 700mA务必使用外部 5V/2A 电源不能仅靠 USB。Q7如何修改烟雾报警阈值编辑smoke_detector.h#define SMOKE_THRESHOLD 15.0 // 报警阈值百分比恢复阈值自动为阈值的 80%12.0如需调整也修改对应代码逻辑。