1、74HC595简介
在51单片机中IO引脚资源十分的紧缺,所以常常需要使用75HC595芯片进行驱动那些需要占用多个IO引脚的芯片。那么75HC595是是什么喃?
74H595是一款串转并移位控制的芯片,通过移位的方式将串行输入的数据变为并行输出的数据。
引脚 | 功能 |
---|---|
VCC | 供电正极(2v~6v) |
GND | 供电负极 |
DS | 串行数据输入引脚(8位) |
Q7~Q0 | 并行数据输出引脚(Q7对应数据高位) |
SHCP | 移位寄存器时钟输入 |
STCP | 输出寄存器时钟输入 |
Q7S | 8位数据串行输出引脚(常用于级联) |
OE | 输出使能引脚 |
2、代码演示
2.1、驱动8位流水灯
①H595.c文件的代码如下:
#include "H595.h"
/**
* 使用74HC595串行数据转换位并行数据
* Byte:输入的串行数据
*/
void HC595_Driver(unsigned char Byte)
{
unsigned char i = 0;
HC595_ST = 0;
for(i = 0; i < 8; i++)
{
HC595_SH = 0; //SH低电平,将数据写入到输入数据引脚上
HC595_DS = (Byte & (0x80 >> i));//高位先行
HC595_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
HC595_ST = 1; //595的ST引脚来一个上升沿将移位寄存器的数据移到存储寄存器
HC595_OE = 0; //输出使能
}
②H595.h文件的代码如下:
#ifndef __H595_H
#define __H595_H
#include <REGX52.H> //包含51头文件,里面全是寄存器地址
sbit HC595_SH = P1^2; //移位时钟引脚
sbit HC595_DS = P1^3; //HC595输入数据口
sbit HC595_ST = P1^4; //存储时钟引脚
sbit HC595_OE = P1^5; //输出使能引脚
void HC595_Driver(unsigned char Byte);
#endif
③main.c文件的代码如下:
#include "H595.h"
#include "Delay.h"
#include "Timer.h"
void main(void)
{
Time0_Intrrupt_Init();
while(1)
{
unsigned char temp = 0x01;
unsigned char i;
for(i = 1; i < 8; i++) //循环7次
{
HC595_Driver(temp);
temp <<= 1; //循环结束num = 1000 0000
Delay_ms(500); //延时500ms
}
for(i = 1; i < 8; i++) //循环7次
{
HC595_Driver(temp);
temp >>= 1; //循环结束num = 0000 0001
Delay_ms(500); //延时500ms
}
}
}
3、74HC595级联
综上:74HC595的Q7S引脚是串行输出引脚,常常用于多片的75HC595芯片的级联。2片级联,则可看成一个2字节数据串转并移位寄存器,4片级联,则可看成4字节数据串转并移位寄存器。
如图:2片74HC595进行级联,他们的SH引脚,ST引脚,OE引脚都连接在一起,第一片H595芯片的Q7S连接着第二片芯片的数据输入引脚。
3.1、驱动16位流水灯
①H595.c文件的代码如下:
/**
* 2片74HC595的级联
* HByte:高位字节的数据
* LByte:低位字节的数据
*/
void HC595_Cascade(unsigned char HByte, unsigned char LByte)
{
unsigned char i = 0;
HC595_ST = 0;
/* 将高位字节的数据移位到第一个595里面 */
for(i = 0; i < 8; i++)
{
HC595_SH = 0;
HC595_DS = (HByte & (0x80 >> i));//高位先行
HC595_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
/* 将高位字节的数据移位到第二个595里面,低位字节的数据移到第一个595里面 */
for(i = 0; i < 8; i++)
{
HC595_SH = 0;
HC595_DS = (LByte & (0x80 >> i));//高位先行
HC595_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
HC595_ST = 1; //595的ST引脚来一个上升沿将移位寄存器的数据移到存储寄存器
HC595_OE = 0; //输出使能
}
②H595.h文件的代码如下:
#ifndef __H595_H
#define __H595_H
#include <REGX52.H> //包含51头文件,里面全是寄存器地址
sbit HC595_SH = P1^2; //移位时钟引脚
sbit HC595_DS = P1^3; //HC595输入数据口
sbit HC595_ST = P1^4; //存储时钟引脚
sbit HC595_OE = P1^5; //输出使能引脚
void HC595_Cascade(unsigned char HByte, unsigned char LByte);;
#endif
③main.c文件的代码如下:
#include "H595.h"
#include "Delay.h"
#include "Timer.h"
void main(void)
{
Time0_Intrrupt_Init();
while(1)
{
unsigned char Htemp = 0x01;
unsigned char Ltemp = 0x01;
unsigned char i;
for(i = 1; i < 8; i++) //循环7次
{
HC595_Cascade(0x00, Ltemp);
Ltemp <<= 1; //S循环结束num = 1000 0000
Delay_ms(500); //延时500ms
}
HC595_Cascade(0x00, Ltemp);
Delay_ms(500); //延时500ms
for(i = 1; i < 8; i++) //循环7次
{
HC595_Cascade(Htemp, 0x00);
Htemp <<= 1; //S循环结束num = 1000 0000
Delay_ms(500); //延时500ms
}
for(i = 1; i < 8; i++) //循环7次
{
HC595_Cascade(Htemp, 0x00);
Htemp >>= 1; //循环结束num = 0000 0001
Delay_ms(500); //延时500ms
}
HC595_Cascade(Htemp, 0x00);
Delay_ms(500); //延时500ms
for(i = 1; i < 8; i++) //循环7次
{
HC595_Cascade(0x00, Ltemp);
Ltemp >>= 1; //循环结束num = 0000 0001
Delay_ms(500); //延时500ms
}
}
}
3.2、驱动8位数码管
①H595_SEG8.c文件的代码如下:
/***********************************/
/* 8位数码管的底层驱动文件 */
/***********************************/
#include "H595_Seg8.h"
/**
* 2片74HC595的级联
* PossData:位选数据
* SegData:段码数字据
*/
void HC595_Cascade_Seg8(unsigned char PossData, unsigned char SegData)
{
unsigned char i = 0;
SEG_ST = 0;
/* 将高位字节的数据移位到第一个595里面 */
for(i = 0; i < 8; i++)
{
SEG_SH = 0;
SEG_DS = (PossData & (0x80 >> i));//高位先行
SEG_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
/* 将高位字节的数据移位到第二个595里面,低位字节的数据移到第一个595里面 */
for(i = 0; i < 8; i++)
{
SEG_SH = 0;
SEG_DS = (SegData & (0x80 >> i));//高位先行
SEG_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
SEG_ST = 1; //595的ST引脚来一个上升沿将移位寄存器的数据移到存储寄存器
SEG_OE = 0; //输出使能
}
/* 字符数据 */
static unsigned char code Data8[] ={
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, //共阳极显示的数值0 ~ 9
0xBF,0x7F,0x8C //-,.P
};
/* 显示的数值缓存区 */
unsigned char SegNum8[] = {0,0,0,0,0,0,0,0};
/**
* 选择哪个数码管显示数据的函数
* Position:哪一个数码管
* Data:需要显示的数据
*/
void Seg8_Choice(unsigned char Position, unsigned char DisplayData)
{
HC595_Cascade_Seg8(0x00,0x00); //让8个数码管先熄灭
HC595_Cascade_Seg8((0x01 << (Position - 1)), Data8[SegNum8[DisplayData]]); //传入段码数据进行显示
}
②H595_SEG8.h文件的代码如下:
#ifndef __H595_Seg8_H
#define __H595_Seg8_H
#include <REGX52.H> //包含51头文件,里面全是寄存器地址
sbit SEG_SH = P0^0; //移位时钟引脚
sbit SEG_DS = P0^1; //HC595输入数据口
sbit SEG_ST = P0^2; //存储时钟引脚
sbit SEG_OE = P0^3; //输出使能引脚
extern unsigned char SegNum8[];
void HC595_Cascade_Seg8(unsigned char PossData, unsigned char SegData);
void Seg8_Choice(unsigned char Position, unsigned char DisplayData);
#endif
③User_SEG8.c文件的代码如下:
/********************************************/
/* 使用74HC595驱动8位数码管的应用层 */
/********************************************/
#include "User_Seg8.h"
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
/**
* X的Y次方函数
*/
unsigned int SEG8_Pow(unsigned char X,unsigned char Y)
{
unsigned int Result = 1;
while(Y--)
{
Result *= X;
}
return Result;
}
/**
* 数码管显示正整数
* Number:0~65535
* Length:有效数值长度
* Lie: 显示的位置1~8
*/
void SEG8_Show_Num(unsigned char Lie,unsigned short Number,unsigned char Length)
{
/* 将整数进行拆分在通过数码管显示 */
unsigned char i,Data;
for(i = 0; i < Length; i++)
{
Data = (Number / SEG8_Pow(10,Length - i -1)) % 10;//将整数进行拆分
SegNum8[0] = Data;
Seg8_Choice(i+Lie,0);//显示
Delay1ms();
}
}
/**
* 数码管显示有符号整数
* Number:-32768~32767
* Length:有效数值长度
* Lie: 显示的位置1~8
*/
void SEG8_Show_SignedNum(unsigned char Lie, short Number,unsigned char Length)
{
/* 若为负数,先显示负号 */
if(Number < 0)
{
Number = -Number;
SegNum8[0] = 10;
Seg8_Choice(Lie,0); //显示"-"
Lie++;
}
SEG8_Show_Num(Lie, Number, Length);
}
/**
* 数码管显示有符号小数
* Number:小数数值
* Floatcount:显示几位小数
* Lie: 显示的位置1~8
* IntCount: 整数的个数
* Floatcount:小数的个数
*/
void SEG8_Show_FloatNum(unsigned char Lie, float Number, unsigned char IntCount, unsigned char Floatcount)
{
short Num = 0;
if(Number < 0)
{
Number = -Number; //将负数转换为正数,例如:3.14 = -(-3.14)
SegNum8[0] = 10;
Seg8_Choice(Lie,0); //显示"-"
Lie++;
}
/* 先提取整数部分 */
Num = Number; //此时Num保存的是小数的整数部分,例如:Number = 3.14,Num = 3
SEG8_Show_Num(Lie,Num,IntCount);
/* 将小数换算为整数 */
Number -= Num; //Number = Number - Num,Number = 3.14 - 3 = 0.14
Num = (short)(Number * SEG8_Pow(10,Floatcount));//将小数将小数换算为整数,0.14 * 100 = 14
SegNum8[0] = 11; //先添加数据的索引
Seg8_Choice(Lie+IntCount-1,0); //显示小数点
/* 显示小数部分 */
SEG8_Show_Num(Lie+IntCount, Num, Floatcount);
}
④User_SEG8.h文件的代码如下:
#ifndef __User_Seg8_H
#define __User_Seg8_H
#include "H595_Seg8.h"
unsigned int SEG8_Pow(unsigned char X,unsigned char Y);
void SEG8_Show_Num(unsigned char Lie,unsigned short Number,unsigned char Length);
void SEG8_Show_SignedNum(unsigned char Lie, short Number,unsigned char Length);
void SEG8_Show_FloatNum(unsigned char Lie, float Number, unsigned char IntCount, unsigned char Floatcount);
#endif
⑤main.c文件的代码如下:
#include "User_Seg8.h"
void main(void)
{
while(1)
{
SEG8_Show_FloatNum(1,-200.123,3,3);
}
}
3.3、驱动8x8点阵屏幕
如图:点亮图上的小亮点,则阴极引脚输出的数据为:1011 1111(0xBF)选中第2列;阳引脚输出的数据为:1000 0000(0x80)表示点亮选中的列的最上的LED
LED显示字符的方法:
例如想要显示如下图是字符:
列扫描:从左到右依次从第一列进行扫描,即(共阴极的引脚数据依次为:0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE)。而共阳极字模数据Data[8] = {0x00,0x00,0xFF,0x08,0x08,0xFF,0x00,0x00};当进行第一列扫描时,共阳极引脚输出Data[0]
①Dot_LED.c文件的代码如下:
#include "Dot_LED.h"
/**
* 2片74HC595的级联
* LEDNode: 阳极数据,选择选定的列中哪几个LED点亮
* LEDCathode: 阴极数据,选择哪一列点亮
*/
void HC595_Cascade_8x8LED(unsigned char LEDNode, unsigned char LEDCathode)
{
unsigned char i = 0;
LED_ST = 0;
/* 将高位字节的数据移位到第一个595里面 */
for(i = 0; i < 8; i++)
{
LED_SH = 0;
LED_DS = (LEDNode & (0x80 >> i));//高位先行
LED_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
/* 将高位字节的数据移位到第二个595里面,低位字节的数据移到第一个595里面 */
for(i = 0; i < 8; i++)
{
LED_SH = 0;
LED_DS = (LEDCathode & (0x80 >> i));//高位先行
LED_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
LED_ST = 1; //595的ST引脚来一个上升沿将移位寄存器的数据移到存储寄存器
LED_OE = 0; //输出使能
}
/**
* 8x8点阵显示函数(列扫描显示)
*/
void Show_8X8Dot_ColumnScan(unsigned char *Data)
{
unsigned char i = 0;
for(i = 0; i < 8; i++)
{
HC595_Cascade_8x8LED(Data[i],~(0x80 >> i)); //依次选择从左到右第1列行开始(行扫描显示)
}
}
②Dot_LED.h文件的代码如下:
#ifndef __Dot_LED_H
#define __Dot_LED_H
#include <REGX52.H> //包含51头文件,里面全是寄存器地址
sbit LED_SH = P2^0;
sbit LED_DS = P2^1;
sbit LED_ST = P2^2;
sbit LED_OE = P2^3;
void HC595_Cascade_8x8LED(unsigned char LEDNode, unsigned char LEDCathode);
void Show_8X8Dot_ColumnScan(unsigned char *Data);
#endif
③main.c文件的代码如下:
#include "Dot_LED.h"
/* 显示H */
static unsigned char H_Data[] = {0x00,0x00,0xFF,0x08,0x08,0xFF,0x00,0x00};//纵向,上为最高位(纵向取模)
void main(void)
{
while(1)
{
Show_8X8Dot_ColumnScan(H_Data); //列扫描显示
}
}
3.4、8x8点阵屏幕滚动显示
从左向右滚动显示“HELLO”
滚动显示的原理: 点阵屏幕一直显示8个字节Buffer中的数据,我们只需要将HELLO的字模数据依次放入到Buffer中。如下图所示:
取模软件的使用:
①Dot_LED.c文件的代码如下:
#include "Dot_LED.h"
#include "Timer.h"
/**
* 2片74HC595的级联
* LEDNode: 阳极数据,选择选定的列中哪几个LED点亮
* LEDCathode: 阴极数据,选择哪一列点亮
*/
void HC595_Cascade_8x8LED(unsigned char LEDNode, unsigned char LEDCathode)
{
unsigned char i = 0;
LED_ST = 0;
/* 将高位字节的数据移位到第一个595里面 */
for(i = 0; i < 8; i++)
{
LED_SH = 0;
LED_DS = (LEDNode & (0x80 >> i));//高位先行
LED_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
/* 将高位字节的数据移位到第二个595里面,低位字节的数据移到第一个595里面 */
for(i = 0; i < 8; i++)
{
LED_SH = 0;
LED_DS = (LEDCathode & (0x80 >> i));//高位先行
LED_SH = 1; //595的SH引脚来一个上升沿将数据进行移位
}
LED_ST = 1; //595的ST引脚来一个上升沿将移位寄存器的数据移到存储寄存器
LED_OE = 0; //输出使能
}
/**
* 8x8点阵显示函数(列扫描显示)
*/
void Show_8X8Dot_ColumnScan(unsigned char *Data)
{
unsigned char i = 0;
for(i = 0; i < 8; i++)
{
HC595_Cascade_8x8LED(0x00,~(0x80 >> i)); //向将扫描的一列的灯全部熄灭
HC595_Cascade_8x8LED(Data[i],~(0x80 >> i)); //依次选择从左到右第1列行开始(行扫描显示)
}
}
/**
* 8x8点阵数据滚动函数
*/
unsigned char LEDBuffer[8] = {0x00,0x00,0xFF,0x08,0x08,0xFF,0x00,0x00};
void LEDData_Move(unsigned char *Data,unsigned char Len)
{
static unsigned char j = 0;
static unsigned char i = 0;
static unsigned char Status = 1;
static unsigned char TimeStatus = 1;
static unsigned long GET_Time = 0;
switch(TimeStatus)
{
case 1:
GET_Time = Get_TimeCount(); //获取TimeCount的值
TimeStatus = 2;
case 2:
if(Get_TimeCount() - GET_Time > 500) //每隔500ms进行向Buff里面放入数据
{
GET_Time = Get_TimeCount();
for (j = 0; j < 7; j++) //先移动
{
LEDBuffer[j] = LEDBuffer[j+1];
}
switch(Status)
{
case 1:
LEDBuffer[7] = Data[i+8]; //第一轮放入从Data[8]开始取出放入
if(i == Len - 8)
{
i = Len - 1;
Status = 2;
}
break;
case 2: //第二轮放入从Data[0]开始取出放入
LEDBuffer[7] = Data[i];
break;
}
i = (i + 1) % Len; //循环遍历 i:0~31
}
break;
}
}
②Dot_LED.h文件的代码如下:
#ifndef __Dot_LED_H
#define __Dot_LED_H
#include <REGX52.H> //包含51头文件,里面全是寄存器地址
sbit LED_SH = P2^0;
sbit LED_DS = P2^1;
sbit LED_ST = P2^2;
sbit LED_OE = P2^3;
extern unsigned char LEDBuffer[8];
void HC595_Cascade_8x8LED(unsigned char LEDNode, unsigned char LEDCathode);
void Show_8X8Dot_ColumnScan(unsigned char *Data);
void LEDData_Move(unsigned char *Data,unsigned char Len);
#endif
③main.c文件的代码如下:
#include "Dot_LED.h"
#include "Timer.h"
/* 显示HELLO */
static unsigned char HELLO_Data[] = {
0x00,0x00,0xFF,0x08,0x08,0xFF,0x00,0x00,
0xFF,0x89,0x89,0x89,0x00,0x00,0xFF,0x01,
0x01,0x01,0x00,0x00,0xFF,0x01,0x01,0x01,
0x00,0x00,0x7E,0x81,0x81,0x7E,0x00,0x00
};//纵向,上为最高位(纵向取模)
void main(void)
{
Time0_Intrrupt_Init(); //启动定时器中断
while(1)
{
LEDData_Move(HELLO_Data,32);
Show_8X8Dot_ColumnScan(LEDBuffer); //列扫描显示
}
}
QQ2025427-13511