74HC165需正确连接PL(低电平有效并行加载)、CLK(上升沿采样)和QH(串行输出),PL须在CLK前≥20 ns稳定为低,CLK建议GPIO翻转而非硬件SPI,QH需上拉,输入端按键低有效时须加10 kΩ上拉。

74HC165 与 MCU 的硬件连接要点
74HC165 是并行输入、串行输出的并行加载移位寄存器,C++(通常指嵌入式 C++,如基于 ARM Cortex-M 的裸机或 RTOS 环境)需通过 GPIO 模拟或硬件 SPI 驱动它。关键不是“用 C++ 写”,而是用 C++ 风格管理硬件资源——比如封装为 ShiftIn 类,但底层仍依赖时序控制。
必须注意三根核心信号线:PL(并行加载,低电平有效)、CLK(移位时钟,上升沿采样)、QH(串行输出,接 MCU 输入引脚)。常见错误是把 CLK 接成下降沿触发,或忽略 PL 的建立时间(需在 CLK 前至少 20 ns 稳定为低)。
-
PL必须由 MCU 主动拉低 → 等待 ≥20 ns → 拉高 → 等待 ≥20 ns → 才能开始移位;否则读到的是上一次锁存的数据 -
CLK建议用 GPIO toggling(非硬件 SPI),因为 74HC165 不需要 MISO 回读,且对时钟占空比不敏感,但要求上升沿干净;过快(>25 MHz)虽标称支持,但实际 PCB 走线长时建议 ≤2 MHz -
QH是纯 CMOS 输出,可直接接 MCU 输入引脚(带上拉电阻至 VCC),不能悬空;若按键是低有效(GND 触发),则 74HC165 输入端需加 10 kΩ 上拉,否则未按按键时读数为不定态
手动模拟移位读取的 C++ 实现逻辑
不用硬件 SPI 更可靠:SPI 模式下若配置为 CPOL=0/CPHA=0,虽能兼容,但部分 MCU 的 SPI 外设在只收不发时会异常(如 STM32F1 的 SPI 在单向接收模式下需 dummy write)。直接 bit-banging 更可控,也便于调试时用逻辑分析仪抓波形。
典型读取流程封装为成员函数 read_bytes(),返回 std::array<uint8_t n></uint8_t>(N = 芯片级联数):
立即学习“C++免费学习笔记(深入)”;
void ShiftIn::read_bytes(std::array<uint8_t, N>& buf) {
// 1. 并行加载
pl_pin_.set_low();
delay_ns(50); // >20 ns
pl_pin_.set_high();
delay_ns(50); // >20 ns
// 2. 移位读取(每芯片 8 bit,高位在前)
for (int i = 0; i < N; ++i) {
uint8_t byte = 0;
for (int b = 0; b < 8; ++b) {
byte <<= 1;
if (data_pin_.is_high()) byte |= 1;
clk_pin_.set_high();
delay_ns(100);
clk_pin_.set_low(); // 下降沿后准备下次采样
}
buf[i] = byte;
}
}
注意点:
- 两次
delay_ns(50)不可省略,尤其在高频 MCU(如 168 MHz)上,编译器优化可能把空循环优化掉,必须用真正延时或 __nop() + 循环计数 - 读顺序是 MSB→LSB,即先读 QH,再 QH-1…所以
byte 后再或入当前位,而非 <code>|= (1 - 若级联多片,QH 接下一芯片的 SER(串行输入),最后一片的 QH 接 MCU;此时
read_bytes()中的内层循环必须完整执行 8×N 次,不能按字节拆开——否则中间芯片数据会丢失
按键去抖与状态解析的 C++ 封装建议
74HC165 本身不处理抖动,必须在软件层做。不要在每次 read_bytes() 后立刻判定按键事件,而应维护一个历史缓冲区(如最近 4 次采样),用「稳定即有效」策略。
推荐用位运算 + 状态机,避免浮点或复杂容器:
class KeypadReader {
std::array<uint8_t, 2> history_{0}; // 双缓冲:prev, curr
uint8_t stable_mask_ = 0;
public:
void update() {
history_[0] = history_[1];
read_bytes(&history_[1]); // 假设 2 片,共 16 键
stable_mask_ = history_[0] ^ history_[1]; // 变化位
if (stable_mask_ == 0) {
// 连续两次相同 → 认为稳定
current_state_ = history_[1];
}
}
bool is_pressed(uint8_t key_index) const {
return !(current_state_ & (1 << key_index)); // 低有效
}
};
关键细节:
- 按键物理连接决定逻辑电平:若按键一端接地、另一端接 74HC165 的 P0–P7,则未按下为高(上拉),按下为低,所以
is_pressed要判! - 仅靠两次相等就认为“稳定”不够鲁棒;实际项目中建议用 4 次缓冲 + 投票(如
(a&b) | (b&c) | (c&d)),但会增加 RAM 占用;资源紧张时,两次 + 10 ms 间隔采样已够用 - 不要在中断里调用
read_bytes()—— 它含延时,会阻塞系统;应在主循环或定时器回调中执行
级联多片时的信号完整性风险
超过 3 片级联(24+ 按键)时,QH → SER 链路变长,边沿畸变加剧,容易导致误读。这不是 C++ 能解决的问题,但代码设计要预留应对接口。
实测发现:STM32F4 在 2 MHz CLK 下,4 片级联时最后 1–2 bit 常出错,加 100 Ω 串联电阻在每个 SER 输入端可显著改善。
- CLK 线必须单独走线,远离电源和大电流路径;若用 PCB,建议包地
- 所有 74HC165 的 VCC 必须就近接 0.1 μF 陶瓷电容到 GND,否则电源波动会导致某片锁存失败
- C++ 层面可加校验:读完后重新读一次,比对两次结果;不一致则丢弃本次,或触发重试计数器(防死锁)
- 不要依赖 74HC165 的
QH bar(反相输出)来“增强驱动”,它的扇出能力并不比QH强,反而增加额外延迟
最易被忽略的是 PL 信号的驱动能力——如果用弱驱动 GPIO 控制 PL,而级联芯片多、输入电容大,PL 下降沿变缓,可能低于 74HC165 要求的 VIH/VIL 阈值,导致部分芯片未成功锁存。务必用足够驱动能力的 GPIO,或加缓冲器。


















