嵌入式开发有一种很特殊的真实感。
Web 服务出了问题,你大概率还能看日志、重启容器、扩容实例、回滚版本。嵌入式设备出了问题,可能在几百公里外的机柜里、路灯杆上、工厂产线旁,网络不稳定,现场人员不懂技术,甚至设备外壳已经封死。
所以嵌入式可靠性不是“最后加点异常处理”,而是从设计第一天就要进入系统。一个设备能在实验桌跑起来,只能说明功能链路打通了;能在现场长期运行,才说明工程真正开始成熟。
实验桌是最友好的环境
实验桌上的环境太理想了:电源稳定,线材很短,调试器连着,开发者盯着串口,温度适宜,外设都是刚买的新模块。
现场完全不同:
- 电源有纹波、浪涌、瞬断
- 线缆变长后信号质量下降
- 电机、继电器、变频器带来干扰
- 温度和湿度变化影响传感器
- 用户可能频繁断电
- 网络可能时好时坏
- 设备可能长时间无人维护
我会把嵌入式项目分成三个阶段:功能可跑、压力可扛、现场可恢复。很多项目急着在第一阶段交付,后面的问题会用更高成本回来。
电源是第一依赖
软件工程师容易低估电源问题。很多“偶现死机”“随机重启”“通信异常”,最后都不是代码逻辑,而是供电瞬态、地线设计、外设启动电流、棕断复位或电磁干扰。
固件层至少要做几件事:
- 正确配置 brown-out detection
- 启动时检查复位原因
- 关键外设初始化加入超时和重试
- 外设电源稳定前不要盲目访问
- 存储写入时考虑掉电保护
- 连续异常重启时进入安全模式
复位原因非常重要。设备重启后如果只打印“system boot”,价值很低。更有用的是知道它是上电复位、看门狗复位、棕断复位、软件复位,还是异常 hard fault。现场问题往往靠这些线索缩小范围。
启动流程要能失败
很多固件启动流程写得像一条直线:初始化时钟、初始化 GPIO、初始化传感器、初始化通信、进入主循环。只要某一步失败,就卡死或无限重试。
现场设备不适合这样。外设可能暂时不可用,通信模块可能还没注册网络,传感器可能上电慢,存储芯片可能忙。
更稳的启动流程应该是状态机:
1. 最小系统启动
2. 读取配置
3. 检查复位原因
4. 初始化关键外设
5. 非关键外设异步恢复
6. 进入降级可运行状态
7. 后台持续健康检查
不是所有模块都必须在启动阶段成功。比如网络失败不应该阻止本地采集,云端同步失败不应该阻止设备进入安全控制状态。可靠系统的一个重要特征是:它知道哪些功能是核心,哪些可以降级。
时序 bug 是现场问题的常客
嵌入式系统里,时序相关 bug 很难复现。
常见场景包括:
- 中断和主循环同时访问共享变量
- DMA 缓冲区还没处理完就被覆盖
- RTOS 任务优先级设置不合理
- 低优先级任务持有锁导致高优先级任务阻塞
- 串口接收在高负载下丢包
- SPI/I2C 总线异常后没有恢复
- 定时器溢出导致长时间运行后异常
我的原则是并发模型尽量简单。中断里只做最短工作,把复杂处理交给任务。能用消息队列就少共享变量,必须共享就明确临界区。每个任务要有清晰职责,不要让一个任务既采集、又通信、又写存储、又控制状态机。
还有一个小习惯:所有超时都要使用能处理溢出的时间比较方式。很多设备不是第一天坏,而是运行 49 天、几十天后因为 tick 溢出暴露问题。
看门狗不是遮羞布
看门狗很重要,但它不是让系统随便崩然后自动重启的借口。
糟糕的设计是在主循环里无脑喂狗。这样即使通信任务死了、采集任务卡了、存储任务阻塞了,只要主循环还活着,系统就继续假装健康。
更好的方式是任务级健康检查:
- 采集任务定期上报心跳
- 通信任务定期上报心跳
- 控制任务定期上报心跳
- 存储任务定期上报心跳
- 主监控任务检查所有关键心跳
- 只有关键任务都健康时才喂狗
看门狗触发前,如果条件允许,应该保存最小现场:复位原因、任务状态、错误码、关键寄存器、最近日志序号。连续看门狗复位时,系统应进入安全模式,而不是无限重启。
看门狗的目标不是“掩盖崩溃”,而是让设备在不可避免的异常里尽量可恢复、可诊断。
日志要为现场设计
嵌入式日志很难做,因为资源有限:Flash 小、RAM 小、写入次数有限、网络不稳定。但没有日志,现场问题基本靠猜。
我喜欢把日志分层:
- 开发期详细串口日志
- 生产期事件日志
- 异常快照
- 关键指标计数器
生产设备不一定需要保存每一行调试信息,但应该保存关键事件:启动、复位原因、配置变更、通信上线下线、升级结果、传感器异常、存储错误、看门狗前状态。
日志格式要结构化,至少包含时间、模块、级别、事件码和参数。现场回传后可以自动聚合,而不是靠人肉读一堆自然语言。
如果设备没有可靠时间源,也要保存单调计数或运行时长。绝对时间不准没关系,事件顺序要可靠。
通信要假设网络一定会坏
很多 IoT 设备的问题集中在通信。网络会断,DNS 会失败,服务器会超时,MQTT 会掉线,蜂窝模块会假在线。
通信设计要避免几个坑:
- 发送失败不能阻塞主业务
- 重连要指数退避,不能疯狂打满网络
- 消息要有队列和上限,防止内存被撑爆
- 关键消息要有确认和重发机制
- 离线数据要考虑存储容量和淘汰策略
- 云端指令要做权限、签名或时效校验
我更喜欢把通信当作“最终一致”的后台能力,而不是设备运行的前置条件。设备应该先保证本地安全运行,再尽力同步云端。
OTA 是可靠性工程,不是附加功能
远程升级是现场设备的生命线,但 OTA 也是高风险功能。一个不可靠的 OTA,比没有 OTA 更可怕。
基本要求包括:
- 固件签名校验
- 完整性校验
- 版本兼容检查
- 双分区或回滚机制
- 升级进度持久化
- 断电后可恢复
- 升级失败自动回滚
- 防止降级到有漏洞版本
- 升级日志可追踪
如果设备资源允许,A/B 分区是非常值得的。新固件启动后不要立刻标记成功,而是完成健康检查后再确认。否则设备可能进入“升级成功但业务不可用”的尴尬状态。
OTA 的核心不是把新固件写进去,而是确保失败时设备还活着。
现场恢复比现场不坏更现实
没有系统能保证永远不坏。可靠性工程的目标不是追求神话,而是让系统在坏的时候可控。
我会设计几个恢复层级:
- 模块级重试
- 外设重新初始化
- 通信模块重启
- 任务重启或状态机复位
- 系统软重启
- 安全模式
- 等待人工维护
每一级恢复都要有触发条件和次数限制。连续失败时不要无限重试,应该升级到更保守的状态。比如传感器连续异常时停止使用该数据源,通信连续失败时降低上报频率,控制异常时进入安全输出。
验证要接近现场
嵌入式测试不能只停留在功能测试。至少要做一些接近现场的验证:
- 长时间老化测试
- 频繁断电测试
- 弱网和断网测试
- 高低温测试
- 外设异常拔插测试
- 总线错误注入
- 存储写满测试
- OTA 断电恢复测试
- 看门狗触发测试
- 日志回传验证
这些测试不一定都能在第一版做到很完整,但必须有意识。越早暴露,成本越低。
我的结论
嵌入式系统最迷人的地方,是代码会和真实世界发生关系。也正因为如此,它比纯软件更需要工程敬畏。
可靠性不是某个模块,也不是最后一轮测试,而是一整套习惯:电源意识、启动降级、简单并发、任务健康、结构化日志、通信容错、安全 OTA、现场恢复。
能在实验桌跑起来只是开始。能在现场长时间运行,出问题时还能解释、恢复和升级,才是嵌入式工程真正的交付。
评论
评论默认审核后展示。你可以匿名留言,也可以登录后保留自己的互动记录。