Skip to content

第 9 章:灭火功能

9.1 任务描述

灭火是少儿编程机器人竞赛中的经典任务:场地内放置一支点燃的蜡烛,小车需要检测火焰、调整朝向、靠近并启动风扇将其吹灭,完成后发出提示音。本章在固定位置完成"检测 → 吹风"的完整流程,第 10 章将其与循迹功能结合。

9.2 模块原理:火焰探测器

火焰探测器(⑩)对火焰辐射的红外波段敏感。模块提供两种输出方式,D3 均已接线:

接口Pico 管脚读数特性适用场景
D 口(数字量)GP26仅 0 和 1 两种状态:有火焰 / 无火焰快速判断火焰是否存在
A 口(模拟量)GP27
0~65535 的连续数值,火焰越强、距离越近,读数越小判断火焰强度与距离

模块供电为 3.3V(接拓展板 3V3 供电输出区)。编程前应先用 A 口测量蜡烛在不同距离下的读数范围,据此确定程序中的判定阈值(可借助本节代码中的 fire_sensor_threshold_test 函数实测)。该过程称为"标定",是使用所有传感器的必要步骤。

火焰探测器 D 口与 A 口积木对比

python
from machine import Pin, PWM
import time

flame_D = Pin(26, Pin.IN)    # 火焰探测器 D 口:有火 = 1,无火 = 0
fan_F = PWM(Pin(18))         # 风扇正转脚(PWM 输出)
fan_R = PWM(Pin(19))         # 风扇反转脚

def pwm(pin, v):
    # Mind+ 的 PWM 范围 0~255 → duty_u16 范围 0~65535,乘 257 换算
    pin.duty_u16(int(v * 257))

def fan_setOnOffByFlameDetect():
    # 用 D 口读取的 0 或 1 来控制风扇转动
    if flame_D.value() == 1:     # 有火
        pwm(fan_F, 150)          # 风扇正转吹风(PWM 150)
        pwm(fan_R, 0)
    else:                        # 无火
        pwm(fan_F, 0)            # 两脚同为 0,电势差为 0,风扇停
        pwm(fan_R, 0)

# 主程序里循环调用即可:
# while True:
#     fan_setOnOffByFlameDetect()
#     time.sleep(0.1)
cpp
const int FLAME_D = 26;   // 火焰探测器 D 口:有火 = 1,无火 = 0
const int FAN_F = 18;     // 风扇正转脚(PWM 输出)
const int FAN_R = 19;     // 风扇反转脚

void fan_setOnOffByFlameDetect() {
  // 用 D 口读取的 0 或 1 来控制风扇转动
  if (digitalRead(FLAME_D) == 1) {   // 有火
    analogWrite(FAN_F, 150);         // 风扇正转吹风(PWM 150)
    analogWrite(FAN_R, 0);
  } else {                           // 无火
    analogWrite(FAN_F, 0);           // 两脚同为 0,电势差为 0,风扇停
    analogWrite(FAN_R, 0);
  }
}

void setup() {
  pinMode(FLAME_D, INPUT);
  pinMode(FAN_F, OUTPUT);
  pinMode(FAN_R, OUTPUT);
}

// 主程序里循环调用即可:
// void loop() {
//   fan_setOnOffByFlameDetect();
//   delay(100);
// }
python
from machine import Pin, PWM, ADC
import time

flame_A = ADC(Pin(27))       # 火焰探测器 A 口:0~65535 连续数值,火焰越近读数越小
fan_F = PWM(Pin(18))         # 风扇正转脚(PWM 输出)
fan_R = PWM(Pin(19))         # 风扇反转脚

FIRE_THRESHOLD_A = 50        # 触发阈值:实测正前方 30cm 有火时 < 50,无火环境约 300
                             # 环境不同读数差异大,请用下面的测试函数实测后灵活调整

def pwm(pin, v):
    # Mind+ 的 PWM 范围 0~255 → duty_u16 范围 0~65535,乘 257 换算
    pin.duty_u16(int(v * 257))

def fan_setSpeedByFlameIntensity():
    # 用 A 口读取的 0~65535 值来控制风扇转动
    # 注意方向:火焰越近,感应值越小
    fire_threshold_a = flame_A.read_u16()
    if fire_threshold_a <= FIRE_THRESHOLD_A:   # 读数够小 = 火焰够近
        pwm(fan_F, 160)        # 风扇正转吹风(PWM 160)
        pwm(fan_R, 0)
    else:                      # 火焰太远或没有:风扇停
        pwm(fan_F, 0)
        pwm(fan_R, 0)

def fire_sensor_threshold_test():
    # 阈值实测函数:在火焰传感器前点燃/吹灭蜡烛,
    # 观察串口数值变化,据此确定合适的触发阈值
    while True:
        print(flame_A.read_u16())
        time.sleep(0.2)

# 主程序里循环调用即可:
# while True:
#     fan_setSpeedByFlameIntensity()
#     time.sleep(0.1)
cpp
const int FLAME_A = 27;   // 火焰探测器 A 口:连续数值,火焰越近读数越小
const int FAN_F = 18;     // 风扇正转脚(PWM 输出)
const int FAN_R = 19;     // 风扇反转脚

const int FIRE_THRESHOLD_A = 50;   // 触发阈值:实测正前方 30cm 有火时 < 50,无火环境约 300
                                   // 环境不同读数差异大,请实测后灵活调整

void fan_setSpeedByFlameIntensity() {
  // 用 A 口读取的 0~65535 值来控制风扇转动
  // 注意方向:火焰越近,感应值越小
  int fire_threshold_a = analogRead(FLAME_A);
  if (fire_threshold_a <= FIRE_THRESHOLD_A) {   // 读数够小 = 火焰够近
    analogWrite(FAN_F, 160);        // 风扇正转吹风(PWM 160)
    analogWrite(FAN_R, 0);
  } else {                          // 火焰太远或没有:风扇停
    analogWrite(FAN_F, 0);
    analogWrite(FAN_R, 0);
  }
}

void fire_sensor_threshold_test() {
  // 阈值实测函数:在火焰传感器前点燃/吹灭蜡烛,
  // 观察串口数值变化,据此确定合适的触发阈值
  while (true) {
    Serial.println(analogRead(FLAME_A));
    delay(200);
  }
}

void setup() {
  Serial.begin(115200);
  analogReadResolution(16);   // 读数范围设为 0~65535,与 MicroPython 一致
  pinMode(FAN_F, OUTPUT);
  pinMode(FAN_R, OUTPUT);
}

// 主程序里循环调用即可:
// void loop() {
//   fan_setSpeedByFlameIntensity();
//   delay(100);
// }

9.3 模块原理:风扇灭火

风扇模块(⑧)的控制原理与基础教具包教程第 8 章一致:正转管脚 GP18、反转管脚 GP19,两管脚的电势差决定转向与启停。灭火任务仅使用"正转向前吹风"(GP18 高电平、GP19 低电平);将管脚改为 PWM 输出可调节风力大小。

安全提示:蜡烛实验必须由成年人负责点火并全程看护;风扇运转时扇叶不得接触手指与头发;吹灭后须确认无火星残留。

9.4 代码实现

程序分为以下步骤:

  1. 待命检测:循环读取火焰探测器 D 口(GP26),检测到火焰后进入下一步;

  2. 方向对准:原地低速旋转,同时读取 A 口(GP27),在读数最小的方向停止旋转(火焰越近读数越小),该方向即为火焰所在方向;

  3. 靠近火焰:前进,直至 A 口读数低于预设的"足够近"阈值;

  4. 启动风扇:风扇正转全速运行(GP18 高电平、GP19 低电平),持续数秒;

  5. 效果确认:关闭风扇,再次读取火焰探测器:火焰消失则蜂鸣器(GP14)鸣响三声提示完成;火焰未灭则返回第 4 步再次吹风。

本章无新知识点,Mind+ 图形化代码从略。重点在于通过算法锻炼编程思维。学员可根据下方 MicroPython 代码自行拖拽还原图形化程序,如需协助请联系编者微信。

python
from machine import ADC

# 火焰探测器:D 口 GP26(有/无火焰),A 口 GP27(火焰强度,0~65535 越小越近越强)
flame_D = Pin(26, Pin.IN)
flame_A = ADC(Pin(27))
# 风扇:GP18 正转(向前吹风)、GP19 反转,电势差控制启停,与电机同理
fan_F = Pin(18, Pin.OUT)
fan_R = Pin(19, Pin.OUT)

# 阈值集中定义(标定时只改这里)
FLAME_ON = 1        # D 口有火时的读数:按 9.2 的实测,有火 = 1
                    # 若所用模块相反(有火输出 0),改成 0 即可(这就是 9.2 说的"标定")
FLAME_NEAR = 50     # A 口"足够近"阈值:读数小于它才算够近(火焰越近读数越小)
                    # 实测参考:正前方 30cm 有火时 < 50,无火环境约 300,请实测后再定

def flame_detected():
    # 有火焰返回 True
    return flame_D.value() == FLAME_ON

def aim_flame():
    # 方向对准:原地旋转一整圈,持续读 A 口,记下读数最小的时刻,
    # 转完回到原位后,再按"时间换角度"转到最小读数的方向
    start = time.ticks_ms()
    best_t = 0          # 最小读数出现的时刻(毫秒)
    best_v = 65536      # 目前见过的最小读数(初值设为比量程还大)
    # 速度 200 约 10 毫秒转 1°(第 4 章标定值),转满 360° ≈ 3600 毫秒
    while time.ticks_diff(time.ticks_ms(), start) < 3600:
        wheel_left_1(-200)   # 左轮向后、右轮向前 = 原地左旋
        wheel_left_2(-200)
        wheel_right_1(200)
        wheel_right_2(200)
        v = flame_A.read_u16()
        if v < best_v:
            best_v = v       # 刷新最小读数
            best_t = time.ticks_diff(time.ticks_ms(), start)
    stop_all()
    # 转满一圈后朝向 = 起始朝向;最小读数出现在 best_t 毫秒处,
    # 对应角度 = best_t ÷ 10 度,再向左转这么多就正对火焰
    turn_angle(best_t / 10)

# ===== 主程序:检测 → 对准 → 靠近 → 吹风 → 确认 =====
# 1. 待命检测:没火就原地等待
while not flame_detected():
    time.sleep(0.1)

# 2. 方向对准:找到 A 口读数最小的方向
aim_flame()

# 3. 靠近火焰:前进,直到 A 口读数低于"足够近"阈值
go_sp(150, 1)
while flame_A.read_u16() > FLAME_NEAR:
    time.sleep(0.05)
stop_all()

# 4~5. 吹风 + 效果确认(闭环):没灭就再吹一次
while True:
    fan_F.value(1)       # 风扇正转全速,向前吹风
    fan_R.value(0)
    time.sleep(5)        # 持续吹 5 秒
    fan_F.value(0)       # 关风扇
    fan_R.value(0)
    time.sleep(0.5)
    if not flame_detected():
        break            # 复测:火焰消失才退出,否则回到循环再吹

beep(3)   # 鸣响三声,任务完成(beep 在第 8 章定义,单独跑本章需先定义)
cpp
// 火焰探测器:D 口 GP26(有/无火焰),A 口 GP27(火焰强度,越小越近越强)
const int FLAME_D = 26;
const int FLAME_A = 27;
// 风扇:GP18 正转(向前吹风)、GP19 反转
const int FAN_F = 18;
const int FAN_R = 19;

// 阈值集中定义(标定时只改这里)
const int FLAME_ON = 1;        // D 口有火时的读数:按 9.2 的实测,有火 = 1
                               // 若所用模块相反(有火输出 0),改成 0 即可(标定)
const int FLAME_NEAR = 50;     // A 口"足够近"阈值:读数小于它才算够近
                               // 实测参考:正前方 30cm 有火时 < 50,请实测后再定

bool flame_detected() {
  // 有火焰返回 true
  return digitalRead(FLAME_D) == FLAME_ON;
}

void aim_flame() {
  // 方向对准:原地旋转一整圈,持续读 A 口,记下读数最小的时刻,
  // 转完回到原位后,再按"时间换角度"转到最小读数的方向
  unsigned long start = millis();
  unsigned long best_t = 0;   // 最小读数出现的时刻(毫秒)
  int best_v = 65536;         // 目前见过的最小读数(初值设为比量程还大)
  // 速度 200 约 10 毫秒转 1°(第 4 章标定值),转满 360° ≈ 3600 毫秒
  while (millis() - start < 3600) {
    wheel_left_1(-200);   // 左轮向后、右轮向前 = 原地左旋
    wheel_left_2(-200);
    wheel_right_1(200);
    wheel_right_2(200);
    int v = analogRead(FLAME_A);
    if (v < best_v) {
      best_v = v;             // 刷新最小读数
      best_t = millis() - start;
    }
  }
  stop_all();
  // 最小读数出现在 best_t 毫秒处,对应角度 = best_t ÷ 10 度
  turn_angle(best_t / 10);
}

void setup() {
  analogReadResolution(16);   // 读数范围设为 0~65535,与 MicroPython 一致
  pinMode(FLAME_D, INPUT);
  pinMode(FAN_F, OUTPUT);
  pinMode(FAN_R, OUTPUT);

  // ===== 主程序:检测 → 对准 → 靠近 → 吹风 → 确认 =====
  // 1. 待命检测:没火就原地等待
  while (!flame_detected()) {
    delay(100);
  }
  // 2. 方向对准:找到 A 口读数最小的方向
  aim_flame();
  // 3. 靠近火焰:前进,直到 A 口读数低于"足够近"阈值
  go_sp(150, 1);
  while (analogRead(FLAME_A) > FLAME_NEAR) {
    delay(50);
  }
  stop_all();
  // 4~5. 吹风 + 效果确认(闭环):没灭就再吹一次
  while (true) {
    digitalWrite(FAN_F, HIGH);   // 风扇正转全速,向前吹风
    digitalWrite(FAN_R, LOW);
    delay(5000);                 // 持续吹 5 秒
    digitalWrite(FAN_F, LOW);    // 关风扇
    digitalWrite(FAN_R, LOW);
    delay(500);
    if (!flame_detected()) {
      break;                     // 复测:火焰消失才退出,否则再吹
    }
  }
  beep(3);   // 鸣响三声,任务完成(beep 在第 8 章定义)
}

void loop() {
  // 主程序在 setup 里跑完即结束,loop 留空
}

说明:第 5 步的"效果确认"体现了闭环控制思想——执行器完成动作后,必须由传感器重新检测确认结果。这一方法适用于所有自动控制项目。

9.5 常见 bug 及理解误区

误区一:以为 A 口"火越大,值越大"

直觉上火焰越猛、距离越近,传感器的读数应该越大——这款火焰探测器恰恰相反:火焰越大、距离越近,A 口读数越小。实测参考:正前方 30 cm 有火时读数小于 50。把比较方向写反(比如"读数大于阈值才算靠近"),小车会离火焰越来越远,或者风扇永远不转。记住这条规律:A 口是"值越小,火越近"。

误区二:以为"没有火焰时读数接近 65535"

不少资料会说模拟量无信号时读数顶满(接近 65535),实际环境中并非如此:室内存在大量人眼看不见的红外(灯光、阳光、发热的电器都会辐射红外),明明没有火焰,A 口也可能读到 300 左右的值。所以阈值绝不能照抄别人的——换一个房间、换一种光照,原本好用的阈值就可能失灵。正确做法:用 9.2 节的 fire_sensor_threshold_test 函数在实际使用场地分别实测"有火"和"无火"的读数,把阈值定在两者之间。

不积跬步,无以至千里;万里之行,始于当下。