3、矩阵扫描基础:矩阵扫描的数学原理(行列寻址)、节省IO口的优势分析
好,咱们今天聊聊矩阵扫描的核心。说白了,就是怎么用最少的IO口,去读最多的按键。
你想想看,一个8x8的键盘,64个键。如果用独立按键,每个键一个IO口,那得64个。主控芯片哪有那么多脚?就算有,布线也够你头疼的。我早年做第一个掌机项目时,就吃过这个亏——选了个IO口多的芯片,结果PCB走线绕得跟蜘蛛网似的,还老出干扰。
矩阵扫描,就是来解决这个问题的。
3.1 行列寻址的数学原理
矩阵扫描的数学原理,其实特别简单。就是「坐标」的概念。
我们把按键排成一个矩阵。比如4行4列,总共16个键。每个按键,都对应一个唯一的坐标——(行号, 列号)。
怎么读这个坐标呢?
我习惯用「行扫描法」。具体做法是:
- 把所有行线设为输出,列线设为输入(带上拉)。
- 一次只让一行输出低电平,其他行输出高电平。
- 然后读列线的电平。如果某列变成低电平,说明这一行、这一列的按键被按下了。
举个例子。4x4矩阵,行线是R0~R3,列线是C0~C3。
我先让R0=0,R1=R2=R3=1。然后读C0~C3。如果C2=0,那就说明(R0, C2)这个键被按下了。
接着,我让R1=0,其他行=1,再读一遍列。如此循环。
你看,这不就是坐标定位吗?
数学上,这就是一个二维数组的遍历。行索引和列索引,唯一确定一个元素。
嗯,这里要注意:实际电路中,列线通常要加上拉电阻,或者用芯片内部的上拉。不然悬空状态会读到不确定的值。我踩过这个坑——有一批板子,上拉电阻虚焊了,结果按键乱跳,排查了好久。
3.2 节省IO口的优势分析
咱们来算笔账。
假设有N个按键。
- 独立按键:需要N个IO口。
- 矩阵扫描:需要R+C个IO口,其中R×C ≥ N。
当N比较大的时候,差距就出来了。
比如64个按键:
- 独立按键:64个IO口。
- 8x8矩阵:8+8=16个IO口。
节省了48个IO口!
再比如,一个标准的游戏手柄,大概12~16个按键。用4x4矩阵,只需要8个IO口。独立按键的话,得16个。
我做过一个项目,主控芯片总共就20个IO口。既要接屏幕,又要接存储,还要接按键。要不是矩阵扫描,根本塞不下。
节省IO口的优势,说白了就是:
- 降低成本:可以用更便宜、引脚更少的芯片。
- 简化布线:PCB走线少,干扰也少。
- 释放资源:省下来的IO口,可以给其他功能用,比如PWM调光、ADC采样。
核心公式:
用M行N列的矩阵,可以扫描M×N个按键,只需要M+N个IO口。
当按键数量超过10个时,矩阵扫描的IO口效率就开始明显优于独立按键了。
3.3 实际设计中的权衡
当然,矩阵扫描也不是完美的。它有个代价——扫描时间。
你想想看,独立按键是并行读的,一瞬间就知道所有按键状态。矩阵扫描是串行读的,一行一行扫过去。如果矩阵很大,扫描一轮的时间就会变长。
比如一个8x8矩阵,扫描一轮要读8次列。每次读列还要等电平稳定,加上去抖动的时间。如果处理不好,就会出现「按键反应慢」或者「丢键」的问题。
我建议,矩阵的规模不要太大。一般游戏机里,4x4或5x5就够用了。超过6x6,就要考虑用专用的键盘扫描芯片了。
另外,还有一个坑:鬼键。
什么叫鬼键?就是你没按它,它却自己触发了。
举个例子。3x3矩阵,你同时按下(0,0)和(1,1),结果(0,1)和(1,0)也被「误触」了。这是因为电流会通过按键串过去,形成回路。
怎么解决?
- 加二极管隔离。每个按键串一个二极管,防止电流倒灌。
- 或者用软件处理。检测到多个按键时,只认第一个,或者用更复杂的算法。
我曾经在一个廉价游戏机上,为了省成本没加二极管。结果玩家同时按「上」和「右」时,会莫名其妙触发「A键」。后来被玩家骂惨了……从那以后,我再也不敢省这个二极管了。
3.4 代码示例:4x4矩阵扫描
给你看一段我常用的代码框架。这是基于STM32的,但思路通用。
// 定义行和列的GPIO
#define ROW_PORT GPIOA
#define ROW_PINS (GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3)
#define COL_PORT GPIOB
#define COL_PINS (GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3)
// 按键状态表
uint8_t key_matrix[4][4] = {0};
void matrix_scan(void) {
for (uint8_t row = 0; row < 4; row++) {
// 所有行先输出高电平
HAL_GPIO_WritePin(ROW_PORT, ROW_PINS, GPIO_PIN_SET);
// 当前行输出低电平
HAL_GPIO_WritePin(ROW_PORT, (1 << row), GPIO_PIN_RESET);
// 稍微延时,等电平稳定
delay_us(10);
// 读取列状态
uint16_t col_state = HAL_GPIO_ReadPin(COL_PORT, COL_PINS);
// 解析列状态
for (uint8_t col = 0; col < 4; col++) {
if ((col_state & (1 << col)) == 0) {
// 按键按下
key_matrix[row][col] = 1;
} else {
// 按键释放
key_matrix[row][col] = 0;
}
}
}
}
这段代码,每调用一次,就扫描一轮矩阵。扫描结果存在key_matrix数组里。主循环里每隔10ms调用一次,就能实现按键检测。
嗯,这里要注意:delay_us(10)不能少。如果延时太短,电平还没稳定就读,会读到错误值。太长的话,扫描速度又太慢。10微秒是我试出来的经验值,你可以根据实际电路调整。
3.5 小结
矩阵扫描,说白了就是用时间换空间。用扫描时间,换取IO口的节省。
对于嵌入式游戏机来说,这是个非常划算的买卖。因为游戏机的按键数量通常在10~20个,矩阵规模不大,扫描速度完全够用。省下来的IO口,可以给屏幕、音频、存储用,何乐而不为?
下一章,我会讲具体的硬件电路设计,包括上拉电阻怎么选、二极管怎么加、怎么避免鬼键。到时候咱们再细聊。