目录
- 单片机 IO 扩展(串转并)
- 74HC595 芯片介绍
- 硬件设计
- 软件实验
- 实验现象
- LED点阵实验
- LED 点阵介绍
- 硬件设计
- 软件设计
- LED点阵(点亮一个点)
- LED 点阵(显示数字)
- LED 点阵(显示图像)
橙色
单片机 IO 扩展(串转并)
本章就来介绍另外一种 IO 口扩展方式-串转并, 使用的芯片是 74HC595。 开发板板载 1 个 74HC595 芯片, 仅需单片机 3 个 IO 口即可扩展 8 个, 如果需要还可以将 2 个 74HC595 级联扩展出 16 个 IO, 这就实现用少数 IO 资源控制多个设备。
本章所要实现的功能是: 通过 74HC595 模块控制 LED 点阵以一行循环滚动显示。
为什么不直接通过51单片机的引脚来驱动LED矩阵呢?最重要的一点就是,通过74HC595芯片控制LED矩阵可以大大减少所需的引脚数量。74HC595是一个串行输入、并行输出的移位寄存器,
使用三个引脚
(时钟、数据、锁存)即可控制多个输出引脚。
74HC595 芯片介绍
74HC595 是一个 8 位串行输入、 并行输出的位移缓存器, 其中并行输出为三态输出(即高电平、 低电平和高阻抗) 。芯片管脚及功能说明如下:
不同人绘制芯片管脚图时可能命名不一样,但这没什么,主要是看芯片的管脚功能
- 15 和 1 到 7 脚 QA–QH: 并行数据输出
- 9 脚 QH 非: 串行数据输出
- 10 脚 SCLR 非 : 低电平复位引脚
- 11 脚 SCK : 移位寄存器时钟输入
- 12 脚 RCK : 存储寄存器时钟输入
- 13 脚 G 非 : 输出有效
- 14 脚 SER : 串行数据输入
移位寄存器 (Shift Register):1、移位寄存器用于接收串行输入数据。数据通过一个引脚(通常是DS或SER)以位的形式输入芯片,按照顺序存储在移位寄存器中。通过时钟信号(通常是SHCP或SCK)的边沿触发,数据从输入端串行地移入寄存器。2、
当新的数据输入时,原有数据会向左移位,为新的数据腾出空间。
这种移位的过程可以持续进行,直到将所有位的数据加载到寄存器中。
存储寄存器 (Storage Register):1、存储寄存器用于存储移位寄存器中的数据,并在需要时并行输出到多个引脚。存储寄存器的更新是通过时钟信号(通常是STCP或RCK)的边沿触发的。当时钟信号触发时,移位寄存器中的数据被复制到存储寄存器中。2、存储寄存器的输出通过并行输出引脚(通常是Q0至Q7)完成。每个输出引脚对应存储寄存器中的一个位,它们可以设置为高电平、低电平或高阻抗状态。
74HC595 是具有 8 位移位寄存器和一个存储器, 三态输出功能。 移位寄存器和存储器是分别的时钟。 数据在 SCK 的上升沿输入, 在 RCK 的上升沿进入到存储器中。 如果两个时钟连在一起, 则移位寄存器总是比存储器早一个脉冲。 移位寄存器有一个串行输入(DS) , 和一个串行输出(Q7 非) , 和一个异步的低电平复位, 存储寄存器有一个并行 8 位的, 具有三态的总线输出, 当 MR 为高电平, OE 为低电平时, 数据在 SHCP 上升沿进入移位寄存器, 在 STCP 上升沿输出到并行端口。
硬件设计
本文使用到硬件资源如下:
(1) 8X8LED 点阵模块
(2) 74HC595 模块
从上图中可以看出, 74HC595 需要用到的控制管脚 SER、 RCLK、 SRCLK (14、12、11管脚)直接连接到 51 单片机的 P3.4-P3.6 IO 口上, 输出端则是直接连接到 LED 点阵模块的行端口上, 即为 LED 发光二极管的阳极, LED 点阵的列则为发光二极管的阴极。
要想控制 LED 点阵, 可以将单片机管脚按照 74HC595 芯片的通信时序要求来传输数据, 这样即可控制 LED 点阵的行数据。 根据 LED 发光二极管导通原理, 当阳极为高电平, 阴极为低电平则点亮, 否则熄灭。 因此通过单片机 P0 口可控制点阵列, 74HC595 可控制点阵行。
软件实验
本章所要实现的功能是: 通过 74HC595 模块控制 LED 点阵以一行循环滚动显示。
#include "reg51.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义74HC595控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
//0x01: 00000001 0x02: 00000010 0x04: 00000100
u8 ghc595_buf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时函数,ms=1时,大约延时1ms
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
/*******************************************************************************
* 函 数 名 : hc595_write_data(u8 dat)
* 函数功能 : 向74HC595写入一个字节的数据
* 输 入 : dat:数据
* 输 出 : 无
*******************************************************************************/
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
{
// 74HC595 是先传输字节的高位后传输低位,所以通过右移运算符依次取值
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//依次把低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=0;
delay_10us(1);
RCLK=1;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
u8 i=0;
LEDDZ_COL_PORT=0x00;//将LED点阵列全部设置为0,即LED阴极为低电平
while(1)
{
for(i=0;i<8;i++)
{
//将存储寄存器的输出引脚清零。这样做的目的是确保 LED 矩阵的显示在每次循环的开始时都是从零状态开始
//防止LED矩阵在切换显示内容时出现短暂的混乱或残影,保证每一次循环的开始状态是清晰的。
hc595_write_data(0x00);//消除前面寄存器缓存数据
hc595_write_data(ghc595_buf[i]);//写入新的数据
delay_ms(500);//延时500ms
}
}
}
实验现象
注意: 做 LED 点阵实验时, 一定要将 LED 点阵旁的 J24 黄色跳线帽短接到 GND一端。
LED点阵实验
本章所要实现的功能是: 在 8*8LED 点阵屏上点亮一个点, 上述功能实现后再显示数字图形等。
LED 点阵介绍
通常应用较多的是 88 点阵, 然后使用多个 88 点阵可组成不同分辨率的 LED点阵显示屏, 比如 1616 点阵可以使用 4 个 88 点阵构成。 因此理解了 88LED点阵的工作原理, 其他分辨率的 LED 点阵显示屏都是一样的。 这里以 88LED 点阵来做介绍。 其内部结构图如下所示:
8*8 点阵共由 64 个发光二极管组成, 且每个发光二极管是放置在行线和列线的交叉点上, 当对应的某一行置 1 电平, 某一列置 0 电平, 则相应的二极管就亮;如要将第一个点点亮, 则 1 脚接高电平 a 脚接低电平, 则第一个点就亮了; 如果要将第一行点亮, 则第 1 脚要接高电平, 而(a、 b、 c、 d、 e、 f、 g、 h ) 这些引脚接低电平, 那么第一行就会点亮; 如要将第一列点亮, 则第 a 脚接低电平,而(1、 2、 3、 4、 5、 6、 7、 8) 接高电平, 那么第一列就会点亮。 由此可见, LED点阵的使用也是非常简单的。
硬件设计
本实验用到的硬件资源如下:
(1)8*8LED 点阵模块
(2)74HC595 模块
从上图中可以看出, 74HC595 需要用到的控制管脚 SER、 RCLK、 SRCLK 直接连接到 51 单片机的 P3.4-P3.6 IO 口上, 输出端则是直接连接到 LED 点阵模块的行端口上, 即为 LED 发光二极管的阳极, LED 点阵的列则为发光二极管的阴极。
要想控制 LED 点阵, 可以将单片机管脚按照 74HC595 芯片的通信时序要求来传输数据, 这样即可控制 LED 点阵的行数据。 根据 LED 发光二极管导通原理, 当阳极为高电平, 阴极为低电平则点亮, 否则熄灭。 因此通过单片机 P0 口可控制点阵列, 74HC595 可控制点阵行。
软件设计
本实验所要实现的功能是: 在点阵屏上点亮一个点, 上述功能实现后再显示数字和图形。
LED点阵(点亮一个点)
#include "reg51.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义74HC595控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : hc595_write_data(u8 dat)
* 函数功能 : 向74HC595写入一个字节的数据
* 输 入 : dat:数据
* 输 出 : 无
*******************************************************************************/
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
{
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//将低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=1;
delay_10us(1);
RCLK=0;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
void main()
{
u8 i=0;
LEDDZ_COL_PORT=0x7f;//将LED点阵左边第一列设置为0,即LED阴极为低电平,其余列为1,即高电平
while(1)
{
hc595_write_data(0x80);//将LED点阵上边第一行设置为1,即LED阳极为高电平,其余行为0,即低电平
}
}
代码很简单, 与上一章核心代码是一样的, 这里主要是理解如何让 LED 点阵的左上角第一个点点亮, 实际上就是将第一个点对应的行为高电平, 列为低电平即可。 也就是让 74HC595 输出 0X80(1000 0000) , 这样点阵第一行就是高电平,而 P0 口输出 0X7F(0111 1111),这样点阵第一列就是低电平, 从而让 LED 点阵第一个点点亮。
LED 点阵(显示数字)
要实现行列不同位置亮灯, 需要使用动态显示的方法, 也要结合扫描的方法。在第一行亮灯一段时间以后灭掉, 点亮第二行一段时间以后灭掉, 点亮第三行一段时间以后灭掉, 如此点亮, 直到八行全部点亮一次, 在第一行点亮到最后一行灭掉的总时间不能超过人肉眼可识别的时间, 即 24 毫秒。 在每一行点亮的时候,给列一个新的数据, 此时对应列的数据就可以体现在这行上要点亮的灯上。 这样就和动态数码管的显示一样, 只不过数码管的 LED 灯是段值。 这里使用 LED 点阵显示数字, 也是多个 LED 同时点亮。
要想在点阵上显示数字等字符, 首先要获取在 LED 点阵上显示数字字符所需的数据, 即一个数字字符在 LED 点阵上显示, 对应的每行每列都会有一些灯点亮或者熄灭, 这样就会构成一组数据, 也就是数字字符的显示数据, 我们只要将这些数据通过 74HC595 发送到点阵对应的行或列就能显示数字字符。
数字字符的显示数据如何获取呢?这里用到了一个取字模软件,不详细介绍,大家可以去搜索。通过该软件可得,
LED 点阵显示数字 0 的行数据为{0x00,0x7C,0x82,0x82,0x82,0x7C,0x00,0x00},这些数据其实就是上述描绘的数字 0 从左到右依次每列对应的行数据。
既然是动态扫描, 就需要不断的扫描每列, 因此可以把 LED 点阵的列控制也用数组存储起来, 为后面循环调用提供方便。根据数字 0 取模的数据特点是从左至右每列对应的行数据, 因此扫描时也应该从左至右的顺序, 如下:
u8 gled_col[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//LED 点阵显示数字 0 的列数据
#include "reg51.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义74HC595控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
u8 gled_row[8]={0x00,0x7C,0x82,0x82,0x82,0x7C,0x00,0x00};//LED点阵显示数字0的行数据
u8 gled_col[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//LED点阵显示数字0的列数据
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : hc595_write_data(u8 dat)
* 函数功能 : 向74HC595写入一个字节的数据
* 输 入 : dat:数据
* 输 出 : 无
*******************************************************************************/
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
{
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//将低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=1;
delay_10us(1);
RCLK=0;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
u8 i=0;
while(1)
{
for(i=0;i<8;i++)//循环8次扫描8行、列
{
LEDDZ_COL_PORT=gled_col[i];//传送列选数据
hc595_write_data(gled_row[i]);//传送行选数据
delay_10us(100);//延时一段时间,等待显示稳定
hc595_write_data(0x00);//消影
}
}
}
实验代码与前面点亮一个点的核心代码是一样的, 这里重点要理解动态扫描。 main 函数中主要是在 while 循环内从上至下, 从左至右不断扫描 8 行、 列, 即首先设置左边第一列有效(P07 输出低电平) , 其余列无效(P06-P00 输出高电平) , 然后通过74HC595 输出该列对应的行数据, 延时一段时间等待显示稳定, 最后清除列对应的行数据, 即消影。 从整个流程下来与动态数码管显示程序是很相似的
LED 点阵(显示图像)
同样是通过取模软件获取图像数据,这里以“心” 形图像为例,LED 点阵显示数字 0 的行数据为{0x38,0x7C,0x7E,0x3F,0x3F,0x7E,0x7C,0x38}
,把该行数据替换为数字 0 的数据即可。其余代码与显示数字完全一样, 这里就不再列出。