实验内容
基于上一次的动态数码管显示(51单片机——动态数码管)https://blog.csdn.net/li_han_han/article/details/142371983这次我们改用定时器中断来执行数码管的动态扫描。
电路部分
电路与上一篇动态数码管中的一样,就不再进行赘述了。
keil代码
再讲解代码前,我们应该先了解一下什么是定时器,以及定时器中断。
在51单片机(也称为8051单片机)中,定时器是一种内置的功能模块,用于实现定时或计数功能。51单片机通常包含两个可编程的16位定时器/计数器(Timer/Counter),即定时器0(T0)和定时器1(T1)。这些定时器/计数器可以在不同的模式下工作,以满足不同的应用需求。
定时器的基本功能
-
定时功能:通过设置定时器的初值,并启动定时器,当定时器从初值计数到全零(0xFFFF)时,可以产生一个定时中断(或称为溢出中断)。通过计算计数周期和单片机的时钟频率,可以精确计算出定时时间。
-
计数功能:定时器也可以配置为外部事件计数器,对外部引脚上的脉冲信号进行计数。这在需要测量外部事件频率或脉冲数量的应用中非常有用。
定时器的工作模式
51单片机的定时器/计数器通常具有以下几种工作模式:
-
模式0:13位定时器/计数器模式。在这种模式下,定时器/计数器使用THx(高8位)和TLx(低5位)寄存器,共13位。
-
模式1:16位定时器/计数器模式。这是最常用的模式,定时器/计数器使用THx和TLx寄存器,共16位。
-
模式2:8位自动重装载模式。在这种模式下,TLx寄存器用作8位定时器/计数器,而THx寄存器的内容在TLx溢出时自动重装载到TLx。
-
模式3:两个8位定时器/计数器模式。在这种模式下,THx和TLx各自独立作为8位定时器/计数器工作。
定时器的应用
定时器在51单片机中的应用非常广泛,包括但不限于:
- 产生精确的延时:通过配置定时器的初值和时钟源,可以实现微秒级到毫秒级的精确延时。
- 测量时间间隔:利用定时器的计数功能,可以测量两个事件之间的时间间隔。
- 产生定时中断:定时器溢出时可以产生中断,用于实现多任务处理或周期性任务。
- PWM(脉宽调制)输出:通过控制定时器的输出和占空比,可以生成PWM信号,用于电机控制等应用。
定时器的配置和使用
在使用定时器之前,通常需要进行以下配置:
- 选择工作模式:通过设置定时器控制寄存器(如TMOD)来选择定时器的工作模式。
- 设置初值:将定时器的初值加载到THx和TLx寄存器中。
- 启动定时器:通过设置定时器控制寄存器(如TCON)中的相关位来启动定时器。
- 配置中断(如果需要):如果需要定时器溢出时产生中断,需要配置中断允许寄存器(如IE)和中断优先级寄存器(如IP)。
通过合理配置和使用定时器,51单片机可以实现各种复杂的定时和计数功能,满足不同的应用需求。
那什么是定时器中断呢?
其实简单点来说(灵感来源于其他博主,但是我忘记是谁的了),原本的main函数中,程序都是一条一条的执行,就像大家去上公共厕所,一共就那么几个坑位,上一个拉完了,下一个才可以进去,但是呢定时器中断就像是串稀快呼之欲出的人,它有特权,它可以直接打开厕所门把正在拉的人抓出去(先不让他啦,让他夹住^_^),然后拉稀的先进去,因为定时器中断都是执行快的、简单的、不那么复杂的程序,跟串稀的一样,一旦释放几下就结束战斗了,所以定时器中断结束后,又回到刚刚被打断的程序继续执行,即让那个夹住的人继续(QaQ).
本次实验代码
在编写的过程中,我出现了两个错误
1、我觉得本次不需要在main中执行其他程序就将while循环删掉,main中的while循环也要写,不写就破坏了单片机的时序,因为数码管是通过定时器中断来更新的, 那么while(1){}循环的存在确保了定时器中断可以被持续处理,每次定时器溢出时,中断服务程序都会被调用,更新数码管的显示.
*如果没有while(1) {}循环,程序将在初始化后立即结束,定时器中断将不会被处理,因此数码管将不会显示任何内容
2、在上次动态数码管显示中,我使用的是for循环来调用每个数码管的段选和位选。本次实验开始前,我想当然以为把for循环显示的内容移动到定时器中断中即可。但是事实不是这样的,因为之前的for循环是一直在main的while中循环,没有什么影响,但是在定时器中断中就不一样了,定时器中断是每隔一个时间段(取决于你定时器的长短),进去一次,进入后for就一直执行,直到for满足条件才停止跳出定时器(数码管的值停在了第八位),所以在protues中显示的效果就是只显示了第八位。
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
uint i=0; //uchar code pos[]={0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F}; //8位数码管的位选
uchar code table[]={0x3f,0x06,0x5b,0x4f,0x66, //共阴型段码表0~9
0x6d,0x7d,0x07,0x7f,0x6f};
uchar xuehao[8]={1,2,0,1,2,1, 1,0}; //学号后8位比如为:12012110
uint CTRL_bit=0;
/******************数码管位选段选****************************************/
void ShowSMG_Bit(unsigned char value, unsigned int pos)
{
//共阴数码管,位选接P3为低,段选接P0为高
//数码管位选
P3 = ~(0X01 << pos);//通过移位选位
//数码管段选
P0 = table[xuehao[value]];
}
/*******定时器*****************************************/
void InitTimer0()//定时器初始化函数
{
TMOD = 0x01; // 设置定时器0为模式1(16位定时器)
TH0 = (65535 - 5000) /256; // 7100以下都可以正常显示数码管,不会闪烁
TL0 = (65535 - 5000) %256; // 加载定时器初值(5000=5ms)
ET0 = 1; // 使能定时器0中断
EA = 1; // 使能全局中断
TR0 = 1; // 启动定时器0
}
/*******定时器中断*****************************************/
void Timer0() interrupt 1
{
TH0 = (65535 - 5000) /256;
TL0 = (65535 - 5000) %256; // 重载定时器初值(也可选择自动重载模式就不需要手动重载)
/*
for(i=0;i<8;i++)
{
ShowSMG_Bit(i,i);
}
此处不可用for代替switch,虽说逻辑相似,但是大有不同
switch是进一次中断刷新一下,for是进一次中断就将8位都显示一遍
最后离开中断数码管停在第八位显示第八位的数
*/
switch(CTRL_bit)
{
case 0 :ShowSMG_Bit(0,0);break;
case 1 :ShowSMG_Bit(1,1);break;
case 2 :ShowSMG_Bit(2,2);break;
case 3 :ShowSMG_Bit(3,3);break;
case 4 :ShowSMG_Bit(4,4);break;
case 5 :ShowSMG_Bit(5,5);break;
case 6 :ShowSMG_Bit(6,6);break;
case 7 :ShowSMG_Bit(7,7);break;
}
CTRL_bit=(CTRL_bit+1)%8;
}
/*****************主函数******************************************/
void main()
{
// TMOD = 0x01; // 设置定时器0为模式1(16位定时器)
// TH0 = (65535 - 5000) /256;
// TL0 = (65535 - 5000) %256; // 加载定时器初值(10ms)
//
//
// ET0 = 1; // 使能定时器0中断
// EA = 1; // 使能全局中断
// TR0 = 1; // 启动定时器0
InitTimer0();
while(1)
{
/*
空的while(1)循环必须要写,因为数码管是通过定时器中断来更新的
那么while(1){}循环的存在确保了定时器中断可以被持续处理
每次定时器溢出时,中断服务程序都会被调用,更新数码管的显示
*如果没有while(1) {}循环,程序将在初始化后立即结束,定时器中断将不会被处理,因此数码管将不会显示任何内容
*/
}
}