一、定时器/计数器
如果要对来自单片机外部的脉冲信号进行计数,或者利用单片机进行定时控制,就需要用到单片机的定时器/计数器。
1.基本概念
计数:51单片机中有T0和T1两个计数器,可以对单片机计数脉冲输入引脚上的脉冲计数,它们分别由两个八位计数单元构成。比如,T0由TH0和TL0两个八位特殊功能寄存器构成,所以T0和T1都是十六位的计数器,最大计数量是65536。T0和T1既可以作为计数器用,又可以作为定时器用。当作为计数器用时,T0和T1计数的脉冲分别从P3.4、P3.5引脚输入,此时这两个引脚只能输入计数脉冲,不能作普通的I/O口用了。
定时:51单片机的T0和T1也可以作为定时器用。设为定时方式时,T0、T1与外部输入断开,而与内部脉冲信号连通,对内部信号计数,这个内部信号就是单片机时钟振荡器12分频后的信号。假如单片机的时钟振荡器可以产生12MHZ的时钟脉冲信号,经12分频后得到1MHZ的脉冲信号,1MHZ的信号每个脉冲的持续时间(1个周期)为1μs。如果定时器T0对1MHZ的信号进行计数,当计到65536时,将需要65536μs,即65.536ms。此时,定时器计数达到最大值,也会溢出并送给CPU一个信号,通知CPU计数器已经计满了。所以定时器定时时间的长短,和单片机所使用的振荡器频率有关,振荡器频率越高,定时器每次加一的时间就越短,定时时间也越短。
2.结构及工作原理
结构:51单片机中的定时器、计数器是对同一种结构进行不同的设置而形成的。定时器/计数器T0和T1分别是由TH0、TH1和TL0、TL1两个八位计数器构成的十六位计数器,两者均为加一计数器。
工作原理:T0和T1用作计数器时,通过单片机的P3.4、P3.5引脚对外部脉冲信号计数,当该引脚出现一个高到低的负跳变时,计数器加一,直到产生溢出。T0和T1用定时器时,晶振产生的振荡信号12分频后作为输入,定时器以12分频后的脉冲周期为基本计数单位,对输入信号计数,直到产生溢出。无论它们工作于计数方式或计时方式,计数时都不占用CPU时间,因此定时器、计数器的工作并不影响CPU其他工作的执行,这就是采用定时、计数器的优点。因为CPU是顺序执行程序的,如果让它延时一段时间再执行某个定时任务,它在延时的时间里就无法进行其他工作,这样做就会让CPU定时之外的其他工作无法进行。
3.控制寄存器与功能设置
定时、计数器必须在寄存器TCON、TMOD的控制下才能准确工作,因此必须掌握好这两个寄存器的控制方法。
方式控制寄存器(TMOD):TMOD属于单片机的特殊功能寄存器,功能是控制T0、T1的工作方式。它的字节地址为89H,不能按位操作,只能对整个字节读或写,比如设定初值可以写为TMOD=0x01,在上电和复位时,TMOD的初值为00H。
位序 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
位定义 | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
TMOD寄存器的高四位用来控制T1,低四位用来控制T0。
GATE:门控制位,用来控制定时、计数器的启动模式。GATE=0时,只要使TCON中的TR0置高电平,就可以启动定时、计数器工作;GATE=1时,除了需将TR0置高电平外,还需要外部中断INT0也为高电平时,才能启动定时、计数器T0工作。
C/T:定时、计数器模式选择位。该位为高电平,T0设置为计数器;该位为低电平,T0设置为定时器。
M1、M0:定时、计数器工作方式设置位。这两位不同取值的组合,可以将定时、计数设置为不同的工作方式。
M1 | M0 | 工作方式 | 说明 |
0 | 0 | 0 | 13位定时器,TH0的八位和TL0的五位,最大计数值8192 |
0 | 1 | 1 | 16位定时器, TH0的八位和TL0的八位,最大计数值65536 |
1 | 0 | 2 | 带自动重装功能的八位计数器,最大计数值256 |
1 | 1 | 3 | T0分成两个独立的八位计数器,T1在方式3时停止工作 |
控制寄存器(TCON):TCON属于特殊功能寄存器,主要功能是接收各种中断源送来的请求信号,同时也对定时器/计数器进行启动和停止控制。字节地址88H,它有八位,每位均可按位置位或复位。如TR0=1,只将该位置高,不改变其它位的状态。
位地址 | 8F | 8E | 8D | 8C | 8B | 8A | 89 | 88 |
位定义 | TF1 | TR1 | TF0 | TR0 |
TF1和TF0:分别是定时、计数器T1、T0的溢出标志位。当定时、计数器工作产生溢出时,会将TF1或TF0位置高,表示计数或计时有溢出。
TR1和TR0:分别是定时/计数器T1、T0的启动、停止位。这两个位置为高电平时,相应的定时、计数器就开始工作;设为低电平,就停止工作。
四种工作方式:T0、T1的工作方式由TMOD的M1、M0位共同控制。
1)方式0:当M1M0=00时,定时器/计数器被选定为工作方式0。
此时十六位寄存器只用了13位,它由 TL1的低五位和 TH1的八位构成。当计数器计数溢出时,则置位 TCON 中的溢出标志位 TF1,表示有溢出。
TMOD中的标志位 C/T 控制的电子开关决定了定时器/计数器的工作模式:
当 C/T=0时,T1为定时器工作模式,此时计数器的计数脉冲是单片机内部振荡器 12分频后的信号。当 C/T=1时,T1为计数器工作模式,此时计数器的计数脉冲为 P3.5 引脚上的外部输入脉冲,当 P3.5引脚上输入脉冲发生负跳变时,计数器加1。
T1或 T0能否启动工作,取决于 TR1、TR0、GATE和引脚INT1、INT0的状态:
当 GATE=0时,只要 TR1、TR0为1,就可以启动 T1、T0工作;当 GATE=1时,只有INT1或INT0引脚为高电平,且 TR1或 TR0置1时,才能启动 T1或 T0工作。
2)方式1:当M1M0=01时,定时器/计数器被选定为工作方式1。
此时为十六位计数器,由 TL1的八位和 TH1的八位构成。当计数器计数溢出时,则置位 TCON 中的溢出标志位 TF1,表示有数据溢出。同时十六位计数器从0开始重新计数。除了计数位数不同外,方式1的原理与方式0完全相同。
3) 方式2:当M1M0=10时,定时器/计数器被选定为工作方式2。
它由 TL1构成八位计数器和作为计数器初值的常数缓冲器 TH1构成。当 TL1计数溢出时,置溢出标志位 TF1为1的同时,还自动将 TH1的初值送入 TL1,使 TL1从初值重新开始计数。这样既提高了定时精度,同时在应用时,只需在开始时赋初值1次,简化了程序。
4) 方式3:方式3只适用于定时器T0。当T0工作在方式3时,被拆成两个独立的八位计数器TH0、TL0。
此时 TL0构成八位计数器可工作于定时/计数状态,并使用 T0的控制位与 TF0的中断源。 TH0只能工作于定时状态,使用 T1中的 TR1、TF1的中断源。
一般情况下,使用方式0~2即可满足需要。但在特殊场合,必须要求 T0工作于方式 3,而 T1工作于方式2,如需要 T1作为串行口波特率发生器。
定时/计数初值的计算:51内核单片机中,T1和T0都是增量计数器,因此不能直接将要计数的值作为初值放入寄存器中,而是将计数的最大值减去实际要计数的值的差存入寄存器中。可采用如下定时器/计数器初值计算公式:计数初值=2n-计数值 式中,n为由工作方式决定的计数器位数。
例如,当 T0工作于方式1时,n=16,最大计数值为65536,若要计数10000次,需将初 值设置为65536-10000=55536。如果单片机采用的晶振为11.0592MHz,则计数一次需要的时间为12分频后的一个脉冲周期:
对外部输入计数信号的要求:计数脉冲来自外部输入引脚T0或T1。当输入信号产生由1至0的跳变(即负跳变)时,计数器的值增1。每个机器周期的S5P2期间,都对外部输入引脚T0或T1进行采样。确认一次负跳变要花两个机器周期,即24个振荡周期,因此外部输入的计数脉冲的最高频率为系统振荡器频率的1/24。故对外部输入信号的要求如下,Tcy为机器周期。
二、单片机的中断
1.基本概念
单片机的中断指的是,单片机正在运行程序时,突然被突发事件打断,转去执行另外一段程序;当另一段程序执行结束,再回到原程序中断的位置继续执行。这种停止正在执行的工作,转去做其它工作,其它工作执行完,又返回执行原来的工作的现象叫做中断。
采用中断技术完全消除了单片机在查询方式中的等待现象,大大提高了单片机的工作频率和实时性。由于中断工作方式的优点明显,因此单片机的片内硬件中都带有中断系统。
中断系统的结构:MCS-51系列单片机的中断系统结构如图所示。
中断源:让CPU产生中断的信号称为中断源,又称中断请求源。51单片机提供了五个中断源,其中两个为外部中断请求源、两个片内定时/计数器的溢出中断请求源、一个串行口发送或接收中断请求源。
中断的优先级别:如果同时有两个中断源发出中断请求信号,CPU会按照硬件结构决定的自然优先级排列顺序,先接收级别高的中断请求,再接收级别低的中断请求。五个独立中断请求源的自然优先级排列顺序如表所示。
中断源 | 自然优先级 | 中断入口地址 | C51编译器的中断编号 |
外部中断INT0 | 第一 | 03H | 0 |
定时器T0 | 第二 | 0BH | 1 |
外部中断INT1 | 第三 | 13H | 2 |
定时器T1 | 第四 | 1BH | 3 |
串行口中断RI、TI | 第五 | 23H | 4 |
C语言编写中断服务函数的格式如下:
函数类型 函数名(形式参数列表)[interrupt n][using m]
其中,interrupt后面的n是中断编号,取值范围为0—4;using中的m表示使用的工作寄存器组号,这个如果不写,默认的就是第0组。例如,定时器 T0的中断服务函数如下编写:
void timer0(void) interrupt 1 using 0
{
//中断服务程序
}
中断的处理过程:CPU处理事件的过程称为中断响应过程。中断后再接着继续执行被中断的程序,称为中断返回。中断的处理过程和普通子程序调用是有本质区别的。中断的产生是随机的,什么时间会产生中断并不确定;而普通子程序调用,在主程序中某个位置会有一条子程序调用指令,产生子程序调用的时间是固定的、已知的。
2.中断系统的结构及控制
中断系统的结构:51单片机的中断系统中有5个中断请求源。中断源寄存器有2个,即定时器/计数器控制寄存器TCON和串行通信控制寄存器SCON,它们可以向CPU发出中断请求。中断允许寄存器有1个IE,功能是控制是否允许CPU产生各个中断。中断优先级寄存器有1个,功能是设置每个中断的优先级别——高级、低级。
中断系统的控制:51单片机中各种中断的控制主要是通过设置以下几个寄存器来实现的。
定时器/计数器控制寄存器TCON:TCON的功能是接收外部中断源(INT0、INT1)和定时器/计数器(T0、T1)送来的中断请求信号。字节地址为88H,可以进行位操作。
8FH | 8EH | 8DH | 8CH | 8BH | 8AH | 89H | 88H |
TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
AFH | —— | —— | ACH | ABH | AAH | A9H | A8H |
EA | —— | —— | ES | ET1 | EX1 | ET0 | EX0 |
在多个中断请求同时发生时,CPU按照自然优先级顺序接受它们的中断请求。当需要优先接受某些自然优先级低的中断请求,就需要通过IP寄存器来设置。IP寄存器的字节地址为B8H,可位操作。
—— | —— | —— | BCH | BBH | BAH | B9H | B8H |
—— | —— | —— | PS | PT1 | PX1 | PT0 | PX0 |
综合上述,一个中断源的中断请求被响应,需满足以下必要条件。
CPU开中断,即IE寄存器中的中断总允许位EA=1;
中断源发出中断请求,即该中断源所对应的中断请求标志位为1;
中断源的中断允许位为1,即该中断没有被屏蔽;
无同级别或更高级中断正在被执行。
3.中断请求的撤销
某个中断请求被响应后,如果不及时的撤消中断请求信号,就可能出现一次中断被多次响应。中断请求的撤消方法如下:
◇定时/计数器中断请求的撤消:该类型中断被响应后,硬件会自动把中断请求标志位(TF0、TF1)清0,因此中断请求是自动撤消的。
◇外部中断请求的撤消:
跳沿方式外部中断请求的撤消:中断标志位清0和外部中断信号的撤消。其中,中断标志位(IE0、IE1)清0是硬件自动完成的。而外部中断请求信号的撤消,由于跳沿信号过后也就消失了,所以是自动撤消的。
电平方式外部中断请求的撤消:中断请求标志是自动撤消的,但中断请求信号的低电平可能继续存在,在以后又会把已清0的IE0或IE1标志位重新置1。为此,需在中断响应后把中断请求信号输入引脚强制改变为高电平。
◇串行口中断请求的撤消:串行口中断的标志位是TI和RI,对这两个中断标志CPU不进行自动清0。因为在响应串行口的中断后,CPU需测试这两个中断标志位的状态,以判定是接收操作还是发送操作。所以撤消只能使用软件。
三、定时器/计数器和中断的应用
【例】定时器T0查询方式控制P2口八位发光二极管闪烁。
要求:T0工作于方式1,LED的闪烁周期是100ms,即亮50ms,再灭50ms。因为 LED 是接在 P2口上的,P2端口输出高电平时 LED 灭,输出低电平时 LED 亮,所以应该将控制 LED的状态,转化为对 P2口输出电平的控制,即每隔50ms让端口电平翻转一次,那 么翻转的间隔也就是定时时间长度应为50ms。
T0工作于定时方式,定时器的工作与单片机晶振频率有关,学习板上晶振频率为12MHz,12分频后送到 T0的脉冲频率为f=12/12MHz=1MHz,脉冲周期为T=1/f= 1μs,即每隔1μs定时器的值加1。计时50ms时,定时器需要计数50000个,所以定时器的初值应设置为65536-50000=15536,这个数在定时器初始化时要分别存入初值寄存器 TH0、TL0中。
查询方式的实现:定时器开始定时时,可通过编程让单片机不断查询溢出标志位 TF0 是否为1,若为1表示定时时间到,则将 P2口的状态取反。
#include<reg51.h>
void main()
{
TMOD=0x01; //设置T0的工作方式为方式1、定时
TH0=(65536-50000)/256; //设置定时器初值
TL0=(65536-50000)%256;
P2=0xff; //让LED的初始状态为熄灭
TR0=1; //启动定时器
while(1) //无限循环
{
while(TF0==0); //定时器无溢出时,循环等待
TF0=0; //定时器溢出时,清除溢出标志
P2=~P2; //P2口状态取反,使灯闪烁
TH0=(65536-50000)/256; //定时器T0的高八位赋初值
TL0=(65536-50000)%256; //定时器T0的低八位赋初值
}
}
【例】用T0的定时中断控制P2.0口指示灯闪烁。
要求:T0工作于方式 1、定时,P2.0 口接的指示灯闪烁周期为 100ms,即 50ms亮、 50ms灭。要用到 T0的定时中断功能,所以在程序开始要开总中断 EA 和 T0的溢出中断 ET0, 这样在定时器溢出时才能执行中断子程序。
#include<reg51.h>
sbit led=P2^0; //定义P2.0口的指示灯
void main()
{
TMOD=0x01; //设置定时器工作方式
TH0=(65536-50000)/256; //设置定时器初值
TL0=(65536-50000)%256;
EA=1; //开总中断
ET0=1; //开定时器T0溢出中断
TR0=1; //启动T0
while(1); //等待中断
}
void Timer0()interrupt 1 //T0定时中断处理子程序
{
led=~led; //控制指示灯闪烁
TH0=(65536-50000)/256; //重赋初值
TL0=(65536-50000)%256;
}
【例】用T0定时中断控制P2.0引脚指示灯以周期为2秒闪烁。
分析:闪烁周期为2秒,即亮1s、灭1s,需要用定时器定时1s。定时时间较长,如果采用十六位定时器,在学习板晶振频率12MHz的前提下,最大定时时间为65536μs,这个时间是小于1s的。如何实现定时1s呢? 实际上,可以将1s的定时时间分成多份,只要每份的时间小于65536μs即可,每份时间到定时器可以产生一次中断,在中断子程序中记录中断的次数,当中断次数等于预定份数时,定时时间刚好为1s。程序设计上可以用一个变量来存储定时器中断的次数,如果 T0每次中断是50ms,当中断次数为20 次时,定时时间刚好是1s。
#include<reg51.h>
sbit led=P2^0;
unsigned char s=0; //定义变量s存放中断次数
void main()
{
EA=1;
ET0=1;
TMOD=0x01; //T0设为方式1定时
TH0=(65536-50000)/256; //设置定时器初值,定时时间50ms
TL0=(65536-50000)%256;
TR0=1;
while(1); //等待中断
}
void timer0()interrupt 1 //T0溢出中断子程序
{
s++; //进入中断,中断计数值加一
if(s==20) //当中断20次时,定时时间到一秒
{
led=~led; //控制灯闪烁
s=0; //定时时间到一秒,中断计数值清0
}
TH0=(65536-50000)/256; //定时器溢出时重赋初值
TL0=(65536-50000)%256;
}
如果要求用定时器中断同时控制多个指示灯以不同频率闪烁,该如何实现呢?
如果控制两个灯闪烁,可以用两个定时器 T0、T1各控制一个灯的闪烁;或者也可以只用一个定时器,在中断次数取不同值时,控制不同灯的状态。
【例】用定时器T0定时中断控制蜂鸣器发出1KHZ的音频。
蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电。蜂鸣器常用的有交流蜂鸣器或直流蜂鸣器。直流蜂鸣器给一定的直流电压就会响,而交流蜂鸣器需要给蜂鸣器一个脉冲才会响。学习板上的蜂鸣器是交流蜂鸣器,要想使它发声就要给它的驱动端一个交变的脉冲信号。
其中芯片ULN2003D是一个单片高电压、高电流的达林顿晶体管阵列集成电路,它的作用是提高控制器端口的驱动能力。该芯片七路TTL电平控制信号输入接IN1—IN7,七路大功率负载输出接OUT1—OUT7。如图中,单片机给蜂鸣器的控制号由P1.5口输出,接到ULN2003D的第五通道上,经达林顿管驱动后控制蜂鸣器鸣响。
要求蜂鸣器发出1kHz的音频,应该让单片机的 P1.5口输出对应频率的方波信号:
要使蜂鸣器发出1kHz音频,音频的一个周期为1/1000=0.001s,即1ms,,半个周期就是0.5ms,所以程序定时每到0.5ms,即进入中断,将蜂鸣器端口状态取反,就可以使蜂鸣器发出1kHz音频。T0定时方式每次加一的时间是1μs,定时0.5ms需要计数500个,依此 可以来设置定时器的定时初值。
#include<reg51.h>
sbit s=P1^5; //定义蜂鸣器的控制端口
void main()
{
TMOD=0x01; //设置T0为方式1
TH0=(65536-500)/256; //T0赋初值
TL0=(65536-500)%256;
EA=1; //开中断
ET0=1;
TR0=1; //启动T0
s=1; //让蜂鸣器初始状态停止发声
while(1); //等待中断
}
void tr0()interrupt 1 //T0中断处理子程序
{
s=~s; //蜂鸣器端口状态取反
TH0=(65536-500)/256; //T0重赋初值
TL0=(65536-500)%256;
}
【例】用外部中断0对负跳变累计计数。
要求:从 P3.0口输出方波信号,负跳变共20次,将这个方波信号接到 P3.2(外部中断 0的输入引脚),用外部中断的方式累计对负跳变计数,并将计数结果送 P2口显示。其中, 学习板上单片机的 P3.0口和 P3.2口应该用短接线相连。
#include<reg51.h>
sbit si=P3^0; //定义输出负跳变的端口
unsigned char ci;
void delay() //延时1秒
{
unsigned int i,j;
for(i=0;i<1000;i++)
for(j=0;j<110;j++);
}
void main()
{
unsigned char i;
EA=1; //开中断
EX0=1;
IT0=1; //负跳变触发中断
ci=0; //计数变量清0
for(i=0;i<20;i++) //共发出20个负跳变信号
{
si=1;
delay();
si=0;
delay();
}
while(1); //程序原地踏步
}
void int0()interrupt 0 //外部中断0的处理子程序
{
ci++; //中断一次计数变量加一
P2=ci; //计数结果送P2口显示
}
【例】用外部中断0测量输入的负脉冲宽度。
要求:由单片机 P3.0输出负脉冲,其信号输入 P3.2(外部中断0输入引脚)检测负脉冲宽度,负脉冲宽度用 T0定时计数,负脉冲结束后 T0定时值送P2口显示输出。负脉冲宽度设置为100μs。
#include<reg51.h>
sbit si=P3^0; //定义负脉冲输出端口
sbit mi=P3^2; //定义外部中断0输入口
void main()
{
TMOD=0x22; //T0和T1设置为定时方式2
EA=1;
ET0=1; //开T0中断
EX0=1; //开外部中断0
PT0=1; //T0中断设为高优先级
IT0=1; //外部中断0为下跳沿触发
TH0=256-100; //T0初值
TL0=256-100;
TH1=0; //T1初值
TL1=0;
TR1=0; //关T1
TR0=1; //启动T0
while(1); //等待中断
}
void timer0()interrupt 1 //T0中断,用于输出方波
{
si=~si;
}
void int0()interrupt 0 //外部中断0,用于启动T1定时
{
TR1=1; //启动T1
TL1=0;
while(mi==0); //输出方波保持低电平时,等待
TR1=0; //方波低电平结束,关T1
P2=TL1+10; //输出T1定时值到P2口
}
(1)定时器的控制要用到哪些特殊功能寄存器? 它们的作用是什么?
答:寄存器 TCON:控制两个定时器/计数器的启动和停止寄存器
TMOD:用来设置定时器/计数器的工作
(2)定时器的4种工作方式有什么区别?
答:1)方式 0:此时十六位寄存器只用了 13 位,它由 TL1 的低五位和 TH1 的八 位构成。
2)方式 1:十六位计数器,由 TL1 的八位和 TH1 的八位构成。
3)方式 2:它由 TL1 构成 8 位计数器和作为计数器初值的常数缓冲器 TH1 构成。 当 TL1 计数溢出时,置溢出标志位 TF1 为 1 的同时,还自动将 TH1 的初值送入 TL1,使 TL1 从初值重新开始计数。这样既提高了定时精度,同时应用时只需在开 始时赋初值 1 次,简化了程序的编写。
4)方式 3:方式 3 只适用于定时器 T0。如果把 T1 置为工作方式 3,它会自动处于 停止状态。当 T0 工作在方式 3 时,被拆成两个独立的八位计数器 TH0、TL0。此时 TL0 构成八位计数器可工作于定时/计数状态,并使用 T0 的控制位与 TF0 的中断 源。TH0 只能工作于定时状态,使用 T1 中的 TR1、TF1 的中断源。
(3)为什么要计算定时器初值?
答:51 内核单片机中,T1 和 T0 都是增量计数器,因此不能直接将要计数的值作 为初值放入寄存器中,而是将计数的最大值减去实际要计数的值的差存入寄存器 中。当计数器计满时,那个瞬间可以用软件捕捉到,因此设置初值让它计满时正好达到计数值。
(4)51单片机的中断源有哪几个? 什么情况下会产生相应的中断?
答:中断源共有五个,分别是:外部中断 0、1,当这两个中断被允许时,P3.2、 P3.3 引脚出现负跳变或低电平时产生中断;定时、计数器 T0、T1 溢出中断,当这两个中断被允许时,并且单片机内部定时器或 P3.4、P3.5 引脚输入的外部脉冲计数使 T0、T1 溢出的同时产生中断;串行口中断,当串行口的接收或发送中断被允许,并且串行口接收到数据或发送成功时产生中断。
(5)中断控制的特殊功能寄存器有哪些? 如何应用?
答:中断源寄存器有两个,即定时器/计数器控制寄存器 TCON 和串行通信控制寄存器 SCON,它们可以向 CPU 发出中断请求。中断允许寄存器有一个 IE,功能是控 制是否允许 CPU 产生各个中断,如果中断源已经发出了中断,但 IE 不允许 CPU 产 生中断,那么中断子程序就不会被执行。中断优先级寄存器有一个,功能是设置 每个中断的优先级别,但只能设置两个级别——高级、低级。
(6)编程:用定时器中断方式控制 P1口的两个指示灯以不同频率闪烁。
#include<reg51.h>
sbit led1=P1^0;
sbit led2=P1^1;
unsigned char s=0;m=0;
void main()
{
EA=1;
ET0=1;
TMOD=0x01;
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
TR0=1;
while(1);
}
void timer0()interrupt 1
{
s++;
m++;
if(m==10)
{
led1=~led1;
m=0;
}
if(s==20)
{
led2=~led2;
s=0;
}
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
}