任务3.5 定时器的初步认识

任务五 定时器的初步认识

3.5.1 任务介绍

在动态数码管的学习中,我们已经用DelayMs()延时函数实现了数字钟的走时,但是这种方式下的数字钟走时不够准确。生活中,我们需要精确计量时间时,通常会借助于走时准确的秒表,51单片机内部也有一个类似于秒表的装置,我们称之为定时器,借助于定时器,我们可以实现走时准确的数字钟。

本节的任务是:利用单片机定时器完成走时准确的数字钟,另外在程序中,数码管扫描也由定时器来驱动。

3.5.2知识准备

1、定时器的引入

在讲述定时器的原理之前,我们先看一下图3.5.1中的水龙头向水盆滴水的画面。在画面中,水龙头由于没有关紧,水 一滴一滴地滴向脸盆,盆的容量是有限的,水会在某一个时刻

从水盆中溢出。假设一开始水盆没有水,65536个水滴恰好可

以把水盆装满,恰好是计数了65536次。如果我们计数1000 次怎么办呢?向水盆中预先装下了64536滴水,然后打开水龙 头,开始滴水,等到水盆中的水溢出了,自然是计数了100次。如果水滴的速度是恒定的,1滴/1秒,计数就变成了计时了。

图3.5.1 水盆滴水 实际古人计时装置“刻漏”的原理和水盆滴水的原理相似。

51单片机的定时器/计数器的原理与上面讲述的水盆滴水的例子类似,如表3.5.1所示。

3.5.1 定时/计数器和水盆滴水的类比

2、定时器的内部结构及工作原理

- 1 -

在51单片机内部有2个定时器,分别称为定时/计数器0、定时/计数器1,每个定时/计数器具有计数和定时两大功能,有4种工作方式。定时/计数器0和定时/计数器1配置上完全相同,现用定时/计数器1的工作方式1来说明定时器内部结构与工作原理。图3.5.2为定时/计数器1在工作方式1下的内部结构图。

(1)定时/计数器1的脉冲源有两种:当定时/计数器1工作于定时方式时,加1脉冲由振荡器的12分频提供(12M 晶振的12分频为1MHz )。当定时/计数器1工作于计数方式时,加1脉冲由外部脉冲源提供,P3.4是定时/计数器0的外部脉冲源输入端,P3.5是定时/计数器1的外接脉冲输入端。定时/计数器工作于定时还是计数方式,取决于选择开关C/T ,当C/T =0时工作于定时方式,C/T =1时工作于计数方式。

(2)脉冲要经过启动开关才能到达RAM 。启动开关=TR1 & (GATE | INT1) ,GATE 为门控位,INT1为外部中断1。当GATE=0时,GATE | INT1的结果为1,则启动开关仅有TR1决定。当GATA=1,则启动开关的不仅由TR1决定,还要由来自于外部中断1的信号决定是否开启中断。

(3)两个8位的RAM ,高八位的RAM 称为TH1,低八位的RAM 称为TL1,组合在一起共16位。每来一个脉冲,RAM 的值加1,当RAM 的数值超过65535时,计数器会溢出,溢出标志位TF1会由0变成1,同时TF1的变化会引发中断事件的发生。要设定不同的定时时间,在定时/计时器启动之前,向RAM 预先装入初值。

图3.5.2 定时/计数器1的内部结构(方式1)

3、定时器的方式控制字

51单片机的特殊功能寄存器(SFR)中有两个属于定时器的寄存器,分别为TMOD 和TCON 。 (1)TMOD 寄存器

表3.5.1 TMOD寄存器

————----------------————----------------————---——

-——

-——

- 2 -

从表3.5.1中我们可以看到,TMOD 被分成两份,每份四位,高四位用于定时/计数器1的控制,低四位用于定时/计数器0的控制。结合图3.5.2 定时/计数1的内部结构,GATE 参与了启动开关的选择,称为门控位。C/T是用来选择定时还是计数,M1M0是下面要介绍的4种工作方式的选择位。

(2)TCON 寄存器

表3.5.2 TCON寄存器

TCON 寄存器也被分成了两份,前四位用于定时/计数器,后四位用于外部中断,如表3.5.2所示。前四位中又被分成了两份,分别属于定时/计数器1和定时/计数器0。其中TF 是定时器溢出标志,没有溢出时为0,溢出后为1。TR 参与了定时器的启动。

需要说明的是,TMOD 寄存器中的每一个位不可以单独寻址,举例:定时/计数器0,定时模式,门控位为0,方式0(M1M0=01),可以写成TMOD=0x05。TCON 寄存器中的每一个位可单独寻址,举例:启动定时器0,TR0=1就可以了。

4、定时/计数器的四种工作方式

51单片机的定时器有四种工作方式,TMOD 寄存器中M1和M0位决定了使用哪种工作方式。

M1M0为的配置和定时器的工作方式的对应关系如表3.5.3所示。

表3.5.3 定时器工作方式

工作方式0和工作方式3很少使用,在这里不做专门的讲解,下面介绍工作在方式1和(1)工作方式1:

当定时/计数器处于工作方式1时,两个8位的RAM 都参与计数,即计数的范围是:0~

- 3 -

工作方式2的特点。

65535。需要注意,工作于方式1时,定时/计数器溢出后,需要手动装入初值。

(2)工作方式2

定时/计数器处于工作方式2时,相比于方式1,其计数范围缩小了,高8位的RAM 不参与计数,低8位的RAM 参与计数。其计数范围是0~255。高8位的RAM 闲置不用吗?实际上不是这样的,首先来分析一下定时/计数器在工作方式1下存在的问题。

开发板的晶振频率为12MHz ,12分频后为1MHz ,脉冲周期是1us ,选择定时模式时,每隔1us ,RAM 的值加1,16位的定时/计数模式,最大定时范围为65.536ms 。我们要实现数字钟的1秒定时,可以先定时50ms (RAM 初值=65536-50000=15536), 然后连续定时20次,就可以实现1秒的定时。在介绍工作方式1时,提到溢出后需要手动装入初值(15536),如果不手动装入初值,则溢出后,RAM 从0开始计数。

RAM 的初值重装是在中断服务函数中完成的。定时/计数器溢出后产生中断,到进入中断服务函数后装入初值,是需要耗费时间的。实际是,定时/计数器溢出后,从0开始计数,运行一段时间初值重装了,又从初值运行。尽管这段时间很小(几十个us ),但连续定时,日积月累,误差就大了,所以工作方式1下,定时的精度不高。

工作方式2下初值的重装不需要手动装入,一旦溢出,硬件自动完成初值的重装,中间没有时间耽搁,方式2比方式1定时要准确。定时/计数器初始化时,高8位RAM 也装入初值,当定时/计数器溢出后,硬件自动把高8位RAM 存放的初值导入到低8位RAM ,完成初值重装。

5、定时器/计数器的初始化和溢出标志位处理 定时器/计数器初始化设置如下:

(1)TMOD 寄存器的配置:定时/计数的选择,GATE 门的设置,工作方式的选择。 (2)装入初值,根据任务的要求,给THn 、TLn(n为0或1) 装入初值。 (3)开总中断:EA=1,开定时器中断:ETn(n为0或1)=1。

(4)如果还有别的中断,根据任务的重要性,需要确定中断优先级,配置IP 寄存器。 (5)启动定时器/计数器,TRn(n为0或1)=1。

定时器/计数器溢出可以采用查询和中断两种方式进行处理。查询方式效率低(主程序中不停的查询溢出标志位TFn(n为0或1) 是否置位),本书中采用中断方式。

6、定时/计数应用举例 例程1:计数的应用

自动化生产线上,12瓶易拉罐饮料为一箱,光电传感器检测到一瓶易拉罐就给送出一个脉冲,控制器连续接收到12个脉冲,则作为一个包装。本例用按键来模拟光电传感器发出的脉冲,按键接P3.4(定时/计数器0的外接脉冲输入端),用发光二极管的状态取反来模拟一个包装的动作,发光二极管接单片机的P0.0。连续按下12次按键,发光二极管状态取反一次。

- 4 -

根据任务要求,定时/计数器的配置如下:使用定时/计数器0,计数、工作方式1。程序如下:

#include

#define uchar unsigned char #define uint unsigned int

sbit LED=P0^0; //LED接口定义 //计数器0初始化 void Timer0Init() {

TMOD=0x05; //GATE=0,C/T=1,M1M0=01; TH0=(65536-12)/256; //RAM高8位赋值 TL0=(65536-12)%12; //RAM低8位赋值 ET0=EA=1;

//开定时器中断和总中断

TR0=1; //启动定时器

}

//主函数

void main(void) {

Timer0Init(); //定时/计数器初始化

while(1) { }

//计数器0中断服务函数

void Timer0Intr(void) interrupt 1 {

TH0=(65536-12)/256;

TL0=(65536-12)%12; //溢出后重新赋初值

LED=!LED; //中断每产生一次,发光二极管的状态取反一次 }

程序解释:

- 5 -

//执行别的任务 }

(1)Timer0Init()函数完成计数器的初始化。外部中断不参与计数器的开启,门控位:GATE=1;计数模式:C/T=1;工作方式1,M1M0=01。每12个脉冲到来,计数器溢出一次,初值为(65536-12),取出高8位和低8位,赋给TH0和TL0。最后开中断,启动计数器。

(2)计数器溢出后,CPU 响应中断服务函数,在中断服务函数中,重新赋予初值,并完成LED 的状态取反。

例程2:定时的应用

用定时的方式实现LED 闪烁,LED 闪烁的频率为1Hz 。LED 接单片机P0.0引脚,0.5秒亮,

0.5秒灭。根据任务要求,定时/计数器的配置如下:使用定时/计数器1,计时、工作方式2。 #include

#define uchar unsigned char #define uint unsigned int

sbit LED=P1^0; //LED接口定义 bit Flag500Ms=0; //500ms标志位 //定时器1的初始化 void Timer1Init() {

TMOD=0x02; //GATE=0,C/T=0,M1M0=10; TH1=256-200;

ET1=EA=1; } //主函数 void main(void) {

Timer1Init(); //定时器的初始化 while(1) {

if(Flag500Ms==1) //500ms标志位置1 {

Flag500Ms=0; //清空500ms 标志位 LED=!LED; //LED状态取反 }

- 6 -

//RAM高八位赋值

//开定时器中断和总中断 //启动定时器

TL1=256-200; //RAM低八位赋值,200us 的定时

TR1=1;

}

//定时器1的中断服务函数

void timer0_intr(void) interrupt 1 {

static uint Cnt200us=0; //200us计数变量

if(++Cnt200us>=2500) //200us*2500=500ms {

Cnt200us=0; //清空计数变量 Flag500Ms=1; //500ms标志位置1

} }

程序解释:

(1)Timer0Init()函数完成定时器1的初始化。门控位不参与定时:GATE=0;定时:C/T=0;工作方式2,自动重装模式,M1M0=10。所以TMOD=0x20。200us 定时器溢出一次,8位自动重装模式,所以TH1=TL1=(256-200)=56。

(2)由于采用了自动重装模式,在中断服务函数中,不需要重新赋初值。本任务的定时目标是500ms ,程序中定时器的定时长度是200us ,所以需要连续定时。每进入一次中断服务函数,200us 计数变量(Cnt200us )加1,Cnt200us 计数到2500,整个计时的时间为200us ×2500次=500ms。

(3)500ms 到来后,程序中并没有像上一个计数例子一样,在中断服务函数中完成LED 的闪烁,而是把500ms 标志位(Flag500Ms)置1,在主程序中,检测到该标志位置1,则完成LED 的闪烁。

(4)计数变量Cnt200us 在定义的时候,uint 前面多了个关键字static ,其作用是声明变量Cnt200us 为局部静态变量。什么是局部静态变量呢?C 语言变量根据定义的位置不同分为全局变量、局部变量,

全局变量是在函数外部定义的变量,又称外部变量。全局变量一般定义在程序的开始处,可以为多个函数共同使用,其有效的作用范围是从它定义的位置开始整个程序文件结束为止。

局部变量是在一个函数内部内部定义的变量,该变量只能在定义它的那个函数范围内有效,不同的函数可以使用相同的局部变量名。由于它们的作用范围不同,不会互相干扰,函数的形式参数也属于局部变量。

局部变量根据存储空间分配的不同又分为自动变量和静态变量。在函数内部定义的变量,如果不做专门说明,则为自动变量。自动变量的特点是只在定义它们的时候才分配存储空间,

- 7 -

}

在定义他们的函数返回时系统回收变量所占用的存储空间,对这些变量存储空间的分配和回收是系统自动完成的,

静态变量在定义是前面多了个关键字static ,局部静态变量仍然保留局部变量的特性,只能在定义的函数内部使用,但与自动变量不同的是,再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。

弄明白了什么是局部静态变量,再来看中断函数中计数变量Cnt200us 的使用就简单多了。首次调用中断函数时,计数变量Cnt200us 定义且赋初值0,紧跟着在函数中Cnt200us 的值加1,退出出中断服务函数后,Cnt200us 的值不释放,仍然为1。

3.5.3任务实施

本节的任务程序实现如下:

#incldue

#define uchar unsigned char #define uint unsigned int

//段码 uchar code Seg7Code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; uchar code Seg7Posit[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; //位码 uchar DispBuffer[6]; //缓冲区 bit SystemFlag1Ms=1; //1ms标志位

//6位数码管显示子函数,在第二位和第四位上显示小数点 void Seg7Display() {

static uchar i=0;

P1=0xff; //消隐 P0=Seg7Code[disp_buff[i]]; //送段码

if((i==1)||(i==3)) P0&=~0x80; //在第二位和第四位数码管上加小数点 P1=Posit[i]; //送位选 if(++i>=6) i=0; }

//把时间送入缓冲区

void TimerToBuffer(uchar nhour,nminute,nsecond) {

DispBuffer[0]=nhour/10;

DispBuffer[1]=nhour%10; //小时的个位和十位的分解

- 8 -

DispBuffer[1]=nhour%10; //小时的个位和十位的分解 DispBuffer[2]=nsecond/10;

DispBuffer[3]=nsecond%10; //分的个位和十位的分解 DispBuffer[4]=nminute/10;

DispBuffer[5]=nminute%10; //秒的个位和十位的分解 }

//定时器0初始化 void Timer0Init() {

TMOD=0x02; //GATE=0,C/T=0,M1M0=02; TH0=56; //高8位RAM 赋值

TL0=56; //低8位RAM 赋值,200us 定时 ET0=EA=1; //开定时器中断和总中断

TR0=1; //开启定时器 }

//主函数

void main(void) {

uchar Hour=9; //小时

uchar Minute=23; //分

uchar Second=37; //秒

uint Cnt1Ms=0; //1ms计数器

Timer0Init(); //定时器0初始化 while(1) {

if(FlagSystem1Ms==1) //定时器产生的1ms 的时标信号到 {

SystemFlag1Ms=0; //时标信号清零

TimerToBuffer(); //显示数据送往缓冲区

Seg7Display(); //6位数码管动态显示

if(++Cnt1Ms>=1000) //1秒到,1ms*1000=1000ms {

Cnt2Ms=0; //2ms计数变量清零

if(++Second>=60) //秒加1,到60秒,则分加1 {

Second=0; //秒清零

if(++Minute>=60) //分家1,到60分,则小时加1

{ - 9 -

Second=0; //秒清零 {

Minute=0; //分清零

if(++Hour>=24) //小时加1,到24小时 Hour=0; //小时清零

}

} } } } }

//定时器0中断服务函数,提供1ms 的时标信号

void timer0_intr(void) interrupt 1 {

static uchar Cnt200us=0; //200us计数变量 if(++Cnt200us>=5) //0.2ms*5=1ms {

Cnt200us=0; //清空计数变量 SystemFlag1Ms=1; //1ms标志位置1 } }

程序解释:

(1)程序中第一次提出了时标信号这个概念,主程序中的任务是在时标信号的驱动下完成的。本程序的“时标信号”是标志位(Flag1ms ), 每隔1ms ,时标信号被置1,主程序中检测到时标信号置1后,再清零。1ms 的是通过定时器定时来完成,定时器200us 溢出一次,连续5次,置位1次时标信号。

(2)主程序中的任务有3个:缓冲区数据刷新(TimerToBuffer())、6位数码管的扫描(Seg7Display())和数字钟计时。缓冲区数据刷新和之前的函数一样,不需解释。数码管扫描的程序中和之前的程序相比,做了一些改动。在之前的Seg7Display()函数中,数码管的逐位扫描是通过延时1ms 来实现的,6位数码管延时需要6ms 的延时,在扫描数码管这段时间内,别的任务只能等待。扫描完数码管后,再执行别的任务,数码管的最后一位点亮的时间并非是1ms ,而是1ms+别的任务的执行时间,这将导致数码管最后一位和其余5位分配时间不一致,最后1位的亮度高于其它5位。

本程序中,利用定时器产生的1ms 时标信号,每间隔1ms 执行一次Seg7Display()函数, 执行一次Seg7Dislplay()只完成一位数码管的扫描,根据静态变量i 的值送入段码和位码,

- 10 -

变量i 加1,下一个1ms 的时标信号到来后,送入下一位数码管的段码和位码,依次循环。

(3)时间的计时也是通过1ms 的时标信号来完成的。1ms 时标信号每到来一次,计数变量Cnt2Ms 加1,计数500次,则为1秒,其余的和以前的程序一样。

总结:在本节之前,程序中任务的时间分配是通过延时函数来实现的,单个任务中有延时函数看不出问题,但当程序中的任务多了,任务之间会相互堵塞,运行效率较低。本节的例程给出了LED 闪烁、数码管扫描等任务采用定时器时标信号轮询的方法,没有延时语句,任务之间不会堵塞,提高了运行效率。

- 11 -

任务五 定时器的初步认识

3.5.1 任务介绍

在动态数码管的学习中,我们已经用DelayMs()延时函数实现了数字钟的走时,但是这种方式下的数字钟走时不够准确。生活中,我们需要精确计量时间时,通常会借助于走时准确的秒表,51单片机内部也有一个类似于秒表的装置,我们称之为定时器,借助于定时器,我们可以实现走时准确的数字钟。

本节的任务是:利用单片机定时器完成走时准确的数字钟,另外在程序中,数码管扫描也由定时器来驱动。

3.5.2知识准备

1、定时器的引入

在讲述定时器的原理之前,我们先看一下图3.5.1中的水龙头向水盆滴水的画面。在画面中,水龙头由于没有关紧,水 一滴一滴地滴向脸盆,盆的容量是有限的,水会在某一个时刻

从水盆中溢出。假设一开始水盆没有水,65536个水滴恰好可

以把水盆装满,恰好是计数了65536次。如果我们计数1000 次怎么办呢?向水盆中预先装下了64536滴水,然后打开水龙 头,开始滴水,等到水盆中的水溢出了,自然是计数了100次。如果水滴的速度是恒定的,1滴/1秒,计数就变成了计时了。

图3.5.1 水盆滴水 实际古人计时装置“刻漏”的原理和水盆滴水的原理相似。

51单片机的定时器/计数器的原理与上面讲述的水盆滴水的例子类似,如表3.5.1所示。

3.5.1 定时/计数器和水盆滴水的类比

2、定时器的内部结构及工作原理

- 1 -

在51单片机内部有2个定时器,分别称为定时/计数器0、定时/计数器1,每个定时/计数器具有计数和定时两大功能,有4种工作方式。定时/计数器0和定时/计数器1配置上完全相同,现用定时/计数器1的工作方式1来说明定时器内部结构与工作原理。图3.5.2为定时/计数器1在工作方式1下的内部结构图。

(1)定时/计数器1的脉冲源有两种:当定时/计数器1工作于定时方式时,加1脉冲由振荡器的12分频提供(12M 晶振的12分频为1MHz )。当定时/计数器1工作于计数方式时,加1脉冲由外部脉冲源提供,P3.4是定时/计数器0的外部脉冲源输入端,P3.5是定时/计数器1的外接脉冲输入端。定时/计数器工作于定时还是计数方式,取决于选择开关C/T ,当C/T =0时工作于定时方式,C/T =1时工作于计数方式。

(2)脉冲要经过启动开关才能到达RAM 。启动开关=TR1 & (GATE | INT1) ,GATE 为门控位,INT1为外部中断1。当GATE=0时,GATE | INT1的结果为1,则启动开关仅有TR1决定。当GATA=1,则启动开关的不仅由TR1决定,还要由来自于外部中断1的信号决定是否开启中断。

(3)两个8位的RAM ,高八位的RAM 称为TH1,低八位的RAM 称为TL1,组合在一起共16位。每来一个脉冲,RAM 的值加1,当RAM 的数值超过65535时,计数器会溢出,溢出标志位TF1会由0变成1,同时TF1的变化会引发中断事件的发生。要设定不同的定时时间,在定时/计时器启动之前,向RAM 预先装入初值。

图3.5.2 定时/计数器1的内部结构(方式1)

3、定时器的方式控制字

51单片机的特殊功能寄存器(SFR)中有两个属于定时器的寄存器,分别为TMOD 和TCON 。 (1)TMOD 寄存器

表3.5.1 TMOD寄存器

————----------------————----------------————---——

-——

-——

- 2 -

从表3.5.1中我们可以看到,TMOD 被分成两份,每份四位,高四位用于定时/计数器1的控制,低四位用于定时/计数器0的控制。结合图3.5.2 定时/计数1的内部结构,GATE 参与了启动开关的选择,称为门控位。C/T是用来选择定时还是计数,M1M0是下面要介绍的4种工作方式的选择位。

(2)TCON 寄存器

表3.5.2 TCON寄存器

TCON 寄存器也被分成了两份,前四位用于定时/计数器,后四位用于外部中断,如表3.5.2所示。前四位中又被分成了两份,分别属于定时/计数器1和定时/计数器0。其中TF 是定时器溢出标志,没有溢出时为0,溢出后为1。TR 参与了定时器的启动。

需要说明的是,TMOD 寄存器中的每一个位不可以单独寻址,举例:定时/计数器0,定时模式,门控位为0,方式0(M1M0=01),可以写成TMOD=0x05。TCON 寄存器中的每一个位可单独寻址,举例:启动定时器0,TR0=1就可以了。

4、定时/计数器的四种工作方式

51单片机的定时器有四种工作方式,TMOD 寄存器中M1和M0位决定了使用哪种工作方式。

M1M0为的配置和定时器的工作方式的对应关系如表3.5.3所示。

表3.5.3 定时器工作方式

工作方式0和工作方式3很少使用,在这里不做专门的讲解,下面介绍工作在方式1和(1)工作方式1:

当定时/计数器处于工作方式1时,两个8位的RAM 都参与计数,即计数的范围是:0~

- 3 -

工作方式2的特点。

65535。需要注意,工作于方式1时,定时/计数器溢出后,需要手动装入初值。

(2)工作方式2

定时/计数器处于工作方式2时,相比于方式1,其计数范围缩小了,高8位的RAM 不参与计数,低8位的RAM 参与计数。其计数范围是0~255。高8位的RAM 闲置不用吗?实际上不是这样的,首先来分析一下定时/计数器在工作方式1下存在的问题。

开发板的晶振频率为12MHz ,12分频后为1MHz ,脉冲周期是1us ,选择定时模式时,每隔1us ,RAM 的值加1,16位的定时/计数模式,最大定时范围为65.536ms 。我们要实现数字钟的1秒定时,可以先定时50ms (RAM 初值=65536-50000=15536), 然后连续定时20次,就可以实现1秒的定时。在介绍工作方式1时,提到溢出后需要手动装入初值(15536),如果不手动装入初值,则溢出后,RAM 从0开始计数。

RAM 的初值重装是在中断服务函数中完成的。定时/计数器溢出后产生中断,到进入中断服务函数后装入初值,是需要耗费时间的。实际是,定时/计数器溢出后,从0开始计数,运行一段时间初值重装了,又从初值运行。尽管这段时间很小(几十个us ),但连续定时,日积月累,误差就大了,所以工作方式1下,定时的精度不高。

工作方式2下初值的重装不需要手动装入,一旦溢出,硬件自动完成初值的重装,中间没有时间耽搁,方式2比方式1定时要准确。定时/计数器初始化时,高8位RAM 也装入初值,当定时/计数器溢出后,硬件自动把高8位RAM 存放的初值导入到低8位RAM ,完成初值重装。

5、定时器/计数器的初始化和溢出标志位处理 定时器/计数器初始化设置如下:

(1)TMOD 寄存器的配置:定时/计数的选择,GATE 门的设置,工作方式的选择。 (2)装入初值,根据任务的要求,给THn 、TLn(n为0或1) 装入初值。 (3)开总中断:EA=1,开定时器中断:ETn(n为0或1)=1。

(4)如果还有别的中断,根据任务的重要性,需要确定中断优先级,配置IP 寄存器。 (5)启动定时器/计数器,TRn(n为0或1)=1。

定时器/计数器溢出可以采用查询和中断两种方式进行处理。查询方式效率低(主程序中不停的查询溢出标志位TFn(n为0或1) 是否置位),本书中采用中断方式。

6、定时/计数应用举例 例程1:计数的应用

自动化生产线上,12瓶易拉罐饮料为一箱,光电传感器检测到一瓶易拉罐就给送出一个脉冲,控制器连续接收到12个脉冲,则作为一个包装。本例用按键来模拟光电传感器发出的脉冲,按键接P3.4(定时/计数器0的外接脉冲输入端),用发光二极管的状态取反来模拟一个包装的动作,发光二极管接单片机的P0.0。连续按下12次按键,发光二极管状态取反一次。

- 4 -

根据任务要求,定时/计数器的配置如下:使用定时/计数器0,计数、工作方式1。程序如下:

#include

#define uchar unsigned char #define uint unsigned int

sbit LED=P0^0; //LED接口定义 //计数器0初始化 void Timer0Init() {

TMOD=0x05; //GATE=0,C/T=1,M1M0=01; TH0=(65536-12)/256; //RAM高8位赋值 TL0=(65536-12)%12; //RAM低8位赋值 ET0=EA=1;

//开定时器中断和总中断

TR0=1; //启动定时器

}

//主函数

void main(void) {

Timer0Init(); //定时/计数器初始化

while(1) { }

//计数器0中断服务函数

void Timer0Intr(void) interrupt 1 {

TH0=(65536-12)/256;

TL0=(65536-12)%12; //溢出后重新赋初值

LED=!LED; //中断每产生一次,发光二极管的状态取反一次 }

程序解释:

- 5 -

//执行别的任务 }

(1)Timer0Init()函数完成计数器的初始化。外部中断不参与计数器的开启,门控位:GATE=1;计数模式:C/T=1;工作方式1,M1M0=01。每12个脉冲到来,计数器溢出一次,初值为(65536-12),取出高8位和低8位,赋给TH0和TL0。最后开中断,启动计数器。

(2)计数器溢出后,CPU 响应中断服务函数,在中断服务函数中,重新赋予初值,并完成LED 的状态取反。

例程2:定时的应用

用定时的方式实现LED 闪烁,LED 闪烁的频率为1Hz 。LED 接单片机P0.0引脚,0.5秒亮,

0.5秒灭。根据任务要求,定时/计数器的配置如下:使用定时/计数器1,计时、工作方式2。 #include

#define uchar unsigned char #define uint unsigned int

sbit LED=P1^0; //LED接口定义 bit Flag500Ms=0; //500ms标志位 //定时器1的初始化 void Timer1Init() {

TMOD=0x02; //GATE=0,C/T=0,M1M0=10; TH1=256-200;

ET1=EA=1; } //主函数 void main(void) {

Timer1Init(); //定时器的初始化 while(1) {

if(Flag500Ms==1) //500ms标志位置1 {

Flag500Ms=0; //清空500ms 标志位 LED=!LED; //LED状态取反 }

- 6 -

//RAM高八位赋值

//开定时器中断和总中断 //启动定时器

TL1=256-200; //RAM低八位赋值,200us 的定时

TR1=1;

}

//定时器1的中断服务函数

void timer0_intr(void) interrupt 1 {

static uint Cnt200us=0; //200us计数变量

if(++Cnt200us>=2500) //200us*2500=500ms {

Cnt200us=0; //清空计数变量 Flag500Ms=1; //500ms标志位置1

} }

程序解释:

(1)Timer0Init()函数完成定时器1的初始化。门控位不参与定时:GATE=0;定时:C/T=0;工作方式2,自动重装模式,M1M0=10。所以TMOD=0x20。200us 定时器溢出一次,8位自动重装模式,所以TH1=TL1=(256-200)=56。

(2)由于采用了自动重装模式,在中断服务函数中,不需要重新赋初值。本任务的定时目标是500ms ,程序中定时器的定时长度是200us ,所以需要连续定时。每进入一次中断服务函数,200us 计数变量(Cnt200us )加1,Cnt200us 计数到2500,整个计时的时间为200us ×2500次=500ms。

(3)500ms 到来后,程序中并没有像上一个计数例子一样,在中断服务函数中完成LED 的闪烁,而是把500ms 标志位(Flag500Ms)置1,在主程序中,检测到该标志位置1,则完成LED 的闪烁。

(4)计数变量Cnt200us 在定义的时候,uint 前面多了个关键字static ,其作用是声明变量Cnt200us 为局部静态变量。什么是局部静态变量呢?C 语言变量根据定义的位置不同分为全局变量、局部变量,

全局变量是在函数外部定义的变量,又称外部变量。全局变量一般定义在程序的开始处,可以为多个函数共同使用,其有效的作用范围是从它定义的位置开始整个程序文件结束为止。

局部变量是在一个函数内部内部定义的变量,该变量只能在定义它的那个函数范围内有效,不同的函数可以使用相同的局部变量名。由于它们的作用范围不同,不会互相干扰,函数的形式参数也属于局部变量。

局部变量根据存储空间分配的不同又分为自动变量和静态变量。在函数内部定义的变量,如果不做专门说明,则为自动变量。自动变量的特点是只在定义它们的时候才分配存储空间,

- 7 -

}

在定义他们的函数返回时系统回收变量所占用的存储空间,对这些变量存储空间的分配和回收是系统自动完成的,

静态变量在定义是前面多了个关键字static ,局部静态变量仍然保留局部变量的特性,只能在定义的函数内部使用,但与自动变量不同的是,再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。

弄明白了什么是局部静态变量,再来看中断函数中计数变量Cnt200us 的使用就简单多了。首次调用中断函数时,计数变量Cnt200us 定义且赋初值0,紧跟着在函数中Cnt200us 的值加1,退出出中断服务函数后,Cnt200us 的值不释放,仍然为1。

3.5.3任务实施

本节的任务程序实现如下:

#incldue

#define uchar unsigned char #define uint unsigned int

//段码 uchar code Seg7Code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; uchar code Seg7Posit[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; //位码 uchar DispBuffer[6]; //缓冲区 bit SystemFlag1Ms=1; //1ms标志位

//6位数码管显示子函数,在第二位和第四位上显示小数点 void Seg7Display() {

static uchar i=0;

P1=0xff; //消隐 P0=Seg7Code[disp_buff[i]]; //送段码

if((i==1)||(i==3)) P0&=~0x80; //在第二位和第四位数码管上加小数点 P1=Posit[i]; //送位选 if(++i>=6) i=0; }

//把时间送入缓冲区

void TimerToBuffer(uchar nhour,nminute,nsecond) {

DispBuffer[0]=nhour/10;

DispBuffer[1]=nhour%10; //小时的个位和十位的分解

- 8 -

DispBuffer[1]=nhour%10; //小时的个位和十位的分解 DispBuffer[2]=nsecond/10;

DispBuffer[3]=nsecond%10; //分的个位和十位的分解 DispBuffer[4]=nminute/10;

DispBuffer[5]=nminute%10; //秒的个位和十位的分解 }

//定时器0初始化 void Timer0Init() {

TMOD=0x02; //GATE=0,C/T=0,M1M0=02; TH0=56; //高8位RAM 赋值

TL0=56; //低8位RAM 赋值,200us 定时 ET0=EA=1; //开定时器中断和总中断

TR0=1; //开启定时器 }

//主函数

void main(void) {

uchar Hour=9; //小时

uchar Minute=23; //分

uchar Second=37; //秒

uint Cnt1Ms=0; //1ms计数器

Timer0Init(); //定时器0初始化 while(1) {

if(FlagSystem1Ms==1) //定时器产生的1ms 的时标信号到 {

SystemFlag1Ms=0; //时标信号清零

TimerToBuffer(); //显示数据送往缓冲区

Seg7Display(); //6位数码管动态显示

if(++Cnt1Ms>=1000) //1秒到,1ms*1000=1000ms {

Cnt2Ms=0; //2ms计数变量清零

if(++Second>=60) //秒加1,到60秒,则分加1 {

Second=0; //秒清零

if(++Minute>=60) //分家1,到60分,则小时加1

{ - 9 -

Second=0; //秒清零 {

Minute=0; //分清零

if(++Hour>=24) //小时加1,到24小时 Hour=0; //小时清零

}

} } } } }

//定时器0中断服务函数,提供1ms 的时标信号

void timer0_intr(void) interrupt 1 {

static uchar Cnt200us=0; //200us计数变量 if(++Cnt200us>=5) //0.2ms*5=1ms {

Cnt200us=0; //清空计数变量 SystemFlag1Ms=1; //1ms标志位置1 } }

程序解释:

(1)程序中第一次提出了时标信号这个概念,主程序中的任务是在时标信号的驱动下完成的。本程序的“时标信号”是标志位(Flag1ms ), 每隔1ms ,时标信号被置1,主程序中检测到时标信号置1后,再清零。1ms 的是通过定时器定时来完成,定时器200us 溢出一次,连续5次,置位1次时标信号。

(2)主程序中的任务有3个:缓冲区数据刷新(TimerToBuffer())、6位数码管的扫描(Seg7Display())和数字钟计时。缓冲区数据刷新和之前的函数一样,不需解释。数码管扫描的程序中和之前的程序相比,做了一些改动。在之前的Seg7Display()函数中,数码管的逐位扫描是通过延时1ms 来实现的,6位数码管延时需要6ms 的延时,在扫描数码管这段时间内,别的任务只能等待。扫描完数码管后,再执行别的任务,数码管的最后一位点亮的时间并非是1ms ,而是1ms+别的任务的执行时间,这将导致数码管最后一位和其余5位分配时间不一致,最后1位的亮度高于其它5位。

本程序中,利用定时器产生的1ms 时标信号,每间隔1ms 执行一次Seg7Display()函数, 执行一次Seg7Dislplay()只完成一位数码管的扫描,根据静态变量i 的值送入段码和位码,

- 10 -

变量i 加1,下一个1ms 的时标信号到来后,送入下一位数码管的段码和位码,依次循环。

(3)时间的计时也是通过1ms 的时标信号来完成的。1ms 时标信号每到来一次,计数变量Cnt2Ms 加1,计数500次,则为1秒,其余的和以前的程序一样。

总结:在本节之前,程序中任务的时间分配是通过延时函数来实现的,单个任务中有延时函数看不出问题,但当程序中的任务多了,任务之间会相互堵塞,运行效率较低。本节的例程给出了LED 闪烁、数码管扫描等任务采用定时器时标信号轮询的方法,没有延时语句,任务之间不会堵塞,提高了运行效率。

- 11 -


相关内容

  • 单片机课程设计报告智能小车
  • 信息与通信工程学院 单片机系统课程设计报告 完成日期:2014年 12 月 8日 系: 专 业: 班 级: 设计题目: 学生姓名: 指导教师: 通信工程系 通信工程 通信工程124班 智能小车 张歆迪 赵京京 吴宝春 摘 要 本设计主要应用STC89C52作为控制核心,与驱动电路相结合,充分发挥ST ...

  • 课程设计八路彩灯
  •  前言: 由于集成电路的迅速发展,使得数字逻辑电路的设计发生了根本性的变化.在设计中更多的使用中,规模集成电路,不仅可以减少电路组件的数目,使电路简捷,而且能提高电路的可靠性,降低成本.因此用集成电路来实现更多更复杂的器件功能则成为必然.随着社会市场经济的不断繁荣和发展,各种装饰彩灯.广告彩灯越来 ...

  • 秒表系统设置课程设计
  • 课 程 设 计 说 明 书 学生姓名: 学 号: 学 院: 专 业: 电子信息工程 题 目: 专业综合实践之硬件部分: 基于单片机的秒表系统设计 指导教师: 职称: 2013 年 12 月 16 日 课程设计任务书 13/14 学年第 一 学期 学 院: 专 业: 电子信息工程 学 生 姓 名: 学 ...

  • 电工电子课程设计l
  • 目录 课设设计任务书 „„„„„„„„„„„„„„„„„„„„„„„„ 2 内容提要 „„„„„„„„„„„„„„„„„„„„„„„„„„„ 4 1 有害气体的检测报警及抽排电路设计„„„„„„„„„„„„„„„„ 5 1.1 设计总体方案„„„„„„„„„„„„„„„„„„„„„„„„„ 5 1. ...

  • 张发锐凌子豪课程设计(出租车计价器)
  • 安徽矿业职业技术学院 出租车计价器设计 课程设计报告书(2014届) 题 目 数字电子出租车计价器 姓 名 张发锐 凌子豪 学 号 [1**********]4 [1**********]5 专 业 电气自动化技术 指导老师 田野 设计时间 2016年6月8日-2016年6月18日 目 录 一.设计 ...

  • 课程设计内容模板(要求) - 副本
  • 辽 宁 工 业 大 学 题目: 基于PLC 的混矿流量控制系统设计 院(系): 电气工程学院 专业班级: 自动化 班 学 号: 学生姓名: 指导教师: (签字) 起止时间: 2014.6.30~2014.7.11 课程设计(论文)任务及评语 院(系):电气工程学院 教研室: 自动化 注:成绩:平时2 ...

  • 单片机10秒秒表课程设计
  • 赣 南 师 范 学 院 物理与电子信息学院 课程设计Ⅳ设计报告书 基于AT89S52单片机的 10秒秒表的设计 姓名: 匡远熹 班级: 09电子信息工程 学号: 090802015 指导老师:刘小燕 时间: 2012.1.01 目 录 内容摘要 ........................... ...

  • 2014年基层党建工作汇报材料
  • 首先,我代表xxx镇党委对各位领导莅临xxx镇检查指导工作,表示热烈的欢迎.下面,我就xxx镇的基层党建工作向大家作简要汇报. 今年以来,我镇党委以xx届三中全会精神和"xxxx"重要思想为指导,在基层党组织建设中与时俱进,开拓创新,深入开展"三级联创".&q ...

  • 喜欢钟表的国王教案
  • 喜欢钟表的国王 活动要求: 1.在故事的情境中,帮助幼儿了解时间在人们日常生活中的重要作用. 2.帮助幼儿建立时间概念,鼓励幼儿结合自己的感受大胆表达.表述. 活动准备: 有关故事的PPT.水彩笔若干支.定时器一只. 活动过程: 一.认识钟表 1.教师出示一个圆,问幼儿:像什么?(幼儿回答问题时,重 ...