薄膜开关在嵌入式产品里通常以矩阵方式接入 MCU:m 行 × n 列,理论占用 m+n 个 GPIO。当按键数量从 4×4 升到 8×8 甚至 12×12 时,GPIO 资源很快见底。下面把工程上三种扩展路径的取舍写一下,方便嵌入式工程师在选型时做对比。
第一种是 74HC165(串入并出)做列扩展,74HC595(并入串出)做行扩展。优点是协议简单,SPI 即可驱动;缺点是 74HC165 在按键扫描时容易引入 1~2 ms 的串行传输延迟,去抖窗口要拉到 5 ms 以上才稳定。SPI 总线在工业现场的抗干扰要做软硬双层加固,MOSI 和 MISO 都要做上拉,否则长线缆下容易出现错位。74HC165 的级联数建议控制在 4 片以内,更长链路下时钟偏移会引入误码。
第二种是 I/O 扩展芯片,比如 PCA9555(I2C 接口,16 位)。一个 PCA9555 可以把 8×8 矩阵压到 2 个 I2C 地址,扫描周期可以做到 200 µs 以内。缺点是 I2C 总线在工业现场的抗干扰要做软硬双层加固,PCA9555 的 INT 引脚要做防抖处理,否则多片 PCA9555 级联时会出现中断风暴。I2C 总线速率 100 kHz 够用,400 kHz 在长线缆下信号质量会下降。
第三种是电阻分压 + 单 ADC 多键识别。每一列按键下串一个不同阻值的电阻,按下时 ADC 读到的电压不同,反推键位。优点是只用 1 个 ADC 引脚;缺点是按键数量超过 8 个时分辨率不足,且湿气环境下电阻漂移会引发误识别。电阻选型要选低温漂金属膜电阻,TC ≤ 50 ppm/℃。分压电阻的精度选 1% 即可,分辨率的关键在 ADC 位数和参考电压稳定性。
工程取舍如下。
8×8 以下:直接 GPIO 扫描 + 定时器中断,扫描周期 1~2 ms。GPIO 翻转后 5~10 µs 启动 ADC 或读取引脚状态,避免尾迹。
8×8 到 12×12:PCA9555 + I2C,配合 100 kHz 总线速率。每片 PCA9555 占用 1 个 I2C 地址,2 片级联可以覆盖 16×16 矩阵。
12×12 以上:考虑把矩阵拆成两块面板,或者用专用触摸 IC 替代。专用触摸 IC(如 CY8C401)的扫描周期可以压到 100 µs,但成本是 GPIO 扫描方案的 3~5 倍。
去抖算法在 MCU 上一般用 5~10 ms 软去抖,配合 74HC165 的传输延迟时需要拉到 15 ms。能耗敏感的电池供电场景,可以把扫描周期放到 20~50 ms,牺牲响应速度换功耗。深度睡眠场景下,扫描可以完全停止,靠外部中断唤醒 MCU 后再做一次扫描,唤醒延迟 1~3 ms。
按键事件的优先级也要分清楚。紧急停止、复位等关键按键建议走专用外部中断,不和扫描矩阵混在一起,避免扫描延迟或漏检导致关键事件丢失。外部中断的引脚要选带内部上拉的型号,省掉外部上拉电阻。
软件端的扫描实现可以用状态机,避免在中断里做耗时操作。状态机分「扫描启动」「等待稳定」「读取引脚」「去抖判定」「事件入队」五个状态,每个状态耗时 < 200 µs,整体扫描周期 1~2 ms。
从嵌入式开发流程看,按键扫描的代码要分层设计。底层是 GPIO 翻转和 ADC 采样驱动(CMSIS HAL 或 vendor SDK),中间层是矩阵扫描状态机(10~20 ms 周期),上层是按键事件队列(FreeRTOS 消息队列或环形缓冲区)。三层各司其职,调试时容易定位是底层硬件问题还是上层软件问题。
去抖算法的实现细节也值得注意。最简单的去抖是用定时器在按键触发后 5~10 ms 重新读取引脚,如果电平仍然相同则确认按键。复杂一点的是统计窗口法,在 50 ms 窗口内统计 8 次采样中 5 次以上相同电平则确认按键。后者抗干扰能力更强但占用 CPU 更多。从代码维护角度看,按键扫描的状态机实现要写单元测试。每个状态(扫描启动、等待稳定、读取引脚、去抖判定、事件入队)都要有对应的测试用例,覆盖正常按键、连按、长按、抖动、串扰五种场景。单元测试覆盖率 ≥ 80% 是嵌入式代码的基本要求。按键事件队列的设计也要考虑多任务同步。FreeRTOS 场景下用消息队列传递按键事件,队列长度根据按键频率和消费速度估算。队列满时新事件直接丢弃,避免阻塞按键扫描。日志里记录事件丢弃次数,作为系统监控的指标之一。
按键事件队列的设计也要考虑多任务同步。队列满时新事件直接丢弃,避免阻塞按键扫描。日志里记录事件丢弃次数,作为系统监控的指标之一。