深色模式
第 9 章:灭火功能
9.1 任务描述
灭火是少儿编程机器人竞赛中的经典任务:场地内放置一支点燃的蜡烛,小车需要检测火焰、调整朝向、靠近并启动风扇将其吹灭,完成后发出提示音。本章在固定位置完成"检测 → 吹风"的完整流程,第 10 章将其与循迹功能结合。
9.2 模块原理:火焰探测器
火焰探测器(⑩)对火焰辐射的红外波段敏感。模块提供两种输出方式,D3 均已接线:
| 接口 | Pico 管脚 | 读数特性 | 适用场景 |
|---|---|---|---|
| D 口(数字量) | GP26 | 仅 0 和 1 两种状态:有火焰 / 无火焰 | 快速判断火焰是否存在 |
| A 口(模拟量) | GP27 | 0~65535 的连续数值,火焰越强、距离越近,读数越小 | 判断火焰强度与距离 |
模块供电为 3.3V(接拓展板 3V3 供电输出区)。编程前应先用 A 口测量蜡烛在不同距离下的读数范围,据此确定程序中的判定阈值(可借助本节代码中的 fire_sensor_threshold_test 函数实测)。该过程称为"标定",是使用所有传感器的必要步骤。

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 代码实现
程序分为以下步骤:
待命检测:循环读取火焰探测器 D 口(GP26),检测到火焰后进入下一步;
方向对准:原地低速旋转,同时读取 A 口(GP27),在读数最小的方向停止旋转(火焰越近读数越小),该方向即为火焰所在方向;
靠近火焰:前进,直至 A 口读数低于预设的"足够近"阈值;
启动风扇:风扇正转全速运行(GP18 高电平、GP19 低电平),持续数秒;
效果确认:关闭风扇,再次读取火焰探测器:火焰消失则蜂鸣器(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 函数在实际使用场地分别实测"有火"和"无火"的读数,把阈值定在两者之间。
