数码管简介
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时间;
所以现在有专用的驱动芯片。
专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可。
静态数字管原理图
本质上,数码管是8LED由灯组成。操作模式和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个数字管从前到后显示1234578,则首先显示1,然后显示2,然后显示3……第八个数字管显示8,因为代码运行太快,人眼跟不上变化,所以看起来是同时显示的。实现代码如下:
下面简单介绍一下74。LS138芯片的基本情况及使用注意事项:
74LS138 为3 线-8 共有线译码器 54/74S138和 54/74LS138 74LS138的工作原理如下:当选择端时(G1)是另外两个选通端(/(G2A)和/(G2B))地址端可用于低电平时(A、B、C)二进制编码在相应的输出端低电平翻译。
下图为其原理结构图和真值表:
我们可以从逻辑图和功能表中看到744LS138的8个输出管脚要么在任何时候都是高电平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] *@retrn */ 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秒内再次出现,就可以实现扫描效果。