七、数码管学习
7.1 单个数码管
- 一个数码管由a、b、c、d、e、f、g、dp,八个二极管组成,八个二极管一段组在一起,另外一端接出来。如果二极管阳极接一起就是共阳极,当二极管阴极接一起就是共阴极。
- 图中为共阴极,如要显示不同的数字,只需要点亮不同的二极管,如要数码管显示6,只需要点亮afedcg,将b和dp关闭即可。
- 数码管内部图,分为共阴极和共阳极。
7.1.1 多个数码管驱动
- 在我们驱动时,需要程序去选定去驱动哪些数码管,这就叫做位选,选定数码管后再对数码管进行驱动操作,其操作方式去单个数码管操作方式一样(后面详讲),这就叫做段选。
7.1.2 数码管与芯片连接
- 为何使用锁存器
- 5mA的电流,如果数码管直接连接单片机,单片机I/O无法输出这么高的电流,对于共阴极的数码管,可以在阳极直接接上上拉电阻,对于共阳极的数码管,可以在阴极接上下拉电阻,但是位选也要另外处理。
- LED工作电流的问题,还可以使用锁存器进行位选和段选。
- 锁存器的电路连接
图中可以看到,连接两块锁存器,一块为位选,一块为段选。上个章节讲了锁存器,这里再次回顾。
根据OE一直接地,所以锁存器工作状态只有前三种,LE软件置1时,锁存器的输入端D与输出端Q同高电平,同低电平,当LE软件置0时,锁存器输出为Q0。
从电路可以看出:
单片机引脚P00~ P07分别连接到U1和U2的D0 ~D7,简单来说就是U1和U2都与P00 ~P07相连,所以当数码管处于工作状态时,两个锁存器不能同时打开,就是两个锁存器LE脚不能同时处于高电平。锁存器U1的输出接数码管的阳极,锁存器U2的输出接数码管WE,控制数码管选择。
锁存器U2控制位选,U2打开时,U1关闭,此时U2的输入和输出同高低电平。如下图,若使用数码管1,则只需D0置0,其他引脚置1,就是芯片P00输出为0,P01 ~P07输出为1,就是P0寄存器的状态值为0xfe(上面低位,从下往上读)。
锁存器U1控制段选,如刚刚位选了数码管1,则接下来段选是对数码管1的操作。此时关闭U2,打开U1,就是U2的LE软件置0,U1的LE软件置1,而U2的LE与芯片的P27相连,U1的LE与芯片的P26相连(如下图),所以我们只需要使芯片的P27输出低电平来关闭U2锁存器,P26输出高电平来打开U1锁存器。
打开U1锁存器后,是数码管显示某个数字,我们在第一节中总结过了,比如显示一个数字6,需要afedcg六个点亮,所以锁存器输出01111101(共阴极数码管,输出1时点亮),就是P0寄存器输出0111101,P0的寄存器的状态值为0x7d。
显示数字6的代码程序如下:
#include <reg52.h>
sbit dula=P2^6;//定义数码管的引脚
sbit wela=P2^7;
void main()
{
wela=1; //段选数码管
P0=0xfe;//显示数字
wela=0;
dula=1;
P0=0x7d;
dula=0;
weile(1);
}
- 现在使用自己的开发板,我这里使用共阳极数码管来驱动。
因为是共阳极所以需要与上面讲的反过来设置,打开阴通道。
- 建立ssd1文件夹,打开四个数码管显示2023。
#include <reg52.h>
unsigned char Display_table[]={
0xc0,0xf9,0xa4,0xb0,0x99,
0x92,0x82,0xf8,0x80,0x90};//这里的数组一定要写完整不然会胡乱显示。
sbit ssd1=P1^0;//定义所有段选引脚
sbit ssd2=P1^1;
sbit ssd3=P1^2;
sbit ssd4=P1^3;
//延时函数
void Delay_ms(unsigned char ms)
{
unsigned char i,j;
for(ms;ms>0;ms--)
{
i=2;
j=239;
do// 循环执行,在条件执行前至少执行一次
{
while(--j);
} while(--i);
}
}
//主函数
void main()
{
//主循环
while(1)
{
ssd1=0;//位选
P0=Display_table[3];//显示数字信息
Delay_ms(3);
ssd1=1;//关闭
ssd2=0;
P0=Display_table[2];
Delay_ms(3);
ssd2=1;
ssd3=0;
P0=Display_table[0];
Delay_ms(3);
ssd3=1;
ssd4=0;
P0=Display_table[2];
Delay_ms(3);
ssd4=1;
}
}
- 分析上述代码:
- U1、U2锁存器LE的芯片引脚P26与P27进行位操作,重新设置名称为dula和wela,表示段选和位选。主函数里,先打开位选,wela=1,选择第一个数码管,则U2锁存器输入和输出都为11111110(从下至上),P0的输出也为11111110,就是0xfe,位选结束后关闭位选:wela=0。
- dula=1,对第一个数码管操作,显示数字6,U1锁存器输入输出均为01111101,就是P0输出为01111101,就是P0的状态值为0x7d。最后关闭断选。
- 特别注意:正是因为锁存器U1和U2同时接在芯片的P0引脚上,所以两个锁存器不能同时打开,否则芯片引脚P0输出的状态值会在U1和U2之间同时发生反应,造成紊乱。
7.2 芯片引脚状态值
以下为共阴数码管值。
- 显示数字“0”,abcdef亮,状态值00111111——>0x3f
- 显示数字“1”,bc亮,状态值00000110——>0x06
- 显示数字“2”,abdeg亮,状态值01011011——>0x5b
- 显示数字“3”,abcdg亮,状态值01001111——>0x4f
- 显示数字“4”,bcfg亮,状态值01100110——>0x66
- 显示数字“5”,acdfg亮,状态值01101101——>0x6d
- 显示数字“6”,acdefg亮,状态值01111101——>0x7d
- 显示数字“7”,abc亮,状态值00000111——>0x07
- 显示数字“8”,abcdefg亮,状态值01111111——>0x7f
- 显示数字“9”,abcdfg亮,状态值01101111——>0x6f
- 显示字母“A”,abcefg亮,状态值01110111——>0x77
- 显示字母“B”,cdefg亮,状态值01111100——>0x7c
- 显示字母“C”,adef亮,状态值00111001——>0x39
- 显示字母“D”,bcdeg亮,状态值01011110——>0x5e
- 显示字母“E”,adefg亮,状态值01111001——>0x79
- 显示字母“F”,aefg亮,状态值0
1110001——>0x71
- 这里顺便给出个人总结的共阳数码管转换值。
0:00000011 头尾调换:11000000 转换十六进制:0xc0
1:10011111 11111001 0xf9
2:00100101 10100100 0xa4
3:00001101 10110000 0xb0
4:10011001 10011001 0x99
5:01001001 10010010 0x92
6:01000001 10000010 0x82
7:00011111 11111000 0xf8
8:00000001 10000000 0x80
9:00001001 10010000 0x90
A:10001000,0x88
B:10000011,0x83
C:11000110,0xC6
D:10100001,0xA1
E:10000110,0x86
F:10001110,0x8E
G:11000010,0xC2
H:10001001,0x89
I:11101111,0xEF
J:11110001,0xF1
K:10000101,0x85
L:11000111,0xC7
M:10101010,0xAA
N:10101011,0xAB
O:10100011,0xA3
P:10001100,0x8C
Q:10011000,0x98
R:10101111,0xAF
S:10011011,0x9B
T:10000111,0x87
U:11000001,0xc1
V:10011101,0x9D
W:10010101,0x95
X:11001001,0xC9
Y:10010001,0x91
Z:10110110,0xB6
在编写代码时将上面的状态值放入数组中,通过数组来实现数码管的动态显示。
放置到如下数组中:
#define uchar unsigned char
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
}
在table数组名字前加了code,代表编码的意思,单片机程序把不需要更改的东西通过code关键字定义为编码,单片机执行程序时,table只需要占用程序存储空间即可,可以理解为占用flash,而不占用RAM。
7.3 数码管动态显示
7.3.1 一个数码管从0到F动态显示
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table []={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
} ;
uchar num;
sbit wela=P2^6;
sbit dula=P2^7;
void delay_ms(uint);
void main()
{
wela=1;
P0=0xfe;
wela=0;
while(1)
{
for(num=0;num<16;num++)
{
dula=1;
P0=table[num];
dula=0;
delay_ms(500);
}
}
}
void delay_ms(uint ms)
{
uint i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
- 这里使用自己的板子实现显示。
- 建立文件ssd2。
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table []={
0xc0,0xf9,0xa4,0xb0,
0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,//这里是0123456789ABCDEF的显示编码,为什么和上面代码不一样,因为这是共阳极
0xC6,0xA1,0x86,0x8E
};//这里的数组一定要写完整不然会胡乱显示。
uchar num=0; //初始化为0
sbit ssd1=P1^0;
sbit ssd2=P1^1;
sbit ssd3=P1^2;
sbit ssd4=P1^3;
void Delay_ms(uint ms){
uchar i,j;
for(ms;ms>0;ms--){
i=2;
j=239;
do{
while (--j);
}while(--i);
}
}
void main(){
unsigned char i;
for(i=0;i<16;i++)
{
num=i;
ssd4=0;
P0=table[num];
Delay_ms(500);
ssd4=1;
}
}
- 此时第一个数码管从0变化到F。
- 若使数码管从0到F反复变化,只需要在weile()语句里添加下面的代码:
if(num==16)
num=0;
- 完整程序如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table []={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
} ;
uchar num;
sbit dula=P2^6;
sbit dula=P2^7;
void delay_ms(uint);
void main()
{
wela=1;
P0=0xfe;
wela=0;
while(1)
{
for(num=0;num<16;num++)
{
dula=1;
P0=table[num];
dula=0;
delay_ms(500);
}
if(num==16) //新加
num=0; //新加
}
}
void delay_ms(uint ms)
{
uint i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
7.3.2 多个数码管从0到F显示
- 和之前的数码管比较,多个数码管的显示只需要改变位选时P0的输出,如果让6个数码管同时显示, 就输出11000000,转换为16进制就是0xc0
位选代码:
wela=1;
P0=0xc0;
wela=0;
- 完整代码:
#include <reg52.h>
#define uint unsigned int //#开头都是预处理指令,include也一样
#define uchar unsigned uchar //define为常量,又称宏定义,功能类似变量但又要区分开,变量表示一个变量,但是宏表示一个常量,可以给变量赋值,但绝不可以给常量赋值,宏表示的常量可以是数字、字符、字符串、表达式。
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
uchar num;
sbit dula=P2^6;
sbit wela=P2^7;
void delay_ms(uint);
void main()
{
wela=1;
P0=0xc0;
wela=0;
while(1)
{
for(num=0;num<16;num++)
{
dula=1;
P0=tabla[num];
dula=0;
delay_ms(500);
}
if(num==16)
num=0;
}
}
void delay_ms(uint ms)
{
uint i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
- 现在使用自己的开发板实现。
- 建立文件夹ssd3。
//4个数码管从0到F循环动态显示
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[]={
0xc0,0xf9,0xa4,0xb0,
0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,//这里是0123456789ABCDEF的显示编码,为什么和上面代码不一样,因为这是共阳极
0xC6,0xA1,0x86,0x8E
};
uchar num;
sbit ssd1=P1^0;//定义所有段选引脚
sbit ssd2=P1^1;
sbit ssd3=P1^2;
sbit ssd4=P1^3;
void delay_ms(uint );
void main()
{
while(1)
{
for(num=0;num<16;num++)
{
ssd1=1;
P0=table[num];
ssd1=0;
delay_ms(500);
ssd2=1;
P0=table[num];
ssd2=0;
delay_ms(500);
ssd3=1;
P0=table[num];
ssd3=0;
delay_ms(500);
ssd4=1;
P0=table[num];
ssd4=0;
delay_ms(500);
}
if(num==16)
num=0;
}
}
void delay_ms(uint ms)
{
uint i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
7.3.3 举个栗子
- 现在有6个数码管,第一个数码管显示数字1,间隔一秒后第二个数码管显示2,以此类推,直到显示第六个数码管显示6。
- 解题:6个数码管需要轮流显示,所以位选的时候需要依次选择数码管1、2、3、4、5、6,每次位选后再段选输出需要显示的数字。
- 第一个数码管的位选:
wela=1;
P0=0xfe;//选择第一个数码管
wela=0;
- 开始段选输出数字1:
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
- 位选时P0的输出之前已经学过了 ,再次回忆,如下图,从左到右分别为数码管1、2、3、4、5、6,再 从下往上读,得到8位二进制编码,变为16进制即可。
- 完整程序:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned uchar
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
uchar num;
sbit dula=P2^6;
sbit wela=P2^7;
void delay_ms(uint);
void main()
{
while(1)
{
wela=1;
P0=0xfe;//第一个数码管
wela=0;
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
wela=1;
P0=0xfd;//第二个数码管
wela=0;
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
wela=1;
P0=0xfb;//第三个数码管
wela=0;
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
wela=1;
P0=0xf7;//第四个数码管
wela=0;
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
wela=1;
P0=0xef;//第五个数码管
wela=0;
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
wela=1;
P0=0xdf;//第六个数码管
wela=0;
dula=1;
P0=table[1];
dula=0;
delay_ms(1000);
}
}
void delay_ms(uint ms)
{
uint i,j;
for(i=ms;i>0;i--)
for(j=100;j>0;j--;);
}