一、看门狗为啥会“睡过头”?
看门狗,说白了就是一个定时器。你每隔一段时间喂它一次,它就一直安静;你要是一直不喂,它就把单片机强行重启。很多项目里用它来防死机,确实挺管用。
但在低功耗项目里,我们想让 Arduino 睡一会儿,比如睡 8 秒,再醒来干活。这时也用看门狗,因为看门狗的中断可以把单片机唤醒。听起来挺顺理成章的,对吧?
问题出在 Arduino 的“睡法”上。睡眠越深,关掉的东西就越多。看门狗虽然有自己的时钟源,但在很多低功耗库里,深度睡眠时会把它关进小黑屋。就算没关,看门狗最长只能睡 8 秒,想睡更久就得手动计数,计着计着误差就出来了。比如你原打算睡 10 秒,结果 20 秒才醒。这在低功耗项目里很要命,因为本来就是要卡着时间采样或上报。电池供电的设备最怕这种事,你不知道它下一次醒是几点,整个系统的时间表全被打乱。
所以,看门狗并不是不能唤醒,而是它在深睡眠模式下不太靠谱。想要真正做到“到点就醒”,就得找一个不受 Arduino 睡眠影响的东西。于是,外部 RTC 就成了很好的帮手。
二、外部RTC独立时钟的原理
2.1 一个自带电池的时钟
RTC 的全称是实时时钟芯片。它有个特点:外接一颗纽扣电池,单片机断电它也能继续走。DS3231 就是很常见的一款。它内部有温度补偿晶振,时间误差非常小,一年也就几分钟。
对我们来说,最重要的不是它多准,而是它“不依靠 Arduino”。你把 Arduino 睡死过去,它还在那儿按自己的节奏走。它有独立的电池和时钟源,谁都不理,也能把时间记着。
2.2 怎么把 RTC 变成“起床闹钟”
DS3231 模块上有一个 SQW 引脚,这个引脚可以输出方波。它有好几种频率,比如 1 赫兹、4 千赫兹、8 千赫兹、32 千赫兹。你把它的频率设置成 1 赫兹,它每一个脉冲就会从高电平跳到低电平一次。
把 SQW 引脚接到 Arduino 的中断引脚上,这样每当 SQW 出现一个脉冲,Arduino 就会被唤醒一次。看门狗不行,但外部中断永远行,因为这是硬件层面的动作,不依赖内部时钟。
为什么外部中断能叫醒深睡模式?因为 Arduino 的睡眠模式再深,外部中断引脚仍然保留着检测电平变化的能力。这就像家里停电了,门铃的电池没停,你按门铃它还是会响。外部中断不靠内部振荡器,它靠的是引脚上的跳变信号。只要这个跳变发生,芯片就会退出睡眠状态。
你还可以用 RTC 的闹钟功能,指定一个时间点,到点让引脚拉低,也能唤醒单片机。闹钟方式更精准,代码也复杂一些。下面的例子先用方波方式,简单清楚。
三、用在哪?应用场景先想想
举几个常见场景。
在农田里放一个土壤湿度监测器,靠电池供电,每小时需要醒来一次测数据,然后继续睡。你不能白天跑去换电池,更不能让它一睡不醒。
家里的智能燃气表,可能几个月才换一次电池,但它每天要上报一次读数。如果看门狗唤醒失败,服务器那边就少了一条记录。
还有动物追踪项圈、户外气象站、无线门磁,基本都是“睡 99% 的时间,醒 1% 的时间”。这种设备最怕该醒的时候不醒。
这些场景的共同点是:对定时可靠性的要求远高于普通开发板项目。看门狗偶尔失灵一次,可能整条数据链就断了。外部 RTC 独立走时,等于给设备上了双保险。如果你做的是共享单车定位器、智能电表、养老手环这类设备,可靠性也绝对是第一位。
四、优缺点分析
4.1 这个方案好在哪
首先,可靠。RTC 芯片自己有晶体和电池,不占 Arduino 的资源,Arduino 睡成什么样它都不受影响。
其次,省电。RTC 工作电流很小,DS3231 在低功耗模式下只有几微安,和模块上的其他元件比,几乎可以忽略。
再次,可调。你可以在程序里随时改变方波频率,或者用闹钟模式设置具体时间,灵活性很高。
对比看门狗方案,看门狗的优点是不用额外硬件,但代价是“能不能醒要看运气”。RTC 方案相当于把“叫醒”这件事外包给了一个可靠的第三方,自己只管睡,醒来就有活干。
4.2 要注意的短板
第一,多一块电路板,体积和成本都会增加。如果你做的是极小型的设备,塞一张扩展板可能不现实。
第二,需要占用一个外部中断引脚。Arduino Uno 上能用外部中断的引脚就那么几个,得提前规划好。
第三,如果忘了给 RTC 电池充电或者电池耗尽,它的走时会乱掉。虽说不影响硬件唤醒,但可能导致定时不准。
五、硬件连接和准备
5.1 材料清单
Arduino Uno 或 Nano 一块。 DS3231 RTC 模块一个。 3.7V 锂电池或者 USB 电源。 杜邦线若干。
5.2 接线表
DS3231 一般有六个引脚,我们只用四个:
DS3231 的 VCC -> Arduino 的 5V
DS3231 的 GND -> Arduino 的 GND
DS3231 的 SDA -> Arduino 的 A4
DS3231 的 SCL -> Arduino 的 A5
DS3231 的 SQW -> Arduino 的 D2
这里要提醒一句,D2 是 Arduino Uno 的外部中断 0。如果你用 Nano,也一样。D3 也可以,但代码里就要改成中断 1。
六、完整示例:Arduino + DS3231 定时唤醒
6.1 技术栈和库
技术栈:Arduino C++(Arduino IDE)
要用到两个库:Adafruit RTClib 和 LowPower。第一个管 RTC,第二个管睡眠。在 Arduino IDE 的“库管理”里搜索并安装即可。
6.2 完整代码
// 技术栈:Arduino C++(Arduino IDE)
// 用途:用 DS3231 的 1Hz 方波,每 10 秒唤醒 Arduino 一次
#include <Wire.h> // I2C 通信库
#include <RTClib.h> // RTC 控制库
#include <LowPower.h> // 低功耗控制库
RTC_DS3231 rtc; // 创建 RTC 对象
volatile int wakeCount = 0; // 记录被唤醒的次数
const int wakeTarget = 10; // 唤醒 10 次就算够 10 秒
const int interruptPin = 2; // SQW 接到 D2,对应外部中断 0
void setup() {
Serial.begin(9600);
pinMode(interruptPin, INPUT_PULLUP);
Wire.begin(); // 启动 I2C 总线
if (!rtc.begin()) { // 检查 RTC 是否连上
Serial.println("没有找到 DS3231,程序停止");
while (1); // 卡住,不让设备空跑
}
if (rtc.lostPower()) {
// 如果 RTC 没在走,就用电脑时间给它设定初始值
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// 先关闭方波输出,然后设置为 1Hz
rtc.writeSqwPinMode(DS3231_OFF);
rtc.writeSqwPinMode(DS3231_SQW1HZ);
Serial.println("设备启动,开始测试");
}
void loop() {
// 挂上唤醒中断,然后睡到地老天荒
LowPower.attachInterruptWakeup(interruptPin, handleWake, FALLING);
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
LowPower.detachInterruptWakeup();
// 醒来后检查次数,达到 10 次就干正事
if (wakeCount >= wakeTarget) {
wakeCount = 0;
doRealWork();
}
}
// 中断处理函数:每次 SQW 从高变低,就加一
void handleWake() {
wakeCount++;
}
// 实际业务:这里只打印信息,你可以改成传感器读取、数据上传等
void doRealWork() {
Serial.println("10 秒到了,该干活啦!");
Serial.print("当前时间:");
Serial.println(rtc.now().timestamp());
}
6.3 代码怎么跑起来
编译上传后,打开串口监视器,把波特率调到 9600。你会看到“设备启动”的提示,然后每隔 10 秒出现一次“10 秒到了”。如果一直没有输出,就检查一下 SQW 引脚是否接对,程序里中断触发方式有没有选对。
这里有个细节:LowPower.attachInterruptWakeup 在睡眠前调用,醒来后要 detachInterruptWakeup,防止影响下一次睡眠。
七、注意事项
第一,DS3231 的 SQW 引脚是开漏输出的,最好加上拉电阻。我们代码里用了 INPUT_PULLUP,也就是启用 Arduino 内部上拉,虽然能工作,但会多耗一点点电。如果对功耗要求特别严,可以在 SQW 与 VCC 之间接一个 10 千欧的外部上拉,然后把 INPUT_PULLUP 改成 INPUT。
第二,不要用看门狗去打断 RTC 方波。我们的示例完全靠外部中断唤醒,看门狗只用来防死机,不要把两个功能混在一起。
第三,RTC 电池要选质量好的纽扣电池,不能图便宜。电池没电后,时间会清零,闹钟也就不准时了。
第四,如果你的板子不是 Uno/Nano,中断引脚编号可能不一样。例如 Arduino Mega 上 D2 和 D3 也是外部中断,但其他板子需要查文档。
第五,示例中使用的是 1Hz 方波,唤醒次数由软件计数。想让精度更高,可以直接用 DS3231 的闹钟功能,在特定时间点触发一次,但代码里要处理闹钟标志,之后可以继续深入。
第六,如果你的项目里还需要保存“上次唤醒时间”,建议把时间值写在 RTC 的闹钟寄存器里,或者写到 EEPROM。每次醒来后先读时间,再设定下一次闹钟,这样就算中途断电,重新上电后也能恢复节奏。
八、总结
低功耗模式下看门狗唤醒不靠谱,不是因为看门狗太差,而是它和单片机共用一套时钟体系。要想让设备真正按自己的计划醒来,外部 RTC 是更好的选择。
RTC 芯片有独立电池和晶体,单片机睡死也不影响它按时输出脉冲。哪怕看门狗睡着了,外部中断照样能把单片机叫醒。这个方案多花一点硬件成本,换来的是定时唤醒的可靠性。
在实际项目中,建议把看门狗保留作防死机兜底,用外部 RTC 作主要唤醒源。这样分工明确,设备又省电又稳定。希望这篇文章能给你一个清晰的思路。
Comments