飞书文档https://x509p6c8to.feishu.cn/wiki/TOQqweKHWinugokUyqzcwb0fnTd
原理:
一个二极管等于八个LED组合在一起,想要显示什么形状,就点亮对应LED即可。
数码管根据其公共端所接的阳极和阴极的不同,分为了共阴极数码管和共阳极数码管。
共阳极接法:几个二极管的阳极接在一起,接到VCC(高电平),我们要想点亮,只要在在对应的二极管的阴极接上低电平即可。
共阴极接法:几个二极管的阴极接在一起,接到GND(低电平),我们要想点亮,只要在在对应的二极管的阳极接上高电平即可。
这节课,我们将使用共阴极数码管,这也就意味着我们要点亮数码管,要在对应的IO设置输出为高电平。
对于共阴极数码管,8个IO控制如下,就可以点亮对应的数字或字母。
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 |
但是这里我们遇到一个问题,点亮一个数码管,需要8个IO,两个数码管则需要16个IO,很多时候,单片机没有这么多IO分配到给这个模块使用,所以我们用两颗74HC595芯片来扩展驱动数码管。
74HC595芯片
该芯片是一个8位串行输入、并行输出的位移缓存器,可以简单理解为用一个IO进行数据输入,可以控制8个IO输出。
SHCP:移位寄存器时钟输入 |
- SHCP是上升沿的时候,写入DS的数据,上升沿时移位寄存器中的数据依次移动一位,即 Q0 中的数据移到 Q1 中,Q1 中的数据移到 Q2 中,依次类推
- STCP是上升沿的时候,把数据从移位寄存器转存带存储寄存器,应用时通常将 STCP置为低点平,移位结束后再在 ST_CP 端产生一个正脉冲更新显示数据。
- 有数据操作的过程中MR必须是高电平,OE必须是低电平,595才能工作。
由74HC595的芯片手册可以知道:74HC595芯片的发送顺序是由Q0,一直到Q7。
本设计使用了一个2位的数码管,为共阴型,为了节省单片机的IO口,使用了两片74HC595作为数码管的驱动芯片,共占用3个IO口。
第一片74HC595芯片的Q7S口,可以向下一片的74HC595芯片的串行输入口输入数据。
第一片74HC595芯片只使用了Q0、Q0两个管脚来管理数码管地址信息。
第二片74HC595芯片是用于控制数码管输出显示。
重要的一点是,先串行输入显示的数据,再串行输入地址。
与单片机相连接的三个脚分别为: DS,STCP,SHCP。
/* USER CODE BEGIN 0 */
void hc595_send_byte(unsigned char byte)
{
unsigned int i;
for(i = 0; i < 8; i++)
{
//串行输入引脚,所谓串行就是使数据在一根信号线上按顺序一位一位地传输
if(byte & 0x80)
HAL_GPIO_WritePin(GPIOC,DS_Pin,GPIO_PIN_SET);
else
HAL_GPIO_WritePin(GPIOC,DS_Pin,GPIO_PIN_RESET);
//SHCP发生一次上升沿的时候,74HC595才会从DS引脚上取得当前的数据
HAL_GPIO_WritePin(GPIOC,SHCP_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,SHCP_Pin,GPIO_PIN_SET);
byte <<= 1;
}
}
void hc595_send_data(unsigned char num, unsigned char addr)
{
hc595_send_byte(num); //先发需要显示的数字
hc595_send_byte(1 << addr); //再发需要点亮的数码管,这时候数字会被移位到第二个595中点亮
//当移位寄存器的8位数据全部传输完毕后,制造一次锁存器时钟引脚的上升沿(先拉低电平再拉高电平)
HAL_GPIO_WritePin(GPIOC, STCP_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, STCP_Pin, GPIO_PIN_SET);
}
//共阳 数码管数组:0-9
//unsigned char num[]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80 ,0x90};
//共阴 数码管数组:0-9
unsigned char num[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void display()
{
hc595_send_data(num[1], 0);
hc595_send_data(num[0], 1);
}
/* USER CODE END 0 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
display();
}
/* USER CODE END 3 */
参考工程:
参考飞书文档
看完上面的原理讲解,可见数码管的显示实际上不就是每一位数码管在肉眼不可见的频率下不间断地轮流刷新,那直接放在主程序的while(1)循环里不就好了。
对于主函数有耗时任务执行的场景,也可以使用定时器来刷新数码管 。定时器刷新数码管,其一,我们可以不用过多地考虑数码管刷新的频率,因为它的刷新频率在一开始就已经设置了;其二,我们不用担心会有其他的东西干扰数码管刷新,因为我们是将数码管放在定时器中断里刷新的,只要配置的中断优先级足够高,就一定不会有其他的进程干扰数码管刷新!