今天咱聊聊数码管,其实数码管这个器件,本身没有什么好讨论的。为什么这里作为单独一节拿出来讨论呢?个人认为,这个小东西虽然简单,实际使用过程中,其实包含了很多。单片机知识以外的内容,譬如一些简单的编程技巧、编程与实际现象的关联,非常值得入门者认真思考。因为在今后的电子之路上,遇到的情况会比这个复杂得多。所以建立一种分析问题解决问题的方法和思考,比掌握一种单片机或者一种外设要重要得多。
先来看看啥是数码管吧,其实数码管就是几个LED并联的产物,看下图的数码管:
这是一个常用的二位8段数码管,可以看到有7个段位和1个小数点,其实对于每一个段位,内部都有与之对应的一个LED。
上图中右上角给出了每个段位的称号,分别是abcdefg和dp,dp指的就是小数点。上图中的03621A和03621B说明的就是:这8个LED在内部的连接方式。值得注意的是,03621A中,所有LED的阳极都连接到了一起,称之为共阳极数码管。不难判断,abcdefg和dp需要给个低电平,才能使得LED导通发光。03621B中,所有LED的阴极都连接到了一起,称之为共阴极数码管。不难判断,abcdefg和dp需要给个高电平,才能使得LED导通发光;因为这是个两位的数码管,所以还设置了DIG1和DIG2来控制那位数码管进行显示。
举个栗子:在共阳极数码管中,阳极连接到电源。如果要显示数字“1”,那么就需要a和c这两个段位一个低电平,其它为高,使得这两段位的LED发光即可。So,a-g的编码就是0101111,以此类推,0-9均可以通过abcdefg的赋值来通过显示。小数则可以通过dp段的赋值来显示。好玩一点的话,整个16进制0-f都可以显示,特别值得一提的是。共阴极和共阳极的输入编码是不一样的在共阳极数码管中,如果要显示数字“1”,a-g分别输入0101111 。而在共阳阴极数码管中,如果要显示数字“1”,a-g分别输入1010000。
到这里,数码管的内部结构应该有了个大致的了解,来一起玩玩吧。上个洞洞板图
背部飞线,焊接功底比较差,请随性鄙视
上个原理图吧三极管在这里不是用于信号放大,而是作为一个开关器件,完成对位的选通。其余每个段位都使用1k左右的电阻作为限流,这个限流电阻值的选取,需要考虑端口的灌电流能力。特别注意a-dp,完全使用P2的8个端口,加上P1.7,总共消耗9个IO口
上代码
#include
sbit smgbit1 = P1^7;
sbit smgbit2 = P1^6;
disp_cache[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8,0x80,0x90};
void delay_ms(unsigned int xms)
{
unsigned int i,j;
for(i=xms;i>0;i--)
{
for(j=124;j>0;j--);
}
}
void main()
{ unsigned char i;
smgbit1 = 1;
smgbit2 = 0;
while(1){
for(i = 0;i <= 9;i++)="">=>
P2 = disp_cache[i];
delay_ms(250);
}
}
}
这段代码里面有个数组disp_cache,第一个元素disp_cache[0]=0xC0,换做二进制便是1100 0000。
对照数码管的段位,dp至a的电平便是1100 0000,abcdef都亮,g和dp不亮,也就形成的数字“0”。以此类推,disp_cache[1]- disp_cache[9]则分别代表了数字“1”-“9”,所以,咱进行数码管显示的时候
只需要关注disp_cache[i]里面i这个变量就行了。方便不少,这种简单有效的编程技巧,会有助于代码更加简洁,主函数里面。将smgbit,也就是p1.7位置1。这个时候,DIG1端口的三极管导通。DIG1口相当于直接连接至Vcc,而DIG2则与Vcc断开。然后在for循环里面,每500ms对i从0-9自加操作。同时在P2口输出disp_cache[i],也就是数码管从0-9进行显示,看实际运行情况
这样就完成了一个基础的数码管显示,但是回头想想.咱这可是2位的数码管哟!如果按照这种显示方式,同时显示2位的话,需要占用8+1+8+1=18个IO口.单片机硬件资源消耗情况实在太夸张了.如何解决?
一是采用ASIC来驱动数码管,这种方式一来会增加硬件成本,二来要研究ASIC复杂的协议.另外一种就是扫描的显示方式了,所谓扫描显示,就是利用人眼的视觉暂留原理'.这个原理在现实生活中用的非常广泛,譬如电源、摇摇棒等等!每次只显示一位数码管,在ms的时间内进行切换.譬如要显示”13”,那就先显示1,再切换显示3.切换时间不超过十多ms的话,人眼会认为是同时显示的.这种方式可以较少IO口的使用,同时降低功耗
上个代码
#include
sbit smgbit1 = P1^7;
sbit smgbit2 = P1^6;
smgdisp_cache[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8,0x80,0x90};
unsigned int count,timer_count;
void init()
{
smgbit1 = 0;
smgbit2 = 1;
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfc;
TL0=0X66;
EA=1;//开总中断
ET0=1;//开定时器0中断
TR0=1;//启动定时器0
}
void SMG_Dis()
{
if(smgbit1){
P2 = smgdisp_cache[count%10];//求余,取个位数显示
}
if(smgbit2){
P2 = smgdisp_cache[count/10];//求商,取十位数显示
}
}
void time0() interrupt 1
{
timer_count++;
if(timer_count == 100)
{count++;
timer_count = 0;
}
smgbit1 = ~smgbit1;
smgbit2 = ~smgbit2;
if(count>99){count = 0;}
SMG_Dis();
TF0 = 0;
TH0 = 0xfc;
TL0 = 0x66;
}
void main()
{ init();
while(1){
}
}
在这段代码中,Init()用于初始化定时器,1ms中断,同时两位数码管只有其中一位可以显示.SMG_Dis()用于进行扫描显示,如果smgbit1和smgbit2.哪个选通,哪个就进行显示,中断服务函数中,处理定时器事宜,每100ms进行一次自加.并且每次中断都将smgbit1和smgbit2取反,保证每次只显示其中一位.看看运行效果
上面的动图中,基本计数显示的功能是达到了,但是,似乎个位的走动会影响到十位的显示.其实就是数码管在刷新下一个数据时,上次的数据会留有余晖导致的.也就是传说中的数码管鬼影.鬼影的消除,无非就是两种,加延时和数据清零.这里使用数据清零的办法,把中断函数做个简单处理void time0() interrupt 1
{ P2 = 0xff; //消除鬼影
timer_count++;
if(timer_count == 100)
{count++;
timer_count = 0;
}
smgbit1 = ~smgbit1;
smgbit2 = ~smgbit2;
if(count>99){count = 0;}
SMG_Dis();
TF0 = 0;
TH0 = 0xfc;
TL0 = 0x66;
}
增加一句P2 = 0xff;及时对数码管的显示数据进行清零.用来对暂态数据进行清除
看效果
可以看出来,基本上残影都被消除掉了.其实鬼影的出现,并非是代码思路有问题.而是一些外部器件的特性使然.今后碰到的器件,很可能会有类似的情况.养成遇山开路、遇水搭桥的思维习惯,才是学习单片机的捷径.Ok,今天就讨论到这里.
了解更多51系列教程,请关注“云汉电子社区”官方微信公众号,关注后回复'活动'可免费参与社区最新活动,有机会获得电子类图书以及京东购物卡!
联系客服