一、根据电路图实现动态数码管
1. 对于动态数码管
首先需要知道的是,动态数码管是一种对数码管的实现方式,并不是数码管的种类,静态数码管也是一种实现方式,所以说,静动态的数码管内部结构没有差异。
动态数码管常见于对多个数码管进行编程,采用扫描方式对各个数码管给源给码(源可以理解成电源,码可以理解成段码,段码即显示的内容),再通过单片机CPU的高速循环,使各个数码管得以同时呈现不同的数字。
2. 电路图
可以看到,该8位数码管是通过J16给源,J12给码的方式来实现数码管的亮灭。电路图中,数码管将8个LED灯的引脚全部接到了J12口的八根线上,统一给码,再通过J16来控制。由于是共阴极数码管,故当J16对某一位给0时,该位对应的数码管则亮。
注:74573本身是个锁存器,但在这个电路中没有用到锁存功能,只是简单的当作功放来用,用来驱动发光管的功率放大。
3. 51 C编程
- 这里使用P0口控制数码管的段码位,P2口控制数码管的COM口。
#include <reg51.h>
unsigned char screenNum[8]={1,3,5,7,2,4,6,8};
void printNum(int i,int state)
{
//0123456789AbCDEF
unsigned char num[16]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
if(state==0) P0=num[i]; //共阳
else P0=~num[i]; //共阴
}
int main()
{
unsigned char i;
unsigned char j;
while(1)
{
for(i=0;i<8;i++)
{
P2=~(1<<i); // P2 接 J16 控制数码管的亮灭
printNum(screenNum[i],1); //显示数字
j=8;
while(j--) ; //延时
P0=0x0; //消隐
}
}
return 0;
}
- 注意:【消隐操作】在送出第N+1位的段码前,一定要先把第N位的位段码关闭,不然第N+1位的段码会在第N位有个短暂的残留。这个短暂的残留影象就是残影了。
二、配合38译码器实现动态数码管
1. 首先,什么是38译码器?
38译码器有3个输入端口A、B、C和8个输出端口Y0~Y7。由输入端口控制输出端口的值。
2. 为什么使用38译码器?
使用38译码器来控制数码管的COM口可以节约IO口,其原理是:输入口的3位总共可以代表8个十进制数0-7,分别对应输出端口的8个引脚,使得38译码器实现以3个引脚控制八个输出端口,不过这样就造成8个端口无法同时被选中,一次只能选择一个端口,但是在实现动态数码管时可以使用它来控制COM口,因为在动态扫描过程中,要的就是一次只选择一个端口。
3. 3个输入端口如何控制8个输出端口?
查询74HC138芯片的数据手册,可找到芯片的译码表。
表中,Enable项已经初始化好了,我们只需要关心Select项中的A、B、C输入端口即可。
4. 编程
- 这里使用P0口控制数码管的段码位,P2口控制数码管的COM口。
#include <reg51.h>
unsigned char screenNum[8]={1,3,5,7,2,4,6,8};
unsigned char placeArr[8]={0x0,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
void printNum(int i,int state)
{
//0123456789AbCDEF
unsigned char num[16]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
if(state==0) P0=num[i]; //共阳
else P0=~num[i]; //共阴
}
int main()
{
unsigned char i;
unsigned char j;
while(1)
{
for(i=0;i<8;i++)
{
P2 = placeArr[i];
printNum(screenNum[i],1);
j=100;
while(j--) ;
P0=0x0;
}
}
return 0;
}
5. 改进
考虑到控制38译码器只用到P2的P2.0、P2.1、P2.2三个引脚,其他引脚不应该去改动它们原本的值,故作以下改进:
- 对P2.0、P2.1、P2.2三位清零,然后在循环中每次加一来改变其内的值。
- 每扫描一排,则重新清零再次累加。
#include <reg51.h>
unsigned char screenNum[8]={1,3,5,7,2,4,6,8};
void printNum(int i,int state)
{
//0123456789AbCDEF
unsigned char num[16]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
if(state==0) P0=num[i]; //共阳
else P0=~num[i]; //共阴
}
int main()
{
unsigned char i;
unsigned char j;
while(1)
{
P2 &= 0xf8;
for(i=0;i<8;i++)
{
printNum(screenNum[i],1);
j=100;
while(j--) ;
P0=0x0;
P2 += 1;
}
}
return 0;
}
- 注意:需要将P2+=1放到消隐之后,原因是:P2从000开始,若加1后再显示则会发生错位。
三、【实例】使用动态数码管实现倒计时
- 接线与上图相同
1. 使用延时函数实现倒数间隔
#include <reg51.h>
unsigned char screenNum[8]={1,3,5,7,2,4,6,8};
void printNum(int i,int state)
{
//0123456789AbCDEF
unsigned char num[16]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
if(state==0) P0=num[i]; //共阳
else P0=~num[i]; //共阴
}
int main()
{
unsigned char i;
unsigned char j;
unsigned long count=10000;
unsigned int timeCount=0;
while(1)
{
unsigned long temp=count;
for(i=0;i<8;i++)
{
screenNum[7-i]=temp%10;
temp/=10;
}
P2 &= 0xf8;
for(i=0;i<8;i++)
{
printNum(screenNum[i],1);
j=100;
while(j--) ;
P0=0x0;
P2 += 1;
}
delay_ms(100);
count--;
}
return 0;
}
烧录后发现,数字变化时会闪烁。
经分析,应该是延时函数导致的,因为动态数码管需要不断扫描数码管,如果存在延时函数则会中断数字的显示。
改进后使用一个计数器来实现近似延时,见2.
2. 使用计数器实现倒数间隔
#include <reg51.h>
unsigned char screenNum[8]={0,0,0,0,0,0,0,0};
void printNum(int i,int state)
{
//0123456789AbCDEF
unsigned char num[16]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
if(state==0) P0=num[i]; //共阳
else P0=~num[i]; //共阴
}
int main()
{
unsigned char i;
unsigned char j;
unsigned long count=10000;
unsigned int timeCount=0;
while(1)
{
unsigned long temp=count;
if(timeCount++>100)
{
for(i=0;i<8;i++)
{
screenNum[7-i]=temp%10;
temp/=10;
}
timeCount=0;
count--;
}
//P2接38译码器控制数码管COM口
P2 &= 0xf8; //P2.0-2置0
for(i=0;i<8;i++)
{
printNum(screenNum[i],1);
j=100;
while(j--) ; //延时
P0=0x0; //消隐
P2 += 1;
}
}
return 0;
}
这里还有两个问题:
①数字变化间隔不为真正的一秒
②数字变化时会轻微闪烁
待解决。。。