当前位置: 首页 > news >正文

用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通常<1MHz
CPU占用率
引脚灵活性固定任意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 GPIOA

2.2 CubeMX GPIO配置步骤

  1. 在Pinout视图中选择要使用的GPIO引脚
  2. 将PL、CP引脚配置为GPIO_Output
  3. 将DS引脚配置为GPIO_Input
  4. 生成代码前确保时钟配置正确

提示:建议为PL和CP引脚添加适当的上拉电阻,提高信号稳定性

3. 软件SPI时序实现

3.1 74HC165工作时序分析

74HC165的数据读取分为两个阶段:

  1. 并行加载阶段:PL拉低→锁存当前输入状态→PL恢复高电平
  2. 串行移位阶段:每个CP上升沿输出一位数据(MSB优先)

典型时序参数:

  • PL脉冲宽度:最小20ns
  • CP高/低电平时间:各需至少25ns
  • DS建立/保持时间:各需至少10ns

3.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 i=0; i<8; 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 性能优化技巧

  1. 延时优化:用__NOP()替代HAL_Delay实现纳秒级延时
  2. 端口操作:直接操作寄存器提升速度(如GPIOA->BSRR
  3. 批量读取:连续读取多个字节时保持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; // PL=0 HC165_DELAY(); HC165_PL_PORT->BSRR = HC165_PL_PIN; // PL=1 for(uint8_t i=0; i<8; i++) { value <<= 1; if(HC165_DS_PORT->IDR & HC165_DS_PIN) { value |= 0x01; } HC165_CP_PORT->BSRR = HC165_CP_PIN; // CP=1 HC165_DELAY(); HC165_CP_PORT->BRR = HC165_CP_PIN; // CP=0 } 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 i=0; i<8; i++) { if(key_pressed & (1<<i)) { printf("Key %d pressed\n", i+1); } } } prev_keys = current_keys; HAL_Delay(10); // 10ms扫描间隔 }

4.3 实际应用注意事项

  1. 电源滤波:在74HC165的VCC和GND之间添加0.1μF去耦电容
  2. 信号质量:长距离连接时考虑添加串联电阻(如100Ω)
  3. 多片级联:通过Q7引脚连接下一片的DS实现扩展
  4. 抗干扰:未使用的并行输入引脚应接地或接VCC

级联读取示例(两片74HC165):

uint16_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 i=0; i<16; 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%。

http://www.jsqmd.com/news/965295/

相关文章:

  • 创始人IP标准体系白皮书-第11卷·危机篇:创始人IP资产熔断、信用捍卫与反脆弱性标准
  • 别再纠结了!Buck电路输入电容到底放芯片旁边还是电感旁边?两种Layout方案实战对比与选择建议
  • 告别位置漂移:手把手教你用TI C2000的CLB模块搞定BISS编码器线路延迟补偿
  • 树莓派蜂鸣器选型避坑指南:有源vs无源,你的项目到底该用哪个?
  • VMware macOS 解锁神器:在Windows和Linux上轻松运行苹果系统
  • 用Vivado和Verilog手把手教你做DDS信号发生器(附完整代码与仿真避坑指南)
  • Windows 10下用VS2019编译FreeCAD 0.19.1源码,我踩过的坑都帮你填好了
  • 手把手教你配置Roundcube密码插件:从postfixadmin加密方式到doveadm命令的完整流程
  • SAP开发者必备:如何用BAPI_INCOMINGINVOICE_PARK批量预制采购发票(附完整代码与避坑点)
  • 影刀RPA教程:从零开发1688店群全自动铺货系统,一个人管理500个店铺的架构复盘
  • 创始人IP标准体系白皮书-第12卷·数智篇:创始人IP语料资产、智能参数评估与数字智能生态信源标准
  • 超越传统压缩:用GAP-TV算法在MATLAB里玩转视频“超低采样”重建
  • 别再手动管理了!用这个Shell脚本一键启停你的Django项目(附Nginx+uWSGI配置)
  • 避开这个坑!用Altium Designer快速检查DCDC电源SW节点寄生电容的3个技巧
  • 物理内存防御重器:基于 C/C++ 内存泄露与越界写堆栈排查及 Valgrind 逆向定位实战
  • 从‘死锁’到‘线程池满’,Visual VM线程分析保姆级教程(含Dump文件解读指南)
  • 天赐范式第65天:因陆续又回忆起目击国家一级宝鸟——东方白鹳头上的黑色辫子等细节——追加双阳水库东方白鹳群体观察完整版
  • DCDC布局实战:开关节点SW铺铜面积到底多大才合适?一个视频讲透EMI共模辐射
  • CAC/IEEE会议投稿查重怎么办?Turnitin国际版实测与降重心得
  • 告别有线束缚:用USR-VCOM虚拟串口+ESP32,实现无线MicroPython调试(附Thonny配置)
  • 别再为字库芯片GT20L16S1Y的竖置横排数据发愁了,手把手教你搞定LCD显示(附完整代码)
  • 手把手教你用Java SDK搞定农行H5电子账户开户(附完整代码与避坑点)
  • Conda虚拟环境创建报错InvalidArchiveError?别急着重装,试试这个权限修复命令
  • 告别功耗焦虑:详解5G NR中BWP设计如何为你的手机省电
  • 告别依赖地狱!用AppImage在Ubuntu 22.04上安装最新版Neovim(附FUSE问题解决)
  • 终极机械键盘连击修复指南:KeyboardChatterBlocker完全教程
  • 魔兽争霸3在Win10/Win11卡顿闪退?3个步骤让老游戏重获新生!
  • 树莓派蜂鸣器避坑指南:有源无源怎么选?GPIO驱动电路详解
  • 移动端 Retina 视网膜屏幕渲染调优:基于 CSS 物理像素对齐(0.5px)与 Canvas 逻辑分辨率缩放防模糊实战
  • PHP反序列化漏洞实战:从一道BUUCTF题看__wakeup绕过的那些坑(含payload构造详解)