51单片机(四)定时器中断(+数码管——24小时制钟表)

定时器中断

  • 一、中断
    • 1、中断概念
    • 2、中断嵌套
  • 二、中断源(优先级顺序必记!)
  • 三、中断允许寄存器IE和中断优先级寄存器IP
    • 1、中断允许寄存器IE(Interrupt Enable )
    • 2、中断优先级寄存器IP(Interrupt Priority)
  • 四、定时器中断
    • 1、定时器工作原理
    • 2、定时器/计数器工作方式寄存器TMOD(Timer Mode)
    • 3、定时器/计数器控制寄存器TCON(Timer Control)
  • 五、总结+面向程序
  • 六、例子
    • 1、流水灯和数码管综合(中断)
    • 2、24小时制时钟
      • 1) 方法一(核心算法不一样)
      • 2) 方法二(核心算法不一样)

一、中断

1、中断概念

单片机处理某一事件A时,事件B突然发生(中断发生),而且事件B的优先级大于事件A的优先级,则单片机暂停对事件A的执行,转向执行事件B(中断响应),执行完事件B后,再回到事件A,继续执行事件A。(中断返回)

2、中断嵌套

若事件的优先级A

注:事件D执行完毕后,返回事件C,并不是重新开始执行事件C,而是接续中断发生时的进度,继续执行。同事件B、C的中断返回

二、中断源(优先级顺序必记!)

中断源 默认中断级别
INT0外部中断0 最高
T0定时器/计数器0中断 第2
INT1外部中断1 第3
T1定时器/计数器1中断 第4
T1/TI串行口中断 第5
T2定时器/计数器2中断 最低

注1:上表中T2定时器/计数器是52单片机特有的。
注2:上表中的的优先级顺序一定要记住!!!接下来的一些寄存器每一位的信息也是按照这个顺序设定的。

三、中断允许寄存器IE和中断优先级寄存器IP

1、中断允许寄存器IE(Interrupt Enable )

位序号 D7 D6 D5 D4 D3 D2 D1 D0
位符号 EA _ ET2 ES ET1 EX1 ET0 EX0

这个寄存器的位符号和顺序一定要熟记,从低位到高位依次是:外部中断0、定时器/计数器0、外部中断1、定时器/计数器1、串行中断、D6位无效(52单片机用,T2)、EA全局中断允许位。

前五位的顺序就是之前我们强调的中断源优先顺序,记住顺序后,这里就很容易记住IE寄存器。

编写中断程序时,第一步需要打开全局中断允许,第二步需要选择中断源。其中寄存器某位软件置1表示打开,0表示关闭,单片机在上电是,IE寄存器每一位被清零,所以中断允许位及各中断源选择都处于关闭状态,需要程序进行操作。

打开头文件:

51单片机(四)定时器中断(+数码管——24小时制钟表)_第1张图片51单片机(四)定时器中断(+数码管——24小时制钟表)_第2张图片
可以看到定义了8位的IE寄存器,编写程序时可直接对该寄存器操作。

比如选择外部中断0作为中断源,则IE寄存器D7位置1打开全局中断允许位,D0位软件置1打开外部中断0,此时IE寄存器的状态值为:10000001,即0x81;
程序为:(根据头文件,IE是有定义的,IE可以直接使用)

IE=0x81;

再查看头文件:

51单片机(四)定时器中断(+数码管——24小时制钟表)_第3张图片

IE寄存器的每一位都重新命名。可以对每一位进行单独操作。所以上述程序还可以写成:

	EA=1;
	EX0=1;

为了加深理解,再举个例子:比如现在需要使用定时器/计数器1作为中断源
方法一:
D7位置1,D3位置1,所以IE状态值:10001000,即0x88

所以程序:

IE=0x88;

方法二:

	EA=1;
	ET1=1;

2、中断优先级寄存器IP(Interrupt Priority)

D7 D6 D5 D4 D3 D2 D1 D0
- - - PS PT1 PX1 PT0 PX0

上表中从低位到高位依次:外部中断0、定时器/计数器0、外部中断1、定时器/定时器1、串行口中断,依旧是我们之前记忆的中断源优先级顺序

中断优先级顾名思义,是用来设置优先级别的,类似STm32的NVIC 的优先级组,在实际应用中根据需要设置高优先级中断,形成中断嵌套。

该寄存器的某一位软件置1,表示将该位设置为高优先级,高优先级优先于低优先级发生中断响应,若俩中断源同为高优先级或同为低优先级,则按照默认顺序发生中断响应(我们一直强调记忆的那个顺序)。正因为有了IP寄存器,所以中断源的响应顺序并不是一直不变,它根据我们的设置来适应需求。

如果程序中没有对IP寄存器进行设置,则单片机上电后IP寄存器每一位都清零,相当于没有设置高优先级,中断响应顺序仍旧按照默认顺序发生。

打开头文件:

51单片机(四)定时器中断(+数码管——24小时制钟表)_第4张图片
51单片机(四)定时器中断(+数码管——24小时制钟表)_第5张图片

IP寄存器是在头文件里定义了的,并且寄存器的每一位都通过位操作重新命名,所以与IE寄存器相同,也有俩种操作方法。

举例说明:
若设置定时器/计数器T0和外部中断1为高优先级,则IP寄存器状态值:00000110,即0x06

程序:

IP=0x06;

或者:

PT0=1;
PX1=1;

此时优先级顺序:
定时器/计数器0——>外部中断1(它俩按照默认顺序排,高优先级)——>外部中断0——>定时器/计数器1——>串行口中断(它仨按照默认顺序排,低优先级)

四、定时器中断

来到本博的重点——定时器中断,首先要知道,定时器/计数器的实质是加1计数器,由高八位和低八位俩个寄存器组成

打开头文件,可以看到定时器1和2的高8位和低8位的寄存器。

51单片机(四)定时器中断(+数码管——24小时制钟表)_第6张图片

1、定时器工作原理

定时器实质是加1计数器,根据计数脉冲来+1计数,脉冲来源有俩种,一种是由系统的时钟振荡器输出脉冲经12分频后送来,另一种是T0或T1引脚输入的外部脉冲源,每来一个脉冲计数器就+1,当计数器16位都为1时(65535),再来一个脉冲后+1,计数器会归零,此时计数器的溢出使TCON寄存器中TF0或TF1置1,向CPU发送中断请求,也就是说定时器计满数就中断请求一次。

假如计数器初值为N0,每t0时间计数器+1,则每次中断间隔时间为:(65536-N0)*t0。

求t0:
设晶振频率为F,晶振产生的信号为正弦波(等效为矩形波,与阈值和占空比有关),则周期为1/F s,一个机器周期为12个振荡周期(所以系统的时钟振荡器输出脉冲经12分频后当作计数脉冲),所以一个机器周期为12/F s,一个机器周期计数器+1,所以计数器每+1一次的时间为:12/F s。
对于12MHZ的晶振,t0=12/(12* 10^6)s=10 ^-6s=1us。
对于11.0592MHZ的晶振,t0=12/(11.0592*10^6)s≈1.09us

求N0:
若初值为0,则12MHZ晶振发生一次中断需要时间:t=t0* 65536=65.536ms
这个t在程序中不好处理,所以加入初值,使t为一个整数,比如50ms,此时若想延时1s,只需中断20次即可。
当t=50ms时,计数器加的数为:50* 10^-3s/(1*10 ^-6s)=50000,所以初始值为:N0=65536-50000=15536

所以每次中断计数器溢出清零后,需重新给计数器设置初值15536,把它放入高8位和低8位寄存器。
高8位存放:
TH0(TH1)=(65536-50000)/256=15536/256=60(等式一)
低8位存放:
TL0(TL1)=(65536-50000)%256=15536%256=176(等式二)

至于为什么对256取余和取商,这里就不在解释(记住就好),学过算法的朋友应该知道,利用分治思想求大整数乘法,分而治之的“分”可能会用的这种方法。

从上面也看到,每50ms产生依次中断,所以等式一和等式二都减了50000(us),若20ms产生一次中断,则:
TH0(TH1)=(65536-20000)/256=45536/256=177
TL0(TL1)=(65536-20000)%256=45536%256=224

12MHZ晶振的装载初值程序如下:

//12MHZ重装载初值,定时器0
TH0=65536-50000/256;
TL0=65536-50000%256;

若是11.0592MHZ晶振,之前说过,计数器+1的时间约为1.09us,若t0=50ms,则N=50* 10^3s/(1.09*10 ^-6s)≈45872

11.0592MHZ晶振的装载初值程序如下:

//11.0592MHZ重装载初值,定时器0,中断间隔50ms
TH0=65536-45872/256;
TL0=65536-45872%256;

2、定时器/计数器工作方式寄存器TMOD(Timer Mode)

位序号 D7 D6 D5 D4 D3 D2 D1 D0
位符号 GATE C/T M1 M0 GATE C/T M1 M0

上表的寄存器 低四位为T0定时器,高四位为T1定时器。
GATE——顾名思义,门,为门控制位

GATE=0,定时器/计数器启动和停止受TCON寄存器(下一部分总结)中TR0(定时器0时)或TR1(定时器1时)控制,TR(0或1)=1时启动相应定时器,TR(0或1)=0时停止相应定时器。

GATE=1,定时器/计数器启动和停止受TCON寄存器中TR0(定时器0时)或TR1(定时器1时)和外部中断引脚INT0或INT1俩者共同控制

C/T——Counter/Timer,定时器和计数器选择位,该位为1时是计数器模式,为0时是定时器模式。

M1M0——工作方式选择。下表所示(记)

M1 M0 工作方式
0 0 方式0,为13位定时器/计数器
0 1 方式1,为16位定时器/计数器
1 0 方式2,8位初值自动重装的8位定时器/计数器
1 1 方式3,仅适用于T0,分成俩个8位计数器,T1停止计数

我们之前计算的装载值N0是方式1的,方式0和方式2的计算类似,我们目前只用方式1的16位寄存器,之后博客中再总结方式0、2、3。

编写程序时需要选择使用定时器/计数器0还是1,此时要对TMOD寄存器的D7和D3的GATE进行操作,需要选择使用定时器还是计数器,此时需要对TMOD寄存器的俩个C/T位进行操作,需要选择工作方式,此时需要对TMOD寄存器的M1M0位进行操作。

查看头文件:
在这里插入图片描述

没有对该寄存器的每一位再重新定义,所以不能对寄存器每位单独操作。现举例说明:

(1)使用定时器0工作方式1

使用定时器0,若只受TCON寄存器的TR0控制启动和停止,低4位的GATE位为0,采用定时器,所以C/T位为0,方式1,所以M1M0位为01,所以TMOD寄存器的状态值:00000001,即0x01

程序:

TMOD=0x01;

(2)使用计数器1工作方式1

采用计数器1,所以TMOD高4位GATE=0(只受TR0控制),C/T位为1,M1M0位为01,所以TMOD的状态值:01010000,即0x50

程序:

TMOD=0x50;

(3)使用定时器1工作方式1、计数器0工作方式1

采用定时器1和计数器0,所以高4位和低4位的C/T位分别时0和1,GETA位都为0(分别只受TR1和TR0控制),M1M0位都为01,所以TMOD寄存器的状态值:00010101,即0x15

程序:

TMOD=0x15;

3、定时器/计数器控制寄存器TCON(Timer Control)

位序号 D7 D6 D5 D4 D3 D2 D1 D0
位符号 TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0

上表从低位到高位:D0、D1是外部中断0,D2、D3是外部中断1,D4、D5是定时器中断0、D6、D7是定时器中断1

D7——>TF1定时器1溢出标志位(Timer Flag)
当定时器1计满溢出时,由硬件使IF1置1,并且申请中断,当进入中断服务函数后该位自动清零。

D6——>TR1定时器1运行控制位(Timer Running)
上一部分TMOD寄存器总结过,当GATE=0时,定时器/计数器启动和停止仅受TCON寄存器的TR0或TR1控制,当GATE=1时定时器/计数器启动和停止受TCON寄存器的(TR0或TR1)和(INT0或INT1)引脚的电平状态一起控制。所以当TMOD寄存器的GATE=1时,且INT1引脚为高电平,TR1软件置1启动定时器1,当TMOD寄存器的GATE=0时,TR1软件置1启动定时器1,不论GATE=1或0,TR1位软件置0即可关闭计数器1。

D5——>TF0定时器0溢出标志位(Timer Flag)
当定时器0计满溢出时,由硬件使IF0置1,并且申请中断,当进入中断服务函数后该位自动清零。

D4——>TR0定时器0运行控制位(Timer Running)
当TMOD寄存器GATE=0时,定时器/计数器启动和停止仅受TCON寄存器的TR0或TR1控制,当GATE=1时定时器/计数器启动和停止受TCON寄存器的(TR0或TR1)和(INT0或INT1)引脚的电平状态一起控制。所以当TMOD寄存器的GATE=1时,且INT0引脚为高电平,TR0软件置1启动定时器0,当TMOD寄存器的GATE=0时,TR0软件置1启动定时器0,不论GATE=1或0,TR0位软件置0即可关闭计数器0。

D3、D2、D1、D0是关于外部中断的,定时器中断用不到这四位,我们之后博客涉及到再详述。

五、总结+面向程序

到此为止,我们总结了四个寄存器分别是:中断允许寄存器IE、中断优先级寄存器IP、定时器/计数器工作方式寄存器TMOD、定时器/计数器控制寄存器TCON。

中断允许寄存器IE
EA是全局中断允许位,如果要使单片机使用中断,该位必须打开,所以程序开头必须对该位软件置1,从D0——D5是按照默认优先级顺序排列的,这6位是中断源允许位,单片机使用什么中断源,就将对应位软件置1。并且EA寄存器各位在头文件中都进行了定义,所以可以对任意一位单独操作。

中断优先级寄存器IP
该寄存器是设置高优先的,对于一般性工程,优先级按照默认顺序即可,没有必要使用IP,所以编写程序时,一般不使用该寄存器。

定时器/计数器工作方式寄存器TMOD
该寄存器高4位控制定时器/计数器1,低4位控制定时器/计数器0,D7、D3位的GATE一般为低电平,所以 计数器/定时器的通断只受TCON寄存器的TR1或TR0控制。D6、D2位C/T是定时器、计数器选择位,该位必须选。此外D5、D4和D1、D0的M1M0是工作方式选择位,一般为方式1的16位定时器/计数器,所以M1M0位为01。

此外需要注意该寄存器没有对每一位进行定义,程序中只能TMOD=0x…来设置TMOD寄存器。

定时器/计数器控制寄存器TCON
定时器中断中只用到高4位,其中D6位的TR1和D4位的TR0是需要软件操作的,因为与TMOD寄存器的GATE有关系,所以需要TR0、TR1来启动定时器/计数器

程序流程:
①TMOD设置GATE、C/T、M1M0(TMOD寄存器)
②装载初值TH(0/1),TL(0/1)
③EA=1 打开全局中断允许位,允许使用中断(IE寄存器)
④ET0=1或ET1=1(定时器中断,用不到外部中断)(IE寄存器)
⑤TR0=1或TR1=1,启动定时器(第一步设置GATE=0时)(TCON寄存器)

例:利用定时器1工作方式1实现LED每0.5s闪烁一次

根据上面的程序流程:
①定时器1工作方式1,所以TMOD高四位GATE=0,C/T=0,M1M0=01,所以TMOD=0x10;
②11.0592HZ晶振,装载初值:TH1=(65536-45875)/256;TL1=(65536-45875)%256;
③EA=1;
④ET1=1;
⑤TR1=1;

代码:

TMOD=0x10;
TH1=(65536-45872)/256;
TL1=(65536-45872)%256;
EA=1;
ET1=1;
TR1=1;

写中断服务函数,格式:
void 函数名()interrupt 中断号 using 工作组
{
函数体
}
①中断服务函数是无返回值的函数,所以用关键字void
②中断号,与默认的优先级顺序一致,定时器0的中断号为1,定时器1的中断号为3
③工作组为单片机RAM区从0-31总共32字节分成四组工作寄存器,每组为8位寄存器,可以指定使用哪一组工作寄存器,C51编译时会自动分配工作组,所以“using 工作组”可以省略不写。

本例题中每50ms产生一次中断,产生10次中断为0.5s,所以每产生10次中断对LED进行一次操作。

void T1_time() interrupt 3
{
     
	num++;
	if(num==10)
	{
     
		num=0;
		led1=~led1;
	}
}

但是每次产生中断后,计数器清零,计数器是从0开始重新计数的,所以每产生一次中断,就应该重新进行一次初始值装载。

完整程序:

#include 
#define uint unsigned int
uint num;
sbit led1=P1^0;
void main()
{
     
TMOD=0x10;
TH1=(65536-45872)/256;
TL1=(65536-45872)%256;
EA=1;
ET1=1;
TR1=1;

while(1);//程序停到这里。一直等待中断
}

void T1_time() interrupt 3
{
     
	TH1=(65536-45872)/256;
	TL1=(65536-45872)%256;
	num++;
	if(num==10)
	{
     
		num=0;
		led1=~led1;
	}
}

六、例子

1、流水灯和数码管综合(中断)

题:
用定时器0工作方式1实现数码管前俩位59s循环计时,定时器1工作方式1实现流水灯间隔1s闪烁。

分析:.
定时器1工作方式1实现流水灯与上节中例子一致,现考虑定时器0工作方式1实现数码管前俩位59s循环计时,数码管显示数字分为个位和十位,俩个数字分开显示,打开数码管2位选,送入个位数字,打开数码管1位选,送入十位数字,数码管显示数字是一直存在的,与中断无关,中断只与数字的改变有关系,所以单独建立数码管显示函数,主函数循环调用数码管显示函数即可。

定时器0实现数码管程序:


#include 
#define uint unsigned int
#define uchar unsigned char
uchar code table[]={
     
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
uchar num;
uint ge ,shi;
sbit dula=P2^6;
sbit wela=P2^7;
void  display_time(uint ,uint);
void delay_ms(uint);
void main()
{
     
	
	TMOD=0x01;
	TH0=(65536-45872)/256;
	TL0=(65536-45872)%256;
	EA=1;
	ET0=1;
	TR0=1;
	while(1)
	{
     
		display_time(ge ,shi);//时间一直显示中
	}
}

void T0_time()interrupt 1
{
     
TH0=(65536-45872)/256;//每50ms产生一次中断
TL0=(65536-45872)%256;//所以每20次中断,个位+1
num++;
if(num==20)
{
     
	num=0;
	ge++;
	if(ge==10)
	{
     
		shi++; //时间进位
		ge=0;
	}
	if(shi==6)
	shi=0; //时间归零
}

}

void  display_time(uint ge ,uint shi)
{
     
	wela=1;
	P0=0xfd; //选择数码管2
	wela=0;
	
	dula=1;
	P0=table[ge];//送入个位数字
	dula=0;
	delay_ms(5);

	wela=1;
	P0=0xfe; //选择数码管1
	wela=0;

	dula=1;
	P0=table[shi];//送入十位数字
	dula=0;	
	delay_ms(5);

}
void delay_ms(uint ms)
{
     
	uint i,j;
	for(i=ms;i>0;i--)
	for(j=110;j>0;j--);
}

注意在上述程序的display_time()函数中,对数码管2和数码管1操作完毕后,都要延时一点时间,保证俩个数码管互不影响。

再加上定时器1控制LED,完整程序如下:


#include 
#define uint unsigned int
#define uchar unsigned char
uchar code table[]={
     
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
uchar num0,num1;
uint ge ,shi;
sbit dula=P2^6;
sbit wela=P2^7;
sbit led1=P1^0;
void  display_time(uint ,uint);
void delay_ms(uint);

void main()
{
     
	TMOD=0x11;//写在一起
	//定时器0
	//TMOD=0x01;
	TH0=(65536-45872)/256;
	TL0=(65536-45872)%256;
	EA=1;
	ET0=1;
	TR0=1;
	
	//定时器1
	//TMOD=0x10;
	TH1=(65536-45872)/256;
	TL1=(65536-45872)%256;
	EA=1;
	ET1=1;
	TR1=1;
	while(1)
	{
     	
		display_time(ge ,shi);//时间一直显示中
	}
}

void T0_time()interrupt 1 //数码管处理 T0定时器
{
     
	TH0=(65536-45872)/256;//每50ms产生一次中断
	TL0=(65536-45872)%256;//所以每20次中断,个位+1
	num0++;
	if(num0==20)
	{
     
		num0=0;
		ge++;
		if(ge==10)
		{
     
			shi++; //时间进位
			ge=0;
		}
		if(shi==6)
		shi=0; //时间归零
	}
}

void T1_time()interrupt 3 //LED处理 T1定时器
{
     
	TH1=(65536-45872)/256;
	TL1=(65536-45872)%256;
	num1++;
	if(num1==20)
	{
     
		num1=0;
		led1=~led1;
	}
}

void  display_time(uint ge ,uint shi)
{
     
	wela=1;
	P0=0xfd; //选择数码管2
	wela=0;
	
	dula=1;
	P0=table[ge];//送入个位数字
	dula=0;
	delay_ms(5);

	wela=1;
	P0=0xfe; //选择数码管1
	wela=0;

	dula=1;
	P0=table[shi];//送入十位数字
	dula=0;	
	delay_ms(5);
}
void delay_ms(uint ms)
{
     
	uint i,j;
	for(i=ms;i>0;i--)
	for(j=110;j>0;j--);
}

2、24小时制时钟

1) 方法一(核心算法不一样)

直接计算小时的个、十位,分钟的个、十位,秒钟的个、十位,然后再分别在六个数码管显示

#include 
#define uint unsigned int
#define uchar unsigned char
uchar code table[]={
     
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
uchar num;
uint hour_ge,hour_shi,minute_ge, minute_shi,second_ge,second_shi;
sbit dula=P2^6;
sbit wela=P2^7;
void  display_time(uint,uint,uint,uint,uint,uint);
void delay_ms(uint);
void main()
{
     
	TMOD=0x01;
	TH0=(65536-45872)/256;
	TL0=(65536-45872)%256;
	EA=1;
	ET0=1;
	TR0=1;
	while(1)
	{
     	
		display_time(hour_shi,hour_ge,minute_shi,minute_ge,second_shi,second_ge);
		//时间一直显示中
	}
}

void T0_time()interrupt 1 //数码管处理 T0定时器
{
     
	TH0=(65536-45872)/256;//每50ms产生一次中断
	TL0=(65536-45872)%256;//所以每20次中断,个位+1
	num++;
	if(num==20)
	{
     
		num=0;
		second_ge++;//秒+1
	}
	if(second_ge==10)
	{
     
		second_ge=0;
		second_shi++;
		if(second_shi==6)
		{
     
			second_shi=0;
			minute_ge++;
			if(minute_ge==10)
			{
     
				minute_ge=0;
				minute_shi++;
				if(minute_shi==6)
				{
     
					minute_shi=0;
					hour_ge++;
					if(hour_ge==10)
					{
     
						hour_ge=0;
						hour_shi++;
						if(hour_shi==2&&hour_ge==4)
						{
     
							hour_shi=0;
							hour_ge=0;
						}

					}	
				}	
			}
		}
	}
	
}



void display_time(uint hour_shi,uint hour_ge,uint minute_shi,uint minute_ge,uint second_shi,uint second_ge)
{
     
	wela=1;
	P0=0xdf;//第六个数码管送入second_ge
	wela=0;

	dula=1;
	P0=table[second_ge];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xef;//第五个数码管送入second_shi
	wela=0;
	
	dula=1;
	P0=table[second_shi];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xf7;//第四个数码管送入minute_ge
	wela=0;

	dula=1;
	P0=table[minute_ge];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xfb;//第三个数码管送入minute_shi
	wela=0;

	dula=1;
	P0=table[minute_shi];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xfd;//第二个数码管送入hour_ge
	wela=0;

	dula=1;
	P0=table[hour_ge];
	dula=0;
	delay_ms(1);
	
  	wela=1;
	P0=0xfe;//第一个数码管送入hour_shi
	wela=0;

	dula=1;
	P0=table[hour_shi];
	dula=0;
	delay_ms(1);

}

void delay_ms(uint ms)
{
     
	uint i,j;
	for(i=ms;i>0;i--)
	for(j=110;j>0;j--);
}

2) 方法二(核心算法不一样)

通过定时器对时间按照秒计时,然后再把秒计算成时.分.秒的形式。但是按照24小时制计时,总共的秒数:60* 60* 24=86400s>65536(单片机无符号整型最大应该是2^16,16位,俩个字节,如果有误,请朋友们评论区提醒我),而12小时的秒数为60* 60*12=43200,可以按照这个数计秒数,当秒数小于这个数,说明是前半天,秒数大于这个数,就是后半天,计算后半天可以在现在的时间基础上+12小时再显示。

完整程序:

#include 
#define uint unsigned int
#define uchar unsigned char
uchar code table[]={
     
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
uchar num;
uint hour_ge,hour_shi,minute_ge, minute_shi,second_ge,second_shi;
uint time,hour,minute,second;
uint m;
sbit dula=P2^6;
sbit wela=P2^7;
void  display_time(uint,uint,uint,uint,uint,uint);
void delay_ms(uint);
void main()
{
     
	TMOD=0x01;
	TH0=(65536-45872)/256;
	TL0=(65536-45872)%256;
	EA=1;
	ET0=1;
	TR0=1;
	while(1)
	{
     	
		display_time(hour_shi,hour_ge,minute_shi,minute_ge,second_shi,second_ge);
		//时间一直显示中
	}
}

void T0_time()interrupt 1 //数码管处理 T0定时器
{
     
	TH0=(65536-45872)/256;//每50ms产生一次中断
	TL0=(65536-45872)%256;//所以每20次中断,个位+1
	num++;
	if(num==20)
	{
     
		num=0;
		time++;//秒+1
	}
	if(time==43201)//进入后半天
	{
     
		time=1; 
		m=12; 
	}
	hour=m+time/3600;//前半天时m=0,后半天时m=12
	minute=(time-hour*3600)/60;
	second=time%60;

	hour_shi=hour/10;
	hour_ge=hour%10;
	minute_shi=minute/10;
	minute_ge=minute%10;
	second_shi=second/10;
	second_ge=second%10;

	if(hour==23&&minute==59&second==59)//每天最后1秒
	  time=0;		
	
}

void display_time(uint hour_shi,uint hour_ge,uint minute_shi,uint minute_ge,uint second_shi,uint second_ge)
{
     
	wela=1;
	P0=0xdf;//第六个数码管送入second_ge
	wela=0;

	dula=1;
	P0=table[second_ge];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xef;//第五个数码管送入second_shi
	wela=0;
	
	dula=1;
	P0=table[second_shi];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xf7;//第四个数码管送入minute_ge
	wela=0;

	dula=1;
	P0=table[minute_ge];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xfb;//第三个数码管送入minute_shi
	wela=0;

	dula=1;
	P0=table[minute_shi];
	dula=0;
	delay_ms(1);

	wela=1;
	P0=0xfd;//第二个数码管送入hour_ge
	wela=0;

	dula=1;
	P0=table[hour_ge];
	dula=0;
	delay_ms(1);
	
  	wela=1;
	P0=0xfe;//第一个数码管送入hour_shi
	wela=0;

	dula=1;
	P0=table[hour_shi];
	dula=0;
	delay_ms(1);

}

void delay_ms(uint ms)
{
     
	uint i,j;
	for(i=ms;i>0;i--)
	for(j=110;j>0;j--);
}

你可能感兴趣的:(51单片机,嵌入式,单片机)