目录
1、关于数码管
1.1 数码管简介
数码管是一种半导体发光器件,基本单位是发光二极管。因此数码管也称LED数码管。按能显示多少个( 8) 可分为 1 位、 2 位、 3 位、 4 位、 5 位、6 位、 7 位、8 位等数码管。 按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。
共阳数码管是指所有的发光二极管的阳极接到一起形成公共阳极(COM)的数码管。当某一段发光二极管的阴极为低电平时,这一段就点亮,高电平灭(共阳数码管-阴极-低电平工作)。
共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管, 共阴数码管在应用时应将公共极 COM 接到地线 GND 上, 当某一字段发光二极管的阳极为高电平时, 相应字段就点亮, 当某一字段的阳极为低电平时, 相应字段就不亮(共阴数码管-阳极-高电平工作)。
(图中就是一个8位数码管和一个1位数码管)
1.2 数码管显示原理
数码管的显示原理都一样,不管几位,都是靠点亮内部的LED来显示数字。
以下是数码管的内部电路:
通过内部电路我们可以看到,一位数码管的引脚是10个,正常显示一个8需要7个小段,然后加上一个小数点(c),所以一位数码管内部一共有8个LED,最后还有一个公共端, 多数生产商为了封装统一, 单位数码管都封装 10 个引脚, 其中第 3 和第 8 引脚是连接在一起的。 而它们的公共端又可分为共阳极和共阴极。
对于共阴数码管来说,其8个发光二极管的阴极在数码管内部连接在一起,所以称为“共阴”,但是它们的阳极是独立的,当我们给数码管的任意一个阳极加一个高电平时,对于这个LED就亮了。比如我们想显示1个“8”,那么就把8个阳极全部送高电平。
值得注意的是,如果使用共阴数码管,需要增加单片机IO口驱动电流,因为共阴数码管是要靠单片机 I/O 口输出电流来点亮的, 但单片机 I/O 口难以输出稳定的、 如此大的电流, 所以数码管与单片机连接时需要加驱动电路,可以用上拉电阻的方式或者用驱动芯片, 比如 74HC573、 74HC245 等, 其输出电流较大,电路接口简单。
理解了共阴数码管,共阳数码管就简单了。共阳极数码管其内部 8 个发光二极管的所有阳极全部连接在一起, 电路连接时, 公共端接高电平, 因此我们要点亮哪个发光管二极管就需要给阴极送低电平,并且此时显示数字的编码与共阴极编码是相反的关系。
但是我们平常往往使用共阳极数码管(单片机上面的模块也是共阳极)为什么呢?这是因为数码管的非公共端往往接在芯片的I/O上,而芯片的驱动能力是比较小的,采用共阴数码管的话,就有可能受限于 IC 芯片输出电流不够而显示昏暗, 要外加上拉电阻或者是增加三极管加大驱动能力。
所以使用共阳极数码管的好处是: 将驱动数码管的工作交到公共端( 一般接驱动电源) , 加大驱动电源的功率自然要比加大 IC 芯片 I/O口的驱动电流简单许多。 另一方面, 这样也能减轻主芯片的负担。
LED数码管显示工作有两种方式:静态显示方式和动态显示方式
静态显示方式:当送入一次字形码后, 显示字形可一直保持, 直到送入新字形码为止。 这种方法的优点是占用 CPU 时间少, 显示便于监测和控制。 缺点是硬件电路比较复杂, 成本较高, 比如使用 4 个静态数码管, 那么就得 32 个 IO 来控制, 这对 51 单片机来说是无法承受的。
动态显示方式则采用动态扫描显示,利用发光管的余辉和人眼作用,使人感觉各位数码管好像同时在显示。同时也导致了动态显示的亮度比静态显示要差一些,所以在选择限流电阻时应略小于静态显示电路中的。
2、动态数码管实验
动态数码管实验用到动态数码管模块、74HC138译码器模块
2.1、动态数码管模块
以下是普中A7的动态数码管模块的线路图:
J6 是动态数码管模块的段选接口,我的接线是P0的0-7针脚分别接J6的A-Dp(8-1)
2.1.1、动态数码管介绍
- 多位数码有两条重要的线,把公共端叫做”位选线“,连接在一起的段线叫做”段选线“,有了这两个线后,我们通过单片机及外部驱动电路就可以任意的数码管显示任意的数字了。
- 当多位一体时,它们内部的公共端是独立的,而负责显示什么数字的段线是连在一起的,独立公共端可以控制多位一体中的任意一位点亮,而段线就用来控制显示什么数字。
- 简单总结就是:位选控制亮不亮,至于显示什么看段选
- 我们的开发板上面使用了2个四位一体的共阴极数码管,这样可以同时显示8个数字。
2.1.2、数码管动态显示原理
- 先来说说静态显示是怎么回事:静态显示应用于只显示一位数码管,或者同时显示相同内容。我们指定当多位数码管应用于某一系统时,它们的“ 位选” 是可独立控制的, 而“ 段选” 是连接在一起的, 我们可以通过位选信号控制哪几个数码管亮, 而在同一时刻, 位选选通的所有数码管上显示的数字始终都是一样的, 因为它们的段选是连接在一起的, 送入所有数码管的段选信号都是相同的, 所以它们显示的数字必定一样。
- 动态显示就是减少段选线,分开位选线,利用位选线不同时选择通断,改变段选数据来实现的。
- 比如在第一次选中第一位数码管时, 给段选数据 0,下一次位选中第二位数码管时显示 1。 为了在显示 1 的时候, 0 不会消失( 当然实际上是消失了) , 必须在人肉眼观察不到的时间里再次点亮第一次点亮的 0。而这时就需要记住, 人的肉眼正常情况下只能分辨变化超过 24ms 间隔的运动。也就是说, 在下一次点亮 0 这个数字的时间差不得大于 24ms。 这时就会发现,数码管点亮是在向右或者向左一位一位点亮, 形成了动态效果。 如果把间隔时间改长就能直接展现这一现象。
2.2、74HC245 和 74HC138 芯片介绍
要想驱动数码管靠单片机IO口肯定是不行的,需要增加外部驱动,我们开发板上面使用的是74HC245芯片。同时考虑到51单片机的IO口有限,我们会使用一种IO口扩展芯片,我们开发板上面使用的是74HC138译码器芯片,只需单片机 3 个 IO 口就可以实现 8 个位选管脚的控制, 节省了芯片的 IO 资源。(74HC245用来驱动,74HC138用来扩展IO口)
2.2.1、74HC138芯片简介
以下是普中A7的74HC138译码器模块的线路图:
J9是74HC138译码器的位选接口,我的接线是P2的P20、P21、P22 分别接J9的ABC(3、2、1)
以下是管脚定义图:
该芯片的工作原理也比较简单:给E1、 E2 使能管脚低电平, E3 管脚为高电平, 至于哪个管脚输出有效电平( 低电平) , 要看 A0, A1, A2 输入管脚的电平状态。 如果 A0, A1, A2 都为低电平, 则Y0 输出有效电平( 低电平) , 其他管脚均输出高电平。 如果 A0 为高电平, A1, A2 都为低电平, 则 Y1 输出有效电平( 低电平) , 其他管脚均输出高电平。如果 E1、 E2 使能管脚任意一个为高电平或者 E3 为低电平, 不论输入是什么, 输出都为高电平。
2.2.2、74HC245芯片简介
以下是普中A7的74HC245译码器模块的线路图:
以下是管脚定义图:
- 给OE是能管脚低电平,DIR 管脚为高电平传输方向是 A->B 输出, DIR 为低电平传输方向是 B->A, 至于输出高电平还是输出低电平取决于输入端的状态, 如果输入为低电平, 输出即为低; 输入为高电平, 输出即为高。 如果 OE 使能管脚为高电平,不论 DIR 管脚是高还是低, 输出是高组态。(简单总结:DIR高电平A->B,DIR低电平B->A,OE为高电平输出高阻态)
- 通常我们使用 74HC245 芯片用作驱动只会让其在一个方向输出, 即 DIR 管脚为高电平, 传输方向是 A->B。
2.3、硬件设计
我们需要用到:
动态数码管模块
74HC138
74HC245
先看图:
其中,出厂的时候,J1和J10端口 已经被跳线连接,就是138译码器的输出端口和动态数码管的位选接口相连。
这里我用P0口接入J6,控制数码管的段选,用P20、P21、P22 分别接入138译码器的J9(A B C)控制数码管的位选。
2.4、段选段码表
根据上图,我的P0口和J6接线线序来看,P00-P07(低位到高位)分别接J6的A-Dp,并且模块中的8位数码管采用共阴接法(这也是上面,共阴接发的局限性,所以添加了74HC245模块来增加数码管的驱动电流),所以,假设我想在某一位显示1,那么从A-Dp,这8个LED,应该让某些LED输入高电平来发光,所以应该是B、C 发光。
由此,可以推出段码表:
# 共阴
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d,
0 1 2 3 4 5
0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c,
6 7 8 9 A B
0x39, 0x5e, 0x79, 0x71, 0x00,
C D E F 无显示
2.5、位选段码表
138译码器的8个管脚J10依次接入动态数码管模块的共阴管脚J1,所以138译码器的J10管脚哪个输出低电平,哪位数码管被选中(位选),这里就不赘述,根据真值表推断即可。
2.6、软件设计
软件实现的功能是:8位数码管从左到右依次显示1-8
先看图:
上代码:
// 静态数码管显示
#include <STC89C5xRC.H>
// 单个数码管的 0-9 的段码
unsigned char NixieTable[] = {
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F
};
// 可调毫秒延时函数(12MHz晶振)
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
// Location 位置;Number 显示数字
// Nixie指的是数码管
void Nixie(unsigned char Location,unsigned char Number){
// 在进行位选之前,清除P0的段选值
P0 = 0x00;
// 这样按位与,分别从低位取到高位的值,赋给相应的针脚
if (Location >= 1 && Location <= 8) {
unsigned char tmp = Location-1;
P22 = (tmp >> 2) & 1;
P21 = (tmp >> 1) & 1;
P20 = tmp & 1;
}
P0 = NixieTable[Number];
delay_ms(1);
// 课程代码、这样麻烦死了
// switch(Location){
// case 1:P22 = 0;P21 = 0;P20 = 0;break;
// case 2:P22 = 0;P21 = 0;P20 = 1;break;
// case 3:P22 = 0;P21 = 1;P20 = 0;break;
// case 4:P22 = 0;P21 = 1;P20 = 1;break;
// case 5:P22 = 1;P21 = 0;P20 = 0;break;
// case 6:P22 = 1;P21 = 0;P20 = 1;break;
// case 7:P22 = 1;P21 = 1;P20 = 0;break;
// case 8:P22 = 1;P21 = 1;P20 = 1;break;
// }
}
void main(){
while(1){
// 数码管有消影
// Nixie周期:位选->段选;位选->段选;位选->段选
// 由于单片机运行速度非常快上一个周期的段选数据容易流窜到下一个位选的数据中,导致显示错误
// 所以需要在Nixie中,在进行位选之前,清除P0的段选值,避免段选数据流窜
char i = 1;
for (i;i<=8;i++){
Nixie(i,i);
}
}
}
下面的是详细的代码解释:
// 静态数码管显示
#include <STC89C5xRC.H>
// 包含STC89C5xRC系列单片机的头文件
// 这个头文件定义了单片机的所有特殊功能寄存器(SFR)
// 比如P0、P1、P2、P3等端口寄存器,以及P20、P21、P22等位定义
// 单个数码管的 0-9 的段码(共阴数码管)
unsigned char NixieTable[] = {
// 七段数码管的段码表,对应数字0-9
// 每个字节的8位对应数码管的8个段:dp g f e d c b a
0x3F, // 0: 00111111 → 点亮a,b,c,d,e,f段
0x06, // 1: 00000110 → 点亮b,c段
0x5B, // 2: 01011011 → 点亮a,b,d,e,g段
0x4F, // 3: 01001111 → 点亮a,b,c,d,g段
0x66, // 4: 01100110 → 点亮b,c,f,g段
0x6D, // 5: 01101101 → 点亮a,c,d,f,g段
0x7D, // 6: 01111101 → 点亮a,c,d,e,f,g段
0x07, // 7: 00000111 → 点亮a,b,c段
0x7F, // 8: 01111111 → 点亮所有段
0x6F // 9: 01101111 → 点亮a,b,c,d,f,g段
};
// 可调毫秒延时函数(12MHz晶振)
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = ms; i > 0; i--) // 外层循环:控制总的毫秒数
for(j = 110; j > 0; j--); // 内层循环:粗略计算1ms的延时
// 12MHz晶振下,每个机器周期1μs,这个循环大约产生1ms延时
}
// Location 位置(1-8);Number 显示数字(0-9)
// Nixie指的是数码管
void Nixie(unsigned char Location, unsigned char Number){
// 消影处理:在进行位选之前,清除P0的段选值
// 这一步非常重要!防止上一个数码管的段选数据流窜到当前数码管
P0 = 0x00; // 将P0口所有引脚置为低电平,关闭所有段选
// 位选控制:选择要点亮哪个数码管(1-8号)
if (Location >= 1 && Location <= 8) {
unsigned char tmp = Location - 1; // 将1-8的位置转换为0-7的索引
// 使用位操作控制P22、P21、P20三个引脚
// 这三个引脚组成3位二进制数,可以表示0-7,对应8个数码管
P22 = (tmp >> 2) & 1; // 取tmp的第2位(二进制最高位):tmp右移2位后与1进行与操作
P21 = (tmp >> 1) & 1; // 取tmp的第1位(二进制中间位):tmp右移1位后与1进行与操作
P20 = tmp & 1; // 取tmp的第0位(二进制最低位):tmp直接与1进行与操作
// 例如:Location=3 → tmp=2 → 二进制010
// P22 = (2>>2)&1 = 0&1 = 0
// P21 = (2>>1)&1 = 1&1 = 1
// P20 = 2&1 = 0
// 结果:P22=0, P21=1, P20=0 → 选择第3个数码管
}
// 段选控制:显示具体的数字
P0 = NixieTable[Number]; // 根据数字从段码表中取出对应的编码值送到P0口
// 例如:Number=5 → NixieTable[5]=0x6D → P0=0x6D → 显示数字"5"
delay_ms(1); // 短暂延时1ms,保持显示稳定
// 这个延时让当前数码管显示一段时间,避免切换太快导致闪烁
}
void main(){
// 单片机程序入口点
while(1){ // 无限循环,让程序持续运行
// 数码管动态扫描原理:
// 虽然叫"静态数码管显示",但实际上使用的是动态扫描技术
// 通过快速轮流点亮每个数码管,利用人眼的视觉暂留效应(约0.1秒)
// 让人感觉所有数码管都在同时显示
char i = 1;
for (i; i <= 8; i++){ // 循环扫描8个数码管
Nixie(i, i); // 在第i个数码管显示数字i
// 例如:i=1 → 在第1个数码管显示数字1
// i=2 → 在第2个数码管显示数字2
// ...
// i=8 → 在第8个数码管显示数字8
}
// 整个for循环执行得非常快(约几毫秒)
// 由于人眼视觉暂留,看起来就像8个数码管同时在显示1-8的数字
}
}
硬件连接说明:
位选控制(选择哪个数码管亮):
P22、P21、P20 → 3-8译码器 → 8个数码管的公共端
二进制000 → 选择第1个数码管
二进制001 → 选择第2个数码管
...
二进制111 → 选择第8个数码管
段选控制(显示什么数字):
P0口 → 数码管的段选端(a,b,c,d,e,f,g,dp)
每个位控制一个段,输出高电平时该段点亮
工作流程:
关闭所有段选(消影)
选择要点亮的数码管(位选)
输出要显示的数字段码(段选)
短暂延时保持显示
快速切换到下一个数码管
循环往复,形成静态显示效果
3、静态数码管实验
静态数码管就简单多了,并且江协科技也没单独讲这个单不愣的一位静态数码管,我自己写了个代码,但是注意这个是共阳极接法,段码和共阴是相反的
下面是共阳的数码管的段码:
# 共阳
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92,
0 1 2 3 4 5
0x82, 0xF8, 0x80, 0x90, 0x88, 0x83,
6 7 8 9 A B
0xC6, 0xA1, 0x86, 0x8E, 0xFF,
C D E F 无显示
3.1、硬件设计
P2口从低位到高位(P20-P27)依次接K1-K8,P0口从低位到高位(P00-P07)依次接静态数码管的(Dp-A)(这里接的和上面共阳接法的段码是反的,应该接(A-Dp),接反了)
3.2、软件设计
代码如下(两种方式)
#include <STC89C5xRC.H>
#define STATIC_LED_GROUP P0
#define NUMBER_BUTTON_GROUP P2
#define NUMBER_BUTTON_DOWN(key_mask) (~NUMBER_BUTTON_GROUP & (key_mask))
// 按键掩码数组
const unsigned char KEY_MASKS[] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};
// 数码管显示值数组
const unsigned char DIGIT_VALUES[] = {
0x9F, 0x25, 0x0D, 0x99, 0x49, 0x41, 0x1F, 0x01
};
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
void main() {
unsigned char i;
// 初始化 静态数码管组P0 按钮组P2 全部高电位
STATIC_LED_GROUP = 0xFF;
NUMBER_BUTTON_GROUP = 0xFF;
while(1) {
// 循环检查所有按键
for(i = 0; i < 8; i++) {
if (NUMBER_BUTTON_DOWN(KEY_MASKS[i])) {
delay_ms(20); // 去抖动
while(NUMBER_BUTTON_DOWN(KEY_MASKS[i])); // 等待释放
delay_ms(20); // 释放去抖动
STATIC_LED_GROUP = DIGIT_VALUES[i]; // 显示对应数字
break; // 退出循环,确保在找到按下的按键后立即停止不必要的检查,提高代码效率和可维护性,确保逻辑清晰:一次循环只处理一个按键事件
}
}
delay_ms(10); // 减少CPU负载
}
}
#include <STC89C5xRC.H>
#define STATIC_LED_GROUP P0
#define NUMBER_BUTTON_GROUP P2
#define NUMBER_BUTTON_DOWN(key_mask) (~NUMBER_BUTTON_GROUP & (key_mask))
enum Numbers {
ONE = 0x9F, TWO = 0x25, THREE = 0x0D, FOUR = 0x99,
FIVE = 0x49, SIX = 0x41, SEVEN = 0x1F, EIGHT = 0x01
};
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
// 通用按键处理函数
unsigned char check_key_press() {
unsigned char key_value = ~NUMBER_BUTTON_GROUP;
if (key_value == 0) return 0;
// 检测按下的按键
if (key_value & 0x01) return 1;
if (key_value & 0x02) return 2;
if (key_value & 0x04) return 3;
if (key_value & 0x08) return 4;
if (key_value & 0x10) return 5;
if (key_value & 0x20) return 6;
if (key_value & 0x40) return 7;
if (key_value & 0x80) return 8;
return 0;
}
void main() {
unsigned char key;
STATIC_LED_GROUP = 0xFF;
NUMBER_BUTTON_GROUP = 0xFF;
while(1) {
key = check_key_press();
if (key != 0) {
delay_ms(20);
while(check_key_press() != 0); // 等待所有按键释放
delay_ms(20);
// 根据按键值执行不同操作
switch(key) {
case 1: STATIC_LED_GROUP = ONE; break;
case 2: STATIC_LED_GROUP = TWO; break;
case 3: STATIC_LED_GROUP = THREE; break;
case 4: STATIC_LED_GROUP = FOUR; break;
case 5: STATIC_LED_GROUP = FIVE; break;
case 6: STATIC_LED_GROUP = SIX; break;
case 7: STATIC_LED_GROUP = SEVEN; break;
case 8: STATIC_LED_GROUP = EIGHT; break;
}
}
delay_ms(10);
}
}
4、更多
实际上江协科技在动态数码管最后面还提到了74HC595以及TM1640驱动动态数码管的方式,板子上有 74HC595 模块,他没往下讲,但是普中官方资料有,等抽空学完了再补上。。。