74HC165级联实战:用STM32F030软件SPI实现16路输入扩展
在嵌入式开发中,按键输入扩展是常见需求。当GPIO资源紧张时,74HC165这类并行输入串行输出(PISO)移位寄存器就成了经济高效的解决方案。本文将深入探讨如何通过级联两片74HC165实现16路输入扩展,并针对STM32F030的软件SPI实现提供完整方案。
1. 74HC165级联原理与硬件设计
74HC165的核心功能是将8位并行数据转换为串行输出。级联多片芯片时,前一片的串行输出(QH)连接到后一片的串行输入(SER),形成数据链。时钟(CLK)和锁存(PL)信号并联连接,确保同步操作。
1.1 典型级联电路设计
对于两片74HC165级联,推荐以下连接方式:
| 信号线 | 连接方式 |
|---|---|
| CLK(时钟) | 并联到所有芯片的CP引脚 |
| PL(锁存) | 并联到所有芯片的PL引脚 |
| 第一片QH | 连接到第二片SER |
| 第二片QH | 连接到MCU的MISO引脚 |
| CE(使能) | 所有芯片接地(常使能) |
关键点:级联时,数据从最后一片芯片输出。在读取时,先移出的是最后一片的数据,最后移出的是第一片的数据。
1.2 STM32F030引脚配置
使用软件SPI时,需要配置三个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 GPIOA2. 软件SPI驱动实现
软件SPI相比硬件SPI的优势在于时序完全可控,特别适合74HC165这类对时序有特殊要求的器件。
2.1 基本读写时序
74HC165的标准操作流程:
- 拉低PL引脚,锁存并行输入
- 拉高PL引脚,准备移位
- 在CLK上升沿移位数据
- 循环8×N次(N为芯片数量)
void HC165_Init(void) { // 初始化所有引脚为输出模式(除DATA) GPIO_InitTypeDef GPIO_InitStruct = {0}; // CLK和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); GPIO_InitStruct.Pin = HC165_CLK_PIN; HAL_GPIO_Init(HC165_CLK_PORT, &GPIO_InitStruct); // DATA配置为输入 GPIO_InitStruct.Pin = HC165_DATA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(HC165_DATA_PORT, &GPIO_InitStruct); // 初始状态 HAL_GPIO_WritePin(HC165_CLK_PORT, HC165_CLK_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_SET); }2.2 16位数据读取函数
对于两片级联(16位输入),读取函数需要移位16次:
uint16_t HC165_Read16Bits(void) { uint16_t data = 0; // 锁存并行数据 HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_RESET); delay_us(1); // tsu(PL)最小保持时间 HAL_GPIO_WritePin(HC165_PL_PORT, HC165_PL_PIN, GPIO_PIN_SET); delay_us(1); // 确保PL上升沿完成 // 读取16位数据 for(uint8_t i = 0; i < 16; i++) { data <<= 1; if(HAL_GPIO_ReadPin(HC165_DATA_PORT, HC165_DATA_PIN)) { data |= 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 data; }注意:delay_us()的实现取决于你的系统时钟。对于STM32F030,可以使用DWT周期计数器或简单的空循环实现。
3. 时序优化与问题排查
软件SPI最常见的挑战是时序问题,特别是级联时数据移位不完整或错位。
3.1 关键时序参数
74HC165有几个关键时序参数需要满足:
| 参数 | 符号 | 典型值(5V) | 注意事项 |
|---|---|---|---|
| PL脉冲宽度 | tW(PL) | 20ns | 确保足够锁存时间 |
| CLK高电平时间 | tW(CH) | 20ns | 上升沿前保持稳定 |
| CLK低电平时间 | tW(CL) | 20ns | 下降沿后保持稳定 |
| 数据建立时间 | tSU | 20ns | 数据在CLK↑前稳定 |
| 数据保持时间 | tH | 5ns | 数据在CLK↑后保持 |
3.2 常见问题及解决方案
数据错位
- 现象:读取的数据位与预期不对应
- 原因:CLK边沿抖动或PL信号不稳定
- 解决:增加GPIO速度设置,确保时序严格
最后几位数据丢失
- 现象:只有前8位数据正确
- 原因:循环次数不足或时钟信号异常
- 解决:检查for循环条件,确保移位足够次数
数据不稳定
- 现象:相同输入读取值波动
- 原因:时序余量不足或电源噪声
- 解决:
- 增加delay_us时间
- 在PL和CLK线上加上拉电阻
- 检查电源滤波电容
3.3 示波器调试技巧
使用示波器观察三个关键信号:
- PL信号:应看到清晰的低脉冲
- CLK信号:应有16个规整的脉冲
- DATA信号:应在每个CLK上升沿前稳定
典型问题波形:
- CLK频率过高→数据建立时间不足
- PL脉冲过窄→锁存不完全
- CLK抖动→数据采样点不稳定
4. 高级应用与优化
4.1 多片级联实现
对于超过两片芯片的级联,原理相同,只需调整移位次数:
// 读取24位(3片级联) uint32_t HC165_Read24Bits(void) { uint32_t data = 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); delay_us(1); for(uint8_t i = 0; i < 24; i++) { data <<= 1; if(HAL_GPIO_ReadPin(HC165_DATA_PORT, HC165_DATA_PIN)) { data |= 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 data; }4.2 按键消抖处理
对于按键输入,需要添加消抖逻辑:
uint16_t last_key_state = 0; uint32_t last_key_time = 0; void Check_Keys(void) { uint16_t current_state = HC165_Read16Bits(); uint32_t now = HAL_GetTick(); if(current_state != last_key_state) { last_key_time = now; last_key_state = current_state; } else if((now - last_key_time) > 20) { // 20ms消抖 if(current_state != 0xFFFF) { // 有按键按下 Process_Key_Press(current_state); } } }4.3 低功耗优化
在电池供电应用中,可以优化功耗:
- 仅在需要时使能74HC165(通过CE引脚)
- 降低采样频率
- 使用中断唤醒而非轮询
// 配置EXTI中断在按键变化时唤醒 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == KEY_INT_PIN) { Check_Keys(); } }在实际项目中,74HC165级联方案不仅限于按键扫描,还可用于多路开关状态监测、DIP开关读取等场景。通过精确控制软件SPI时序,即使在资源受限的STM32F030上也能实现稳定可靠的扩展输入。