新手学单片坤之定时器
文章目录
一、Proteus和普中单片机板上分别完成采用定时计数器控制LED灯每隔1s周期性亮灭的实验
代码1
``#include <REGX52.H>`
void Timer0Init()//1毫秒@12.000MHz
{
//TMOD=0x01; //0000 0001,特殊功能寄存器里,通过赋值选择定时器工作模式
TMOD&=0xF0;//把TMOD低四位清零,高四位保持不变
TMOD|=0x01;//把TMOD最低位置一,高四位保持不变
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值,通过STC软件给的延时
// TH0=64535/256;自己计算的延时
// TL0=64535%256;
TF0=0;
TR0=1;
ET0=1;
EA=1;
PT0=0;
}
void main()
{
Timer0Init();
while(1)
{
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC;
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
P2_0=~P2_0;
}
}
**代码具体解释1:**也可以观看B站江协进行理解
1、从main函数进入,调用Timer0Init()
函数
2、TMOD=某个16位二进制数,就是选择定时器工作模式,因为定时器有不止一个工作模式,在此次实验中,我们选择模式1,并且只用到了定时器0
如上图,模式1是16位,所以我们在选择时就将TMOD=0x01
,至于TMOD&=0xF0;
和TMOD|=0x01;
是为了防止我们在同时使用定时器1和0的时候刷新定时器1的状态,但本次实验只用到了定时器0,所以采用第一种方式也是可以的。
3、TL0 = 0x18;
与TH0 = 0xFC;
,从模式1中我们找到这一模块,这模块就相当于是定时模块,每个1us加1,总共16位,所以范围就是0~65535。这里我们分别给低八位和高八位赋值。我们先将1ms的时间计算出来,就能够得到从64535直到65535就是1ms。通过上面的代码注释可以看见我们使用的是软件给的数。如果需要我们自己去计算的话,就是用65535除256取整得到高八位的数,取余得到低八位的数。为什么是256,因为八位二进制最大就是255。所以我们也可以用自己计算的数去得到1ms的延时。
4、TF0=0;
TR0=1;
这个先记住这么写就行,也可以看上图去理解。
5、ET0=1;
EA=1;
PT0=0;
上图是中断结构,我们此次用的是T0计时器,所以把ET0置1,EA置一,PT0默认是置0,也可以不写。
至此定时器部分就介绍完了。
6、由于定时器是自己呆在一边计时,所以当定时器记满时就会触发中断溢出标志位,然后跳转到中断程序
什么是中断:中断即打断,实至CPU再执行当前程序时,由于系统出现了某种需要处理的紧急情况,CPU暂停正在执行的程序,转而去执行另一段特殊程序来处理的出现的紧急事务,处理结束后CPU自动返回到原先暂停的程序中去继续执行,这种执行过程由于外界的的原因被中间打断的情况成为中断
void Timer0_Routine() interrupt 1
这就是中断函数,具体的函数内容根据项目要求来设置。其中 interrupt
是中断函数的固定写法。”1“是代表了中断号,你使用哪一个中断源就要用对应的中断号。下图是51中常见的中断源。
7、static unsigned int T0Count;
设置一个静态变量
8、TL0 = 0x18;
TH0 = 0xFC;
又给定时器赋初值,因为当定时器溢出的时候会重新从0开始每1us加1。所以就需要给他再次赋初值,让定时器固定1ms进入中断。
9、 T0Count++;
if(T0Count>=1000)
{
T0Count=0;
P2_0=~P2_0;
}
}
如果进入中断, T0Count++;
就++,直到大于等于1000,这样就相当于定时器循环了1000次1us,加起来就是1s,就能达到我们想要的效果。然后又将 T0Count++;
变为0,重新开始加。led就重复亮灭。
Proteus仿真效果
开发板实验图
Keil中logic图
可以看出周期正好是1s。
二、采用计数器中断,实现 按4次按钮开关后,P2口的8只LED闪烁不停。
代码2
#include <REGX52.H>
void Delay(unsigned int i)
{
unsigned int j;
for(;i>0;i--) //变量i由实际参数传入一个值 //因此i不能赋初值
for(j=0;j<125;j++)
{;}
}
void main()
{
//主函数
TMOD=0x05; //设置定时器T0为方式1计数
TH0=0xff; //向TH0写入初值的高8位
TL0=0xfc; //向TL0写入初值的低8位
EA=1; //总中断允许
ET0=1; //定时器T0中断允许 TR0=1;
while(1)
{
}
}
void T0_int(void) interrupt 1 //T0中断函数
{
for(;;) //无限循环
{
P2=0xff; //8位LED全灭
Delay(500) ; //延时500ms
P2=0; //8位LED全亮
Delay(500); //延时500ms
}
}
代码解释2
主要解释一下TMOD=0x05;
这段代码就是二进制0000 0101,对应TOMD的功能可以看上图。当C/T等于1时就可以通过外部引脚计数了。
仿真效果如下
由于没有买额外的按键,所以就没有开发板效果图了
三、中断函数中要尽量避免使用执行时间较长(耗时)的代码,以避免中断服务影响到主程序代码的执行效率。但是在上面外部中断的实验中,中断函数采用了软件延时函数去控制LED亮灭的间隔周期。这是一种不好的编程。请你思考,换一种更合理的方式,不在中断函数使用延时循环,实现同样的功能。
可见本次实验中的第一个代码,并没有像第二个代码那样在中断函数中调用延时函数。
四、外部中断控制流水灯
代码3
#include <REGX52.H>
void Delay(unsigned int i) //延时函数Delay( ),i形式参数,不能赋初值`
{
unsigned int j;
for(;i > 0;i--)
for(j=0;j<333;j++) //晶振为12MHz,j选择与晶振频率有关`
{;} //空函数
}
void main() //主函数`
{
EA=1;//总中断允许`
EX0=1;//允许外部中断0中断`
IT0=1;
while(1){};//循环`
}
void int0( ) interrupt 0 //外中断0的中断服务函数`
{
static unsigned int count;
count++;
if(count==1)
{Delay(100);
P2_7=1;
Delay(100);
P2_0=0;
}
if(count==2)
{
Delay(100);
P2_0=1;
Delay(100);
P2_1=0;
}
if(count==3)
{
Delay(100);
P2_1=1;
Delay(100);
P2_2=0;
}
if(count==4)
{
Delay(100);
P2_2=1;
Delay(100);
P2_3=0;
}
if(count==5)
{
Delay(100);
P2_3=1;
Delay(100);
P2_4=0;
}
if(count==6)
{
Delay(100);
P2_4=1;
Delay(100);
P2_5=0;
}
if(count==7)
{
Delay(100);
P2_5=1;
Delay(100);
P2_6=0;
}
if(count==8)
{
Delay(100);
P2_6=1;
Delay(100);
P2_7=0;
count=0;
}
}
代码较为简单死板就不做过多解释了,其中给管脚赋值还可以用位移符号来实现,当时没想这么多,哈哈。
一旦我们按下外部中断就INT0,就进入中断程序,然后判断count的值,再进入对应的语句,然后再退出中断,如此往复。
Proteus仿真效果
代码较为简单死板就不做过多解释了,其中给管脚赋值还可以用位移符号来实现,当时没想这么多,哈哈。
一旦我们按下外部中断就INT0,就进入中断程序,然后判断count的值,再进入对应的语句,然后再退出中断,如此往复。
五、总结:
了解了定时器与中断的使用,感觉定时器更为复杂,因为定时器也需要用到中断,哈哈。