“看门狗定时器”是这样一种东西,从功能上说它可以让微控制器在意外状况下(比如软件陷入死循环)重新回复到系统上电状态,以保证系统出问题的时候重启一次。就跟我们现在用电脑一样,死机了你就按一下reset键重启一次电脑,看门狗就是负责干这个事儿的。它是52单片机增加的一个功能,以前Intel 8031、……、AT 89C51时代单片机片内都没有“看门狗”功能,需要我们外扩看门狗芯片,比如X5045。
很多人初次接触不太理解怎么用,书上也讲的含含糊糊,故意说的很复杂很玄妙(可能是现在写书人的通病,生怕写的简单的别人觉得他没水平)。其实要是说明白点:“看门狗”就是一个计数器,由于位数有限计数器能够装的数值是有限的(比如8位的最多装256个数、16位的最多装65536个数),从开启“看门狗”那刻起,它就开始不停的数机器周期,数一个机器周期就计数器加1,加到计数器盛不下了(术语叫溢出)就就产生一个复位信号,重启系统。
注解:这里顺便说一下,一般教材上叫“看门狗定时器”,其实定时器原理还是计数器,只是计的是时钟周期,所以我为了初学者好理解叫统一叫“计数器”,这里阐明一下。
明白了上面的原理,我们在设计程序时,先根据看门狗计数器的位数和系统的时钟周期算一下计满数需要的时间,就是说在这个时间内“看门狗”计数器是不会装满的,然后在这个时间内告诉它重新开始计数,就是把计数器清零,这个过程叫“喂狗”,这样隔一段时间喂一次狗,只要程序正常运行他就永远计不满,一旦出现死循环之类的故障,没有及时来清零计数器,就会导致装满了溢出,他就重启系统,这就是看门狗的看门原理,其实想想傻傻的、笨笨的。
举个例子说:8051 单片机选用12MHz晶振,一个时钟周期为1us,如果“看门狗计数器”是16位的,最大计数65536个,那么从0开始计到65535需要约65ms,所以我们可以在程序的50ms左右清零一次计数器(“喂狗”),让他重新从0开始计,再过50ms,再清,……,这样下去只要程序正常运行,计数器永远不会计满,也就永远不会被“看门狗”复位。当然这个喂狗的时间是大家自己选的,只要不超过65ms,你选多少都可以,一般不要喂得太勤,这样单片机运行时间浪费了,比如你1ms喂一次就太勤了,也不要说那我65ms喂一次,这样太边缘,这样抗干扰能力就下降了,最好是留一定的余量,这个就是设计者自己掌握了,我一般是让计到90%左右就清一次。
每种单片机的“看门狗”实现方法不尽相同,但是原理都一样,而且“看门狗”都是启动了之后就不能被关闭,只能系统复位(重新断电在上电)才能关闭。设置“看门狗”的一般步骤如下:
1. 设置“看门狗”相关寄存器,?启动“看门狗”;
2.? 隔一段时间清零一次,“喂狗”;
3. 如果程序正常,一直运行;如果程序出错,没有按时“喂狗”,“看门狗”就在溢出的时候复位系统。
值得提一下:
由于现在AT89S52应用比较广泛,所以我先说说ATMEL的看门狗;再说说本次试验用的STC89C52RC的看门狗;注意两个不一样!!!
AT89S52单片机看门狗定时器是14位的,最大计数214=16384个数,每计16384个时钟周期就溢出一次。也就是说如果使用12M晶振的话,至少应该在16.384ms内喂一次狗。
STC89C5X系列单片机由于采用了“预分频技术”,它的溢出时间是=(N*Prescale*32768)/晶振频率(不要问我为什么,他们就是这么设计的,我们就这么用就行)。
- 其中N是单片机的时钟周期,STC89C5X系列单片机提供6时钟周期和12时钟周期两种时钟周期,可以在烧写程序时修改;
- Prescale是预分频数,通过设置【看门狗控制寄存器】可以设置为2、4、8、16、32、64、128、256;怎么设置演示程序中有介绍;
- 晶振频率就是系统选用的晶振。
所以如果同样选择12MHz晶振,使用传统的12时钟周期,它最小的溢出时间是(12*2*32768)/(12*106)=65.536ms,最大溢出时间是(12*256*32768)/(12*106)≈8.38s。如果选择256分频,也就是说只要在8.38秒之内喂一次狗就可以了。戏谑的说:这只狗比较抗饿,J~~
对于我们用户来说,看门狗的时间是越长越好,这样可以节省更多的单片机资源,尤其是对时间要求精准的系统,如果执行过程中我们不停地“喂狗”,那么是比较浪费时间的。所以STC89C5X系列单片机的看门狗更有优势一些。当然这个也是个人的选择,如果对时间要求的不苛刻的话,勤喂几次狗也没关系。
下面我就以STC89C52RC单片机为例说说典型的51单片机的看门狗程序如何写,关于STC89C52RC单片机的“看门狗”定义请看STC89C51RC-RD单片机使用说明。以下程序在Keil 2和Keil 3下调时通过,下载在本校的实验板上达到预期效果。STC89C52RC/54RD+/58RD+/516RD+单片机上测试正常运行。
如果没有我们的实验板,请按照下面的硬件原理图自己在最小系统上搭建一个实验环境也很容易。
图1. STC89C52最小单片机系统+两个指示灯
图2. 串行口接口(用于下载程序和测试本次试验)
001 | /*************************************************************************** |
002 | 程序功能:本程序演示STC51单片机看门狗程序 |
003 | 程序设计:燕山大学 鲁信琼 |
004 | 晶振选择:11.0592MHz, 如果晶振不匹配,请修改延时函数参数 |
005 | 承接51单片机、PIC单片机程序、VB/VC++上位机程序、电子产品软硬件设计开发工作 |
006 | EMail: xqlu(at)ysu.net.cn? QQ: 9790335? |
007 | ? ? |
008 | 由于现在AT89S52很流行,所以我先说说ATMEL的看门狗;再说说本次试验用的STC89C52RC的看门狗;注意两个不一样!!! |
009 | ? ? |
010 | ★下面是关于ATMEL-51单片机看门狗的描述 |
011 | ???? 【看门狗计数器】(watchdog timer)是一个14位的计数器,它以机器周期(晶振频率/12)增加,当计数值计满(16383/0x3FFF)了就使单片机软复位; |
012 | ???? 当启动了【看门狗计数器】之后,我们需要在它计数没有满之前复位计数器强制它不能够溢出,这个过程称作喂狗。 |
013 | ? ? |
014 | ???? "看门狗"原理: |
015 | ???? 1. 系统上电并不启动看门狗计数器,通过设置【看门狗重置寄存器(WDTRST SFR)】启动【看门狗计数器】,一般设置是给WDTRST写入0x1E和0xE1启动; |
016 | ???? 2. 【看门狗计数器】一旦启动不可停止,除非是硬件RST或者看门狗的软复位才能使其停止; |
017 | ???? 3. 设计程序在适当的时间喂狗一次,使其不能计满,程序就能不间断执行; |
018 | ???? 4. 如果程序中出现死循环或者执行某一步超时,看门狗计数器就会计满溢出,(这个时候我们认为程序没有按照预定计划执行--程序跑飞),则复位系统。 |
019 | ? ? |
020 | ★下面是关于STC89C5XX-51单片机看门狗的描述 |
021 | ???? WDT_CONTR位置0xE1; [-] [-] [EN_WDT] [CLR_WDT] [IDLE_WDT] [PS2] [PS1] [PS0] |
022 | ???? EN_WDT: 看门狗允许位,置1启动看门狗,看门狗不能自动启动,需要设置该位后启动,一旦启动不能关闭(只能系统重新上电和看门狗复位可以关闭) |
023 | ???? CLR_WDT:??? 看门狗计数器清零位,置1清零看门狗计数器,当计数器开始重新计数,硬件清零该位。 |
024 | ???? IDLE_WDT:?? 单片机IDLE模式看门狗允许位,当IDLE_WDT=1时,单片机在IDLE模式(空闲模式)依然启用看门狗 |
025 | ???? PS2~PS0:??????? 看门狗定时器预分频器,下表中Prescale表示预分频数 |
026 | ???? PS2???? PS1???? PS0???? Prescale |
027 | ???? 0?????? 0?????? 0?????? 2 |
028 | ???? 0?????? 0?????? 1?????? 4 |
029 | ???? 0?????? 1?????? 0?????? 8 |
030 | ???? 0?????? 1?????? 1?????? 16 |
031 | ???? 1?????? 0?????? 0?????? 32 |
032 | ???? 1?????? 0?????? 1?????? 64 |
033 | ???? 1?????? 1?????? 0?????? 128 |
034 | ???? 1?????? 1?????? 1?????? 256 |
035 | ? ? |
036 | ???? 看门狗溢出时间:(N*Prescale*32768)/晶振频率,其中N表示指令周期数N=12表示12时钟周期模式;N=6表示6时钟周期模式 |
037 | ? ? |
038 | 关于实验的注意事项: |
039 | 1.? 本次试验使用的是11.0592MHz晶振,设置WDT_CONTR=(0011 0100)B,32预分频,单片机使用12指令周期模式。 |
040 | ???? 计算看门狗溢出时间:[12*32*32768/(11059200)]≈1s。 |
041 | 2.? 本次试验的硬件电路很简单,就是最小系统上增加两个LED灯,原理图见正文,用户可以很容易实现。 |
042 | ***************************************************************************/ |
043 | #include <REG52.H> |
044 | sfr WDT_CONTR=0xE1;???????????????? //定义特殊功能寄存器:STC单片机看门狗控制寄存器 |
045 | #define uchar unsigned char |
046 | #define true 1 |
047 | #define false 0 |
048 | #define WEIGOU WDT_CONTR=0x34?????? //看门狗启动设置和“喂狗”操作 |
049 | sbit LED=P1^6;????????????????????? //信号灯,系统正常工作就一闪一闪的 |
050 | sbit LED_busy=P1^7;???????????????? //工作灯,上电灭一会儿(约800ms),然后正常工作的时候一直亮着;用于指示系统是否重启 |
051 | uchar timer0_ctr,i; |
052 | const uchar str[]= "I love MCU!" ;??? //定义一句话,让他从串口输出,只有系统重启的时候才输出一次,所以也是用于验证看门狗有没有重启系统 |
053 | ? ? |
054 | /*************************************************************************/ |
055 | //延时函数,11.0592MHz晶振下延时约xms毫秒 |
056 | void delay_ms(unsigned xms) |
057 | { |
058 | ???? unsigned x,y; |
059 | ???? for (x=xms; x>0; x--) |
060 | ???????? for (y=110; y>0; y--); |
061 | } |
062 | /*************************************************************************/ |
063 | ? ? |
064 | /*************************************************************************/ |
065 | //主程序初始化函数 |
066 | void InitMain() |
067 | { |
068 | ???? //初始化时两盏灯都熄灭 |
069 | ???? LED=1; |
070 | ???? LED_busy=1; |
071 | ? ? |
072 | ???? TMOD=0x21;????????????? //定时器0工作在方式1,作为16位定时器;定时器1工作在方式2,作为串行口波特率发生器 |
073 | ???? TH0=0x4C;?????????????? //定时器0装初值:每隔50ms溢出一次 |
074 | ???? TL0=0x00; |
075 | ???? IE=0x82;??????????????? //IE=(1000 0010)B, 使能定时器0中断 |
076 | ???? TR0=1;????????????????? //启动定时器0 |
077 | } |
078 | /*************************************************************************/ |
079 | ? ? |
080 | /*************************************************************************/ |
081 | //串行口初始化程序 |
082 | void InitCOM() |
083 | { |
084 | ???? SCON=0x50;????????? //SCON=(0101 0000)B,波特率不加倍,允许接收 |
085 | ???? TH1=0xFD;?????????? //设置波特率=9600bps |
086 | ???? TL1=TH1; |
087 | ???? TR1=1;????????????? //启动定时器1 |
088 | } |
089 | /*************************************************************************/ |
090 | ? ? |
091 | /*************************************************************************/ |
092 | //定时器0中断服务程序程序,控制信号灯闪烁。如果系统正常运行,信号灯1.5秒闪一次 |
093 | void Timer0_isr() interrupt 1 |
094 | { |
095 | ???? TH0=0x4C; |
096 | ???? TL0=0x00; |
097 | ???? timer0_ctr++; |
098 | ? ? |
099 | ???? if (timer0_ctr>=30) |
100 | ???? { |
101 | ???????? TR0=0;????? //定时器0暂停,否则再次来中断会冲断程序 |
102 | ???????? timer0_ctr=0; |
103 | ???????? LED=0; |
104 | ???????? delay_ms(100); |
105 | ???????? LED=1; |
106 | ???????? TR0=1;????? //定时器0重新启动 |
107 | ???? } |
108 | } |
109 | /*************************************************************************/ |
110 | ? ? |
111 | void main() |
112 | { |
113 | ???? WEIGOU;???????????????? //上来第一步设置看门狗定时器,并且启动 |
114 | ???? InitMain(); |
115 | ???? InitCOM(); |
116 | ? ? |
117 | ???? //开机通过串口发送一次“I love MCU!”,使用串口调试助手可以查看 |
118 | ???? //由于在while大循环外边,所以只要系统不重新启动,则上电后只会发送一次,用于判断系统是否重启 |
119 | ???? i=0; |
120 | ???? while (str[i]!= '\0' ) |
121 | ???? { |
122 | ???????? SBUF=str[i]; |
123 | ???????? while (TI==0); |
124 | ???????? TI=0; |
125 | ???????? i++; |
126 | ???? } |
127 | ? ? |
128 | ???? //while大循环 |
129 | ???? while ( true ) |
130 | ???? { |
131 | ???????? //约每隔800ms喂一次狗,可以通过调整这里的喂狗时间来验证看门狗是否有效 |
132 | ???????? //我们设置的看门狗约1秒。所以可以用800和2000分别做一次试验,看是否会被看门狗复位 |
133 | ???????? delay_ms(2000); |
134 | ???????? LED_busy=0;???????? //第一次上电约延时800ms工作灯点亮,如果系统不重启,他将一直亮着,用于指示系统是否重启 |
135 | ???????? WEIGOU; |
136 | ???? } |
137 | } |