定时器中断
- 一、中断
-
- 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<B<C<D…单片机执行事件A时,发生了事件B,则暂停执行事件A而转向执行事件B,在执行事件B时,又发生了事件C,则停止执行事件B,进而转向执行事件C,执行事件C时,事件D发生,则单片机停止执行事件C,转向执行事件D…当事件D执行完毕,单片机返回事件C继续执行事件C,事件C执行完毕,单片机返回事件B继续执行事件B,事件B执行完毕,返回事件A继续执行事件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寄存器每一位被清零,所以中断允许位及各中断源选择都处于关闭状态,需要程序进行操作。
打开头文件:
可以看到定义了8位的IE寄存器,编写程序时可直接对该寄存器操作。
比如选择外部中断0作为中断源,则IE寄存器D7位置1打开全局中断允许位,D0位软件置1打开外部中断0,此时IE寄存器的状态值为:10000001,即0x81;
程序为:(根据头文件,IE是有定义的,IE可以直接使用)
IE=0x81;
再查看头文件:
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寄存器每一位都清零,相当于没有设置高优先级,中断响应顺序仍旧按照默认顺序发生。
打开头文件:
IP寄存器是在头文件里定义了的,并且寄存器的每一位都通过位操作重新命名,所以与IE寄存器相同,也有俩种操作方法。
举例说明:
若设置定时器/计数器T0和外部中断1为高优先级,则IP寄存器状态值:00000110,即0x06
程序:
IP=0x06;
或者:
PT0=1;
PX1=1;
此时优先级顺序:
定时器/计数器0——>外部中断1(它俩按照默认顺序排,高优先级)——>外部中断0——>定时器/计数器1——>串行口中断(它仨按照默认顺序排,低优先级)
四、定时器中断
来到本博的重点——定时器中断,首先要知道,定时器/计数器的实质是加1计数器,由高八位和低八位俩个寄存器组成
打开头文件,可以看到定时器1和2的高8位和低8位的寄存器。
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 <reg52.h>
#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 <reg52.h>
#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 <reg52.h>
#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 <reg52.h>
#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 <reg52.h>
#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--);
}