若该文为原创文章,转载请注明原文出处。

一、项目背景与意义

火灾是危害公共安全的主要灾害之一。传统火灾报警器通常只依赖单一传感器(烟雾或温度),误报率高,且无法远程监控。随着AI视觉和物联网技术的发展,将图像识别与多传感器融合,再结合云平台,可以实现精准识别、快速响应、远程可视、历史可溯的智能消防系统。

本项目设计了一套增强型智能火焰联动报警与远程监管系统,以STM32F103C8T6为主控,K210通过YOLOv2实现火焰视觉检测,结合MQ-2烟雾传感器和DS18B20温度传感器进行融合判断,通过ESP8266(ESP-01S)接入中国移动OneNet云平台,实现实时数据上报、远程控制、历史记录查询等功能。系统还具备本地声光报警、JQ8900语音播报、OLED信息显示、SD卡日志存储等完整本地交互能力。


二、系统总体架构

2.1 系统分层

层级 功能模块 硬件设备
感知层 火焰视觉检测、烟雾浓度采集、温度采集 K210+摄像头、MQ-2、DS18B20
控制层 逻辑决策、外设控制、通信处理 STM32F103C8T6
执行层 声光报警、联动控制、语音播报 蜂鸣器/LED、继电器、JQ8900语音模块
交互层 本地信息显示、按键操作 OLED屏、按键
存储层 报警日志记录 MicroSD卡(SPI接口)
通信层 WiFi联网、数据上报、远程控制 ESP-01S (ESP8266)
云平台 数据展示、历史记录、远程控制 OneNet物联网平台

2.2 系统框图

三、硬件选型与设计

3.1 核心硬件清单

模块 型号 数量 接口 作用
主控MCU STM32F103C8T6 1 - 系统逻辑核心
AI视觉 正点原子K210 + OV2640 1 UART 火焰YOLO检测,输出置信度
WiFi模块 ESP-01S (ESP8266) 1 UART 连接OneNet,MQTT通信
烟雾传感器 MQ-2 1 ADC 烟雾/可燃气体浓度检测
温度传感器 DS18B20 1 1-Wire 环境温度检测
语音播报 JQ8900 MP3模块 1 UART/触发IO 播放预先存入TF卡的语音文件
OLED显示屏 0.96寸 SSD1306 1 I2C 显示系统状态、传感器数值
MicroSD卡模块 SPI接口 1 SPI 存储报警日志(CSV格式)
有源蜂鸣器 5V 1 GPIO 声音报警
高亮LED 红/蓝双色 1 GPIO 光报警
继电器模块 5V 2路 1 GPIO 控制排烟扇、喷淋电磁阀
按键 轻触开关 3 GPIO 复位/消音、自检、页面切换
电源 5V/2A适配器 1 DC口 系统供电

3.2 引脚连接表(STM32端)

STM32引脚 外设 说明
PA9 (USART1_TX) K210 (RX) 发送查询指令
PA10 (USART1_RX) K210 (TX) 接收火焰检测结果
PA2 (USART2_TX) ESP-01S (RX) 发送AT/MQTT数据
PA3 (USART2_RX) ESP-01S (TX) 接收WiFi/MQTT数据
PA9 (或其它UART) JQ8900 (RX) 语音播报控制(若UART复用,可采用IO触发模式)
PB0 蜂鸣器 高电平驱动
PB1 LED(红) 报警指示灯
PB2 LED(蓝) 运行指示灯
PB3 继电器1(排烟) 控制排烟风扇
PB4 继电器2(喷淋) 控制喷淋电磁阀
PB5 KEY1(复位/消音) 输入,下拉
PB6 KEY2(自检) 输入,下拉
PB7 KEY3(页面切换) 输入,下拉
PB8 OLED SCL I2C时钟
PB9 OLED SDA I2C数据
PA4 SD_CS SPI片选
PA5 SD_SCK SPI时钟
PA6 SD_MISO SPI主入从出
PA7 SD_MOSI SPI主出从入
PA0 MQ-2输出 ADC输入
PA1 DS18B20 1-Wire数据

说明:JQ8900支持串口指令播放(如“播放第1首”、“音量调节”等),也可通过IO触发播放指定编号的音频。为节省UART资源,可采用IO触发模式,将JQ8900的触发引脚连接到STM32的GPIO,通过电平脉冲选择播放不同的音频文件(预先烧录到JQ8900的TF卡中)。

四、功能模块详细设计

4.1 火焰视觉检测(K210)

  • 模型:YOLOv2,输入尺寸224×224,训练数据集包含火焰和干扰物(如红色灯光、太阳光等),输出两个类别:no(无火)、fire(有火)。

  • 通信协议:K210通过UART向STM32发送数据,格式:FIRE:0.87(检测到火焰,置信度0.87)或 NONE(未检测到火焰)。

  • 帧率:约10~15 FPS,满足实时性要求。

4.2 多传感器融合与分级报警策略

STM32每200ms采集一次三路数据,综合判断火警等级:

等级 火焰置信度 烟雾浓度(ADC值) 温度(℃) 动作
0(正常) <0.5 <100 <40 无动作,LED蓝灯闪烁
1(预警) ≥0.5且<0.7 ≥100或≥40 ≥40 蜂鸣器慢速鸣叫(1Hz),OLED显示“预警”
2(一般火警) ≥0.7 ≥200或≥45 ≥45 蜂鸣器中速(2Hz),启动排烟继电器,语音播报“请注意,检测到火情”,上报OneNet
3(紧急火警) ≥0.8 ≥350或≥60 ≥60 蜂鸣器快速(4Hz),启动排烟+喷淋,语音播报“紧急火警,请立即疏散”,上报OneNet紧急事件

4.3 本地人机交互

  • OLED显示:默认页面显示实时传感器值、火警等级、WiFi状态;按KEY3可切换至日志查看页面(显示最近5条报警记录)。

  • 按键功能

    • KEY1(短按):消音/复位当前报警;

    • KEY1(长按3秒):进入系统自检模式(依次测试蜂鸣器、LED、继电器、语音);

    • KEY2:手动触发一次模拟火警(用于功能演示);

    • KEY3:切换OLED显示页面。

4.4 语音播报(JQ8900)

  • 音频准备:将语音文件(MP3格式)按序号命名(如001.mp3为“系统启动”、002.mp3为“检测到火情”、003.mp3为“紧急火警,请立即疏散”等)存入JQ8900的TF卡。

  • 播放控制:STM32通过IO触发方式,拉低对应的触发引脚(如PLAY1、PLAY2等)即可播放指定音频。若使用串口指令,则通过UART发送“播放第X首”指令。

  • 音量调节:可通过指令设定音量大小。

4.5 远程通信与OneNet云平台

  • WiFi模块:ESP-01S(AT固件),STM32通过AT指令控制连接WiFi、建立TCP/MQTT连接。

  • 协议:MQTT(基于OneNet的MQTT旧版协议)。

  • 数据上传格式:JSON格式,包含:

{
  "fire_level": 2,
  "smoke": 250,
  "temp": 42.5,
  "confidence": 0.85,
  "timestamp": 1691234567
}
  • 上传触发:火警等级变化时立即上传,正常状态下每30秒上传一次心跳(包含当前传感器数值,等级=0)。

  • 下行指令:订阅OneNet的“命令”Topic,可接收:

    • {"cmd":"reset"}:复位报警

    • {"cmd":"fan_on"}:强制启动排烟

    • {"cmd":"fan_off"}:关闭排烟

    • {"cmd":"sprinkler_on"}:启动喷淋

    • {"cmd":"sprinkler_off"}:关闭喷淋

    • {"cmd":"set_threshold","smoke":300,"temp":50}:修改报警阈值

4.7 OneNet平台与历史记录查询

  • OneNet产品创建:创建MQTT产品,设备注册后获取设备ID和API密钥。

  • 数据流:定义多个数据流(fire_level, smoke, temp, confidence)。

  • 可视化:使用OneNet的“可视化”组件创建仪表盘,展示实时数据及历史曲线。

  • 历史查询:OneNet默认保存数据流的历史数据(可通过API或控制台查询)。手机APP可通过OneNet官方APP或自定义APP(调用OneNet RESTful API)查询历史记录。

手机APP查询流程

  1. 用户打开APP(或微信小程序);

  2. 调用OneNet API:GET /devices/{device_id}/datapoints?datastream_id=smoke&start=...&end=...

  3. 获取指定时间段的历史数据并列表展示,同时可绘制曲线图。


五、软件流程

5.1 主程序流程图

系统初始化(时钟、GPIO、UART、I2C、SPI、ADC、1-Wire、FATFS)
加载默认阈值(从SD卡/Flash读取,若不存在则使用默认值)
初始化OLED显示“系统启动”
初始化JQ8900播放“系统启动”语音
ESP-01S初始化(AT测试、连接WiFi、连接OneNet MQTT)
进入主循环:
  1. 读取K210串口数据(非阻塞),更新火焰状态
  2. 读取MQ-2 ADC值 → 滤波 → 换算浓度
  3. 读取DS18B20温度
  4. 综合评估火警等级
  5. 如果等级变化:
       记录日志到SD卡
       触发声光报警(等级对应频率)
       控制继电器(排烟/喷淋)
       播放对应语音
       上传数据到OneNet
  6. 如果等级=0且之前有报警且已持续正常30秒,则自动复位(或等待手动KEY1)
  7. 刷新OLED(每200ms更新数值,按KEY3切换页面)
  8. 检测按键(消音、自检、页面切换)
  9. 处理ESP-01S接收的MQTT消息(解析指令并执行)
  10. 定时(每30秒)上传一次心跳数据(包含所有传感器值)
  11. 延时100ms,循环

5.2 K210端程序(框架)

K210端使用正点原子提供的YOLO例程,仅需修改主循环,通过UART发送检测结果:

# YOLO 火灾检测 - 适配正点原子 K210 (支持 Flash/SD 加载 + 串口输出)
import sensor, image, lcd, time
from maix import KPU
import gc, sys
from machine import UART
from fpioa_manager import fm
from board import board_info

input_size = (224, 224)
labels = ['no', 'fire']
anchors = [6.94, 4.56, 2.44, 2.12, 0.34, 0.41, 0.56, 0.78, 1.34, 1.22]
anchor_num = len(anchors) // 2  # 5

# ========== 配置区域 ==========
USE_FLASH = True                # True=从Flash加载,False=从SD卡加载
MODEL_ADDR = 0x300000           # Flash地址(使用前请确认实际烧录地址)
MODEL_SIZE = 1906856                 # 模型大小(字节),请根据实际文件大小填写
MODEL_PATH = "/sd/model-56191.kmodel"  # SD卡路径

# LCD/摄像头旋转设置(180度)
LCD_ROTATION = 2                # 0:0°, 1:90°, 2:180°, 3:270°
SENSOR_HMIRROR = True           # 水平翻转
SENSOR_VFLIP = True             # 垂直翻转

# YOLO参数
CONFIDENCE_THRESHOLD = 0.5
NMS_THRESHOLD = 0.3
# ================================

def lcd_show_except(e):
    import uio
    err_str = uio.StringIO()
    sys.print_exception(e, err_str)
    err_str = err_str.getvalue()
    img = image.Image(size=input_size)
    img.draw_string(0, 10, err_str, scale=1, color=(0xff,0x00,0x00))
    lcd.display(img)

class Comm:
    def __init__(self, uart):
        self.uart = uart

    def send_detect_result(self, objects, labels):
        """发送检测结果,格式: x:y:w:h:class_id:confidence:label, ..."""
        msg = ""
        for obj in objects:
            x, y, w, h, class_id, confidence = obj
            label = labels[class_id]
            msg += "{}:{}:{}:{}:{}:{:.2f}:{}, ".format(x, y, w, h, class_id, confidence, label)
        if msg:
            msg = msg[:-2] + "\n"   # 去掉末尾空格和逗号,加换行
        self.uart.write(msg.encode())

def init_uart():
    """初始化UART1,优先使用board_info定义的引脚,否则使用固定引脚10/11"""
    try:
        fm.register(board_info.EX_UART1_TX, fm.fpioa.UART1_TX, force=True)
        fm.register(board_info.EX_UART1_RX, fm.fpioa.UART1_RX, force=True)
        uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=256)
        print("UART1 configured with board_info")
        return uart
    except Exception:
        pass
    fm.register(10, fm.fpioa.UART1_TX, force=True)
    fm.register(11, fm.fpioa.UART1_RX, force=True)
    uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=256)
    print("UART1 configured with fixed pins (10,11)")
    return uart

def main(anchors, labels=None, model_addr=MODEL_ADDR, model_size=MODEL_SIZE,
         model_path=MODEL_PATH, use_flash=USE_FLASH,
         sensor_window=input_size, lcd_rotation=LCD_ROTATION,
         sensor_hmirror=SENSOR_HMIRROR, sensor_vflip=SENSOR_VFLIP,
         conf_threshold=CONFIDENCE_THRESHOLD, nms_threshold=NMS_THRESHOLD):

    # 1. 初始化传感器
    sensor.reset()
    sensor.set_pixformat(sensor.RGB565)
    sensor.set_framesize(sensor.QVGA)
    sensor.set_windowing(sensor_window)
    sensor.set_hmirror(sensor_hmirror)
    sensor.set_vflip(sensor_vflip)
    sensor.run(1)

    # 2. 初始化LCD
    lcd.init(type=1)
    lcd.rotation(lcd_rotation)
    lcd.clear(lcd.WHITE)

    # 3. 加载标签
    if not labels:
        try:
            with open('labels.txt','r') as f:
                exec(f.read())
        except Exception:
            print("no labels.txt, using default labels")
            labels = ['no', 'fire']
    if not labels:
        print("no labels")
        img = image.Image(size=(320, 240))
        img.draw_string(90, 110, "no labels", color=(255, 0, 0), scale=2)
        lcd.display(img)
        return 1

    # 4. 显示启动画面
    try:
        img = image.Image("startup.jpg")
        lcd.display(img)
    except Exception:
        img = image.Image(size=(320, 240))
        img.draw_string(90, 110, "loading model...", color=(255, 255, 255), scale=2)
        lcd.display(img)

    # 5. 初始化UART
    uart = init_uart()
    comm = Comm(uart)
    print("UART ready, system start...")

    # 6. 创建KPU对象并加载模型
    task = None
    try:
        task = KPU()
        if use_flash:
            print("Loading model from Flash: 0x{:06X}, size: {}".format(model_addr, model_size))
            task.load_kmodel(model_addr, model_size)
        else:
            print("Loading model from SD: {}".format(model_path))
            task.load_kmodel(model_path)
        print("Model loaded successfully!")

        # 7. 初始化YOLO2
        net_w, net_h = sensor_window
        layer_w = net_w // 32
        layer_h = net_h // 32
        print("YOLO2 init: layer_w={}, layer_h={}".format(layer_w, layer_h))
        task.init_yolo2(
            anchors,
            anchor_num=anchor_num,
            img_w=net_w,
            img_h=net_h,
            net_w=net_w,
            net_h=net_h,
            layer_w=layer_w,
            layer_h=layer_h,
            threshold=conf_threshold,
            nms_value=nms_threshold,
            classes=len(labels)
        )
        print("YOLO2 initialized successfully!")

        # 8. 主循环
        while True:
            img = sensor.snapshot()
            t = time.ticks_ms()

            # 推理
            task.run_with_output(img)
            objects = task.regionlayer_yolo2()

            t = time.ticks_ms() - t

            # 绘制检测结果并串口发送
            if objects:
                for obj in objects:
                    x, y, w, h, class_id, confidence = obj
                    # 画框
                    img.draw_rectangle(x, y, w, h, color=(0, 255, 0))
                    # 显示标签和置信度
                    label_text = "{}:{:.2f}".format(labels[class_id], confidence)
                    img.draw_string(x+2, y+2, label_text, scale=2, color=(255, 0, 0))
                # 串口发送
                comm.send_detect_result(objects, labels)

            # 显示FPS等信息
            img.draw_string(0, 200, "t:%dms" % t, scale=2, color=(255, 0, 0))
            img.draw_string(0, 2, "Fire Detector", scale=1.5, color=(0, 255, 255))
            if use_flash:
                load_info = "Flash:0x{:06X}".format(model_addr)
            else:
                load_info = "SD:{}".format(model_path.split('/')[-1])
            img.draw_string(0, 30, load_info, scale=1, color=(128, 128, 128))

            lcd.display(img)
            gc.collect()

    except Exception as e:
        print("Error:", e)
        raise e
    finally:
        if task is not None:
            try:
                task.deinit()
            except Exception:
                pass
        gc.collect()

if __name__ == "__main__":
    try:
        main(anchors=anchors, labels=labels,
             model_addr=MODEL_ADDR,
             model_size=MODEL_SIZE,
             model_path=MODEL_PATH,
             use_flash=USE_FLASH,
             sensor_window=input_size,
             lcd_rotation=LCD_ROTATION,
             sensor_hmirror=SENSOR_HMIRROR,
             sensor_vflip=SENSOR_VFLIP)
    except Exception as e:
        sys.print_exception(e)
        lcd_show_except(e)
    finally:
        gc.collect()

5.3 OneNet平台配置简要步骤

  1. 登录OneNet平台,创建“MQTT”产品;

  2. 注册设备,获取设备ID、鉴权信息(API Key);

  3. 在设备详情中创建多个数据流(fire_levelsmoketempconfidence);

  4. 下载OneNet提供的MQTT接入示例,根据文档修改clientIdusernamepassword等参数;

  5. 编写STM32端MQTT封装代码(参考OneNet官方C SDK或使用AT指令+自定义封装);

  6. 开发手机APP(可使用OneNet官方APP直接绑定设备,或基于API开发微信小程序)。


六、项目实施步骤

阶段 任务 时间估算
硬件搭建 焊接/插接各模块,检查供电和连接 1天
驱动调试 分别调试各外设(OLED、SD卡、传感器、语音等) 2天
K210模型烧录与通信 将火焰检测模型烧录到K210,调试UART数据格式 1天
STM32逻辑开发 实现主状态机、融合算法、本地交互 3天
ESP-01S网络通信 实现AT指令控制、MQTT连接、数据收发 2天
OneNet平台配置 创建产品、设备、数据流,测试数据上传 1天
APP历史查询 调用OneNet API开发简易查询页面 2天
系统联调与优化 联合调试,调整阈值,测试稳定性 2天

七、创新点与技术特色

  1. 视觉-传感融合:将AI视觉与常规传感器结合,提高了火灾识别的准确性和抗干扰能力。

  2. 分级报警机制:不同风险等级对应不同响应策略,避免单一报警导致的过度反应或漏报。

  3. 语音交互:JQ8900语音播报让报警信息更直观,适用于嘈杂环境。

  4. 低成本、易部署:所有硬件模块均为市面常见型号,成本低廉,可快速复制。


八、项目展示效果

  • 本地端:OLED实时显示传感器数值和系统状态;报警时蜂鸣器LED声光齐鸣,继电器动作,语音播报。

  • 云端端:OneNet平台显示实时数据曲线,历史报警事件可查询;手机APP可随时查看设备状态和报警记录。

  • 远程控制:管理员可通过APP下发指令,实现远程复位、启停排烟喷淋等操作

九、常见问题与解决

问题 可能原因 解决方法
K210不发送数据 串口波特率不匹配或接线错误 检查波特率(115200)和TX/RX交叉连接
ESP-01S连接WiFi失败 SSID/密码错误或信号弱 检查AT指令,靠近路由器测试
语音不播报 JQ8900供电不足或音频文件格式不对 确保5V供电,音频为MP3格式,命名正确
SD卡写入失败 初始化失败或文件系统未格式化 确保SD卡为FAT32格式,检查SPI接线
OneNet连接超时 MQTT参数错误或网络延迟 确认设备ID和API Key正确,检查防火墙

十、总结与展望

本项目实现了从火焰检测到本地联动、云端监控的全链路智能消防系统。通过引入AI视觉和多传感器融合,有效降低了误报率;结合OneNet云平台,实现了远程监管和历史数据追溯。未来可进一步扩展功能:

  • 增加人脸识别,实现报警时抓拍现场图片并上传;

  • 接入多个K210摄像头,实现多区域监控;

  • 增加GPS定位,支持移动式火情追踪;

  • 采用低功耗设计,适配野外或无人值守场景。

如有侵权,或需要完整代码,请及时联系博主。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐