数码管简介
LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件。比如红绿灯。
单个数码管:
多个数码管:
这些引脚由对应的寄存器控制着,具体查看原理图和数据手册即可。
编程时要注意是共阴极,还是共阳极。
注意,共阳极数码管一般是将共阳极外接电源电路,有足够的电流点亮数码管,如果是共阴极数码管,那么就需要单片机来提供阳极电流,一般单片机没有这么大的电流,所以通常会接一个芯片,起到放大电流的作用,比如下方的74573。
位选信号决定亮哪个数码管,段选信号决定显示数字几。
因为数字引脚是共用的,所以一次只能显示一种数字,即使位选了多个数码管,多个数码管也只能显示同一个数字,这就叫数码管的静态显示。
那如何动态显示呢?就要利用视觉暂留效应。依次让不同的数码管显示不同的数字,只要单片机扫描的频率快到人眼察觉不到,就可以在不同的数码管显示不同的数字。
比如:
while(1){
数码管1显示1;
数码管2显示2;
数码管3显示3;
}
因为频率很快,所以人眼察觉不到在闪动。不过,可以通过在中间加上延时来验证。
比如:
while(1){
数码管1显示1;
delay(1);
数码管2显示2;
delay(1);
数码管3显示3;
delay(1);
}
这种情况虽然能够动态显示,但是会存在消隐现象。什么意思,我们理一下这个过程。
位选 段选
位选 段选
位选 段选
每一轮都不断的重复扫描,上一轮LED段还没完全熄灭,下一轮就来了,上一个轮就会有残影。解决方法是,在每一轮的每一位显示后,将段选清零(注意,是段选,不是位选,位选通过138译码器连接,无法关闭)。
while(1){
数码管1显示1;
上一个的段选清零;
数码管2显示2;
上一个的段选清零;
数码管3显示3;
上一个的段选清零;
}
单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间;
所以现在都有专用的驱动芯片。
专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可。
静态数码管原理图
数码管本质上就是8个LED灯组成的。操作方式和LED灯没有本质区别。
将JP3和P0端口用杜邦线连接起来。
因为不太清楚引脚对应的顺序,可能需要测试下端口的对应关系。
显示数字6,代码如下:
/** *@file pipeshowsix.c *@author Timi *@date 2022.07.13 *@version 1.0 */ #include <reg51.h> #define NUMSIX 0x82 void ShowSix(); void main(void) { ShowSix(); } /** *@brief 静态数码管显示6 *@param[in] *@param[out] *@return */ void ShowSix() { P0 = NUMSIX; }
动态数码管
8个数码管的段选端是共用的,通过位选来使能不同的数码管。
原理图如下:
可见,需要两根杜邦线。
我将J12连接到P0端口,将J16连接到P1端口。
因为段选端是共用的,如果想要8个数码管显示同样的数字,比较简单。
但是如果想要8个数码管同时显示不同的数字,就需要扫描。
比如,8个数码管从前到后显示12345678,那么先第1个数码管显示1,然后第2个数码管显示2,然后第3个数码管显示3……第8个数码管显示8,因为代码运行的速度太快,人眼跟不上变化,所以看起来就是同时显示的。实现代码如下:
暂时还写不了代码,因为我在开发板上找J16找了好久,才发现J16和J15用跳帽连接了起来,而J15引脚连接了一个74LS138译码器。
下面我来简单介绍一下74LS138芯片的基本情况和使用注意事项:
74LS138 为3 线-8 线译码器,共有 54/74S138和 54/74LS138 两种线路结构型式,其74LS138工作原理如下:当一个选通端(G1)为高电平,另两个选通端(/(G2A)和/(G2B))为低电平时,可将地址端(A、B、C)的二进制编码在一个对应的输出端以低电平译出。
下图是它的原理结构图以及真值表:
无论从逻辑图还是功能表我们都可以看到74LS138的八个输出管脚,任何时刻要么全为高电平1—芯片处于不工作状态,要么只有一个为低电平0,其余7个输出管脚全为高电平1。如果出现两个输出管脚在同一个时间为0的情况,说明该芯片已经损坏。
所以,这里通过连接译码器来减少引脚的使用吗?将本来需要8个引脚减少到3个引脚。我的理解是这样的。
如果是这样,那么我就需要将J6连接到P1端口的P1.0、P1.1、P1.2。
代码如下:
/** *@file scantube.c *@author Timi *@date 2022.07.14 */ #include <reg51.h> //数码管数字编码 #define TUBE_NUM0 0x3F #define TUBE_NUM1 0x06 #define TUBE_NUM2 0x5B #define TUBE_NUM3 0x4F #define TUBE_NUM4 0x66 #define TUBE_NUM5 0x6D #define TUBE_NUM6 0x7D #define TUBE_NUM7 0x07 #define TUBE_NUM8 0x7F #define TUBE_NUM9 0x6F #define TUBE_NOSHOW 0x0; //数码管位选 #define TUBE1 0x0 #define TUBE2 0x1 #define TUBE3 0x2 #define TUBE4 0x3 #define TUBE5 0x4 #define TUBE6 0x5 #define TUBE7 0x6 #define TUBE8 0x7 void FirstTubeSixToZero(); void TubeShowOneToEight(); void Delay(); //函数入口 void main(void) { FirstTubeSixToZero(); Delay(); TubeShowOneToEight(); } /** *@brief *@param[in] LED的编号,0—7 *@param[out] *@return */ void FirstTubeSixToZero() { P1 = TUBE1; P0 = TUBE_NUM6; Delay(); P0 = TUBE_NUM5; Delay(); P0 = TUBE_NUM4; Delay(); P0 = TUBE_NUM3; Delay(); P0 = TUBE_NUM2; Delay(); P0 = TUBE_NUM1; Delay(); P0 = TUBE_NUM0; } /** *@brief *@param[in] *@param[out] *@return */ void TubeShowOneToEight() { while(1) { P1 = TUBE1; P0 = TUBE_NUM1; P0 = TUBE_NOSHOW; P1 = TUBE2; P0 = TUBE_NUM2; P0 = TUBE_NOSHOW; P1 = TUBE3; P0 = TUBE_NUM3; P0 = TUBE_NOSHOW; P1 = TUBE4; P0 = TUBE_NUM4; P0 = TUBE_NOSHOW; P1 = TUBE5; P0 = TUBE_NUM5; P0 = TUBE_NOSHOW; P1 = TUBE6; P0 = TUBE_NUM6; P0 = TUBE_NOSHOW; P1 = TUBE7; P0 = TUBE_NUM7; P0 = TUBE_NOSHOW; P1 = TUBE8; P0 = TUBE_NUM8; P0 = TUBE_NOSHOW; } } /** *@brief 延时 *@param[in] *@param[out] *@return */ void Delay() { int i = 0, j = 0; for(i; i < 30000; i++) { for(j; j < 30000; j++); } }
优化总结
其实不难从上面的程序看出来,有太多的重复代码了。
这时候,就需要重构,可以考虑使用函数再次进行封装;或者将相似的参数组装成一个数组,然后用循环语句来解决。
如下所示:
/** *@file scantube.c *@author Timi *@date 2022.07.14 */ #include <reg51.h> void FirstTubeEightToZero(unsigned char tube[], unsigned char tubeNum[], int tubeNumCount); void TubeShowOneToEight(unsigned char tube[], unsigned char tubeNum[], int tubeCount, int tubeNumCount); void Delay(); //函数入口 void main(void) { //数码管位选 unsigned char tube[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; //数码管数字编号0—9,最后一位为全不亮 unsigned char tubeNum[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00}; int tubeCount = sizeof(tube) / sizeof(tube[0]); int tubeNumCount = sizeof(tubeNum) / sizeof(tubeNum[0]); FirstTubeEightToZero(tube, tubeNum, tubeNumCount); TubeShowOneToEight(tube, tubeNum, tubeCount, tubeNumCount); } /** *@brief 8—0数倒计时 *@param[in] *@param[out] *@return */ void FirstTubeEightToZero(unsigned char tube[], unsigned char tubeNum[], int tubeNumCount) { int i = tubeNumCount - 3; P1 = tube[0]; for(i; i >= 0; i--) { P0 = tubeNum[i]; Delay(); } } /** *@brief *@param[in] *@param[out] *@return */ void TubeShowOneToEight(unsigned char tube[], unsigned char tubeNum[], int tubeCount, int tubeNumCount) { while(1) { int i = 0; for(i; i < tubeCount; i++) { P1 = tube[i]; P0 = tubeNum[i + 1]; P0 = tubeNum[tubeNumCount - 1]; } } } /** *@brief 延时 *@param[in] *@param[out] *@return */ void Delay() { int i = 0, j = 0; for(i;i<30000;i++) { for(j;j<30000;j++); } }
关于亮度问题
数码管(二极管)的亮度取决于电流停留的时长,时长越短就越暗(类似于PWM波的占空比原理),所以,当扫描过快时,每一次停留时长较短,数码管就没那么亮。
解决方法是,适当地增加延时。
人眼感觉不到卡顿的界限通常是25Hz,比如电影就是1秒24帧或者25帧画面。也就是说,视觉暂留的理想时间是0.04秒。只要在0.04秒内再次出现,就可以实现扫描效果。