第三章 数码管的显示原理及实现
第三章 数码管的显示原理及实现
实验板上用了2片74HC573来驱动数码管,分别控制位选和段选信号。
- 让第一个数码管显示一个8字,代码如图1所示。
图1 数码管显示8
- 让6个数码管同时点亮,间隔0.5s,依次显示0-F。
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit wela=P2^6;
sbit dula=P2^7;
uchar num;
uchar code table[]={0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delayms(uint);
void main()
{
wela=1; // turn on u2 latch
P0=0xc0; // 6 bits on
wela=0; // turn off u2 latch
while(1)
{
for(num=0;num<16;num++)
{
dula=1; //turn on seg choose
P0=table[num];
dula=0; //turn off seg choose
delayms(500);
}
}
}
void delayms(uint xms)
{
uint i,j;
for(i=xms;i>0;i++)
for(j=110;j>0;j++);
}
难点在于,位选只需要打开一次,就被锁存了,也就是这里的6的数码管都一直被选中了,然后用段选在6个数码管上同时显示0-f。这也是用74573锁存器的好处。
- 数码管动态显示。每个数码管依次显示不同的数字。
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit dula=P2^6;
sbit wela=P2^7;
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delayms(uint);
void main()
{
while(1)
{
dula=1; //seg1 display
P0=table[1];
dula=0;
P0=0xff;
wela=1;
P0=0xfe;
wela=0;
delayms(500);
dula=1;
P0=table[2];
dula=0;
P0=0xff;
wela=1;
P0=0xfd;
wela=0;
delayms(500);
dula=1;
P0=table[3];
dula=0;
P0=0xfb;
wela=1;
P0=0xfe;
wela=0;
delayms(500);
dula=1;
P0=table[4];
dula=0;
P0=0xff;
wela=1;
P0=0xf7;
wela=0;
delayms(500);
dula=1;
P0=table[5];
dula=0;
P0=0xff;
wela=1;
P0=0xef;
wela=0;
delayms(500);
dula=1;
P0=table[6];
dula=0;
P0=0xff;
wela=1;
P0=0xdf;
wela=0;
delayms(500);
}
}
void delayms(uint xms)
{
uint i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
这段代码依次控制6个数码管先锁存段选,再取消,然后锁存位选,再延时。其中,P0=0xff,是为了消隐。可以尝试把延时分别调至100ms,10ms,1ms,可以看到数码管可以越来越稳定的显示1-6,就像6个数码管同时显示一样。这就是动态显示效果。所谓动态显示,即轮流向各位数码管送出字形码和相应的位选,利用发光管的余晖和视觉暂留作用,使人觉得数码管好像在同时显示,而实际上是多位数码管一位一位显示,只是轮流的速度非常快,人眼已无法分辨出来。
- 中断概念
52单片机共有6个中断源:
INT0:外部中断0,由P3.2口引入,低电平或下降沿引起;
INT1:外部中断1,由P3.3口引入,低电平或下降沿引起;
T0:定时器/计数器0中断,由T0计数器计满回0引起;
T1:定时器/计数器1中断,由T1计数器计满回0引起;
T2:定时器/计数器2中断,由T2计数器计满回0引起;
TI/RI:串口中断,串口完成一帧字符发送/接收后引起。
单片机使用时,通常需要设置两个与中断有关的寄存器:中断允许寄存器IE和中断优先级寄存器IP。
中断允许寄存器:
EA:全局中断允许位,1开,0关;
ET2:定时器2/计数器2中断允许位;
ES:串口中断允许位;
ET1:定时器1/计数器1中断允许位;
EX1:外部中断1中断允许位;
ET0:定时器0/计数器0中断允许位;
EX0:外部中断0中断允许位。
中断优先级寄存器IP:
PS:串口中断优先级控制位,1高优先级;0低优先级;
PT1:定时器/计数器1中断优先级控制位;
PX1:外部中断1中断优先级控制位;
PT0:定时器/计数器0中断优先级控制位;
PX0:外部中断0中断优先级控制位。
3.5 定时器中断
定时器/计数器的实质是加1计数器,由高8位和低8位两个寄存器组成。TMOD是其工作方式寄存器,确定工作方式和功能;TCON是控制寄存器,控制T0,T1的启动,停止及设置溢出标志。
加1计数器输入的计数脉冲有两个来源,一个是由系统的时钟振荡器输出脉冲经12分频后送来;另一个是由T0或T1引脚输入的外部脉冲源。每来一个脉冲,计数器加1,当加到计数器全为1时,再输入一个脉冲,就使计数器回零,且计数器的溢出,使TCON寄存器的TF0或TF1置1,向CPU发出中断请求(定时器/计数器中断允许时)。如果定时器/计数器工作于定时模式,则表示定时时间已到。如果工作于计数模式,则表示计数已满。
由此可见,由溢出时计数器的值减去计数初值才是加1计数器的计数值。
设置为定时器模式时,加1计数器是对内部机器周期计数(1个机器周期等于12个振荡周期,即计数频率为晶振频率的1/12)。计数值N乘以机器周期Tcy就是定时时间t。
设置为计数器模式时,外部事件计数脉冲由T0或T1引脚输入到计数器。在每个机器周期的S5P2期间采样T0、T1引脚电平。当某周期采样到一高电平输入,下一周期又采样到一低电平时,则计数器加1。更新的计数值在下一机器周期S3P1期间装入计数器。由于检测一个从1-0下降沿需要2个机器周期,因此要求被采样的电平至少要维持一个机器周期。当晶振频率为12M时,最高计数频率不超过1/2M,即计数脉冲周期要大于2us。
TMOD的高4位用于设置定时器1,低4位用于设置定时器0。
GATE-门控制位;
GATE=0,定时器/计数器的启动、停止仅受TCON中的TRX控制。
GATE=1,定时器/计数器的启动、停止受TRX和外部中断引脚INT0,INT1上的电平共同控制。
C/T:0-计数器模式;1-定时器模式;
M1M0:工作方式选择位。00-13位定时器/计数器;01-16位定时器/计数器;10-8位初值自动重装的8位定时器/计数器;11-仅适用于T0,分成两个8位计数器,T1停止计数。
TCON用来控制启停,标志溢出和中断情况。
TF1-定时器1溢出标志位。当定时器1计满溢出时,由硬件使TF1置1,并申请中断。进入中断服务程序后,由硬件自动清0.
TR1-定时器1运行控制位。
TF0-定时器0溢出标志位。
TR0-定制器0运行控制位。
IE1-外部中断1请求标志。
IT1-外部中断1触发方式选择。
IE0-
IT0-
定时器初值的计算方法:当用定时器方式1时,设机器周期为Tcy,定时器产生一次中断的时间为t,那么需要计数的个数N=t/Tcy,装入THX和TLX中的数分别为
THX=(65536-N)/256;
TLX=(65536-N)%256;
在写单片机定时器程序时,在程序开始处需要对定时器及中断寄存器做初始化设置,通常定时器初始化过程如下:
对TMOD赋值,以确定T0和T1的工作方式;
计算初值,并将初值写入TH1,TL1或TH0,TL0;
中断方式时,对IE赋值,开放中断;
使TR0,TR1置位,启动定时器/计数器定时或计数。
//发光管以1秒间隔闪烁
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit led1=P1^0;
uchar num;
void main()
{
TMOD=0x01; //设置定时器0为工作方式1(M1M0为01)
TH0=(65536-45872)/256; //装初值11.0592M晶振定时50ms数为45872
TL0=(65536-45872)%256; //
EA=1; //开总中断
ET0=1; //开定时器0中断
TR0=1; //启动定时器0
while(1); //程序停在这里等待中断发生
}
void T0_time() interrupt 1
{
TH0=(65536-45872)/256; //重装初值
TL0=(65536-45872)%256;
num++; //num每加1次判断是否到达20次
if(num==20) //如果到了20次,说明1秒时间到
{
num=0; //把num清0重新计20次
led1=~led1; //让发光管状态取反
}
}
分析:进入主程序后,首先是对定时器和中断有关的寄存器初始化,我们按照上面讲到的通常初始化过程来操作。定时50ms,初值为45872.启动定时器后,主程序停在while(1)处,中断是如何执行呢?一旦开启定时器,定时器便开始计数,当计数溢出时,自动进入中断服务程序。执行完中断服务程序,回到原来处继续执行,也就是继续等待。
用定时器0的方式1实现第1个发光二极管以200ms间隔闪烁。用定时器1的方式1实现数码管前两位59s循环计时。
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit dula=P2^6;
sbit wela=P2^7;
sbit led1=P1^0;
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delayms(uint);
void display(uchar,uchar);
uchar num,num1,num2,shi,ge;
void main()
{
TMOD=0x11; //定时器0和1工作方式
TH0=(65536-45872)/256; //装初值
TL0=(65536-45872)%256;
TH1=(65536-45872)/256; //装初值
TL1=(65536-45872)%256;
EA=1; //开总中断
ET0=1; //开定时器0中断
ET1=1; //开定时器1中断
TR0=1; //启动定时器0
TR1=1; //启动定时器1
while(1)
{
display(shi,ge);
}
}
void display(uchar shi,uchar ge) //显示子函数
{
dula=1;
P0=table[shi]; //送段选数据
dula=0;
P0=0xff; //送位选数据前关闭所有显示,防止打开位选锁存时
//原来段选数据通过位选锁存器造成混乱
wela=1;
P0=0xfe; //送位选数据
wela=0;
delayms(5); //延时
dula=1;
P0=table[ge];
dula=0;
P0=0xff;
wela=1;
P0=0xfd;
wela=0;
delayms(5);
}
void delayms(uint xms)
{
uint i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
void T0_time() interrupt 1
{
TH0=(65536-45872)/256; //重装初值
TL0=(65536-45872)%256;
num1++;
if(num1==4) //200ms
{
num1=0;
led1=~led1;
}
}
void T1_time() interrupt 3
{
TH1=(65536-45872)/256;
TL1=(65536-45872)%256;
num2++;
if(num2==20) //1s
{
num2=0;
num++;
if(num==60) //这个数送显,到60后归0
num=0;
shi=num/10;
ge=num%10;
}
}
本例用了2个中断函数,进入哪个中断,是靠interrupt后面的序号决定。