单片机-89C51部分:6、数码管
飞书文档https://x509p6c8to.feishu.cn/wiki/WRNLwDd0iiG8OWkyatOcom6knHf
一、数码管简介
通俗解释:
一个数码管等于八个LED组合在一起,想要显示什么形状,就点亮对应LED即可。
| |
一般数码管分为共阴极数码管和共阳极数码管。
共阳极接法:几个二极管的阳极接在一起,接到VCC(高电平),我们要想点亮,只要在在对应的二极管的阴极接上低电平即可。 |
在LED章节中,我们使用的是类似共阳极的接法,设置IO输出为低电平,点亮对应的LED,这节课,我们将使用共阴极数码管,这也就意味着我们要点亮数码管,要在对应的IO设置输出为高电平。
| |
二、单片机控制数码管的问题
单片机控制共阴极数码管时,一般会存在两个问题
第一个:IO的驱动能力不够
51单片机的引脚拉电流能力很一般,1mA左右,直接点亮数码管驱动能力明显不够,这时我们可以用三极管电路来增加IO的驱动能力。
但是,如果我们需要控制多路IO,三极管电路就会显得有点麻烦了,器件非常多。
所以我们可以考虑集成芯片方案,用一颗74HC245芯片来增加单片机引脚的驱动能力,这颗芯片的引脚拉电流能够达到7.8mA。
74HC245芯片的使用方法。第19引脚接地,使能芯片。方向控制引脚接VCC,则A端输入、B端输出。如果我们把这两只引脚接入单片机的IO端口,则可以通过单片机来控制74HC245的使能及输出方向。这是单片机的八个输出引脚,也是这颗芯片的八个输入引脚。
所以编写代码很简单,例如我们数码管的8个IO都接在P2上,我们只需要直接操作P2输出对应电平即可。
#include <reg52.h> int main() //主函数
{P2 = 0x06; //送入段选信号 0x06为16进制表示方法,转换为二进制为0000 0110,代表着显示数字1while(1); //程序到这里停止
}
具体显示数字可以参考(共阴极数码管)
16进制表示 | 显示的数字 | 点亮的位置 | 二进制表示 |
0x3f | 0 | abcdef亮 | 00111111 |
0x06 | 1 | bc亮 | 00000110 |
0x5b | 2 | abdeg亮 | 01011011 |
0x4f | 3 | abcdg亮 | 01001111 |
0x66 | 4 | abcdg亮 | 01100110 |
0x6d | 5 | acdfg亮 | 01101101 |
0x7d | 6 | acdefg亮 | 01111101 |
0x07 | 7 | abc亮 | 00000111 |
0x7f | 8 | abcdefg亮 | 01111111 |
0x6f | 9 | abcdfg亮 | 01101111 |
0x77 | A | abcefg亮 | 01110111 |
0x7c | B | cdefg亮 | 01111100 |
0x39 | C | adef亮 | 00111001 |
0x5e | D | bcdeg亮 | 01011110 |
0x79 | E | adefg亮 | 01111001 |
0x71 | F | aefg亮 | 01110001 |
0x00 | 熄灭 | 全灭 | 00000000 |
如果需要循环显示0-10数字,可以参考,无需烧录验证,因为开发板没有这部分电路
#include <reg52.h>unsigned char table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f
};unsigned char num;//带参延时函数
void delay_ms(unsigned int xms) //@12MHz
{unsigned int i, j;for(i=xms;i>0;i--){for(j=124;j>0;j--){}}
}void main()
{while(1){ for(num=0;num<10;num++){P0=table[num];delay_ms(500); }}
}
第二个:IO数量不够
51单片机也就32个IO,如果我们要驱动两个数码管,用上述方法,需要16个IO,驱动三个数码管需要24个IO,那其它功能就没办法做了。
如何用3个IO控制一个数码管
这时我们就可以考虑这类串行转并行的芯片方案了。简单来说就是通过一个IO控制8个IO的输出,该芯片就是74HC595。
74HC595是一个8位串行输入、并行输出的位移缓存器,可以简单理解为用一个IO进行数据输入,可以控制8个IO输出。
| |
SHCP:移位寄存器时钟输入 |
开始工作前,MR必须是高电平,OE必须是低电平,595才能工作。
SHCP是上升沿的时候,写入DS的数据,每写入一个数据时,移位寄存器中的数据依次移动一位
STCP是上升沿的时候,把数据从移位寄存器转存至锁存寄存器。
步骤2:
步骤3:
由74HC595的芯片手册可以知道:74HC595芯片的发送顺序是由Q0,一直到Q7。下图展示发送1位数据的过程
如果需要发送多位数据,则可以在DS发送8位数据后,再进行输出:
代码实现
#include <reg52.h>sbit ds_pin = P0^3;
sbit stcp_pin = P0^4;
sbit shcp_pin = P0^5;//共阴 数码管数组:0-9
unsigned char num[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};void hc595_send_data(unsigned char byte)
{unsigned int i;for(i = 0; i < 8; i++){//串行输入引脚,所谓串行就是使数据在一根信号线上按顺序一位一位地传输if(byte & 0x80)ds_pin = 1;elseds_pin = 0;//SHCP发生一次上升沿的时候,74HC595才会从DS引脚上取得当前的数据shcp_pin = 0;shcp_pin = 1;byte <<= 1;}stcp_pin = 0;stcp_pin = 1;
}void main(){while(1){//在第一个数码管显示数字0hc595_send_data(num[0]);}
}
如何用三个IO控制多个数码管?
前面流水灯的章节中,我们使用延时可以达到流水灯的效果,如果我们把延时缩短,我们可以看看现象是怎么样的?
#include <reg52.h>sbit led1 = P2^5;
sbit led2 = P2^6;
sbit led3 = P2^7;void delay_ms(unsigned int xms)
{unsigned int i, j;for(i=xms;i>0;i--){for(j=124;j>0;j--){}}
}void main()
{while(1){led1 = 0;led2 = 1;led3 = 1;delay_ms(1);led1 = 1;led2 = 0;led3 = 1;delay_ms(1);led1 = 1;led2 = 1;led3 = 0;delay_ms(1);}
}
我们可以看到,虽然我们在频繁开关LED,但是由于速度快,人眼是看不出闪烁的,而多位数码管也用的是同样的原理,快速逐个控制。
本设计使用了一个2位的数码管,为共阴型,为了节省单片机的IO口,使用了两片74HC595作为数码管的驱动芯片,共占用3个IO口。
第一片74HC595芯片的Q7S口,可以向下一片的74HC595芯片的串行输入口输入数据。
第一片74HC595芯片只使用了Q0、Q0两个管脚来管理数码管地址信息。
第二片74HC595芯片是用于控制数码管输出显示。
重要的一点是,先串行输入显示的数据,再串行输入地址。
与单片机相连接的三个脚分别为: DS,STCP,SHCP。
简化如下:
0x0110 1101
第一步:发送第一个需要显示的内容
第二步:选择第一个需要点亮的数码管
第三步:发送第二个需要显示的内容
第四步:选择第二个需要点亮的数码管
第五步:重复第一步
#include <reg52.h>sbit ds_pin = P0^3;
sbit stcp_pin = P0^4;
sbit shcp_pin = P0^5;//共阴 数码管数组:0-9
unsigned char num[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};void hc595_send_byte(unsigned char byte)
{unsigned int i;for(i = 0; i < 8; i++){//串行输入引脚,所谓串行就是使数据在一根信号线上按顺序一位一位地传输if(byte & 0x80)ds_pin = 1;elseds_pin = 0;//SHCP发生一次上升沿的时候,74HC595才会从DS引脚上取得当前的数据shcp_pin = 0;shcp_pin = 1;byte <<= 1;}
}//num-需要显示的内容 addr-在哪个数码管显示
void hc595_send_data(unsigned char num, unsigned char addr)
{hc595_send_byte(num); //先发需要显示的数字//我们将移位寄存器的8个位填满后,再往移位寄存器中塞数据,数据会被从9脚输出。//再发需要点亮的数码管,这时候移位寄存器中的数据被移位到第二个595中if(addr == 0)hc595_send_byte(0xFE); //Q0控制 0b1111 1110 0xFEelse if(addr == 1) hc595_send_byte(0xFD); //Q1控制 0b1111 1101 0xFD//当移位寄存器的8位数据全部传输完毕后,制造一次锁存器时钟引脚的上升沿(先拉低电平再拉高电平)stcp_pin = 0;stcp_pin = 1;
}void main(){while(1){//在第一个数码管显示数字0hc595_send_data(num[0], 0);//在第二个数码管显示数字3hc595_send_data(num[3], 1);}
}
看完上面的原理讲解,可见数码管的显示实际上就是每一位数码管在肉眼不可见的频率下不间断地轮流刷新,达到同时显示的效果。
但我们可以思考一个问题,如果在while循环中,我们除了要刷新数码管,还有别的耗时任务需要做,那么我们的数码管还能够正常稳定的刷新嘛?答案是肯定不能了,这得到的结果就会是数码管不断地在频闪刷新。
既然如此,那难道就没有别的更好的方法刷新数码管了吗?我们可以使用定时器来刷新数码管 。定时器刷新数码管,其一,我们可以不用过多地考虑数码管刷新的频率,因为它的刷新频率在一开始就已经设置了;其二,我们不用担心会有其他的东西干扰数码管刷新,因为我们是将数码管放在定时器中断里刷新的,只要配置的中断优先级足够高,就一定不会有其他的进程干扰数码管刷新!