9 数码管的使用
9.1 数码管的静态使用
9.1.1 认识数码管
数码管也叫LED数码管,内部是由多个发光二极管封装在一起组成,他们可以有很多种颜色,很多种外形,很多种样式,但是本质来说他们都是通过点亮内部的LED来显示的,只要面板做好了,理论可以显示任意的字符或者图案。
![[Pasted image 20250115110814.png]]
以这种最普通的数码管来说,一个“8”我们称之为1位数码管,两个“8”就是2位数码管,以此类推。
按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管,尾缀A表示共阳,K表示共阴,共阳数码管在接线上显示的是数码管正极一端全部接入VCC输入,共阴接法则是负极一端全部接入GND,输入低电平的时候,共阳数码管亮起;接入高电平的时候,共阴数码管亮起,总得来说二者在控制电平上有所区别,一般来说共阳数码管适合使用低电平驱动(如使用NPN型三极管或N沟道MOSFET控制),共阴数码管合适使用高电平驱动(使用PNP型三极管或P沟道MOSFET控制)。
![[Pasted image 20250115110957.png]]
使用多个数码管如何使用电平驱动多个数码管呢?如下图所示则是多位数码管的接线图,数码管中ABCDEFGDP等针脚皆接在一起,假设11针脚接入高电平,控制相对应数码管的低电平针脚如12、9、8、6来依次控制想要亮的数码管。
![[Pasted image 20250115111017.png]]
9.1.2 静态数码管控制原理
在实验箱上,所对应的数码管接线如下所示:
![[Pasted image 20250115113158.png]]
![[Pasted image 20250115113207.png]]
在上图中可见到数码管的针脚分配,对于如上图的数码管,可以罗列出一个表格来将要显示的信息首先根据数码管符号和引脚指示图排列出,数码管内部的符号一次按照==abcdefgdp==来排列,所对应的控制针脚为P60~P67,排列想要显示的信息后,可用二进制排序好转换为十进制和十六进制,想要表示0~9个数字,可以参考如下表格:
![[Pasted image 20250115113425.png]]
9.1.3 数码管显示实践
当计算好要显示的信息后,可将十六进制内容写入到数组当中,并使用变量定义:
u8 SEG_Tab[10] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
在程序中,除了定义数码管的位数,选择想要驱动的数码管,在上图中三极管驱动数码管电路中,选用P7.0端口,在代码中定义P70,P6输出段码:
while(1){
P70 = 0; //开启一个数码管
P6 = SEG_Tab[0]; //该数码管输出段码
}
在如上代码中,段码和位码的区别在于:==abcdefgdp==称之为段,位则是8位数码管的位,选择第几个数码管的位数。注意,在实际工程代码编写的情况下,要注意工程名称命名规范,减少使用中文命名的情况,一些特殊情况下中文命名会导致乱码现象产生。通过上述代码,可在实际实验箱上显示数字0。
那么添加一个0~9的循环应该如何实现呢?
在这里,可以写入9个显示段码程序,但这种程序编写费时费力,完全不友好,所以可以定义一个变量来控制读取段码值,再通过if语句的判断来规定num变量值的范围:
while(1) {
P70 = 0;
P6 = SEG_Tab[num];
num++;
if( num>9 )
num = 0;
delay_ms(1000);
}
那么可以控制按键按下增减数值吗?下列则是使用按键控制数码管的程序:
P70 = 0;
P6 = SEG_Tab[num];
if(KEY1 == 0){
delay_ms(10);
if(KEY1 == 0){
BEEP = 0;
delay_ms(10);
BEEP = 1;
while(KEY1 == 0);
if( num<9 )
num++;
}
}
if(KEY2 == 0){
delay_ms(10);
if(KEY2 == 0){
BEEP = 0;
delay_ms(10);
BEEP = 1;
while(KEY2 == 0);
if( num>0 )
num--;
}
}
在如上代码中,首先定义了需要使用到的位数码管,接着在该数码管上显示段码,显示段码可进行刷新,使用按键来刷新数码管段码,范围0~9内,使用if语句规定==num==变量的值,当==num==变量变化时,蜂鸣器响一声并且变化段码。
9.2 数码管的动态使用
9.2.1 数码管动态刷新原理
定义:在静态数码管中,所有显示的数字或字符同时被点亮并且保持不变。每一位数码管的每一段(每一个数字的组成部分)都由独立的电路直接驱动。
工作原理:每一位数码管的每个显示段直接通过驱动电路与电源连接。所有显示的数字在同一时刻都会被点亮,没有闪烁。例如,若要显示数字“8”,每一位数码管的所有段都会被点亮。
定义:动态数码管通过“时间分割”的方式来控制每一位数码管的显示。多位数码管的显示内容是通过快速轮流点亮每一位数码管,利用人的视觉暂留效应,产生连续显示的效果。
工作原理:多位数码管的显示通过轮流点亮每一位数码管实现。例如,假设有4位数码管,系统会轮流点亮每一位并显示相应的数字,在每一帧显示的时间间隔内,肉眼无法察觉到数码管切换的闪烁效果。
每次只有一位数码管的各个显示段被点亮,其它数码管则保持关闭状态。通过快速刷新,肉眼会看到所有数码管同时显示内容。总结如下表:
特性 |
静态数码管 |
动态数码管 |
工作方式 |
每一位数码管同时显示内容 |
通过轮流刷新每一位数码管来显示内容 |
显示内容 |
所有数码管同时显示 |
逐位显示,利用刷新频率实现连续显示 |
电流消耗 |
高电流(每个段独立驱动) |
低电流(逐位刷新,避免同时点亮) |
驱动电路 |
复杂,需要更多的驱动电流 |
简单,只需要控制每一位的显示 |
显示效果 |
显示稳定清晰,适用于少位数的显示 |
显示可能有闪烁,但适合显示多位数 |
应用场景 |
数字少,要求显示稳定的设备 |
数字多,显示内容动态变化的设备 |
控制原理具体的控制的流程如图所示,==N==表示有几个数码管。其中需要注意每个延时不能太短,我们这边程序就以1ms为准,且需要保证总共一个循环结束的时间不能大于20ms,因为人眼的视觉不容易分辨出50HZ以上的动态刷新。
![[Pasted image 20250115154114.png]]
9.2.2 动态刷新控制实践
在知道动态刷新控制原理的情况下,在上节课基础上新增一个位码数组,通过调用数组选择位码,新建一个数组选择每个位需要显示的内容。
通过排列数码管的位数后,对需要点亮几位数码管的二进制进行换算后得到16进制,并且定义动态数码管变量:
![[Pasted image 20250115160306.png]]
u8 COM_Tab[8] = {0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
先写一部分例程,用于显示四位数码管使用动态显示,先定义需要显示的位码,后定义要显示的段码信息,代码如下:
P70 = 0;
P7 = COM_Tab[0];
P6 = SEG_Tab[0];
delay_ms(200);
P7 = COM_Tab[1];
P6 = SEG_Tab[1];
delay_ms(200);
P7 = COM_Tab[2];
P6 = SEG_Tab[2];
delay_ms(200);
P7 = COM_Tab[3];
P6 = SEG_Tab[3];
delay_ms(200);
在如上代码中,可明显感受到动态数码管的显示,延迟200ms,可降低延时低于20ms后达到人眼无法分辨的情况下。但在如上代码中,修改delay延迟较为繁琐,有什么办法可以快速修改呢?可以先定义一个数码管延迟的变量:
#define SEG_Delay 200
然后在每个delay语句括号内引用定义的SEG_Delay变量即可。
如上代码逻辑不变,优化代码结构则需要使用到if语句进行判断:
P7 = COM_Tab[num];
P6 = SEG_Tab[num+1]; //段码+1的作用是使得第一位数码管显示非0
delay_ms(SEG_delay);
num++:
if( num>7 )
num = 0;
通过num++不断增加值来变换位码和要显示的段码,当位码值大于7(因为共8位数码管)后将num置零。在单个位码上显示的段码不同,那么需要变换的则是段码的值,这时需要区分开位码和段码数组中的变量。那么单个数码管如何显示不同的段码呢?根据需求分析后,位码是不需要进行修改,所需要修改变更的是段码,对应的位码下的段码需要显示并输出,但很显然直接向段码P6输出Show_Tab是不对的,无法显示1这个数字,需要输入相对应段码1的值。
P70 = 0; //开启一个数码管
P7 = COM_Tab[num]; //位码选择
P6 = SEG_Tab[Show_Tab[num]]; //需要显示的数字内码
delay_ms(SEG_delay); //延迟函数
num++;
if( num>7 )
num = 0;
那么根据可实现的原理
主要实现如下功能:
简易10秒免单计数器
1.在前四位数码管上显示目标时间,即“ 10. 00 ”表示定时时间10秒钟
2.后四位显示当前的计时00.00,最小单位为10ms,
3.按下开始按钮后,每10ms最末尾的数字+1;知道按下结束按钮后停止计数。
在原有的代码的基础上,首先给段码显示中加上可带小数点显示的0-9数字:
u8 SEG_Tab[20] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10}; //0-9段码 + 0-9带小数点段码
然后设定每个位码需要显示的段码:
Show_Tab[0] = 1; //选择1
Show_Tab[1] = 10; //选择0.
Show_Tab[2] = 0; //选择0
Show_Tab[3] = 0; //选择0
添加一个开关量,用于计数开始和结束:
bit Run_State = 0;
然后在按键检测中添加一个对开关量进行取反的功能,当按键按下来改变开关量:
if(KEY1 == 0){
delay_ms(10);
if(KEY1 == 0){
BEEP = 0;
delay_ms(10);
BEEP = 1;
while(KEY1 == 0);
Run_State = !Run_State;
}
}
虽然说这里对开关量进行处理当按键按下即可计数,那么在之前的代码对计数器进行计数的语句需要添加一条if语句进行判断,当开关量为1的时候也就是开始计数时才可对计数器进行加减:
if( Run_State == 1){
TimCount++;
}
最后得出的总代码为:
#include "COMM/stc.h" //调用头文件
#include "sys_init.h"
#include "delay.h"
#include "usb_download.h"
#define KEY1 P32 //定义按键
#define KEY2 P33 //定义按键
#define BEEP P54 //定义蜂鸣器
#define SEG_delay 2 //设置数码管动态刷新频率
u8 SEG_Tab[20] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10}; //0-9段码 + 0-9带小数点段码
u8 COM_Tab[8] = {0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}; //0-7位码数组
u8 Show_Tab[8] = {0,0,0,0,0,10,0,0}; //显示位码的段码
u8 num = 0; //计数器
u32 TimCount = 0; //计数单位1ms
bit Run_State = 0; //开始运行/结束运行量
void main()
{
sys_init();
usb_download();
usb_init();
EA = 1;
while(1) // 死循环
{
/*-------------------串口打印代码-------------------*/
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady )
{
usb_OUT_done();
}
//设定位码显示的段码
Show_Tab[0] = 1; //选择1
Show_Tab[1] = 10; //选择0.
Show_Tab[2] = 0; //选择0
Show_Tab[3] = 0; //选择0
/*------------------数码管显示代码-------------------*/
P70 = 0; //开启一个数码管
P7 = COM_Tab[num]; //位码选择
P6 = SEG_Tab[Show_Tab[num]]; //需要显示的数字内码
delay_ms(SEG_delay);
if( Run_State == 1){
TimCount++;
Show_Tab[4] = TimCount/10000%10; //取万位
Show_Tab[5] = TimCount/1000%10+10; //取千位和显示小数点
Show_Tab[6] = TimCount/100%10; //取百位
Show_Tab[7] = TimCount/10%10; //取十位
}
num++;
if( num>7 )
num = 0;
if(KEY1 == 0){
delay_ms(10);
if(KEY1 == 0){
BEEP = 0;
delay_ms(10);
BEEP = 1;
while(KEY1 == 0){ //按键按下后等待
P7 = COM_Tab[num]; //位码选择
P6 = SEG_Tab[Show_Tab[num]]; //需要显示的数字内码
delay_ms(SEG_delay);
num++;
if( num>7 )
num = 0;
}
if( Run_State == 0)
TimCount = 0;
Run_State = !Run_State;
}
}
}
}
在如上代码中,有创建一个变量为Show_Tab的变量,存储着显示的数组,在实际使用中,可以修改数组中的值,比如想要显示3,将0改成3即可,如果要显示带小数的数值,可以在3前面加上1
u8 Show_Tab[8] = {0,0,0,0,0,10,0,0};
上述代码中有数码管动态刷新的两段代码较为相似,此处可以添加一个用于数码管动态刷新用的函数,方便编程时候使得主程序简洁:
void SEG_Fre(void){
P70 = 0; //开启一个数码管
P7 = COM_Tab[num]; //位码选择
P6 = SEG_Tab[Show_Tab[num]]; //需要显示的数字内码
delay_ms(SEG_delay);
num++;
if( num>7 )
num = 0;
}
调整后的所有程序如下所示:
#include "COMM/stc.h" //调用头文件
#include "sys_init.h"
#include "delay.h"
#include "usb_download.h"
#define KEY1 P32
#define KEY2 P33
#define BEEP P54
#define SEG_delay 2
u8 SEG_Tab[20] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10}; //0-9段码 + 0-9带小数点段码
u8 COM_Tab[8] = {0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}; //0-7位码数组
u8 Show_Tab[8] = {0,0,0,0,0,10,0,0};
u8 num = 0;
u32 TimCount = 0; //计数单位1ms
bit Run_State = 0; //开始运行/结束运行量
void SEG_Fre(void);
void main()
{
sys_init();
usb_download();
usb_init();
EA = 1;
while(1) // 死循环
{
/*-------------------串口打印代码-------------------*/
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady )
{
usb_OUT_done();
}
//设定位码显示的段码
Show_Tab[0] = 1; //选择1
Show_Tab[1] = 10; //选择0.
Show_Tab[2] = 0; //选择0
Show_Tab[3] = 0; //选择0
/*------------------数码管显示代码-------------------*/
if( Run_State == 1){
TimCount++;
Show_Tab[4] = TimCount/10000%10; //取万位
Show_Tab[5] = TimCount/1000%10+10; //取千位和显示小数点
Show_Tab[6] = TimCount/100%10; //取百位
Show_Tab[7] = TimCount/10%10; //取十位
}
SEG_Fre();
if(KEY1 == 0){
delay_ms(10);
if(KEY1 == 0){
BEEP = 0;
delay_ms(10);
BEEP = 1;
while(KEY1 == 0){ //按键按下后等待
SEG_Fre();
}
if( Run_State == 0)
TimCount = 0;
Run_State = !Run_State;
}
}
}
}
void SEG_Fre(void){
P70 = 0; //开启一个数码管
P7 = COM_Tab[num]; //位码选择
P6 = SEG_Tab[Show_Tab[num]]; //需要显示的数字内码
delay_ms(SEG_delay);
num++;
if( num>7 )
num = 0;
}