用STM32F103的GPIO口驱动74HC165扩展16个按键,附完整代码和接线图
2026/6/5 3:54:53 网站建设 项目流程

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级联电路需要关注以下关键点:

  1. 引脚连接

    • 第一片的Q7接第二片的SER
    • 两片的CLK、CLK_INH并联
    • 共用LOAD(PL)信号
  2. 上拉电阻选择

// 推荐使用10kΩ上拉电阻 #define PULL_UP_RESISTOR 10000 // 单位:欧姆
  1. 抗干扰设计
    • 每个按键并联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); // PL=1 GPIO_ResetBits(GPIOB, GPIO_Pin_1); // CLK=0 }

2.2 带硬件消抖的读取算法

uint16_t HC165_ReadKeys(void) { uint16_t keys = 0; // 加载并行数据 GPIO_ResetBits(GPIOB, GPIO_Pin_0); // PL=0 delay_us(5); // 保持时间≥tPLH(典型值30ns) GPIO_SetBits(GPIOB, GPIO_Pin_0); // PL=1 // 逐位移出数据 for(uint8_t i=0; i<16; i++) { keys <<= 1; if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)) { keys |= 0x01; } // 产生时钟上升沿 GPIO_SetBits(GPIOB, GPIO_Pin_1); // CLK=1 delay_us(1); // 保持时间≥tPHL(典型值20ns) GPIO_ResetBits(GPIOB, GPIO_Pin_1); // CLK=0 } 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 i=0; i<16; i++) { if(diff & (1<<i)) { ks->timestamp[i] = tick; } else if((tick - ks->timestamp[i]) > DEBOUNCE_MS) { if(ks->current & (1<<i)) { 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 & (1<<key_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-20ms

4.2 低功耗设计策略

  1. 动态扫描机制
void Enter_LowPowerMode(void) { GPIO_SetBits(GPIOB, GPIO_Pin_0); // PL=1 GPIO_ResetBits(GPIOB, GPIO_Pin_1); // CLK=0 // 配置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); }
  1. 唤醒检测方案
    • 使用EXTI中断检测首个按键按下
    • 通过定时器唤醒周期检测(如100ms)
    • 硬件上增加按键唤醒电路

在实际智能门锁项目中,采用这种方案使待机电流从5mA降至80μA。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询