用STM32F030的普通IO口驱动74HC165扩展8个按键(软件SPI时序详解) 用STM32F030普通IO实现74HC165按键扩展软件SPI全解析在嵌入式开发中按键扩展是常见需求。当硬件SPI资源紧张或需要灵活配置时软件模拟SPI成为实用解决方案。本文将深入讲解如何利用STM32F030的普通GPIO口通过74HC165芯片扩展8个独立按键并详细解析软件SPI的时序控制原理。1. 硬件基础与原理分析74HC165是一款8位并行输入/串行输出移位寄存器特别适合按键扩展场景。其核心功能是将8个并行输入信号转换为串行数据输出仅需3根控制线即可完成数据采集。1.1 74HC165关键引脚功能引脚名称功能描述典型连接方式PL并行加载(低电平有效)STM32任意GPIOCP时钟输入STM32任意GPIOQH串行数据输出STM32任意GPIOCE芯片使能(低电平有效)通常直接接地D0-D7并行数据输入连接按键矩阵1.2 工作时序解析74HC165的工作分为两个阶段并行加载阶段PL引脚拉低时芯片会立即锁存D0-D7的当前状态到内部寄存器串行移位阶段PL引脚恢复高电平后每个CP时钟上升沿会将数据从QH引脚依次移出注意时钟信号的电平变化速度必须满足芯片的最小脉冲宽度要求(74HC165典型值为20ns)2. 软件SPI驱动设计与硬件SPI不同软件SPI需要手动控制所有时序信号。下面以STM32F030为例展示完整的驱动实现。2.1 GPIO配置首先在CubeMX中配置三个GPIO引脚// 引脚定义(根据实际连接修改) #define HC165_PL_PIN GPIO_PIN_4 #define HC165_PL_PORT GPIOA #define HC165_CLK_PIN GPIO_PIN_3 #define HC165_CLK_PORT GPIOB #define HC165_DATA_PIN GPIO_PIN_6 #define HC165_DATA_PORT GPIOA初始化代码应设置PL和CLK为输出模式DATA为输入模式GPIO_InitTypeDef GPIO_InitStruct {0}; // PL引脚配置 GPIO_InitStruct.Pin HC165_PL_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(HC165_PL_PORT, GPIO_InitStruct); // CLK引脚配置(同上) // DATA引脚配置为输入 GPIO_InitStruct.Pin HC165_DATA_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; // 推荐启用上拉 HAL_GPIO_Init(HC165_DATA_PORT, GPIO_InitStruct);2.2 核心读取函数实现完整的74HC165数据读取函数如下uint8_t HC165_ReadByte(void) { uint8_t value 0; // 1. 并行加载阶段 HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_RESET); delay_us(1); // 保持PL低电平至少20ns HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_SET); // 2. 串行移位阶段 for(uint8_t i 0; i 8; i) { value 1; // 左移一位 if(HAL_GPIO_ReadPin(HC165_DATA_PORT, HC165_DATA_PIN) GPIO_PIN_SET) { value | 0x01; // 读取当前数据位 } // 产生时钟上升沿 HAL_GPIO_WritePin(HC165_CLK_PORT, HC165_CLK_PIN, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(HC165_CLK_PORT, HC165_CLK_PIN, GPIO_PIN_RESET); delay_us(1); } return value; }2.3 关键时序控制要点PL脉冲宽度至少20ns(实际代码中1us已足够)时钟频率软件SPI通常控制在100kHz以内数据采样点应在时钟上升沿之前稳定提示delay_us()函数可通过SysTick或定时器实现对于STM32F0301us延时足够满足74HC165的时序要求3. 多级级联与抗干扰设计当需要扩展更多按键时可以级联多片74HC165。3.1 两级74HC165级联连接[74HC165(1)] QH → [74HC165(2)] SER 共用CLK和PL信号读取16位数据的代码调整uint16_t HC165_ReadDoubleByte(void) { uint16_t value 0; // 并行加载 HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_RESET); delay_us(1); HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_SET); // 读取16位数据 for(uint8_t i 0; i 16; i) { value 1; if(HAL_GPIO_ReadPin(HC165_DATA_PORT, HC165_DATA_PIN) GPIO_PIN_SET) { value | 0x0001; } HAL_GPIO_WritePin(HC165_CLK_PORT, HC165_CLK_PIN, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(HC165_CLK_PORT, HC165_CLK_PIN, GPIO_PIN_RESET); delay_us(1); } return value; }3.2 硬件抗干扰措施在PL和CLK信号线上添加100Ω电阻在74HC165的VCC和GND之间放置0.1μF去耦电容长距离连接时考虑使用74HC245等缓冲芯片4. 实际应用与性能优化4.1 按键消抖处理软件SPI读取后应添加消抖逻辑#define DEBOUNCE_TIME 20 // 消抖时间(ms) uint8_t last_key 0; uint32_t last_time 0; uint8_t GetDebouncedKey(void) { uint8_t current HC165_ReadByte(); uint32_t now HAL_GetTick(); if(current ! last_key) { last_time now; last_key current; return 0; // 按键状态变化返回无按键 } if((now - last_time) DEBOUNCE_TIME) { return current; // 稳定后的按键状态 } return 0; }4.2 中断驱动优化为避免频繁轮询可配置外部中断检测按键变化将74HC165的QH引脚连接到STM32的外部中断引脚配置下降沿触发中断在中断服务程序中读取按键状态// 中断回调函数示例 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin HC165_DATA_PIN) { uint8_t key GetDebouncedKey(); if(key ! 0) { // 处理按键事件 } } }4.3 性能对比测试在STM32F03048MHz下的实测数据读取方式单次读取时间最大理论频率硬件SPI8μs1MHz软件SPI(本文)24μs40kHz虽然软件SPI速度较慢但对于按键扫描等低频应用完全足够。实际项目中我发现将CLK延时缩短到500ns仍能稳定工作可将读取时间减少到12μs。