资讯动态

MicroPython软件看门狗实现:带恢复机制的嵌入式设备防死机方案

发布时间:2026/9/8 7:47:55 来源:尧图企业网站定制
做了这么多年嵌入式最怕听到的一句话就是“设备死机了得派人去现场断电重启”。尤其设备已经在客户那边跑了好几个月你连个串口都摸不到的时候那个滋味是真不好受。所以后来我给自己定了个规矩凡是部署到现场的设备必须带看门狗不管是硬件狗还是软件狗至少得有一个。这篇文章就想跟你聊聊在没有额外硬件成本的情况下怎么用 MicroPython 在嵌入式设备上实现一个带恢复机制的软件看门狗让设备在异常卡死之后能自己回过神来而不是等着人上门服务。内容不算复杂但我会把背后的设计思路、为什么这么写、踩过的坑都讲清楚。无论是用 ESP32、RP2040 还是其他支持 MicroPython 的开发板这套思路都能直接搬过去用。如果你是刚从 Arduino 或者纯 C 开发转到 MicroPython 的开发方式那这篇文章正好适合你。1. 为什么软件看门狗在 MicroPython 里格外重要1.1 硬件看门狗与软件看门狗的定位差异在看门狗这个事儿上很多人的第一反应是“板子上有硬件看门狗芯片或者芯片内部有硬件看门狗定时器直接用不就完了”确实硬件看门狗是最底层的兜底方案它的本质是一个独立的计数器由硬件电路或者芯片内部的外设实现一旦启动后软件必须在规定时间内“喂狗”否则计数器溢出后直接触发芯片复位。这个过程不依赖 CPU 是否正常执行用户代码哪怕主程序跑飞了只要喂狗操作没执行复位信号照样能产生。但硬件看门狗有一个非常明显的弱点它只知道“喂了”还是“没喂”不知道程序到底卡在了哪里、卡了多久、当前状态是什么。而且很多 MicroPython 开发板比如一些基于 ESP32 的模组并没有把硬件看门狗的功能完整暴露出来或者驱动接口不统一写起来并不顺手。更重要的是硬件看门狗一旦复位程序是冷启动之前的运行状态、错误信息、现场数据全都丢了你根本不知道设备为什么死机。软件看门狗的思路就不一样了。它是在应用层模拟看门狗的机制用一个后台任务或者定时器中断去监控主循环的运行状态发现异常后不一定要立刻复位而是可以先尝试记录现场、保存上下文、做一些清理工作甚至执行一段“软恢复”流程。这种机制在 MicroPython 这种偏应用层开发的场景里非常实用因为 MicroPython 本身跑得比 C 慢内存管理也更容易出幺蛾子硬件看门狗能兜底但软件看门狗才是真正能帮你定位问题、优雅恢复的工具。1.2 MicroPython 环境下程序卡死的常见原因我接触过的 MicroPython 设备死机案例大部分都不是 CPU 真的跑飞了而是下面这几种情况阻塞式网络请求。用 socket 或者 urequests 请求一个外部服务对方迟迟不响应又没有设置超时时间程序就挂在 recv 上了。死循环里出现了异常但没被捕获。MicroPython 的报错信息有时候会被大量输出刷掉代码里 try-except 覆盖不全一个异常直接让主循环退出设备看起来就像“死机”了。内存碎片化严重。MicroPython 跑久了频繁创建和销毁对象内存碎片越来越多某次 malloc 失败直接抛出 MemoryError如果没有全局兜底程序就停了。I2C/SPI 总线挂死。外设没应答读取函数没有超时机制总线锁住后续所有通信全部卡住。这些问题的共同点是程序并没有真正“死透”只是卡在了某个不可等待的环节里。硬件看门狗能救急救命但救完你什么都不知道。软件看门狗的价值就在于它能在卡死前的一瞬间把现场信息留下来然后根据你预设的恢复策略动作。2. 带恢复机制的软件看门狗整体设计思路2.1 核心机制喂狗计数与超时判定软件看门狗的实现原理非常直白。你可以把它想象成一个“打卡机”主循环每隔一段时间就去打一次卡也就是调用一下喂狗函数喂狗函数会把一个计数器加一。与此同时后台有一个独立的监控者它每隔固定时间检查一次计数器的值有没有变化。如果检查时发现计数值跟上一次一样说明主循环已经很久没有来打过卡了那就判定程序卡死。这里有个关键点监控者不能跑在主循环里否则主循环卡死了监控者一起卡死看门狗就没意义了。所以监控者要用 MicroPython 的定时器回调或者放到一个独立的线程里。我个人更推荐用定时器回调因为 MicroPython 的线程受限于 GIL而且线程调度在某些端口上并不稳定定时器回调在中断上下文执行只要回调函数里不做太重的操作不会对主流程造成明显影响。伪代码逻辑大概是这样的feed_counter 0 last_counter 0 def feed(): global feed_counter feed_counter 1 def check(): global last_counter if feed_counter last_counter: # 超时未喂狗触发恢复流程 do_recovery() else: last_counter feed_counter2.2 分级恢复策略从软恢复到硬复位真正的“恢复机制”不是一句“单片机重启”就完事儿的。我习惯把恢复动作分成几个等级按照严重程度依次执行第一级是状态复位。比如某个全局状态机卡在了错误状态看门狗发现异常后先把状态机重置把任务队列清空然后让系统从主循环重新开始这种恢复方式对连续生产任务影响最小也快。第二级是软重启。调用machine.reset()或者sys.exit()后重新执行主程序相当于让 MicroPython 虚拟机重新跑一遍。这个动作会清掉 Python 层面的所有变量但不会重新初始化硬件外设。如果设备是电池供电的软重启比硬复位温和得多。第三级是硬件复位。直接操作machine.reset()配合一些特殊参数或者翻转复位引脚。这个动作连 MicroPython 解释器都重新加载了所有外设寄存器全部恢复默认值相当于一次冷启动。在实际项目里我通常这样配置恢复策略第一次超时就做软恢复并且把错误次数记录下来。如果短时间内连续触发了多次软恢复说明问题比较顽固再升级到硬复位。如果硬复位之后还是反复重启那就不要再折腾了直接把设备挂在一个“安全模式”里只保留最基本的功能比如只亮故障灯、只发心跳报文等人工介入。这种分级设计的核心思想是不要盲目重启尽量让设备自己降级运行实在不行再彻底重启而且重启不能白重启得把线索留下来。2.3 状态保存与现场信息记录说到线索这是带恢复机制的软件看门狗和普通看门狗最大的区别。普通看门狗复完了就完了要查问题还得靠运气复现。软件看门狗可以在判定超时的时候把当前的关键状态存下来存到哪里有讲究。存到文件系统。适合带 Flash 文件系统的开发板缺点是频繁擦写 Flash 有寿命问题不能每次死机都写。存到 RTC 内存。很多芯片的 RTC 域有一块独立的小内存掉电不丢失适合存错误计数器、上次错误码这种小数据关键是启动速度快不伤 Flash。通过日志串口输出。适合开发阶段直接打印错误现场方便跟调试器配合排查。我在实际项目里的做法是看门狗触发恢复之前先把错误码、当前任务名、喂狗间隔、内存剩余量这些信息写进一个内存缓冲区然后根据恢复等级决定是否写入 Flash。如果是软恢复就先不写 Flash等确认恢复失败再写。这样做能最大程度延长 Flash 寿命同时保证关键错误信息不会全部丢失。3. MicroPython 软件看门狗核心代码实现3.1 基础版一个可用的最小实现先看一个最基础但能直接用的实现目标平台是 ESP32其他平台改一下定时器和重置函数的调用方式就行import machine import time import sys class SoftwareWatchdog: 软件看门狗主循环喂狗定时器监控超时 def __init__(self, timeout_ms5000, check_interval_ms500): self.timeout_ms timeout_ms self.check_interval_ms check_interval_ms self.feed_counter 0 self.last_counter 0 self.last_feed_time time.ticks_ms() self.recovery_count 0 self.timer machine.Timer(1) def start(self): 启动看门狗定时器 self.timer.init( periodself.check_interval_ms, modemachine.Timer.PERIODIC, callbackself._watchdog_check ) def feed(self): 主循环调用喂狗 self.feed_counter 1 self.last_feed_time time.ticks_ms() def _watchdog_check(self, timer): 定时器回调检查喂狗情况 if self.feed_counter self.last_counter: # 计数器没变说明主循环没有执行到 feed() elapsed time.ticks_diff(time.ticks_ms(), self.last_feed_time) self._handle_timeout(elapsed) else: self.last_counter self.feed_counter def _handle_timeout(self, elapsed_ms): 超时处理默认记录日志并重启 print(WATCHDOG TIMEOUT, elapsed:, elapsed_ms, ms) self.recovery_count 1 # 这里可以扩展保存现场、分级恢复逻辑 machine.reset() def stop(self): 停止看门狗用于正常休眠前 self.timer.deinit() # 使用示例 watchdog SoftwareWatchdog(timeout_ms5000) def main_loop(): watchdog.start() while True: try: # 实际业务逻辑 time.sleep(0.1) except Exception as e: print(main error:, e) finally: watchdog.feed() main_loop()这个版本已经能实现基本功能主循环每 100ms 循环一次喂狗一次。定时器每 500ms 检查一次喂狗计数是否变化。如果主循环因为任何原因卡住超过 5 秒定时器回调里就会检测到计数值没变触发machine.reset()。注意我把超时时间设成 5000ms检查间隔设成 500ms意思是主循环最多允许 5 秒不去喂狗超过这个阈值就会触发恢复。检查间隔决定了检测的精度如果你希望更快反应可以把这两个时间同时调小。但别把检查间隔设得太小比如小于 10ms因为定时器回调本身会占用 CPU 时间太频繁会影响主流程。3.2 进阶版带状态保存与分级恢复基础版只能做到“卡死就重启”这还不够好。我实际部署的版本是这样的import machine import time import json import sys class AdvancedWatchdog: 带恢复机制的软件看门狗 采用分级恢复策略 第一级软恢复重置业务状态 第二级软重启重新运行主程序 第三级硬复位彻底重新初始化 如果连续多次硬复位仍失败进入安全模式 恢复现场信息会保存到 RTC 内存开机时可读取上次死机原因。 # 错误码定义 ERR_NONE 0 ERR_MAIN_LOOP_STUCK 1 ERR_MEMORY_ERROR 2 ERR_BUS_LOCK 3 def __init__(self, timeout_ms8000, check_interval_ms500, max_reboot3): self.timeout_ms timeout_ms self.check_interval_ms check_interval_ms self.max_reboot max_reboot self.feed_counter 0 self.last_counter 0 self.last_feed_time time.ticks_ms() self.recovery_count 0 self.last_error self.ERR_NONE self.safe_mode False self.timer machine.Timer(2) self.rtc machine.RTC() self._load_rtc_state() def _load_rtc_state(self): 开机时从 RTC 内存读取上次的死机记录 try: data self.rtc.memory() if data: state json.loads(data.decode()) self.recovery_count state.get(recovery_count, 0) self.last_error state.get(last_error, self.ERR_NONE) if state.get(safe_mode, False): self.safe_mode True except Exception: pass def _save_rtc_state(self): 把当前状态写入 RTC 内存 state { recovery_count: self.recovery_count, last_error: self.last_error, safe_mode: self.safe_mode, timestamp: time.time() } try: self.rtc.memory(json.dumps(state).encode()) except Exception: pass def start(self): if not self.safe_mode: self.timer.init( periodself.check_interval_ms, modemachine.Timer.PERIODIC, callbackself._check ) def feed(self): self.feed_counter 1 self.last_feed_time time.ticks_ms() def report_error(self, err_code): 业务代码可以主动上报错误类型帮助看门狗定位问题 self.last_error err_code self._save_rtc_state() def _check(self, timer): if self.feed_counter self.last_counter: elapsed time.ticks_diff(time.ticks_ms(), self.last_feed_time) self.last_error self.ERR_MAIN_LOOP_STUCK self.recovery_count 1 self._save_rtc_state() self._do_recovery() else: self.last_counter self.feed_counter def _do_recovery(self): 分级恢复核心逻辑 print([watchdog] recovery_count , self.recovery_count) if self.recovery_count 1: # 第一次超时软恢复清掉业务状态 self._soft_recovery() elif self.recovery_count self.max_reboot: # 多次超时软重启 self._soft_reset() else: # 重启太多进入安全模式 self._enter_safe_mode() def _soft_recovery(self): 软恢复只重置业务状态不清除 Python 运行时 具体操作取决于项目里的状态机设计 # 假设有一个全局的业务状态对象 try: import app_state app_state.reset() except Exception: pass print([watchdog] soft recovery done) def _soft_reset(self): 软重启重新启动 MicroPython 程序 print([watchdog] soft reset) self.timer.deinit() machine.reset() def _enter_safe_mode(self): 进入安全模式停止主业务只保留诊断信息 print([watchdog] enter safe mode) self.safe_mode True self.timer.deinit() self._save_rtc_state() # 这里跳转到安全模式代码 self._run_safe_main() def _run_safe_main(self): 安全模式主循环只闪灯发诊断数据不执行业务 led machine.Pin(2, machine.Pin.OUT) while True: led.value(not led.value()) time.sleep(1) def get_state(self): return { recovery_count: self.recovery_count, last_error: self.last_error, safe_mode: self.safe_mode }这个进阶版的核心是分级恢复。第一次超时先尝试把业务状态重置期望程序能从主循环开头重新正常执行。如果短时间内连续超时次数增加说明软恢复没解决问题就升级到软重启。如果软重启都扛不住就硬复位。复位次数超过阈值说明设备在当前环境或当前固件下大概率没法稳定运行了继续重启只会耗电和刷日志不如进入安全模式等人工处理。RTC 内存在这里扮演了“黑匣子”的角色。每次异常都会把 recovery_count 和错误码写进 RTC 内存下次开机后 watchdog 能读到这些数据这样一来你就知道设备在无人值守期间发生了多少次恢复动作而不是两眼一抹黑。3.3 业务代码里的喂狗策略有了 watchdog 类接下来要解决的是“在哪里喂狗”的问题。很多人想都不想就在主循环顶部加一行watchdog.feed()这其实是有效的但不是最优的。因为如果主循环里有一个阻塞时间超过 timeout_ms 的调用比如一个 30 秒的网络请求看门狗就会误判为死机发生不必要的重启。更好的做法是把喂狗点放在关键业务步骤之间而不是笼统地放在循环开头。比如一个采集数据上传服务器的循环可以这样组织watchdog.start() while True: # 读取传感器数据假设最多 1 秒 sensor_data read_sensor() watchdog.feed() # 发送数据到服务器设置超时时间为 3 秒 send_data(sensor_data, timeout3000) watchdog.feed() # 本地保存数据可能涉及 Flash 擦写耗时不确定 save_to_flash(sensor_data) watchdog.feed()这样设计有几个好处第一喂狗点分布在关键操作之间任何一个操作卡死下一个喂狗点都不会到达看门狗就能在超时时间后触发恢复。第二对于已知耗时的长任务可以预估其最大执行时间并在任务期间不喂狗任务结束后再喂这样不会误报。第三日志里可以看到最后喂狗的位置配合前面保存的现场信息排查问题会方便很多。还要注意一种特殊情况主循环正常执行但其中某个分支调用了一个永远不会返回的while True如果这个分支里没有喂狗看门狗同样能捕捉到。所以写业务代码的时候凡是可能出现长时间阻塞的地方都要意识到“这期间看门狗还在运行”。3.4 定时器回调中的注意事项这部分是很多人第一次写 MicroPython 软件看门狗最容易踩的坑。定时器回调里绝对不能做这几件事不能做内存分配。MicroPython 在中断上下文中分配内存会导致异常甚至死机。回调里创建列表、字典、字符串拼接都是高危操作。如果需要记录信息尽量用模块级别的预分配变量。不能执行耗时操作。回调函数里写文件、定期打印、访问网络这些操作都可能让中断处理时间过长影响系统其他中断的响应。不能调用machine.reset()之外的重型恢复函数。有些操作在中断回调里会因为上下文不可用而失败比如某些外设库的重新初始化。不能直接调用 Python 的gc.collect()。垃圾回收在中断上下文里执行非常危险可能触发不可预期的行为。回到看门狗本身回调里最安全的做法就是比较两个整数变量然后设置一个标志位真正的恢复动作放在主循环里执行。但这里有个矛盾如果主循环已经卡死了主循环里的恢复动作也执行不了。所以我的折中方案是回调里只做超时判定和计数累加然后用machine.reset()这种“原子”操作来复位。如果用的是软恢复而不是复位那软恢复逻辑最好是由定时器回调触发一个高优先级的中断标志同时确保主循环在醒过来之后能立即处理这个标志。实际操作中MicroPython 的定时器回调确实能打断主循环的阻塞状态尤其是time.sleep所以把恢复动作写到回调里只要不涉及分配内存大多数时候是可行的但为了安全起见我通常还是尽量让回调简单把复杂逻辑放到一个专门的处理函数里并且测试到位。4. 实操验证与常见问题排查4.1 如何验证看门狗是否真的有效代码写完了必须实测验证不能想当然。我常用的验证手段有两个一个是模拟死机一个是观察恢复日志。模拟死机的最好办法是写一个测试脚本让主循环在某次执行时主动进入一个空转死循环并且不喂狗import machine import time from watchdog import AdvancedWatchdog watchdog AdvancedWatchdog(timeout_ms3000, check_interval_ms500) watchdog.start() loop_count 0 while True: loop_count 1 print(loop, loop_count) if loop_count 10: print(simulate stuck...) while True: # 模拟死循环不喂狗 pass time.sleep(0.2) watchdog.feed()把 timeout_ms 设成 3000就是 3 秒内不喂狗就触发恢复。预期现象是串口打印到 loop 10 之后程序卡住然后 3 秒后看门狗触发恢复设备复位串口重新开始输出 boot 日志。如果能看到这个过程说明超时检测有效。观察恢复日志的做法是在 watchdog 恢复逻辑里打印 recovery_count 和错误码同时用 RTC 内存保存这些字段。复位后从 RTC 读回来如果能看到上一次恢复次数是 1、错误码对应 MAIN_LOOP_STUCK就说明状态保存链路也是通的。实际测试时我会在设备上电后主动打印一次watchdog.get_state()能把上次的运行状态直接输出到串口方便确认。4.2 常见问题速查表下面这几类问题是我在给不同项目集成软件看门狗时遇到过并且在社区里也频繁被问到的问题现象可能原因解决方案看门狗没有触发恢复喂狗点太多主循环虽然卡死但某个阻塞函数内部意外喂了狗梳理喂狗点确保卡死路径上不会执行 feed()设备频繁重启但业务看起来没卡死timeout_ms 设置得太短主循环某个分支在正常情况下就超过了这个时间把正常情况下的最长耗时统计出来timeout_ms 设置为它 2~3 倍定时器回调偶尔报 MemoryError回调里做了字符串拼接或列表操作触发了内存分配把打印和记录操作移到主循环回调里只做整数比较恢复后业务状态混乱出现重复初始化软恢复没有正确清理全局状态导致新旧状态混在一起软恢复应调用统一的 reset 入口而不是靠变量覆盖RTC 内存读出来是乱码json 编解码异常或 RTC 内存大小不够检查 RTC 内存容量减少保存的字段数量或者改成二进制格式看门狗在深度睡眠期间误触发进入睡眠前没有停掉看门狗定时器在 sleep 前调用 stop()醒来后重新 start()硬复位后外部外设没有重新初始化有些外设比如 OLED、传感器在 Python 代码重跑时才初始化但硬件电平状态没复位硬复位前主动调用外设的 deinit/reset或者采用硬件复位电路表中的前三个问题最常见基本都是设计阶段没想清楚“喂狗周期”和“业务周期”的关系。记住一个原则喂狗频率必须远高于看门狗超时时间看门狗超时时间必须远高于正常业务最长阻塞时间。否则就会误报或者漏报。4.3 调试技巧让看门狗自己“说”问题看门狗开发调试的时候我建议把恢复动作先改成“只记录不重启”也就是触发超时后先打印断言信息然后继续运行而不是立刻复位。这样你能在开发阶段看到完整的问题现场def _check(self, timer): if self.feed_counter self.last_counter: elapsed time.ticks_diff(time.ticks_ms(), self.last_feed_time) # 开发阶段只打印不重启 print(DEBUG: watchdog timeout, elapsed , elapsed) self.last_counter self.feed_counter # 重置计数避免反复触发 else: self.last_counter self.feed_counter这种“debug 模式”下程序卡死时你会在串口看到一条超时打印但设备不会重启你就有机会用调试器或者继续观察现场变量。等确认恢复逻辑没问题了再打开真正的恢复动作。另外MicroPython 的time.ticks_ms()返回的是毫秒级 tick它是会回绕的所以判断超时要用ticks_diff而不是直接比较大小。我自己一开始也是直接用差值小于某个值来判断后来设备连续跑了 49 天之后突然出现一次误判查了半天才发现是 tick 回绕的问题。MicroPython 的ticks_diff就是为这个设计的老老实实用它别自己造轮子。5. 进阶扩展多任务监控与状态持久化5.1 多任务场景下的看门狗扩展如果你的系统里不止一个主循环比如有一个采集线程、一个网络线程还有一个 UI 刷新循环那么一个看门狗实例可能不够用因为每个循环都可能卡住而主循环喂狗只能证明主循环还活着不能证明其他线程没事。扩展方法并不复杂给每个监控对象分配一个独立的喂狗计数器看门狗定时器检查所有计数器。这里给出一个简单的多任务版实现思路class TaskMonitor: 单个任务的喂狗记录 def __init__(self, name, timeout_ms): self.name name self.timeout_ms timeout_ms self.feed_counter 0 self.last_counter 0 self.last_feed_time time.ticks_ms() class MultiWatchdog: 支持多个任务的软件看门狗 def __init__(self, check_interval_ms200): self.tasks {} self.timer machine.Timer(3) self.check_interval_ms check_interval_ms def register_task(self, name, timeout_ms): self.tasks[name] TaskMonitor(name, timeout_ms) def feed(self, name): if name in self.tasks: self.tasks[name].feed_counter 1 self.tasks[name].last_feed_time time.ticks_ms() def start(self): self.timer.init( periodself.check_interval_ms, modemachine.Timer.PERIODIC, callbackself._check_all ) def _check_all(self, timer): now time.ticks_ms() for task in self.tasks.values(): if task.feed_counter task.last_counter: elapsed time.ticks_diff(now, task.last_feed_time) if elapsed task.timeout_ms: print(Task, task.name, timeout, elapsed, elapsed) # 针对具体任务的恢复策略 machine.reset() return else: task.last_counter task.feed_counter本质上就是把单任务看门狗的计数器换成字典每个键对应一个任务。定时器检查的时候逐个比对计数器和超时时间。这样做的好处是你能精确知道是哪个任务先卡住了而不是只知道“某个地方死了”。实际使用中每个任务的feed(name)要放在对应任务循环的最尾部确保任务真的跑到了一轮末尾才喂狗。如果任务中间有 2 秒的网络请求那这个任务的 timeout_ms 要至少给到 6 秒才稳妥。5.2 与硬件看门狗叠加使用的“双保险”方案前面讲了软件看门狗很强但它有一个天然的弱点软件看门狗跑在 MicroPython 环境里如果 MicroPython 解释器自身挂了比如堆栈溢出、固件 bug、底层驱动崩了那软件看门狗也跟着失效。所以对可靠性要求比较高的设备我会选择“硬件看门狗 软件看门狗”双保险。硬件看门狗放在最底层超时时间可以设得很长比如 60 秒。它平时基本不会触发只在软件看门狗彻底失效时兜底。软件看门狗负责精细监控超时时间设成 5 秒正常情况都是软件看门狗先动作完成状态保存和分级恢复。如果软件看门狗自己也卡了60 秒后硬件看门狗会强行复位整个芯片。这样设计的核心思想是能优雅恢复就优雅恢复不能优雅恢复就强制恢复。双保险配置在 MicroPython 里实现也不难硬件看门狗在 ESP32 上就是import machine # 启动硬件看门狗60 秒超时 hw_wdt machine.WDT(timeout60000) hw_wdt.feed()然后在主循环里喂硬件看门狗的位置要和喂软件看门狗的错开最好放在不同的执行点。比如软件看门狗在主循环末尾喂硬件看门狗在主循环开头喂这样即使软件喂狗逻辑出现死循环硬件喂狗也不可能连续执行。5.3 状态持久化让每次恢复都有据可查如果设备出问题后你要靠串口日志才能知道原因那在现场运维中是比较被动的因为现场可能没有人接串口。所以把看门狗状态持久化到本地存储是带恢复机制看门狗的一个重要能力。除了前面讲的 RTC 内存还可以把恢复日志写到文件系统里。但要注意写入频率每次恢复都写一次 Flash几百次之后 Flash 就可能挂了。我的做法是只在恢复等级升级时写日志比如第一次超时只记内存标志第二次软重启才写文件第三次硬复位再写一次。这样即使一个晚上重启了 20 次实际写入 Flash 的次数也不会太多。日志格式可以很简单{ recovery_count: 5, last_error: 1, safe_mode: false, uptime_before_reset: 3600, free_mem_before_reset: 28432 }uptime_before_reset和free_mem_before_reset这两个字段特别有用可以帮你判断设备是运行了很久之后才卡死还是一上电就跑几步就挂。如果是后者多半是初始化阶段的问题排查范围一下子缩小很多。6. 从软件看门狗看 MicroPython 项目的可靠性设计思路6.1 不要把所有可靠性的宝押在一种机制上做过三四年嵌入式的人都会明白一个道理没有任何一种机制是绝对可靠的处于任何时间点都可能出错你的程序可能出错看门狗本身也可能出错。所以设计可靠性方案的时候永远要想清楚“如果这个兜底机制也失效了会怎么样”。软件看门狗解决了“业务卡死”的大部分场景但它不能解决“MicroPython 解释器崩溃”的问题。硬件看门狗解决了“系统级死机”的问题但它太粗暴保存不了一点上下文。所以最可靠的设计是分层业务层代码里做好超时处理、异常捕获、内存控制尽量让程序不那么容易卡死。监控层软件看门狗监控各个任务循环的执行情况捕获异常状态执行分级恢复。硬件层硬件看门狗兜底保证系统即使遇到最极端的故障也能恢复。当然这已经超出“软件看门狗”本身的范畴了但我觉得既然做嵌入式就应该从全局视角来看待可靠性设计。软件看门狗不是银弹它只是监控层里最重要的一个零件。6.2 软件看门狗设计的“三要三不要”如果要给新手总结几条经验我会说要做的要让喂狗点覆盖所有关键业务路径尤其是阻塞操作之后。要让超时时间有足够冗余避免正常业务被误杀。要让恢复动作分层软恢复优先硬复位兜底。不要做的不要在定时器回调里做内存分配、文件写入、网络请求。不要用一个喂狗点监控所有任务多任务就得多路监控。不要在正式发布时还留着一堆调试打印这些打印本身可能成为卡死的诱因。6.3 一个实际项目中的使用心得最后分享一个实际的案例。我之前做一个环境监测节点用的就是 ESP32 MicroPython板子放在户外配电箱里要求至少连续运行 3 个月不用人工维护。刚开始测试的时候设备经常在运行一两天后就没响应了排查发现是 4G 模块的信号偶尔很差导致网络请求阻塞了几十秒主循环卡在那里一直等。后来我加了软件看门狗第一次超时就触发软重启同时还把网络请求改成非阻塞模式。结果设备稳定运行了一个多月没有再出现需要人工干预的情况。期间看门狗的 recovery_count 有过几次从 0 变到 1说明它确实起到了作用但每次都是软恢复就解决了没有触发硬复位。很多人觉得 MicroPython 不适合做严肃的嵌入式产品因为性能和实时性都跟 C 有差距。但只要你把可靠性机制设计到位MicroPython 完全能支撑起对稳定性有要求的物联网终端设备。软件看门狗就是这套可靠性机制里性价比最高的一块拼图写起来不复杂却能帮你省下大量现场运维的麻烦。当然光有看门狗还不够代码本身的健壮性才是根子上的事看门狗是给你兜底的队友不是给你擦屁股的保姆。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价