HC595级联原理及实例 - STM32

74HC595的最重要的功能就是:串行输入,并行输出。其次,74HC595里面有2个8位寄存器:移位寄存器、存储寄存器。74HC595的数据来源只有一个口,一次只能输入一个位,那么连续输入8次,就可以积攒为一个字节了。

引脚图

14脚:DIN(SER),串行数据输入引脚

13脚:OE, 输出使能控制脚,它是低电才使能输出,所以接GND

12脚:RCK,存储寄存器时钟输入引脚。上升沿时,数据从移位寄存器转存带存储寄存器。

11脚:SCK,移位寄存器时钟引脚,上升沿时,移位寄存器中的bit 数据整体后移,并接受新的bit(从SER输入)。

10脚:SCLR,低电平时,清空移位寄存器中已有的bit数据,一般不用,接高电平即可。

9 脚 :串行数据出口引脚。当移位寄存器中的数据多于8bit时,会把已有的bit"挤出去",就是从这里出去的。用于595的级联。

Qx:并行输出引脚

使用参数

VCC:2V~6V,5V最好

I Qn:+- 35mA

移位寄存器

74HC595的14脚:DIN,是串行数据输入口。595的数据来源只有这一个口,一次只能输入一个位,那么连续输入8次,就可以积攒为一个字节了。

74HC595的11脚,(shift register clock input) 移位寄存器时钟引脚。上升沿有效。

首先我们要介绍这个引脚的作用,当一个新的位数据要进来时,已经进入的位数据就在移位寄存器时钟脉冲的控制下,整体后移,让出位置。

上升沿:电平从低到高的那个过程。移位寄存器时钟在上升沿这个过程中才起作用。

存储寄存器

595是怎么将移位寄存器的数据转移到存储寄存器。存储寄存器是直接和8个输出引脚相通的,将移位寄存器的数据转移到存储寄存器后,Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 就可以接收到我们开始输入的一个字节的数据。所谓存储寄存器,就是数据可以存在这个寄存器中,并不会随着一次输出就消失,只要595不断电,也没有新的数据从移位寄存器中过来,数据就一直不变且有效。新的数据过来后,存储寄存器中的数据就会被覆盖更新。

74HC595的12脚: (storage register clock input ) 存储寄存器时钟

数据从位移寄存器转移到存储寄存器,也是需要时钟脉冲驱动的,这就是12脚的作用。它也是上升沿有效。

74HC595级联

通过上面的介绍,见识到595的厉害了吧。138译码器通过3个输入口控制8个输出口,而且还只能是特定的8个输出值,而595只用了一个输入口就可以输任意的8位数据,可谓短小精悍。

你觉的1位控制8位输出还不够?

在上面的程序中用到的9脚,没用起作用,如果要让2个595串联起来的话,就需要它了。想一下,我们将移位寄存器的8个位填满后,再往移位寄存器中塞一个会怎么样?也许你想到了。对!移位寄存器的最后一个位数据会被挤出去,从哪里出去?就是从9脚输出的。如果我们把第一个595的9脚连接到第二个的串行数据输入脚SER,那么,就形成了595的级联。这样,如果我们用2个595组合成了一个新的超级595,这个超级595的移位寄存器和存储寄存器的容量都翻倍了,1口控制16口,有木有!你还可以继续级联下去!

74HC595级联实例

cpp 复制代码
#ifndef __HC_595_H__
#define __HC_595_H__	

#include "stm32h7xx_hal.h"


// HC595的16位输出端定义位号
// Q0 - 0 .....Q15 -15
#define DIO_HC_A0           (6)
#define DIO_HC_B0           (7)
#define DIO_HC_C0           (0)
#define DIO_HC_A1           (15)
#define DIO_HC_B1           (8)
#define DIO_HC_C1           (1)
#define DIO_HC_A2           (10)
#define DIO_HC_B2           (13)
#define DIO_HC_C2           (14)
/* RLY */
#define DIO_RLY0            (5)
#define DIO_RLY1            (2)
/* S1 S2 */
#define DIO_S1              (11)
#define DIO_S2              (12)
/* CUR */
#define DIO_CUR             (9)

/*----------------------------------------- HC595 引脚配置宏 -----------------------------------------------*/

#define HC_595_DIN_ENABLE     		 __HAL_RCC_GPIOE_CLK_ENABLE()			// 使能DIN引脚时钟
#define HC_595_DIN_PORT   			 GPIOE                 					// DIN引脚端口
#define HC_595_DIN_PIN     		 	 GPIO_PIN_1  							// DIN引脚
         
#define HC_595_RCK_ENABLE     		 __HAL_RCC_GPIOE_CLK_ENABLE()			// 使能RCK引脚时钟
#define HC_595_RCK_PORT   			 GPIOE                 					// RCK引脚端口
#define HC_595_RCK_PIN     		 	 GPIO_PIN_3  							// RCK引脚

#define HC_595_SCK_ENABLE     		 __HAL_RCC_GPIOE_CLK_ENABLE()			// 使能SCK引脚时钟
#define HC_595_SCK_PORT   			 GPIOE                 					// SCK引脚端口
#define HC_595_SCK_PIN     		 	 GPIO_PIN_2  							// SCK引脚

#define HC_595_SCLR_ENABLE     		 __HAL_RCC_GPIOC_CLK_ENABLE()			// 使能SCLR引脚时钟
#define HC_595_SCLR_PORT   			 GPIOC                 					// SCLR引脚端口
#define HC_595_SCLR_PIN     		 GPIO_PIN_4  								// SCLR引脚

/*-------------------------------------------- IO口操作 ---------------------------------------------------*/   

#define HC_595_DIN(a)	if (a)	\
										HAL_GPIO_WritePin(HC_595_DIN_PORT, HC_595_DIN_PIN, GPIO_PIN_SET); \
									else		\
										HAL_GPIO_WritePin(HC_595_DIN_PORT, HC_595_DIN_PIN, GPIO_PIN_RESET)	

#define HC_595_RCK(a)	if (a)	\
										HAL_GPIO_WritePin(HC_595_RCK_PORT, HC_595_RCK_PIN, GPIO_PIN_SET); \
									else		\
										HAL_GPIO_WritePin(HC_595_RCK_PORT, HC_595_RCK_PIN, GPIO_PIN_RESET)	
									
#define HC_595_SCK(a)	if (a)	\
										HAL_GPIO_WritePin(HC_595_SCK_PORT, HC_595_SCK_PIN, GPIO_PIN_SET); \
									else		\
										HAL_GPIO_WritePin(HC_595_SCK_PORT, HC_595_SCK_PIN, GPIO_PIN_RESET)	

#define HC_595_SCLR(a)	if (a)	\
										HAL_GPIO_WritePin(HC_595_SCLR_PORT, HC_595_SCLR_PIN, GPIO_PIN_SET); \
									else		\
										HAL_GPIO_WritePin(HC_595_SCLR_PORT, HC_595_SCLR_PIN, GPIO_PIN_RESET)	
																		

// 函数声明
void HC_595_GPIO_Config(void);
void HC_595_Send_Byte(unsigned short Q15_Q0);
void HC595_Write_QX(unsigned short index,unsigned short sta);
									
#endif
cpp 复制代码
#include "hc595.h"

static unsigned short Q0_Q15_S = 0;

/*****************************************************************************************
*	函数名: HC595_Delay
*	入口参数: t - 延时时间,以时钟周期数为单位
*	返回值: 无
*	函数功能: 简单延时函数
*	说明: 为了移植的简便性且对延时精度要求不高,所以不需要使用定时器做延时
******************************************************************************************/
void HC595_Delay(unsigned int t)
{
	while(t--); // 简单的循环延时,延时时间由入口参数 t 决定
}


/*****************************************************************************************
*	函数名: HC_595_GPIO_Config
*	入口参数: 无
*	返回值: 无
*	函数功能: 初始化移位寄存器的 GPIO 口
*	说明: 配置数据输入引脚(DIN)、存储寄存器时钟引脚(RCK)、移位寄存器时钟引脚(SCK)和清除引脚(SCLR)为推挽输出模式,
*		 不带上下拉,速度配置为低速。然后将这些引脚初始化,并设置初始电平状态。
******************************************************************************************/
void HC_595_GPIO_Config(void)
{	
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	// 初始化IO口时钟
	HC_595_DIN_ENABLE;
	HC_595_RCK_ENABLE;
	HC_595_SCK_ENABLE;	
	HC_595_SCLR_ENABLE;	
	
	// 配置DIN引脚
	GPIO_InitStruct.Pin 			= HC_595_DIN_PIN;
	GPIO_InitStruct.Mode 			= GPIO_MODE_OUTPUT_PP;	// 推挽输出
	GPIO_InitStruct.Pull 			= GPIO_NOPULL;			// 不带上下拉
	GPIO_InitStruct.Speed 			= GPIO_SPEED_FREQ_LOW;	// 低速
	HAL_GPIO_Init(HC_595_DIN_PORT, &GPIO_InitStruct);

	// 配置RCK引脚
	GPIO_InitStruct.Pin 			= HC_595_RCK_PIN;
	HAL_GPIO_Init(HC_595_RCK_PORT, &GPIO_InitStruct);		

	// 配置SCK引脚
	GPIO_InitStruct.Pin			= HC_595_SCK_PIN;
	HAL_GPIO_Init(HC_595_SCK_PORT, &GPIO_InitStruct);				

	// 配置SCLR引脚
	GPIO_InitStruct.Pin 			= HC_595_SCLR_PIN;
	GPIO_InitStruct.Pull 			= GPIO_PULLUP;			// 上拉
	HAL_GPIO_Init(HC_595_SCLR_PORT, &GPIO_InitStruct);

	// 初始化引脚状态
	HAL_GPIO_WritePin(HC_595_RCK_PORT, HC_595_RCK_PIN, GPIO_PIN_RESET);	// RCK输出低电平
	HAL_GPIO_WritePin(HC_595_SCK_PORT, HC_595_SCK_PIN, GPIO_PIN_RESET);	// SCK输出低电平
	HAL_GPIO_WritePin(HC_595_SCLR_PORT, HC_595_SCLR_PIN, GPIO_PIN_SET);	// SCLR输出高电平
}


/*****************************************************************************************
*	函数名: HC_595_Send_Byte
*	入口参数: Q15_Q0 - 要发送的16位数据
*	返回值: 无
*	函数功能: 向移位寄存器发送一个16位数据
*	说明: 首先设置移位寄存器的控制引脚为初始状态,然后逐位发送数据,最后将数据加载到移位寄存器中。
******************************************************************************************/
void HC_595_Send_Byte(unsigned short Q15_Q0)
{
	// 设置移位寄存器的控制引脚初始状态
	HC_595_DIN(0);
	HC_595_RCK(0);
	HC_595_SCK(0);

	HC595_Delay(10);	
	
	for( int i = 0 ; i < 16 ; i ++ )
	{
		// 逐位发送数据
		HC_595_SCK(0);
		if( Q15_Q0 & 0x8000 )
		{
			HC_595_DIN(1);
		}
		else
		{
			HC_595_DIN(0);
		}
		HC595_Delay(1);		
		HC_595_SCK(1);		// 移位寄存器输入一个字节数据
		HC595_Delay(1);
		Q15_Q0 <<= 1;
	}
	
	// 将数据加载到移位寄存器中
	HC_595_RCK(1);			// 存储寄存器输入,Q1-Q15输出
	HC595_Delay(1);
	
	HC_595_RCK(0);
	HC_595_SCK(0);
}

/* w_gpio_sem */
static unsigned char w_gpio_sem = 0;

/*****************************************************************************************
*	函数名: HC595_Write_QX
*	入口参数: index - 输出引脚的编号,sta - 输出状态,1为高电平,0为低电平
*	返回值: 无
*	函数功能: 控制移位寄存器的输出引脚状态
*	说明: 根据输入的输出引脚编号和状态,更新移位寄存器中对应引脚的状态,并发送更新后的状态到移位寄存器。
******************************************************************************************/
void HC595_Write_QX(unsigned short index,unsigned short sta)
{
	// 检查是否有其他操作在进行
	if( w_gpio_sem )
	{
		return;
	}
	
	// 锁定
	w_gpio_sem = 1;
	
	// 检查输出引脚编号是否合法
	if( index > 16 )
	{
		return;
	}
	
	// 根据状态设置输出引脚状态
	if( sta )
	{
		Q0_Q15_S |= ( 1 << index );
	}
	else
	{
		Q0_Q15_S &=~ ( 1 << index );
	}
	
	// 更新移位寄存器中的输出状态
	HC_595_Send_Byte(Q0_Q15_S);
	
	// 解锁
	w_gpio_sem = 0;	
}
  1. HC_595_GPIO_Config: 初始化HC595芯片的GPIO口,包括数据输入引脚(DIN)、存储寄存器时钟引脚(RCK)、移位寄存器时钟引脚(SCK)和清除引脚(SCLR)。配置这些引脚为推挽输出模式,并设置初始电平状态。

  2. HC_595_Send_Byte: 向HC595芯片发送一个16位数据,通过移位寄存器将数据加载到芯片中,控制输出引脚的状态。

  3. HC595_Write_QX: 根据输入的输出引脚编号和状态,更新移位寄存器中对应引脚的状态,并通过 HC_595_Send_Byte 函数发送更新后的状态到移位寄存器。

  4. HC595_Delay: 简单的延时函数,用于产生一定时间的延时,以时钟周期数为单位。

注意

HC595的DIN输入端输出的数据会从Q0开始,继续输入,之前的Q0数据会移位至Q1,一直循环。

完整的输入一个字节数据,假设输入的是unsigned char Q_byte =(高)1011 1111(低) 。因为HC_595_Send_Byte程序中是从高位开始发送的,最高位的数据再经过完整的一个字节循环后就被push到了Q7(很像队列)

Q0 = 1;Q1=1;Q2=1;Q3=1;Q4=1;Q5=1;Q6=0;Q7=1;

我们要改变Q0-Q7中的某个位做法:

假设改变Q3位,则把 (高)1011 0111(低)重新写入HC595中。

Q_byte = Q_byte & ~ (1<<3) = 1011 1111 & 1111 0111 = 1011 0111;

这是一个595的情况,级联情况下同理,代码,处理的就是级联情况。所以应用HC595_Write_QX函数就可以操作Q0-Q15任意一个输出位的电平了。

相关推荐
清风66666640 分钟前
基于单片机与DAC0832的双路波形信号发生系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
azwsm2 小时前
电路元器件和GPIO控制器
单片机·嵌入式硬件
kebidaixu5 小时前
FreeRTOS 移植到 STM32F407VETX 记录(一)
stm32·单片机·嵌入式硬件
CSDN官方博客6 小时前
「谁说嵌入式只是调包和焊板子?」—— 2026嵌入式全栈技术征锋令
嵌入式硬件·物联网·embedding
点灯小铭6 小时前
基于单片机的数码管定时插座设计与定时开关功能实现
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
云栖梦泽6 小时前
玩转RK3506SDK
linux·嵌入式硬件
数智工坊8 小时前
机器人四大主控板系统分层选型指南:树莓派、ESP32、STM32与Arduino的能力边界与实战定位
stm32·嵌入式硬件·机器人
进击的小头9 小时前
第8篇:IGBT 从零到精通:核心原理、关键参数、选型指南与工业级应用要点
经验分享·嵌入式硬件·学习
点灯小铭9 小时前
基于单片机的多模式智能洗衣机设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
项目題供诗9 小时前
STM32-AD单通道&AD多通道(十九)
stm32·单片机·嵌入式硬件