做了好几年的嵌入式开发,帮朋友装过店铺防盗,也给某些课程设计做过可复用的演示方案。凡是涉及报警的项目,最后十有八九都会落到同一个套路:单片机做主控,无线模块传信号,传感器做探测,再加一个鸣叫的东西。听起来简单,但真正把一套“基于单片机无线智能报警系统”从图纸变成能跑的实物,中间要填的坑比想象中多得多。这篇就完整记录我设计这套系统的全过程,从需求拆解、无线选型、协议设计,到代码实现和现场调试,能直接照着抄的那种。
1. 需求拆解与系统框架
1.1 这类报警系统到底要解决什么问题
先别急着买元器件,得想清楚这套东西是给谁用的、要防什么。不同场景对报警系统的要求差异很大。
家庭场景最常见的是防入侵:人不在家的时候有人破门、翻窗,系统要在几秒内发现并报警。店铺仓库最常见的是夜间防盗和防火,而且往往面积大、房间多,靠一个探测点根本不够。还有一类是老人看护场景,老人在卫生间摔倒、长时间没有活动,需要及时提醒家人。
这些场景提炼出来,核心需求其实只有三条:
- 实时探测异常事件,比如人体红外触发、门窗被打开;
- 无线传输告警信息,避免现场布线破坏装修,节点可以灵活摆放;
- 声光提醒和状态展示,让用户知道哪里报警、设备是否在线。
无线在这里的价值特别大。有线报警系统布线又丑又麻烦,而且一旦线路被剪断,整个系统就废了。无线节点可以放在门窗、客厅、走廊的任意位置,墙体开洞这种施工基本不需要。这也就是为什么这类项目十有八九都要用无线方案。
1.2 系统拓扑:主机加节点两级结构
我最后采用的是两级结构:一台主机加多个无线探测节点。
主机是整个系统的核心,负责显示状态、接收各节点上报的信息、控制布防撤防、驱动声光报警。节点则负责感知现场,每个节点配一个或多个传感器,平时处于低功耗状态,一旦探测到异常就通过无线模块上报给主机。
为什么不用节点之间互相通信的网状结构?因为报警系统的数据量很小,节点只会偶尔上报一次,节点之间根本不需要频繁交换数据。网状组网协议复杂、调试难度高,还得考虑路由、转发、握手这些问题,对单片机项目来说性价比太低。两级结构把复杂度集中在主机上,节点尽量“傻一点”,反而更稳定可靠。
整个数据流向是这样的:
传感器触发 -> 节点MCU被唤醒 -> 无线模块发送报警帧 -> 主机无线模块接收 -> 主机MCU解析校验 -> 声光报警与显示1.3 功能清单与性能指标
做项目之前一定要先写下功能和指标,不然做到一半很容易跑偏。这套系统我划定的功能如下:
- 支持多个无线探测节点,至少能识别 16 个节点 ID;
- 支持布防和撤防操作,布防状态下触发报警,撤防状态下只记录不上报;
- 报警时主机蜂鸣器鸣叫,LCD 显示报警节点编号与事件类型;
- 每个节点带上电量检测,电压过低时上报低电告警;
- 主机记录最近若干条报警事件,掉电不丢失。
性能指标建议这样定:
- 无线通信距离在室内空旷环境不低于 50 米,有隔墙时不明显丢包;
- 从传感器触发到主机发出报警,整体延迟不超过 2 秒;
- 节点待机电流控制在 50uA 以下(不含传感器供电),用两节 AA 电池能撑半年以上;
- 误报率控制在可接受范围,不能三天两头瞎叫。
有了这些量化指标,后续的选型和设计才有依据,你才知道该往哪个方向努力。
2. 器件选型与硬件设计细节
2.1 主控MCU怎么选
主控是整个系统的脑子,选错了后面全得推倒重来。
主机这边我用的是 STM32F103C8T6。这是最常见的入门级 ARM 芯片,便宜、资料多、外设齐全,跑 72MHz 主频,有 USART、SPI、I2C、ADC,管脚数量 48,做一个报警器主机绰绰有余。另一个选择是 STC8/STC15 系列的 51 单片机,比如 STC8A8K64D4。如果你只会 51,用增强型 8051 也能做,Flash 和 RAM 完全够用,价格也很低。但 51 内核在开发效率和外设丰富程度上确实比 STM32 差一截,我建议时间允许就直接上 STM32,哪怕是从零学起也值得。
节点这边就要换思路了,节点是电池供电的,低功耗优先级最高。我选的是 STC8G1K08,这是一个封装很小、功耗比较出色的 8 位单片机,SOP8 封装,20MHz 主频,带 1K RAM 和 8K Flash。关键是它有掉电模式和外部中断唤醒,掉电电流可以做到微安级别。你要用 STM32 做节点也行,比如 STM32L0 系列,但成本和开发复杂度都上去了,对这种传感器节点来说有点大材小用。
举个例子说明选型逻辑。节点的任务非常简单:读一次传感器状态、拼一个数据帧、让无线模块发出去,然后继续睡觉。这个工作用 8 位单片机干绰绰有余。反过来,主机要处理复杂的人机交互和协议解析,8 位机的 RAM 和栈空间容易紧张,所以选资源更充裕的 STM32。
2.2 无线模块选型:三种方案的对比
无线模块是整套系统的关键,一定要多花心思。
市面上做无线报警常见的有三种方案,先看对比表:
| 方案 | 模块示例 | 频段 | 室内典型距离 | 节点功耗 | 成本 | 开发难度 |
|---|---|---|---|---|---|---|
| 2.4G短距 | NRF24L01 / nRF24L01+ | 2.4GHz | 10~30m(普通版) | 发射时约10~15mA | 最低 | 中等(SPI驱动) |
| 433M串口透传 | HC-12 / E07 | 433MHz | 50~200m | 待机约20mA,需配置 | 低 | 低(串口直接收发) |
| LoRa | SX1278 / Ra-01 | 433/470MHz | 500m~数公里 | 发射时约100mA | 偏高 | 中等(SPI驱动+配置) |
NRF24L01 是最常见的选择,模块非常便宜,一两块钱就能买到。但 2.4GHz 频段的劣势很明显:波长短,绕射和穿墙能力弱,室内隔一道承重墙信号可能衰减一半以上。普通版本的功率大概 0dBm,空旷距离几十米,隔墙之后就不太行了。如果要选它,建议直接买带 PA 和 LNA 的版本,发射功率到 20dBm,室内距离能拉到很多。但带 PA 的模块发射电流会到几百毫安,对节点供电电路是个考验。
433MHz 串口透传模块走的是另一个路线,比如 HC-12。优点是你不需要了解射频协议,MCU 把数据帧通过串口发给模块,模块自动发送、自动接收,接收方再把数据从串口吐出来。这大大降低了开发难度,特别适合单片机新手做报警项目。缺点是模块在空闲时仍然有比较高的接收电流,如果一直开着对电池寿命很不友好,需要在软件上给模块做电源开关。
LoRa 是另一个思路,距离远、抗干扰能力强,适合大场地、露天仓库、车间这类场景。成本比前两者高,但换来的是超强的穿透力和超远的距离。SX1278 的 SPI 驱动不难写,只是配置寄存器比较多,需要前期花时间读数据手册。
我最后主机端选用带 PA 的 NRF24L01 模块,节点端用普通 NRF24L01+,配合软件上的发射后断电策略来控功耗。如果做的是仓库或者跨楼栋场景,直接换 HC-12 或者 SX1278 就行,协议层几乎不用改动,这是模块化设计的好处。
2.3 传感器选型:不同的探测需求对应不同器件
传感器决定了系统能“觉察”什么,选错传感器,再好的协议也白搭。
人体红外 PIR 模块是最常用的入侵探测器。HC-SR501 这类模块便宜且容易买到,输出是数字电平,接一个 IO 口就能读。它靠热释电效应感应人体红外辐射,对移动的人体非常敏感。但要注意,它对热源、空调风、宠物也可能误触发,而且安装高度和灵敏度旋钮直接影响探测范围。HC-SR501 自带两个电位器,一个调灵敏度,一个调输出延时和封锁时间,实操中要反复试。
门磁传感器,也叫干簧管传感器,用于门窗开合检测。一个磁铁和一个干簧管,门合上时两者靠近,开关闭合;门打开后开关断开。MCU 检测电平变化即可。这是所有传感器里最稳定的,因为没有误报问题,继电器/干接点输出,接法简单,强烈推荐在每个入户门和窗户上装一个。
烟雾和燃气传感器,对应的是火灾和燃气泄漏场景。MQ-2 一类的气敏传感器需要加热丝维持工作,加热电流不小,所以不适合电池供电的节点,更适合主机附近有 5V 电源的地方。如果要做低功耗节点上的烟雾报警,不如直接集成带数字输出的光电式烟雾探测头,功耗低很多,价格也合理。
震动传感器可以用在窗户和保险柜上,玻璃被敲碎或者柜体被撬动时产生震动。这类传感器通常做成高灵敏度的振动开关或者压电陶瓷片加比较器,输出数字电平或者模拟量。
还有一种很实用的输入是物理开关:比如一个防拆开关,节点盖子被打开就触发。这是很多报警系统容易忽略的功能。入侵者如果找到节点并把电池取走,系统就失效了,防拆开关能让孩子不会轻易拉闸断电。
2.4 主机与节点硬件设计的几个关键用力点
原理图看似简单,但有几个地方处理不好会折腾很久。
电源是首要考虑的问题。STM32F103 和 NRF24L01 都是 3.3V 供电,而蜂鸣器、LCD 和按键电路通常在 5V 环境下更舒服。主机板我直接用 12V 适配器输入,经过 MP1584 或 AMS1117 一类稳压芯片转出 5V 和 3.3V 两个电压轨。注意 3.3V 要给无线模块单独加一个 10uF 和 0.1uF 的去耦电容,最好用 LDO 单独供,因为无线发射瞬间电流很大,共用电源会把 MCU 电压拉低导致复位。
节点端用的是锂电池或两节 5 号电池。普通 NRF24L01 发射电流约 10~15mA,传感器模块待机约 1mA,MCU 掉电后电流极低,总的待机电流主要被传感器吃掉。为了延长续航,我给传感器的电源脚接了一个 MOS 管开关,MCU 只有在需要读传感器时才给传感器供电,读完马上断电。这样待机电流直接降到 50uA 以内。
蜂鸣器驱动不要直接把 IO 接到蜂鸣器上。无源蜂鸣器要用三极管或者 ULN2003 驱动,有源蜂鸣器电流更小但也要注意驱动能力。否则 IO 口拉不住电流容易损坏。
按键和 LCD 这些外围电路,注意按键接上拉电阻或者打开单片机内部上拉,LCD 要加对比度电位器。用 STM32 的话,LCD1602 直接接到 GPIO 上写驱动即可,I2C 版本的 PCF8574 转接板能省很多线,强烈推荐。
3. 通信协议与组网设计——无线报警系统的灵魂
3.1 为什么必须自己定一套协议
很多入门项目一上来就直接调用无线模块的发送接收函数,数据能通就认为完成了。但直接裸传数据在报警系统里几乎不可用。无线模块本身只知道把字节从一个点搬到另一个点,它不关心这一帧数据是给谁的、是正常上报还是报警、数据有没有在传输过程中出错。
这里必须定义一套应用层协议,把下面的信息写在帧里:
- 帧头,用来找数据的起始位置;
- 节点 ID,用来区分是多节点中的哪一个;
- 类型,表示报警、心跳、按钮状态、配置命令等;
- 载荷数据,比如电池电压值、传感器状态;
- 校验码,用来检测数据在空气中飞的时候有没有被干扰出错。
没有这套协议,你收到的只是一堆莫名其妙的十六进制数,完全无法判断是哪个节点在报警,也没办法过滤错误帧。
3.2 帧格式设计
我用的帧格式非常直接,适合单片机处理:
帧头(2字节) | 节点ID(1字节) | 类型(1字节) | 数据长度(1字节) | 数据区(N字节) | 校验(1字节)帧头固定为0xAA 0x55,接收端连续收到这两个字节就知道一帧开始了。节点 ID 范围 0x01~0x0F,能挂 15 个节点。类型字段目前定义如下:
| 类型值 | 含义 | 数据内容 |
|---|---|---|
| 0x01 | 报警事件 | 报警原因编码,比如 1=人体红外 2=门磁 3=震动 |
| 0x02 | 心跳上报 | 电池电压值(放大10倍后发送) |
| 0x03 | 低电告警 | 电池电压值 |
| 0x04 | 返回确认ACK | 无 |
| 0x10 | 布防命令(主机→节点) | 无 |
| 0x11 | 撤防命令(主机→节点) | 无 |
校验用的是 CRC8,多项式取 0x31。CRC8 比简单的累加校验更能捕获突发错误,在无线环境中更靠谱。代码不复杂,可以先查表实现,也可以用位循环实现。
举个例子:节点 3 触发人体红外报警,发送的数据帧是:
AA 55 03 01 01 01 5C其中第五个字节01是数据区,表示报警原因为人体红外。最后一位5C是校验值。
接收端在主机程序里跑一个状态机:逐字节查找帧头,找到后按格式接收剩余字节,收完计算 CRC,对比帧尾校验,一致才认为帧有效。
3.3 多节点识别与通信方式:主动上报还是轮询
多节点组网这里有个方向选择。很多人想当然地认为应该主机依次问每个节点“你有没有事要报告”,也就是轮询。但在报警系统里轮询有几个致命弱点:节点必须随时保持接收状态才能应答,功耗高;报警发生时机完全随机,如果刚好在两次轮询之间发生,要等很久才被主机发现,系统反应变慢;多个节点还要按固定时隙错开,协议复杂度上升。
所以我的方案是主动上报加“发送后等待 ACK”。节点平时睡觉,被传感器唤醒后立刻发送报警帧,然后切换到接收状态等主机的 ACK。如果 200ms 内没收到 ACK,节点重发一帧,最多重发 5 次。主机收到有效帧后回复一帧 ACK。这样节点的接收窗口只有短短几百毫秒,其余时间都睡觉,又把系统响应时间控制在一秒以内。
布防和撤防走的是相反方向,主机广播一个命令帧,给所有节点下命令。因为命令不影响传感器触发逻辑,节点不需要回确认,只要在唤醒后检查一下是否有未执行的命令即可。为了让命令尽量不丢失,主机可以每隔 1 秒连发三次广播命令,节点收到后更新状态。
为了避免多个节点同时发送导致无线冲突,节点在收到传感器触发之后不是立刻发,而是先取一个 0~300ms 的随机延迟。这不是简单的“拖时间”,它是低成本无线冲突规避的最有效手段。用 C 的rand()或者计数器低位做伪随机都行。
3.4 让协议更可靠:重发、去重、心跳
无线通信最大的问题是丢帧。信号被遮挡、干扰,甚至模块供电波动都可能导致数据错。所以协议里必须有重发和去重机制。
节点发送报警帧后会等待 ACK,如果收不到,就按 10ms、20ms、40ms、80ms、160ms 的指数退避时间重发。到了第五次还没有 ACK,就放弃本次上报,暂停 10 秒后再重新尝试。这个策略在实际调试中效果很好。主机端收到重复的报警帧时,如果相同节点、相同事件、间隔小于 5 秒,就认为是一次重复上报,忽略掉,避免蜂鸣器响个不停。
心跳机制也很重要。节点每隔 10 分钟主动上报一次心跳帧,内容包括节点在线状态和电池电压。主机这边设置一个 30 分钟的超时窗口,超过 30 分钟没收到某个节点的心跳,LCD 上就显示该节点离线。这样用户能知道哪些节点失联了,而不是等真出事时才发现“那个屋的传感器其实早就没电了”。
4. 软件设计与代码实现
4.1 主机软件架构:一个状态机管理所有事情
主机这边我用的是一个简单的状态机加中断驱动。
程序初始化后进入主循环,循环里完成按键扫描、LCD 刷新、超时检测,而无线数据的接收放到串口或者 SPI 中断里。有数据来了,中断先攒到一个环形缓冲区,然后解析函数从缓冲区里提取完整帧。
主机的运行状态有四个:
- 撤防状态:接收节点心跳,但不响应报警帧;
- 布防状态:接收报警帧后立即进入报警状态;
- 报警状态:驱动蜂鸣器和 LED,显示报警节点,等待用户按键确认;
- 延迟布防状态:用户按下布防键后等待 30 秒再真正进入布防状态,给自己留出出门时间。
状态之间的跳转逻辑要在代码里画清楚。我吃过亏:当时布防和撤防按键处理没做去抖,人按一下键,状态在布防和撤防之间跳了好几次,最后只能靠加延时去抖解决。
主机还有一个定时器做 1 秒节拍,用来刷新 LCD 上的时间、计数延迟布防剩余秒数、计时报警持续时间。用定时器而不是 Delay 来做这些是因为 Delay 会卡死整个主循环,无线中断和按键响应都会变慢。
4.2 节点低功耗设计:睡觉为主,醒来得干活
节点的软件设计核心是低功耗。
STC8G1K08 提供了掉电模式,执行一行指令就能睡过去:
PCON |= 0x02; // 进入掉电模式 _nop_(); _nop_(); // 被唤醒后从这里继续执行唤醒源有两个。一个是外部中断:传感器信号接在 P32/P33 这类支持外部中断的引脚上,比如门磁从闭合变断开就产生下降沿,唤醒 MCU。另一个是定时器唤醒:如果主循环里需要周期性上报心跳,就用内部定时器定一个 10 分钟的唤醒周期,然后 MCU 继续掉电,定时器溢出后自动唤醒。
节点的主程序结构大概是这个思路:
void main() { init_gpio(); init_uart(); init_sensor_power_control(); while (1) { if (wakeup_reason_is_sensor()) { check_sensor_and_send_report(); wait_for_ack_with_timeout(); } else if (wakeup_reason_is_timer()) { send_heartbeat(); wait_for_ack_with_timeout(); } sleep_and_enable_interrupts(); } }这里有个关键设计:传感器供电开关。PIR 模块自己就有 50uA 到 1mA 的待机电流,比 MCU 还高。我给传感器电源加了一个 MOS 管,平时关闭,读传感器前短暂打开,读完后立刻关闭。实测整机待机电流只有约 45uA,用两节 2000mAh 的 AA 电池,理想情况下能撑一年多。
4.3 NRF24L01 使用要点与代码
如果用 NRF24L01,需要写 SPI 驱动和模块寄存器配置。这是一个坑比较多的环节,给一下核心配置思路。
NRF24L01+ 的初始化要设置收发地址、RF 频道、空气速率、CRC 使能、数据宽度。发数据和收数据分别操作发送缓冲区和接收缓冲区,状态寄存器通过读寄存器清零。
初始化里的关键寄存器参数:
- 发送/接收地址,两端要一致,我用的是 5 字节地址
0xE7 0xE7 0xE7 0xE7 0xE7; - 无线频道 RF_CH 设为 30,避开 2.4GHz 的 WiFi 拥挤频段;
- 数据速率 250kbps,距离比 1Mbps 更远;
- CRC 设为 2 字节,增强错误检测能力;
- 数据包长度固定为 32 字节以内,我的自定义帧不超过 8 字节。
一个简单的发送函数:
uint8_t NRF24L01_SendPacket(uint8_t *buf, uint8_t len) { NRF24L01_CE_LOW(); NRF24L01_WriteReg(WRITE_REG + STATUS, 0x70); // 清状态 NRF24L01_WriteReg(WRITE_REG + TX_ADDR, (uint8_t*)TX_ADDRESS, 5); NRF24L01_WriteReg(WRITE_REG + RX_ADDR_P0, (uint8_t*)TX_ADDRESS, 5); NRF24L01_Write_Buf(WR_TX_PLOAD, buf, len); // 写入发送缓冲区 NRF24L01_CE_HIGH(); // 启动发送 delay_us(10); NRF24L01_CE_LOW(); // 等待最大发送完成时间并读取状态 }不过说实话,如果只是做报警系统而不是专门研究 2.4G 射频,我更推荐用 HC-12 这种串口透传模块。HC-12 把整个射频协议都封装好了,MCU 的 UART 直接发字节过去就行,开发工作量一下子少了一个星期。但 HC-12 的接收功耗高,节点可以不一直开着接收,而是仅在发送后切换成接收模式来等 ACK,其余时间直接断电,这个我在实际项目中验证过可行。
4.4 主机端协议解析函数
主机解析会比较繁琐,放核心逻辑:
uint8_t frame[16]; uint8_t frame_len = 0; void ParseByte(uint8_t ch) { static uint8_t state = 0; switch (state) { case 0: if (ch == 0xAA) state = 1; break; case 1: if (ch == 0x55) { state = 2; frame_len = 0; } else if (ch == 0xAA) state = 1; else state = 0; break; case 2: frame[frame_len++] = ch; if (frame_len >= 3) { // 已取得节点ID和类型和数据长度 frame_len = 6 + frame[3]; // 总长度 = 数据长度 + 固定头尾 } if (frame_len >= sizeof(frame)) frame_len = sizeof(frame) - 1; if (frame_len > 0 && frame_len == frame[3] + 5) { // 帧接收完成,校验 uint8_t crc = CalcCRC8(frame, frame_len - 1); if (crc == frame[frame_len - 1]) { ProcessValidFrame(frame, frame_len); } state = 0; } break; } }这种状态机比较容易出 bug 的地方是帧头再入和超时判断。如果只收到一个0xAA,后面跟的不是0x55,要把状态切回找帧头状态,而不是直接丢弃字节。我在实际调的时候因为这里少写了一个 else,经常收到底层模块吐的半截乱码帧后,一直卡在错误状态里,后面的正常帧全被吞了。
5. 调试、实测与常见问题排查
5.1 无线距离不够:隔墙后收不到报警
项目调试最崩溃的环节就是无线距离。室内空旷地方测试,NRF24L01 普通版能到 20~30 米,但是把它装在靠近地面的墙角后,对着主机隔了一堵混凝土墙,丢包率直线上升。后来我把节点挪到窗户上方、高于人头的位置,情况明显好转。无线信号被家具、金属门、墙壁吸收非常严重,设备安装位置比功率大小更关键。
如果是带 PA 的 NRF24L01,安装时要尤其注意供电。有些劣质面包板供电线太长,压降大,PA 在发射瞬间需要 200mA 以上的电流,电压一掉,发射功率反而上不去。解决方法是给 PA 模块单独拉一根短粗的电源线,并在模块引脚旁边加 100uF 电解电容和 0.1uF 陶瓷电容。
如果距离还是不够,就可能要考虑升级到 433MHz 或 LoRa。HC-12 在同环境下穿墙表现显著好于 2.4GHz,代价是模块本身体积大、功耗高,需要改节点供电策略。
5.2 误报问题:半夜响起能把自己吓清醒
误报是报警系统里最容易被低估的问题。刚装好 PIR 模块时,我在客厅调试,离模块三四米走过去能触发,但从门口经过、阳光照射、空调吹风也会触发。因为 PIR 检测的是热释电信号变化,温度变化同样会造成误报。
要解决误报,硬件上两个办法:把 PIR 的灵敏度电位器逆时针调到最小,缩小探测范围;把模块的延时调到最短,避免连续触发。安装高度建议 1.8~2.2 米,向下倾斜 15 度左右,覆盖人员走动的腰部区域,减少宠物触发概率。
软件上也要配合做去抖:节点触发后不急着发报警帧,先等 1 秒,如果传感器持续保持触发状态超过 1 秒才认为有效。门磁这种干接点信号反而不会有误报问题,但是要处理一个特殊情况:门本来就是开着的,布防后门轻轻晃动一下就会报警。我在节点里加了一个布防前状态校准:拉药布防命令后,节点先读一次门磁当前状态,把当前状态存起来,只有状态变化超过 500ms 才报警。
5.3 串口透传出现粘包怎么破
用 HC-12 这类透传模块时,接收端经常一次收到两三条帧拼在一起,这叫粘包。原因很简单:主机串口接收太慢,模块在接收缓冲区里积攒了多帧数据,一次性吐出来。解决方法是解析时加入字节间超时判断,比如相邻两个字节间隔超过 20ms,就认为当前帧结束。或者干脆不用逐字节状态机,改用 DMA 接收加环形缓冲区,主循环定期取数据拆帧。
NRF24L01 则是按包收发,天然不会有粘包问题。但它有另外的坑:SPI 的 MOSI 和 MISO 接反了会收到全0xFF或者全0x00。这个错误特别隐蔽,因为模块电源正常、CS 正常、CLK 正常,就是收不到任何有效数据。遇到这种情况第一时间用示波器或者逻辑分析仪抓一下 SPI 波形,看看 MISO 上有没有数据返回。
5.4 电池续航达不到预期
节点用普通 NRF24L01 加 PIR,理论待机应该很低,但实测电池几周就没电了。排查后发现两个原因:一是 PIR 模块一直通电,待机电流就 1mA 多;二是 NRF24L01 的 CE 脚没有拉低,模块一直处于接收状态,电流也上去了。给传感器加电源开关、给无线模块也做成发送时才开启电源,问题就解决了。
实际测到的电流分配大概是:MCU 掉电态 3uA,传感器断电后 0uA,无线模块断电后 0uA,整机待机不到 50uA。发送一次报警帧加等待 ACK,标称约 15mA 的电流持续 50ms,一天触发 10 次也才消耗 0.008mAh 级别。于是瓶颈就只剩电池自放电,用两节碱性电池撑半年完全没问题。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 主机收不到节点数据 | NRF24L01 地址不匹配 | 检查两端 RX/TX 地址是否一致 |
| 主机显示乱码或解析失败 | 串口波特率不一致 | 确认两端波特率完全相等,检查时钟误差 |
| 发送距离骤减 | 无线模块供电不足 | 模块附近加大电容,缩短电源线 |
| 蜂鸣器不响 | 驱动电路损坏或 IO 电平不够 | 用万用表测三极管基极电压 |
| 节点频繁上报 | PIR 灵敏度太高或未做去抖 | 调节电位器、加软件去抖延迟 |
| LCD 显示对比度太淡 | 对比度电位器没调好 | 调整 Vo 引脚电压到 0.5V 左右 |
| 节点心跳丢失但偶尔能收到 | 无线模块天线虚焊 | 检查天线焊点和网络匹配器件 |
6. 从课程设计到真正可用的产品级扩展
6.1 可靠性补强:程序跑飞、断电、日志不能丢
课程设计做到功能演示就结束了,但真要长期使用,可靠性是个大课题。
第一件事是加看门狗。现场程序跑飞、卡死都是潜在的灾难,主机陷入死循环时,报警系统就等于不存在了。STM32 用 IWDG,节点用 STC 的 WDT,在初始化时打开,主循环里定期喂狗。一个原则:狗不能喂得太频繁,要给极端情况留余量,但又不能太长耽误报警响应。
第二件事是断电保存。报警事件的日志是重要证据,掉电以后不能丢。主机可以加一个 I2C 接口的 EEPROM(比如 AT24C02),报警事件逐条写入,包括时间戳和节点 ID。我实际记录最近 100 条,循环覆盖,这样断电后上电还能看到历史报警。
第三件事是反破坏。节点外壳加防拆开关,一旦被打开就马上上报防拆报警。门窗磁节点还要考虑攻击者会先把磁铁悄悄拿走再开门,所以磁铁和干簧管要隔着窗户两条边框贴合,使开关状态变化明确。
6.2 远程报警:加 4G 模块、WiFi 或云平台
本地报警的局限是:家里没人,蜂鸣器响破天也没人听。真正“智能”的报警系统最好能把消息推到手机上。
最传统的做法是给主机加一个简单的 GSM 模块,比如 A6/A7 或者 SIM800C,检测到报警后直接发一条短信到预设号码。“烟雾报警,位置:客厅节点”。短信方案成熟可靠,农村地下室也有信号,唯一的问题是模块价格稍高,还需要一张专门的物联网卡。
更现代的做法是给主机加 ESP8266 或 ESP32,通过 WiFi 接入路由器,把报警事件用 MQTT 发布到云平台,再由微信小程序或手机 App 接收推送。这需要额外搭一套后台,但对会写简单后端的人来说并不难。协议层不用改动太多,只要在主机解析到有效报警帧后,通过串口把事件转发给 WiFi 模块即可。
如果你的项目是毕业设计做展示,这个扩展方向非常加分,本地报警加云推送一块做,整体完成度就上了一个台阶。
最后分享一点个人体会。这套系统我前前后后改了四五版,最耗时间的环节既不是 STM32 编程也不是无线模块调试,而是误报和续航这两个看上去很不起眼的问题。做报警系统和做普通小工具的最大区别在于:普通工具出错顶多是功能不工作,报警系统出错则是“该响的时候不响、不该响的时候瞎响”,两种都让人抓狂。所以在设计时永远要把误报抑制和电源管理当成核心需求来做,而不是功能跑通就完事。所有传感器数据都要过一遍软件去抖,所有无线数据都要有确认机制,所有节点都要能上报电量。哪怕多花几天调试,系统也会稳定非常多。