第5章 定时器应用案例 基于Proteus硬件仿真工具和Keil程序设计软件,本章设计了微控制器中定时/计数器技术原理及应用相关的案例,包括计数器功能应用、定时器控制流水灯、定时器控制交通灯,每个案例提供了详尽的软硬件设计,包括仿真电路和完整的参考程序。 5.1计数器功能的应用 5.1.1案例概述 通过8051系列微控制器控制实现如下功能: (1) 从外部输入2个计数脉冲到定时/计数器引脚T0后产生中断,在T0计数中断服务程序中,控制接在P1口的8个LED依次点亮200ms,返回主程序; (2) 从外部输入5个计数脉冲到定时/计数器引脚T1后产生中断,在T1计数中断服务程序中,控制接在P2.1引脚的蜂鸣器发出报警声5次,返回主程序; (3) 定时/计数器T0和T1总的中断次数通过P0口输出到七段BCD数码管显示。 5.1.2要求 (1) 了解中断的步骤及定时/计数器的工作方式; (2) 关闭和启动定时器的条件和关闭和打开中断的条件; (3) 计数初值的定义。 5.1.3知识点 定时/计数器硬件原理、计数器功能应用编程。 5.1.4电路原理图 案例电路原理图如图51所示。 图51计数器应用电路图 在电路图中,利用虚拟元件脉冲信号发生器产生外部脉冲信号,然后送入定时/计数器的输入引脚P3.4、P3.5。因为定时/计数器T0、T1设置为计数功能,用来检测P3.4、P3.5引脚接收的脉冲信号个数,当达到设置的计数值的个数后,产生计数器溢出中断,然后暂停主程序,转而执行计数器中断子程序。 案例通过P0口驱动2个BCD七段数码管显示计数器总中断次数。BCD七段数码管的输入是4位二进制BCD码。BCD七段数码管按8421BCD编码来点亮数码管显示数字,例如,当输入8421码DCBA=0000时,数码管就显示0; 当 DCBA=0100时,应显示4; DCBA=0011时显示3; DCBA=1111时显示F,以此类推。另外,微控制器通过P2.3引脚输出高电平驱动蜂鸣器发音。 5.1.5案例应用程序 #include <intrins.h> #include <REG51.H> #define TRUE 1 #define uchar unsigned char uchar t3=0; //总中断次数 unsigned char ucTimes; sbitBEEP=P2^3; void Play(unsigned char t); void time(unsigned int ucMs); //延时单位: ms void main(void) { TMOD=0x55; //设置定时器0、1为外部脉冲输入计数器工作,方式1,16位计数器 TH0=0xFF; TL0=0xFE; //设置计时器0的初值为FFFEH,即2个计数脉冲产生中断 TH1=0xFF; TL1=0xFB; //设置计时器1的初值为FFFBH,即5个计数脉冲产生中断 TR0=1;//开启定时器0 TR1=1;//开启定时器1 IE=0x8a;//打开定时器0、1中断 P0=t3;//总中断次数送P1口显示 while(1){}//等待定时器0、1的中断 } //定时器0的中断服务程序 void timer0(void) interrupt 1 { EA=0; //关总中断 TR0=0; //停止计时 t3++; //总中断次数加1 P0=t3; //总中断次数送P0口显示 for(ucTimes=0;ucTimes<=8;ucTimes++) //循环点亮P1口的8个灯 { P1=0xff-(0x01<<ucTimes); //亮灯需低电平驱动 time(700); } TH0=0xFF; TL0=0xFE; TR0=1; EA=1; } void timer1(void) interrupt 3 { EA=0;//总中断 TR1=0; //停止计时 t3++;//总中断次数加1 P0=t3; //总中断次数送P0口显示 for(ucTimes=0;ucTimes<5;ucTimes++) { Play(2); time(200); } TH1=0xFF; TL1=0xFB; TR1=1;//开启定时器 EA=1;//开总中断 } void DelayMS(unsigned intx) { uchar t; while(x--) { for(t=0;t<120;t++); } } void Play(unsigned char t) { uchar i; for(i=0;i<100;i++) { BEEP=~BEEP; DelayMS(t); } BEEP=0; } void delay_5us(void)//延时5μs { _nop_(); _nop_(); } void delay_50us(void)//延时50μs { unsigned char i; for(i=0;i<4;i++) { delay_5us(); } } void delay_100us(void)//延时100μs { delay_50us(); delay_50us(); } void time(unsigned int ucMs)//延时单位: ms { unsigned char j; while(ucMs>0){ for(j=0;j<10;j++) delay_100us(); ucMs--; } } 5.1.6案例分析 案例代码中主要有3个函数: 主函数main()、计数器0中断函数timer0()和计数器1中断函数timer1(),3个函数是并行关系。主函数和两个计数器中断函数实现不同的功能,可以直观地理解3个函数的并行性以及中断函数timer0()和timer1()执行的随机性。 通过本案例,可以将计数器的中断工作方式、计数初值、计数溢出中断标识位、中断号与对应的C51语言程序控制语句关联起来,更好地理解定时计数器硬件结构和工作原理。 另外,在计数器1中断函数timer1()中要实现P2.3引脚驱动蜂鸣器发出5次报警声。通过改变P2.3引脚输出高低电平信号的间隔时间控制报警声的频率,具体实现代码如下: void Play(unsigned char t) { uchar i; for(i=0;i<100;i++) { BEEP=~BEEP; DelayMS(t); } BEEP=0; 通过改变P2.3引脚输出高低电平信号的次数控制报警声的长短,具体实现代码如下: for(ucTimes=0;ucTimes<5;ucTimes++) { Play(2); time(200); } } 5.2定时器控制流水灯 5.2.1案例概述 通过8051系列微控制器控制实现如下功能: 使用定时器T1的中断来控制P1连接的8个LED灯的流水闪烁,即依次轮流控制每个灯点亮50ms,实现流水闪烁效果。 5.2.2要求 (1) 学习使用微控制器定时器的工作原理、应用和编程方法; (2) 熟悉微控制器中定时器中断处理程序编写。 5.2.3知识点 定时器硬件结构、工作方式、定时器中断方式的应用。 5.2.4电路原理图 案例控制电路如图52所示,8个LED灯连接在微控制器的P1口的8个引脚上,限流作用的电阻阻值设定为200Ω,灯采用灌电流连接方式连接。 图52定时器控制流水灯电路图 5.2.5案例应用程序 #include <REG51.H> unsigned char kkk; unsigned char Count=0; void main(void) { TMOD=0x10; //定时器T0的工作方式1 TH1=(65536-60536)/256;//定时器T0的高8位的初值 TL1=(65536-60536)%256;//定时器T0的低8位的初值 TR1=1; //启动定时器T0 kkk=0xfe; P1=kkk;//流水灯初始状态控制 EA=1; //开总中断 ET1=1; //定时器T0中断允许 while(1) ;//无限循环,等待定时器溢出中断 } void Time1(void) interrupt 3 using 0 //定时器T0的中断服务程序 { TH1=(65536-60536)/256;//重新设置定时器T0的高8位的初值 TL1=(65536-60536)%256;//重新设置定时器T0的低8位的初值 if(++Count!=10) return;// 50ms(10×5ms)延时切换 Count=0; kkk=kkk<<1;//数据左移1位 kkk=kkk|1;//数据末位置1 if(kkk==0xff)kkk=0xfe; //设置流水灯的控制数据初始值 P1=kkk;//更新流水灯状态控制数据 } 5.2.6案例分析 案例中,需要控制每个LED灯点亮50ms,定时由定时器硬件实现。程序中设置定时器每次定时5ms,为了简化初值计算,其初值设置如下: TH1=(65536-60536)/256; //重新设置定时器T0的高8位的初值 TL1=(65536-60536)%256; //重新设置定时器T0的低8位的初值 50ms的时间实现通过定时器中断函数Time1()中的两条语句实现,即 if(++Count!=10) return;//50ms(10×5ms)延时切换 Count=0; 每当定时器硬件5ms定时到就产生溢出中断,暂停主程序main()函数后,CPU执行中断函数Time1(),通过if(++Count!=10) 判断10次5ms延时是否完成。具体方法是:++Count后变量Count的值不等于10,则return返回到调用此语句所在函数的地方,即返回到调用定时器1中断函数Timer1()的地方,就是主程序的while(1)处,即回到main()函数继续等待下一次50ms定时中断发生。这个过程重复10次后,即延时10次5ms之后(此时,P1口的引脚状态保持50ms,即流水灯状态保持了50ms),不再return返回到主程序的while(1)处,而是顺序执行中断函数后续语句: Count=0; kkk=kkk<<1;//数据左移1位 kkk=kkk|1;//数据末位置1 if(kkk==0xff)kkk=0xfe; //设置流水灯的控制数据初始值 P1=kkk;//更新流水灯状态控制数据 通过这段代码,首先Count=0,为下次50ms定时做准备。然后更新P1口的引脚状态值,从而更新流水灯的状态。 5.3定时器控制交通灯 5.3.1案例概述 通过8051系列微控制器控制十字路口模拟交通灯的变化,分别实现如下功能: (1) 东西向绿灯与南北向红灯亮4s; (2) 东西向黄灯开始闪烁6次,绿灯关闭; (3) 东西向红灯与南北向绿灯亮4s; (4) 南北向黄灯开始闪烁6次,绿灯关闭; (5) 如此往复。 其中定时功能采用微控制器的定时器硬件模块实现。 5.3.2要求 (1) 掌握微控制器定时器的原理、工作方式和应用编程; (2) 掌握通过定时器硬件实现模拟交通灯的延时的时间控制方法。 5.3.3知识点 定时器硬件原理、定时器功能的应用编程。 5.3.4电路原理图 定时器控制十字路口模拟交通灯电路如图53所示。 图53交通灯电路 案例中,东西向的红、黄、绿3个灯分别由微控制器的P0.0、P0.1、P0.2 3个引脚控制。南北向的红、黄、绿3个灯分别由微控制器的P0.3、P0.4、P0.5 3个引脚控制。另外,所有LED灯都是通过灌电流连接方式连接的。 5.3.5案例应用程序 #include<reg51.h> #define uchar unsigned char #define uint unsigned int sbit R_A=P0^0; //东西向指示灯 sbit Y_A=P0^1; sbit G_A=P0^2; sbit R_B=P0^3; //南北向指示灯 sbit Y_B=P0^4; sbit G_B=P0^5; uchar TC=0,FC=0,K=1; //定义延时倍数、闪烁次数、操作类型变量 //定时器1中断函数 void Timer1()interrupt3 { TL1=-50000/256; TH1=-50000%256;//50ms定时的初值 switch(K) { case 1: //东西向绿灯与南北向红灯亮4s R_A=1;Y_A=1;G_A=0;//P0^2=0,灯亮 R_B=0;Y_B=1;G_B=1; if(++TC!=80) return; //4s(80×50ms)切换 TC=0; K=2; break; case 2://东西向黄灯开始闪烁,绿灯关闭 if(++TC!=10) return;//延时10×50ms TC=0; Y_A=~Y_A;G_A=1; if(++FC!=12) return; //闪烁6次 FC=0; K=3; break; case 3://东西向红灯与南北向绿灯亮4s R_A=0;Y_A=1;G_A=1; R_B=1;Y_B=1;G_B=0; if(++TC!=50) return; //4s(80×50ms)切换 TC=0; K=4; break; case 4://南北向黄灯开始闪烁,绿灯关闭 if(++TC!=10) return; //延时10×50ms TC=0; Y_B=~Y_B;G_B=1; if(++FC!=12) return; //闪烁6次 FC=0; K=1; break; } } //主程序 void main() { TMOD=0x10; //T1方式1 TL1=-50000/256; //初值设置,方式1最多56ms TH1=-50000%256; IE=0x88; TR1=1; while(1); } 5.3.6案例分析 案例代码中主要有两个函数: 主函数main()和定时器1的中断函数timer1()。主函数完成定时器1的初始化工作,交通灯亮灭的4种效果控制代码由函数timer1()完成。 案例中,为了简化定时器初值的十六进制转换计算,初值高8位采取取商计算,即TH0=(65536-50000)/256; 初值低8位采取取余计算,即TL0=(65536-50000)%256。因此,定时器初值为: TL1=-50000/256; TH1=-50000%256; 交通灯中的黄灯闪烁6次的实现在switch语句的case 2分支中,具体代码如下: if(++FC!=12) return; //闪烁6次 FC=0; Y_A=~Y_A; G_A=1; 每次执行语句++FC后判断变量FC的值。如果FC的值不等于12,则返回到调用此语句所在函数的地方,也就是返回到调用定时器1中断函数timer1()的地方,即主程序的while(1)处。此时黄灯状态为灭。 当定时器定时时间达到50ms后就产生溢出,随后程序重新进入中断函数timer1(),再次执行case 2分支,重新执行如下代码: if(++TC!=10) return; //延时10×50ms TC=0; Y_A=~Y_A;G_A=1; 当定时器硬件定时50ms溢出过程达到10次,即延时10个50ms后,才继续执行代码Y_A=~Y_A和G_A=1,实现Y_A取反,此时黄灯状态为亮。从而实现黄灯亮灭一次,实现闪烁的效果。 直到上述过程执行12次后,FC=12,不再return返回到主程序的while(1)处,而是顺序执行后边语句(FC=0; K=3;),为下次黄灯闪烁6次做准备,并切换交通灯的操作类型为3。另外,switch分支结构中的case 4分支实现的原理与case 2原理相同。 交通灯中的红灯或绿灯亮4s的实现在switch语句的case 1分支中,具体代码如下: if(++TC!=80) return; TC=0; 每次执行语句++TC后判断变量TC的值。如果TC的值不等于80,则返回到调用此语句所在函数的地方,也就是返回到调用定时器1中断函数timer1()的地方,即主程序的while(1)处。 当定时器定时时间达到50ms后就产生溢出,随后程序重新进入中断函数timer1(),再次执行case 1分支,重新执行如下代码: if(++TC!=80) return; TC=0; 当定时器硬件定时50ms溢出过程达到80次,即延时80次50ms后,不再return返回 到主程序的while(1)处,而是顺序执行TC=0,为下次延时4s做准备。此时已经延时80×50ms,实现绿灯或红灯持续亮4s的效果。另外,switch分支结构中的case 3分支实现的原理与case 1原理相同。