用STM32F103的GPIO口驱动74HC165扩展16个按键,附完整代码和接线图

发布时间:2026/6/5 3:54:58

用STM32F103的GPIO口驱动74HC165扩展16个按键,附完整代码和接线图 STM32F103通过74HC165实现16键高效扫描方案从硬件设计到软件防抖全解析在嵌入式开发中按键输入是最基础的人机交互方式之一。当项目需要接入16个独立按键时若直接使用GPIO口连接不仅会耗尽宝贵的引脚资源还会增加电路复杂度和成本。本文将详细介绍如何利用一片售价不足1元的74HC165移位寄存器仅占用3个GPIO口即可实现16个按键的稳定读取。1. 硬件设计从芯片选型到电路优化1.1 74HC165核心特性解析74HC165是一款8位并行输入/串行输出移位寄存器关键参数如下参数典型值说明工作电压2V-6V完美兼容STM32的3.3V电平时钟频率25MHz(max)远超手动按键扫描需求输入电流±1μA几乎不增加系统功耗级联支持是多片串联可扩展更多输入实际应用中发现在3.3V供电时芯片静态功耗仅0.2μA特别适合电池供电场景。1.2 两级级联电路设计要点典型的两片74HC165级联电路需要关注以下关键点引脚连接第一片的Q7接第二片的SER两片的CLK、CLK_INH并联共用LOAD(PL)信号上拉电阻选择// 推荐使用10kΩ上拉电阻 #define PULL_UP_RESISTOR 10000 // 单位欧姆抗干扰设计每个按键并联104瓷片电容靠近芯片放置0.1μF去耦电容信号线长度超过10cm时加串100Ω电阻注意避免将PL信号与高速通信线如SPI共用可能引起信号冲突2. 软件驱动高效状态采集方案2.1 初始化配置最佳实践void HC165_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 使能端口时钟以PB为例 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置PL(LOAD)为推挽输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); // 配置CLK为推挽输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_1; GPIO_Init(GPIOB, GPIO_InitStruct); // 配置DATA为输入带上拉 GPIO_InitStruct.GPIO_Pin GPIO_Pin_2; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, GPIO_InitStruct); // 初始状态设置 GPIO_SetBits(GPIOB, GPIO_Pin_0); // PL1 GPIO_ResetBits(GPIOB, GPIO_Pin_1); // CLK0 }2.2 带硬件消抖的读取算法uint16_t HC165_ReadKeys(void) { uint16_t keys 0; // 加载并行数据 GPIO_ResetBits(GPIOB, GPIO_Pin_0); // PL0 delay_us(5); // 保持时间≥tPLH(典型值30ns) GPIO_SetBits(GPIOB, GPIO_Pin_0); // PL1 // 逐位移出数据 for(uint8_t i0; i16; i) { keys 1; if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)) { keys | 0x01; } // 产生时钟上升沿 GPIO_SetBits(GPIOB, GPIO_Pin_1); // CLK1 delay_us(1); // 保持时间≥tPHL(典型值20ns) GPIO_ResetBits(GPIOB, GPIO_Pin_1); // CLK0 } return keys; }性能优化技巧将delay_us替换为NOP空操作在72MHz主频下约14ns使用DMA定时器实现自动扫描采用状态机实现非阻塞式读取3. 高级应用状态检测与事件处理3.1 基于时间窗口的消抖算法typedef struct { uint16_t current; uint16_t last; uint32_t timestamp[16]; uint8_t state[16]; // 0释放,1按下,2保持 } KeyStatus; void UpdateKeyStatus(KeyStatus* ks) { static uint32_t tick 0; uint16_t diff ks-current ^ ks-last; for(uint8_t i0; i16; i) { if(diff (1i)) { ks-timestamp[i] tick; } else if((tick - ks-timestamp[i]) DEBOUNCE_MS) { if(ks-current (1i)) { ks-state[i] (ks-state[i]0) ? 1 : 2; } else { ks-state[i] 0; } } } ks-last ks-current; tick; }3.2 事件触发机制实现#define KEY_EVENT_PRESS 0x01 #define KEY_EVENT_RELEASE 0x02 #define KEY_EVENT_HOLD 0x04 uint8_t GetKeyEvents(KeyStatus* ks, uint8_t key_num) { if(key_num 16) return 0; uint8_t event 0; switch(ks-state[key_num]) { case 1: event KEY_EVENT_PRESS; break; case 0: if(ks-last (1key_num)) event KEY_EVENT_RELEASE; break; default: if((HAL_GetTick() - ks-timestamp[key_num]) HOLD_THRESHOLD) event KEY_EVENT_HOLD; } return event; }4. 工程实践从原型到量产优化4.1 常见问题排查指南现象可能原因解决方案读取数据全为1DATA引脚未正确上拉检查硬件电路确保10kΩ上拉高位数据异常级联时序不满足增加PL保持时间至1μs以上随机误触发电源噪声干扰添加0.1μF去耦电容按键响应延迟消抖时间设置过长调整为10-20ms4.2 低功耗设计策略动态扫描机制void Enter_LowPowerMode(void) { GPIO_SetBits(GPIOB, GPIO_Pin_0); // PL1 GPIO_ResetBits(GPIOB, GPIO_Pin_1); // CLK0 // 配置GPIO为模拟输入模式降低功耗 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.GPIO_Pin GPIO_Pin_2; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOB, GPIO_InitStruct); }唤醒检测方案使用EXTI中断检测首个按键按下通过定时器唤醒周期检测如100ms硬件上增加按键唤醒电路在实际智能门锁项目中采用这种方案使待机电流从5mA降至80μA。

相关新闻