eefocus_4087784 发表于 2026-8-6 18:25:09

零知派——STM32智能小车系列教程(五):DHT11温湿度、火焰传感器与风扇灭火模块原理

引言:智能小车要实现的功能很多——避障、跟随、贴边、灭火、直线行驶,每一个模块单独调试起来都有自己的难点。如果一开始就把所有模块塞进一个工程里联调,出了问题很难分清到底是传感器硬件的问题、电平极性的问题,还是上层控制算法的问题
      所以这一系列教程的思路是:先把每个功能模块单独拎出来,配一个不依赖其他硬件的最小测试程序,把模块本身调通调透,再回头看它在完整项目里是怎么被使用的

目录
一、引言
二、模块硬件原理
2.1 DHT11单总线通信协议2.1.1 完整通信时序2.1.2 数据格式2.2 红外火焰传感器原理2.2.1 火焰传感器工作原理2.2.2 灵敏度调节三、硬件接线说明
3.1 引脚定义3.2 接线方案表3.3 连接示意图四、独立测试程序
4.1 调试流程4.2 视频演示五、整车项目灭火模式实现
5.1 灭火模式状态机5.2 关键设计细节5.3 参数调优六、常见问题 FAQ
Q1:灭火模式下小车不执行灭火动作,但指示灯亮了?Q2:灭火模式中,火焰熄灭后风扇不关闭?
一、引言      前面四篇讲的都是“运动控制”相关的模块——循迹让小车知道“路在哪”、超声波让小车知道“障碍在哪”、红外让小车知道“目标在哪”、编码器让小车知道“跑多快”。这一篇转向“环境感知与执行”——检测环境温度湿度、检测火焰、控制风扇灭火

灭火模式在整个项目中的定位与其他模式完全不同:

特性其他模式(循迹/避障/跟随等)灭火模式
控制权完全由代码自动控制手动遥控为主,代码自动干预为辅
触发方式进入模式即开始自动运行由用户遥控驾驶,检测到火焰时自动接管
退出方式切换模式退出火焰熄灭3秒后自动归还控制权
用户交互不需要用户随时可用方向键控制移动
这种“用户驾驶 + 自动灭火”的混合设计思路——小车平时由用户遥控行驶,只有检测到火焰时才自动干预执行灭火动作,灭火完成后立即归还控制权——是嵌入式项目中“人机协同”的一个典型实例

这个系列教程拆成以下几篇:

篇号主题核心硬件
第一篇(已完成)五路循迹模块TCRT5000 + LM393/LM339
第二篇(已完成)HC-SR04超声波+SG90舵机云台HC-SR04 + SG90
第三篇(已完成)红外双目跟随模块红外避障对管
第四篇(已完成)红外对射式测速+直线行驶PID闭环槛型红外计数模块+码盘+PID
第五篇(本文)DHT11温湿度、火焰传感器、风扇灭火DHT11+火焰传感器+风扇
第六篇(待更新)蓝牙/串口双通道通信协议HC-05/BT06
终篇(待更新)整车项目架构总览全模块整合

二、模块硬件原理      本篇涉及三个独立硬件:DHT11负责测环境温湿度(本项目里主要作为状态展示,不直接参与灭火决策)、红外火焰传感器负责判断"有没有火",风扇负责执行"吹灭"这个物理动作

模块核心元件输出类型
DHT11电阻式湿敏元件+NTC温敏元件+8位单片机单总线数字信号,40bit一次完整数据
红外火焰传感器红外接收管(对760~1100nm波段敏感)+LM393比较器数字开关量,检测到火焰输出低电平
风扇直流电机+扇叶单路开关控制(本项目未做调速)
三者各自独立工作,靠代码逻辑串联成一套完整流程
​2.1 DHT11单总线通信协议
2.1.1 完整通信时序      DHT11采用单总线(Single-bus)通信,一根数据线既传输命令也传输数据,靠严格的时序约定区分不同信号

起始信号
      主机(开发板)接入数据线的I/O输出低电平,且低电平保持时间不能小于 18ms
https://file1.elecfans.com/web3/M00/6F/98/wKgZO2p0VGOAe5K_AACda-rda8M746.png响应信号
      等待模块的响应信号到来。将数据线改为输入模式,如果接入到低电平,说明接收到模块的响应
https://file1.elecfans.com/web3/M00/6F/97/wKgZPGp0VGuAen2UAACUKjD_508532.png数据传输
      主机接收模块发送的40位数据,其中,位数据 ‘0’ 表示54us的低电平,27us的高电平;位数据 ‘1’ 表示54us的低电平,74us的高电平
https://file1.elecfans.com/web3/M00/6F/98/wKgZO2p0VHaAXSukAAEPiLAD2Qw367.png两个格式的分辨主要是高电平的输出时长不同
结束信号
      模块的数据线输出 40 位数据后,是以低电平结束,它会继续输出低电平 54 微秒后转为输入状态,主机需要转为输出状态,输出高电平释放总线
https://file1.elecfans.com/web3/M00/6F/98/wKgZO2p0VIGAZOJdAAAqII-YL-A755.png2.1.2 数据格式      完整的一次数据传输是40位,格式是8位湿度整数数据+8位湿度小数数据+8位温度整数数据+8位温度小数数据+8位校验位

0011 01010000 00000001 10000000 01000101 0001
湿度高8位湿度低8位温度高8位温度低8位校验位
校验和计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001,与接收的数据一致
数据时序图
https://file1.elecfans.com/web3/M00/6F/97/wKgZPGp0VImATpr3AAEJUN1u9JQ757.png高位先发送,对DHT11来说湿度小数字节和温度小数字节固定为0,校验位是前四个字节相加后取低8位

2.2 红外火焰传感器原理2.2.1 火焰传感器工作原理      火焰燃烧时会释放特定波长的红外线,火焰传感器中的红外光电三极管接收到红外辐射后,其集电极电流随光强增加而增大,通过 LM393 比较器与预设阈值比较后输出数字电平

与循迹模块 TCRT5000 的红外检测有何不同:

对比项TCRT5000 循迹模块火焰传感器
检测对象反射回来的红外光主动发射的红外光
检测原理发射→反射→接收接收环境中的红外辐射
检测距离1~2.5cm0.3~3m
检测角度窄(约 15°)宽(约 60°)
核心用途近距离检测地面颜色远距离检测火源
区别:TCRT5000 需要主动发射红外光然后接收反射光,而火焰传感器只接收不发射——它被动接收火焰燃烧产生的红外辐射。这个区别决定了火焰传感器的检测距离远(可达数米),但也会被阳光、热源等其他红外辐射源干扰

2.2.2 灵敏度调节模块上那颗蓝色方形可调电位器(3296 型)用于调节火焰检测的阈值电压,从而改变检测灵敏度

调节方向阈值变化检测效果
顺时针旋转参考电压降低更灵敏(检测更远火焰,但易误触发)
逆时针旋转参考电压升高更不灵敏(只检测强火焰,抗干扰强)
实际调试方法

[*]将火源放在传感器正前方约 30cm 处
[*]慢慢旋转电位器,观察模块上的状态指示灯
[*]找到指示灯在“有火源”和“无火源”之间刚好切换的临界点
[*]再往灵敏方向回调一点(顺时针约 1/8 圈)

三、硬件接线说明3.1 引脚定义<p>本项目三个模块的引脚定义(与<code>pinsdefine.h</code>一致):</p><pre><code class="language-cpp">#define FAN_PIN         22   // 风扇(装在车身左中部)
#define FLAME_PIN       23   // 火焰传感器(装在车尾左侧)
#define DHT11_PIN       27   // DHT11 单总线数据脚</code></pre>​
四、独立测试程序在整合到整车项目之前,先用下面这个程序单独验证火焰传感器和风扇的功能:
/**************************************************************************************
* 文件: Fire_Extinguish_Standalone_Test.ino
* 作者:零知实验室(深圳市在芯间科技有限公司)
* 功能:火焰传感器 + 风扇灭火 独立测试程序
*       不依赖其他硬件,用于验证火焰检测灵敏度和风扇控制
*
* 引脚(与主项目 pinsdefine.h 完全一致):
*   FLAME_PIN = D23
*   FAN_PIN   = D22
*
* 串口命令(波特率115200):
*   f   手动开关风扇(切换状态)
*   s   打印一次温湿度(DHT11)
*   t   火焰检测灵敏度测试模式(连续监测)
*   h   打印帮助信息
*
* 使用方法:
*   1. 烧录本程序到零知派迷你板
*   2. 打开串口监视器,波特率115200
*   3. 用打火机(不点燃)或手机闪光灯作为火源
*   4. 在不同距离测试火焰检测的灵敏度
**************************************************************************************/

#include <SimpleDHT.h>

#define FLAME_PIN   23
#define FAN_PIN   22
#define DHT11_PIN   27

SimpleDHT11 dht11(DHT11_PIN);

// ── 状态变量 ──────────────────────────────────────────────────────
bool fanState = false;          // false=关, true=开
bool testMode = false;          // false=命令模式, true=持续监测模式
unsigned long lastFlameDetectedMs = 0;

// ── 串口命令处理 ──────────────────────────────────────────────────
void printHelp()
{
    Serial.println(F("========================================"));
    Serial.println(F(" 火焰传感器 + 风扇 独立测试程序"));
    Serial.println(F("========================================"));
    Serial.println(F(" 命令:"));
    Serial.println(F("   f         手动开关风扇"));
    Serial.println(F("   s         读取并打印温湿度"));
    Serial.println(F("   t         进入/退出持续监测模式"));
    Serial.println(F("   h         打印本帮助信息"));
    Serial.println(F(""));
    Serial.println(F(" 当前状态:"));
    Serial.print(F("火焰检测: "));
    Serial.println(digitalRead(FLAME_PIN) == LOW ? "有火焰 (LOW)" : "无火焰 (HIGH)");
    Serial.print(F("风扇状态: "));
    Serial.println(fanState ? "ON" : "OFF");
    Serial.println(F("========================================"));
}

void handleCommand(String cmd)
{
    cmd.trim();
    if (cmd.length() == 0) return;

    if (cmd == "f") {
      fanState = !fanState;
      digitalWrite(FAN_PIN, fanState ? HIGH : LOW);
      Serial.print(F(" "));
      Serial.println(fanState ? "ON" : "OFF");
      return;
    }
    if (cmd == "s") {
      byte temp = 0, humi = 0;
      int err = dht11.read(&temp, &humi, nullptr);
      if (err != SimpleDHTErrSuccess) {
            Serial.print(F(" 读取失败 err="));
            Serial.println(err);
            if (err == SimpleDHTErrStartLow) {
                Serial.println(F(" 提示:DHT11 需要至少 1s 采样间隔"));
            }
      } else {
            Serial.print(F(" 温度="));
            Serial.print((int)temp);
            Serial.print(F("°C湿度="));
            Serial.print((int)humi);
            Serial.println(F("%RH"));
      }
      return;
    }
    if (cmd == "t") {
      testMode = !testMode;
      Serial.print(F(" "));
      Serial.println(testMode ? "持续监测模式 ON" : "持续监测模式 OFF");
      return;
    }
    if (cmd == "h") {
      printHelp();
      return;
    }
    Serial.print(F(" 未知命令: "));
    Serial.println(cmd);
}

// ── 火焰检测封装 ──────────────────────────────────────────────────
bool flameDetected()
{
    return (digitalRead(FLAME_PIN) == LOW);
}

// ── setup ─────────────────────────────────────────────────────────
void setup()
{
    Serial.begin(115200);
    delay(200);

    pinMode(FLAME_PIN, INPUT_PULLUP);
    pinMode(FAN_PIN, OUTPUT);
    digitalWrite(FAN_PIN, LOW);

    Serial.println(F("========================================"));
    Serial.println(F(" 火焰传感器 + 风扇 独立测试程序"));
    Serial.println(F(" 波特率:115200"));
    Serial.println(F(" 命令:f(开关风扇)s(读温湿度)t(监测模式)h(帮助)"));
    Serial.println(F("========================================"));
    Serial.println(F("提示:火焰传感器检测到火焰时 DO 输出 LOW"));
    Serial.println(F(""));
}

// ── loop ──────────────────────────────────────────────────────────
void loop()
{
    // ── 串口命令 ──────────────────────────────────────────────────
    while (Serial.available()) {
      String cmd = Serial.readStringUntil('\n');
      handleCommand(cmd);
    }

    // ── 持续监测模式 ─────────────────────────────────────────────
    if (testMode) {
      bool flame = flameDetected();
      static bool lastFlame = false;
      if (flame != lastFlame) {
            lastFlame = flame;
            Serial.print(F(" "));
            Serial.println(flame ? "检测到火焰!" : "无火焰");
      }
      // 检测到火焰时自动开启风扇(模拟灭火)
      if (flame) {
            if (!fanState) {
                fanState = true;
                digitalWrite(FAN_PIN, HIGH);
                Serial.println(F(" 火焰检测触发 → 风扇 ON"));
            }
            lastFlameDetectedMs = millis();
      } else {
            // 连续3秒无火焰则关闭风扇
            if (fanState && (millis() - lastFlameDetectedMs > 3000)) {
                fanState = false;
                digitalWrite(FAN_PIN, LOW);
                Serial.println(F(" 连续3秒无火焰 → 风扇 OFF"));
            }
      }
      delay(50);
    } else {
      // 命令模式下,每隔1秒打印一次火焰状态(非侵入式)
      static unsigned long lastPrintMs = 0;
      if (millis() - lastPrintMs >= 1000) {
            lastPrintMs = millis();
            Serial.print(F(" 火焰="));
            Serial.print(flameDetected() ? "有" : "无");
            Serial.print(F("风扇="));
            Serial.println(fanState ? "ON" : "OFF");
      }
      delay(100);
    }
}

4.1 调试流程①打开串口监视器
      显示帮助信息和当前状态,发送 t 进入持续监测模式,串口实时打印火焰状态变化

②靠近火焰传感器
https://file1.elecfans.com/web3/M00/6F/97/wKgZPGp0VJmAc450AAWnR1Msr0w788.png      检测到火焰时自动开启风扇,移开光源等待 3 秒,风扇自动关闭

③发送 s 读取温湿度
https://file1.elecfans.com/web3/M00/6F/98/wKgZO2p0VKyATnNHAAWs_hzj0zw816.png      打印当前温湿度数值,发送 f 手动开关风扇,风扇 ON/OFF 切换


4.2 视频演示火焰传感器+风扇灭火模块独立测试
输入h展示当前实时状态,用打火机在远、中、近三个距离依次靠近传感器,配合t模式下边沿触发提示和自动开关风扇的串口输出同框录制,重点展示"检测到火焰立刻响应、拿开后延迟3秒才关闭"这个防抖设计的实际效果。切到s命令读取温湿度,展示正常读数和"采样间隔不足"报错两种情况的对比

五、整车项目灭火模式实现5.1 灭火模式状态机整车项目中,FireBehavior 类实现了完整的灭火状态机
四种状态
enum FireState : uint8_t {
    FIRE_IDLE,      // 空闲态:遥控驾驶,后台监测火焰
    FIRE_BACK,      // 倒车靠近火源
    FIRE_PAUSE,   // 停车吹风
    FIRE_FORWARD    // 前进离开
};状态转换图
https://file1.elecfans.com/web3/M00/6F/97/wKgZPGp0VLaAdQzbAA5oa4L02pw418.png5.2 关键设计细节①全局火焰监测(独立于状态)
bool flame = g_env.flameDetected();
if (flame) _lastFlameMs = millis();无论当前处于哪个子状态,每帧都会检查火焰检测状态,并更新 _lastFlameMs(最后一次检测到火焰的时间戳)。
② 灭火成功的判定条件
if (_st != FIRE_IDLE && (millis() - _lastFlameMs > NO_FLAME_MS)) {
    // 连续3秒未检测到火焰 → 判定灭火成功
    g_env.setFan(false);
    g_motor.stop();
    _st = FIRE_IDLE;
}只有在非 IDLE 状态下才进行这个判断(IDLE 时本来就没有火焰,不需要判定),避免逻辑混乱。
③ 反复灭火机制
case FIRE_FORWARD:
    if (millis() - _ts > FORWARD_MS) {
      g_motor.stop();
      if (flame) {
            // 火焰还在 → 继续倒车吹风
            g_motor.motorRun(-BACK_SPEED, -BACK_SPEED);
            _st = FIRE_BACK;
            _ts = millis();
      } else {
            _st = FIRE_IDLE;
      }
    }
    break;前进阶段结束后,如果还检测到火焰,会自动回到 FIRE_BACK 继续灭火,形成往复循环,直到火焰完全熄灭。
④ 手动驾驶控制权
sc_comms.cpp 中的 parseCmd() 函数将 MODE_FIRE 加入了方向键白名单
if (g_mode == MODE_MANUAL || g_mode == MODE_STRAIGHT || g_mode == MODE_FIRE) {
    switch (c) {
      case '1': g_motor.goWithDirec(carForward);break;
      case '2': g_motor.goWithDirec(carBack);   break;
      // ... 方向键处理
    }
}在 FIRE_IDLE 状态下,用户可以自由遥控驾驶小车。检测到火焰时,代码自动接管控制权执行灭火,灭火完成后自动归还控制权

5.3 参数调优灭火模式的参数定义
static const long BACK_MS   = 500;    // 倒车靠近火源的时长
static const long PAUSE_MS    = 800;    // 停留吹风时长
static const long FORWARD_MS= 500;    // 前进离开的时长
static const long NO_FLAME_MS = 3000;   // 连续多久未检测到判定灭火成功
static const intBACK_SPEED= 60;   // 倒车/前进速度调参建议

参数作用调大效果调小效果
BACK_MS倒车时长风扇更靠近火源,但可能撞墙离火源较远,效果可能差
PAUSE_MS吹风时长灭火更彻底,但耗时更长响应更快,但可能吹不灭
NO_FLAME_MS判定灭火的等待时间更稳定,避免误判响应更快,但可能误判
BACK_SPEED移动速度动作更快,但可能吹动火焰更平稳,但移动慢

六、常见问题 FAQQ1:灭火模式下小车不执行灭火动作,但指示灯亮了?      A:_st 状态标志没有被正确设置,火焰检测的极性判断错误。确认 EnvSensor::flameDetected() 返回值正确,检测到火焰返回 true。串口打印 _st 值,确认状态机是否正确进入 FIRE_BACK
Q2:灭火模式中,火焰熄灭后风扇不关闭?
      A:NO_FLAME_MS = 3000 意味着需要连续 3 秒未检测到火焰才判定灭火成功。如果传感器偶尔误读到火焰,会重置计时器。确认火焰传感器没有误触发,适当降低 NO_FLAME_MS


灭火模式是整个项目中唯一一个“用户驾驶为主、代码自动干预为辅”的混合模式——小车平时由用户遥控行驶,只有检测到火焰时才会自动介入执行灭火动作,灭火完成后立即归还控制权。这种“人机协同”的设计思路在其他模式(循迹、避障、跟随)中是见不到的,是一个非常典型的“人机协同”嵌入式设计

页: [1]
查看完整版本: 零知派——STM32智能小车系列教程(五):DHT11温湿度、火焰传感器与风扇灭火模块原理