bug修补者-watchdog
最近在自己的一个Arduino项目中发现了一个很奇怪的异常,系统开机一段时间后出现死机,死机出现的时间和触发原因看起来似乎没有任何规律,检查程序也找不到任何错误,同时因为死机出现的随机性,也很难进行调试。问题可能是程序中的一些隐藏bug,或者与硬件有关,但无论如何,一直没有找到确切原因。因此在其中加入watchdog作为一种修补,至少每次出现异常后系统可以自动复位总比一直卡在那好。
Arduino的WDT
Arduino的语法参考中没有包括WDT,但相关开发板的atmega系列单片机的数据手册均显示是支持wdt的,同时因为Arduino IDE本身是基于AVR GCC编译器,因此可以直接在Arduino IDE中使用AVR的原生语句与函数库。需要包含头文件#include <avr/wdt.h>
。可以参考avr的详细库函数说明
avr的WDT 的基本功能语句有三个:
- WDT使能定时间隔设置
wdt_enable()
,参数可以从几十ms到几s之间调整,当超过这个时间而单片机又没有复位WDT定时器时,单片机就会进行复位操作,一般来说不要设置过低的时间间隔。 - WDT禁用语句
wdt_disable()
,为防止再次烧写程序时复位后反复进入
wdt复位,需要在初始化程序的最开始位置禁用wdt,这在wdt定时间隔设置过短时尤为重要,否则可能会导致无法再次烧写。 - WDT定时器复位语句
wdt_reset()
,如果在WDT的定时间隔内没有使用该语句复位定时器,则WDT会自动复位单片机,该语句的位置根据具体程序的耗时决定,可能需要多处加入。
下面给出了一个wdt的测试程序,主循环内的led闪烁频率会越来越慢,当闪烁间隔超过wdt定时后,会触发wdt复位,在setup()
中led快速闪烁三次以标志进入复位。
#include <avr/wdt.h>
const int onboardLED = 13;
void setup() {
wdt_disable(); //disable wdt at the first of code to prevent it always reboot
//the WDT time can be choose from
//WDTO_25MS WDTO_30MS WDTO_60MS WDTO_250MS WDTO_500MS
//WDTO_1S WDTO_3S WDTO_4S WDTO_8S
//be very carfully when use too short time value because most avr's WDT still on work even after reboot
wdt_enable(WDTO_4S);
pinMode(onboardLED, OUTPUT);
for (int k = 1; k <= 10; k = k + 1) {
digitalWrite(onboardLED, HIGH);
delay(50);
digitalWrite(onboardLED, LOW);
delay(50);
}
delay(750L);
}
void loop() {
for (int k = 1; k <= 10000; k = k + 1) {
wdt_reset(); //reset the watchdog timer every loop to prevent reboot
digitalWrite(onboardLED, HIGH);
delay(k * 250L);
digitalWrite(onboardLED, LOW);
delay(250L);
}
}
bootloader 与WDT冲突解决
用上面的代码实际测试中发现当自己用的arduino产生触发wdt复位的条件时,单片机并没有复位而是直接卡死,指示灯快速闪烁或者无反应。且再次烧写程序时出现了问题,强制上电复位才成功。查阅了一些资料后可能原因在于旧版本的arduino的bootloader与wdt间有冲突。
我拿来一块新买的mega2560测试了同样的程序,发现果然可以正常使用wdt。于是我又用avr的usb asp烧写器烧写了新版本的bootloader进我的旧的arduino中,结果显示mega2560使用新的bootloader后可以正常工作,但pro mini仍然无法正常工作。关于如何烧写bootloader已经超过了本篇的范围,请参考我的另外一篇博客Arduino烧写bootloader