用STM32F030的普通IO口驱动74HC165扩展8路按键(软件SPI保姆级教程)

发布时间:2026/6/20 19:22:07
用STM32F030的普通IO口驱动74HC165扩展8路按键(软件SPI保姆级教程) 用STM32F030普通IO口实现74HC165按键扩展软件SPI实战指南在嵌入式开发中经常会遇到GPIO资源紧张的情况。当硬件SPI接口被其他外设占用时如何用普通IO口实现SPI功能就成了一项必备技能。本文将带你从零开始用STM32F030的任意GPIO模拟SPI时序驱动74HC165扩展8路按键输入。1. 硬件SPI与软件SPI的抉择硬件SPI通常被认为是高效、稳定的首选方案但在某些场景下软件模拟SPI反而更具优势引脚灵活性不受硬件SPI固定引脚限制可任意选择空闲GPIO时序可控可根据实际需求调整时钟频率和采样边沿资源节省在简单应用中避免占用专用SPI外设学习价值深入理解SPI底层通信机制74HC165作为经典的并行输入转串行输出芯片其工作电压范围宽2V-6V与3.3V的STM32F030完美兼容。以下是两种实现方式的对比特性硬件SPI软件SPI时钟频率最高18MHz通常1MHzCPU占用率低高引脚灵活性固定任意GPIO开发复杂度低中等适用场景高速数据传输低速、简单外设2. 硬件连接与CubeMX配置2.1 74HC165引脚说明74HC165的关键引脚功能如下PL(Parallel Load)低电平时锁存并行输入数据CP(Clock Pulse)时钟输入上升沿触发数据移位DS(Serial Data)串行数据输出CE(Clock Enable)低电平使能时钟输入通常接地推荐连接方式/* STM32F030与74HC165连接示例 */ #define HC165_PL_PIN GPIO_PIN_4 // PA4 #define HC165_PL_PORT GPIOA #define HC165_CP_PIN GPIO_PIN_3 // PB3 #define HC165_CP_PORT GPIOB #define HC165_DS_PIN GPIO_PIN_6 // PA6 #define HC165_DS_PORT GPIOA2.2 CubeMX GPIO配置步骤在Pinout视图中选择要使用的GPIO引脚将PL、CP引脚配置为GPIO_Output将DS引脚配置为GPIO_Input生成代码前确保时钟配置正确提示建议为PL和CP引脚添加适当的上拉电阻提高信号稳定性3. 软件SPI时序实现3.1 74HC165工作时序分析74HC165的数据读取分为两个阶段并行加载阶段PL拉低→锁存当前输入状态→PL恢复高电平串行移位阶段每个CP上升沿输出一位数据MSB优先典型时序参数PL脉冲宽度最小20nsCP高/低电平时间各需至少25nsDS建立/保持时间各需至少10ns3.2 核心读取函数实现uint8_t HC165_ReadByte(void) { uint8_t value 0; // 并行加载阶段 HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_RESET); HAL_Delay(1); // 保持PL低电平至少1μs HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_SET); // 串行移位阶段 for(uint8_t i0; i8; i) { value 1; if(HAL_GPIO_ReadPin(HC165_DS_PORT, HC165_DS_PIN) GPIO_PIN_SET) { value | 0x01; } // 产生时钟上升沿 HAL_GPIO_WritePin(HC165_CP_PORT, HC165_CP_PIN, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(HC165_CP_PORT, HC165_CP_PIN, GPIO_PIN_RESET); } return value; }3.3 性能优化技巧延时优化用__NOP()替代HAL_Delay实现纳秒级延时端口操作直接操作寄存器提升速度如GPIOA-BSRR批量读取连续读取多个字节时保持PL高电平优化后的读取示例#define HC165_DELAY() do { __NOP(); __NOP(); __NOP(); } while(0) uint8_t HC165_ReadByte_Fast(void) { uint8_t value 0; HC165_PL_PORT-BRR HC165_PL_PIN; // PL0 HC165_DELAY(); HC165_PL_PORT-BSRR HC165_PL_PIN; // PL1 for(uint8_t i0; i8; i) { value 1; if(HC165_DS_PORT-IDR HC165_DS_PIN) { value | 0x01; } HC165_CP_PORT-BSRR HC165_CP_PIN; // CP1 HC165_DELAY(); HC165_CP_PORT-BRR HC165_CP_PIN; // CP0 } return value; }4. 按键处理与实战应用4.1 按键去抖动实现机械按键通常需要10-20ms的去抖动时间。以下是一个简单的状态机实现typedef struct { uint8_t current_state; uint8_t last_state; uint8_t stable_state; uint32_t last_change_time; } KeyState; void Key_Update(KeyState* key, uint8_t new_state, uint32_t current_time) { key-last_state key-current_state; key-current_state new_state; if(key-current_state ! key-last_state) { key-last_change_time current_time; } if((current_time - key-last_change_time) 20) { // 20ms去抖 key-stable_state key-current_state; } }4.2 多路按键状态监测当需要监测多个按键状态变化时可以采用位操作uint8_t prev_keys 0xFF; uint8_t current_keys 0; while(1) { current_keys HC165_ReadByte(); // 检测按键按下下降沿 uint8_t key_pressed (prev_keys ^ current_keys) ~current_keys; // 检测按键释放上升沿 uint8_t key_released (prev_keys ^ current_keys) prev_keys; if(key_pressed ! 0) { // 处理按键按下事件 for(uint8_t i0; i8; i) { if(key_pressed (1i)) { printf(Key %d pressed\n, i1); } } } prev_keys current_keys; HAL_Delay(10); // 10ms扫描间隔 }4.3 实际应用注意事项电源滤波在74HC165的VCC和GND之间添加0.1μF去耦电容信号质量长距离连接时考虑添加串联电阻如100Ω多片级联通过Q7引脚连接下一片的DS实现扩展抗干扰未使用的并行输入引脚应接地或接VCC级联读取示例两片74HC165uint16_t HC165_Read2Bytes(void) { uint16_t value 0; // 并行加载 HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_SET); // 读取16位数据 for(uint8_t i0; i16; i) { value 1; if(HAL_GPIO_ReadPin(HC165_DS_PORT, HC165_DS_PIN) GPIO_PIN_SET) { value | 0x0001; } HAL_GPIO_WritePin(HC165_CP_PORT, HC165_CP_PIN, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(HC165_CP_PORT, HC165_CP_PIN, GPIO_PIN_RESET); } return value; }在最近的一个智能家居面板项目中我们使用STM32F030的3个普通IO口驱动4片级联的74HC165实现了32个按键的可靠检测。实际测试表明即使采用软件SPI方式在1ms的扫描间隔下CPU占用率也不足5%。