简介
LED点阵屏实际上就是将每一个LED作为一个像素点,通过矩阵的连接方式进行连接,用少数IO口操作大量的LED显示出图像。
LED点阵屏与之前提到的数码管类似,都是将每行每列LED的阴极或阳极连接,达到逐行逐列扫描控制的效果,当某个LED满足导通条件则会发光。
74HC595
为了进一步减少IO口的使用,我们还需要用芯片74HC595来控制LED点阵屏。
功能简介
通过控制串行输入数据、寄存器时钟、串行输出时钟,74HC595可以将串行输入的数据并行输出。
图片来自b站江科大视频:
当串行时钟的上升沿到来时,串行数据被压入缓存区(类似于手枪弹匣上子弹,一颗一颗压入,最先压入的在最底下)
如果缓存区满了仍然写入数据,多出的数据就会从QH`输出,此时如果将QH`接到下一块74HC595的SER引脚就可以实现数据级联,再将两块芯片的两个时钟引脚连在一起,实现时钟同步,就实现了两块芯片的级联,可以实现16位的并行输出,而且不会增加外部引脚(仍然是3条)
当寄存器时钟的上升沿到来时,缓存区的数据被同时发送到输出缓存(并行输出)
注意:并行输出的最后一位是串行输出的第一位!
芯片引脚介绍
引脚意为Output Enable,输出使能。上方的横杠代表低电平使能,观察原理图左侧的J24部分,我们将OE和GND使用跳线帽接在一起,确保输出使能开启。
RCLK引脚意为Register Clock,寄存器时钟。
引脚意为Serial Clear,串行清零。清零引脚一般都设置为低电平触发。直接接到VCC意味着在运行过程中都不会清空。
SRCLK引脚意为Serial Clock,串行时钟。
SER引脚为串行数据输入引脚。
QA~QH为并行输出。
QH`用于多芯片级联。当缓存区满了之后溢出的数据会发送到QH`引脚。
51单片机上电引脚默认为高电平。
使用74HC595的另一个原因
IO口自身类型
虽然这里是为了减少IO口的使用,但是实际上我们也勉强负担的起16个IO口的需求,为什么行和列不都使用IO口直接控制呢?这样不是可以减少芯片的使用吗?
不这么做还有一个原因:单片机的IO口是弱上拉类型的,原理图大致如下:
从原理图可以看出,因为高电平保护电阻的存在,IO口低电平能够经过的电流是远大于高电平的。这也是它被称为弱上拉型的原因。
所以如果像我们上面说的那样连接,因为有保护电阻,最后我们的点阵屏就会很暗,效果不好。
改进方式
虽然如此,但是我们有时也会遇见需要IO口直接驱动外设的情况,此时外设需要的电压一般都会超出IO口能够提供的电压(3.3V、5V...),这时候我们可以考虑使用IO口驱动三极管来实现目标。
大致原理图如下:
对于NPN型的三极管,只需要基极(IO口)为高电平,就可以输出需要的VCC给外设,实现了IO口直接控制外设的功能。
当然为了防止设备损坏可以加上一个保护电阻。
实际代码
sfr、sbit
sfr意为special function register,特殊功能寄存器声明。
sbit意为special bit,特殊位声明。
这两个声明都类似于给寄存器取一个别名,便于我们记忆和提高代码的可读性。
例如:sfr P0 = 0x80 ;这一句代码就是来自于头文件REGX52.H里面,意味着我们直接写关于P0的代码,实际上就是写代码控制地址为0x80的寄存器。
sbit P0_0 = 0x80 ;则是单独控制0x80这一位,上面的sfr控制的是0x80之后的8位。
为了增加代码的可读性,此处进行声明:
sbit SER = P3_4 ;
//不叫RCLK的原因是在头文件<REGX52.H>里面已经有关于RCLK的定义了,会引起重定义
sbit RCK = P3_5 ;
sbit SRCLK = P3_6 ;
向74HC595写入数据
想要控制74HC595输出我们想要的数据,就需要将一个8位的数据按照它的规则串行写入。即先写入第八位,再写入第七位...直到第一位。
假设我们想要输出的八位数据为Byte,但是SER只有一位,应该如何把Byte的某一位赋值给SER呢?
SER如果接收到数据,只有两种情况:
1.零,SER = 0 ;
2.非零,SER = 1 ;
我们想要提取Byte的某一位,实际上就是判断Byte的某一位是0还是1,以最高位为例,我们只需要:SER = Byte & 0x80 ;此时如果Byte最高位为0,SER = 0;Byte最高位为1,SER = 0x80,也就是SER = 1。这样我们就实现了向SER写入Byte的最高位数据。
接下来就需要把SER的数据写入缓存区,即给串行时钟一个上升沿:SRCLK = 1 ;
当然记得进行软件复位:SRCLK = 0 ;
这样就写入了一位数据,接下来需要写入第二位数据,观察我们会发现,除了SER = Byte & 0x80 ;这一句代码以外,其他代码都不需要进行改变只需要将0x80改为0x40,也就是把1的位置右移一位,所以得到最终代码:
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i);
SRCLK=1;
SRCLK=0;
}
RCK=1;
RCK=0;
选中列
74HC595控制的是行的状况,所以我们还需要选择要控制的列。
在实际的使用过程中,我们需要的当然是整个点阵屏,所以需要进行逐列扫描,与LED数码管类似,先进行段选、再进行位选、延时约1ms、进行位清零(可以参考学习笔记(一))
最终代码
效果为显示棋盘
#include <REGX52.H>
#include "Delay.h"
sbit RCK=P3^5;//原理图里面的RCLK
sbit SRCLK=P3^6;
sbit SER=P3^4;//定义P34寄存器为SER(别名,增强文件可读性)(位声明)
/**
* @brief 控制74HC595输出想要的数据
* @param Byte 目标数据
* @retval 无
*/
void _74HC595_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i);//从输入信息的最高位开始判断是0还是1
SRCLK=1;//上升沿移位置一,SER的数据进入临时寄存器(临时寄存器内部数据下移一位)
SRCLK=0;//再次恢复原来状态
}
RCK=1;//上升沿锁存置一,数据送到输出口
RCK=0;//恢复原来状态
}
/**
* @brief 控制点阵屏某一行显示想要的图像
* @param col 目标行(从左到右为0-7行);data 想要点亮的位置(点亮为1,从上到下对应高位到低位)
* @retval 无
*/
void MatrixLED_ShowCol(unsigned char col,unsigned char num)
{
_74HC595_SendByte(num);
P0 = ~(0x80 >> col);//给0为选中
Delay(1);
P0 = 0xFF;
}
void main()
{
RCK = 0;
SRCLK = 0;//单片机上电默认为高电平,为了不丢失第一个数据,初始化为低电平
while(1)
{
MatrixLED_ShowCol(0,0xAA);
MatrixLED_ShowCol(1,0x55);
MatrixLED_ShowCol(2,0xAA);
MatrixLED_ShowCol(3,0x55);
MatrixLED_ShowCol(4,0xAA);
MatrixLED_ShowCol(5,0x55);
MatrixLED_ShowCol(6,0xAA);
MatrixLED_ShowCol(7,0x55);
}
}