74HC165三线驱动和四线驱动及HAL库示例
本文介绍了74HC165移位寄存器的使用指南。首先讲解了引脚定义:CE(片选)、PL(并行加载)、CLK(时钟)和Q7(串行输出)的功能。接着详细说明四线和三线驱动方法,包括级联连接方式。然后提供了一个兼容三线/四线、支持多片级联的C语言驱动程序,包含初始化函数和数据采集函数。最后通过实测对比了两种数据采集方法的执行时间(约58.9μs),推荐根据默认电平状态选择更高效的位操作方式。文中还附带了关
网上没看到一眼能看懂的把所有东西一次讲清的文章,总结了一下,做个笔记留着。
1.引脚定义
74CH165一共有四根线需要接,三根控制,一根输出。
CE:Chip enable,低电平有效,三线控制可以直接接地。
PL:Parallel load,拉低会把并口8位采集到寄存器,并把最高位通过Q7输出,把PL拉高后,CLK给上升沿Q7会一个个移位输出次高位。
CLK/CP:Clock,PL拉高时,上升沿会触发移位寄存器移位。
Q7/DATA:串行数据输出脚。
2.驱动方法
四线驱动方法一般是:
把PL拉低,再把PL拉高,获取输入,拉低CE,此时输出最高位,采集最高位,然后CLK的上升沿会触发移位,逐个采集信号。
三线驱动方法:
CE默认拉低,所以PL拉低后,Q7输出最高位,然后PL拉高,CLK上升沿触发移位,需要在PL拉低和第一个上升沿之间采集最高位,每个上升沿之后采集后面的数据。
三线驱动,两个芯片级联,触发第一个芯片的最高位,采集波形如下:
74CH165可以连接很多个,只需要把下一个的Q7连接到到前一个DS上,PL,CLK接一起,数据是依次输出第一个,第二个…
3.示例程序
写了一个基于HAL库的兼容三线四线容易移植的程序,兼容多个芯片,容易移植。
头文件如下:
/*移植需要改的地方只有GPIO,CHIP_NUM/即有几片级联,如有CE引脚,则取消CE引脚注释*/
/*使用示例
*uint8_t data[CHIP_NUM]={0};
*Init_74HC165();
*Data_capture(data);
*/
#ifndef _74HC165_driver
#define _74HC165_driver
#include "sys.h"
#define PIN_CP_Pin GPIO_PIN_14
#define PIN_CP_GPIO_Port GPIOC
#define PIN_PL_Pin GPIO_PIN_15
#define PIN_PL_GPIO_Port GPIOC
#define PIN_Q7_Pin GPIO_PIN_13
#define PIN_Q7_GPIO_Port GPIOC
//#define CE_ENABLE//定义是否有CE脚
#ifdef CE_ENABLE
#define PIN_CE_Pin GPIO_PIN_12
#define PIN_CE_GPIO_Port GPIOC
#endif
#define __HAL_RCC_165PORT_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define CHIP_NUM 2
void Init_74HC165(void);
void Data_capture(uint8_t *data);
#endif
驱动函数文件如下:
#include "./74HC165/74HC165.h"
void Init_74HC165(){
__HAL_RCC_165PORT_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure ITPUT PIN*/
GPIO_InitStruct.Pin = PIN_Q7_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(PIN_Q7_GPIO_Port, &GPIO_InitStruct);
/*Configure OUTPUT PIN */
GPIO_InitStruct.Pin = PIN_CP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(PIN_CP_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = PIN_PL_Pin;
HAL_GPIO_Init(PIN_PL_GPIO_Port, &GPIO_InitStruct);
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(PIN_PL_GPIO_Port, PIN_PL_Pin, GPIO_PIN_SET);
#ifdef CE_ENABLE
GPIO_InitStruct.Pin = PIN_CE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(PIN_CE_GPIO_Port, &GPIO_InitStruct);
HAL_GPIO_WritePin(PIN_CE_GPIO_Port, PIN_CE_Pin, GPIO_PIN_SET);
#endif
}
void Data_capture(uint8_t *data){
#ifdef CE_ENABLE
HAL_GPIO_WritePin(PIN_CE_GPIO_Port, PIN_CE_Pin, GPIO_PIN_RESET);
#endif
int i,j = 0;
for(i=0;i<CHIP_NUM;i++)
{
data[i]=0xFF;
}
HAL_GPIO_WritePin(PIN_PL_GPIO_Port, PIN_PL_Pin, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(PIN_Q7_GPIO_Port,PIN_Q7_Pin)==GPIO_PIN_RESET)
{
data[0] &= (~(1<<7));
}
HAL_GPIO_WritePin(PIN_PL_GPIO_Port, PIN_PL_Pin, GPIO_PIN_SET);
for(j=6;j>=0;j--)
{
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(PIN_Q7_GPIO_Port,PIN_Q7_Pin)==GPIO_PIN_RESET)
{
data[0] &= (~(1<<j));
}
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_RESET);
}
for(i=1;i<CHIP_NUM;i++)
{
for(j=7;j>=0;j--){
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(PIN_Q7_GPIO_Port,PIN_Q7_Pin)==GPIO_PIN_RESET)
{
data[i] &= (~(1<<j));
}
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_RESET);
}
}
#ifdef CE_ENABLE
HAL_GPIO_WritePin(PIN_CE_GPIO_Port, PIN_CE_Pin, GPIO_PIN_SET);
#endif
}
4.使用方法
最简单的是注释里面的
uint8_t data[CHIP_NUM]={0};
Init_74HC165();
Data_capture(data);
如果需要把状态分开放可以使用联合体:
typedef union {
uint8_t raw_data;
struct {
uint8_t b0 : 1;
uint8_t b1 : 1;
uint8_t b2 : 1;
uint8_t b3 : 1;
uint8_t b4 : 1;
uint8_t b5 : 1;
uint8_t b6 : 1;
uint8_t b7 : 1;
} bits;
} BoolPack;//b0是最低位,b7是最高位,不同MCU情况可能不同,自行判断
BoolPack sample=data[0];
//此时可以使用sample.bits.b0调用最低位,需要的话自行定义数组循环赋值
5.datasheet
datasheet关键参数如下,我用的F103,输出速度为HIGH,打不到频率和时延的极限,使用其他芯片或者级联走线过长产生过大寄生电容需要自行斟酌添加时延。
6.写下一点点时间优化操作。
6.1 for循环代码丑可能更好用,用memset代码好看可能占空间大且效率低,因为需要调用库函数,库是为了方便调用,使用方便,两种都可以用,只用一次的话可以写丑一点
目前程序使用的是for循环赋值
for(i=0;i<CHIP_NUM;i++)
{
data[i]=0xFF;
}
编译大小如下
运行时间如下,为1.5us

当然可以使用memset,需要添加头文件string.h
#include <string.h>
memset(data,0xFF,CHIP_NUM);
编译大小如下,Code增加78,RO-data增加2
运行时间如下,为4us

6.2 编程与电路实际情况结合,用尽量少的执行步数来完成任务,再看下寄存器编程有多少优势
参照网友写的程序是默认数组全0,若检测到1,则执行或与操作置1,程序大概如下:
for(i=0;i<CHIP_NUM;i++)
{
data[i]=0x00;
}
for(j=7;j>=0;j--)
{
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(PIN_Q7_GPIO_Port,PIN_Q7_Pin)==GPIO_PIN_SET)
{
data[i] |= (1<<j);
}
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_RESET);
}
问题是我的电路默认为1,所以从头到尾都需要执行if里面的操作,看下运行时间为63.3us

修改一下,默认赋值为1,检测到按键触发才赋值为0,按键不触发时只执行轮询检测,不执行赋值
for(i=0;i<CHIP_NUM;i++)
{
data[i]=0xFF;
}
for(j=7;j>=0;j--)
{
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(PIN_Q7_GPIO_Port,PIN_Q7_Pin)==GPIO_PIN_RESET)
{
data[i] &= (~(1<<j));
}
HAL_GPIO_WritePin(PIN_CP_GPIO_Port, PIN_CP_Pin, GPIO_PIN_RESET);
}


运行时间为58.1us,好像优化了,又好像没优化
一个是置0,然后或置1,一个是置1,通过与置0,好像没啥用。
掏出唯一真神寄存器编程来试试看
for(j=7;j>=0;j--){
GPIOA->BSRR = PIN_CP_Pin;
if(PIN_Q7_GPIO_Port->IDR & PIN_Q7_Pin)
{
data[i] &= (~(1<<j));
}
GPIOA->BRR = PIN_CP_Pin;
}
时间降到23.1us,结论是真时间敏感的话建议寄存器编程(手动狗头)

有问题随时说我随时改。
更多推荐



所有评论(0)