|
当我小的时候,我拿到一本书《PC升级与维护大全》(Upgrading And Repairing Pcs),现在已经到第22版了。这是第一本向我解释PC架构的书。我当时考虑过,当键盘上有101个键的时候,AT键盘的接口有多少引脚?那是我第一次了解到键盘矩阵。
令我吃惊的并不是键盘矩阵本身,而是在键盘内部有一个完全独立的8位微控制器。早期的键盘可能已经使用了P8049AH,现在仍然会有一些库存可供选购。这个想法让我着迷。需要一个完整的计算机运行键盘,才能使用我的“真实”计算机。为什么要采用微控制器那样复杂的元件?
键盘矩阵的主要优势是减少了捕获大量按键的输入信号所需的引脚数量。即使PC键盘上有101个键,也不意味着需要有一个101个引脚的微控制器。也不需要超过100根电线的电缆。
我将首先解释简单的四个和九个按钮的例子。
不使用矩阵
首先,我们来看看四个按钮会发生什么。不使用矩阵,每个开关将得到一个输入引脚。这个数字可能听起来不错。现在,如果你用九个按钮而不是四个按钮呢?
你将需要9个I / O引脚!另外,考虑许多独立按钮的接线成本。如果这些按钮与微控制器位于不同的PCB上,或者您正手工接线一个原型,那么需要很多导线。
让我们把这九个按钮放到一个3×3矩阵中。这种方法可以节省三个引脚!
键盘矩阵的元素
每个矩阵都有行和列。通过访问单行和单列,我们可以单独访问每个按钮。这种方法需要驱动一端,然后采集另一端。
在原理图中,包含了阻塞二极管。二极管防止称为“重影”的情况。在键盘矩阵中,重影意味着您看到不存在的按钮按压。
上面的图片比较了有和没有二极管的相同的按钮按压。 (1欧姆电阻使iCircuit免受短路的困扰。)在右侧示意图中,读取“选定”按钮时没有额外的电流路径。
键盘矩阵代码
循环和数组组成了整个代码。扫描键盘矩阵的步骤包括:
1. 使能该列
2. 扫描每一行
3. 捕捉按钮状态
4. 禁用该列 - // Keyboard Matrix Tutorial Example
- // baldengineer.com
- // CC BY-SA 4.0
-
- // JP1 is an input
- byte rows[] = {2,3,4};
- const int rowCount = sizeof(rows)/sizeof(rows[0]);
-
- // JP2 and JP3 are outputs
- byte cols[] = {8,9,10};
- const int colCount = sizeof(cols)/sizeof(cols[0]);
-
- byte keys[colCount][rowCount];
-
- void setup() {
- Serial.begin(115200);
-
- for(int x=0; x<rowCount; x++) {
- Serial.print(rows[x]); Serial.println(" as input");
- pinMode(rows[x], INPUT);
- }
-
- for (int x=0; x<colCount; x++) {
- Serial.print(cols[x]); Serial.println(" as input-pullup");
- pinMode(cols[x], INPUT_PULLUP);
- }
-
- }
-
- void readMatrix() {
- // iterate the columns
- for (int colIndex=0; colIndex < colCount; colIndex++) {
- // col: set to output to low
- byte curCol = cols[colIndex];
- pinMode(curCol, OUTPUT);
- digitalWrite(curCol, LOW);
-
- // row: interate through the rows
- for (int rowIndex=0; rowIndex < rowCount; rowIndex++) {
- byte rowCol = rows[rowIndex];
- pinMode(rowCol, INPUT_PULLUP);
- keys[colIndex][rowIndex] = digitalRead(rowCol);
- pinMode(rowCol, INPUT);
- }
- // disable the column
- pinMode(curCol, INPUT);
- }
- }
-
- void printMatrix() {
- for (int rowIndex=0; rowIndex < rowCount; rowIndex++) {
- if (rowIndex < 10)
- Serial.print(F("0"));
- Serial.print(rowIndex); Serial.print(F(": "));
-
- for (int colIndex=0; colIndex < colCount; colIndex++) {
- Serial.print(keys[colIndex][rowIndex]);
- if (colIndex < colCount)
- Serial.print(F(", "));
- }
- Serial.println("");
- }
- Serial.println("");
- }
-
- void loop() {
- readMatrix();
- if (Serial.read()=='!')
- printMatrix();
- }
复制代码
1. 使能该列(第32行)
通过将引脚设置为OUTPUT,然后将其设置为LOW来使能键盘矩阵列。这一步提供了通往地的路径。其余列引脚保持高阻抗状态,从矩阵中有效地禁用它们。
2.扫描每一行(第39行)
for()循环遍历行数组的每个引脚。该引脚的输入上拉电阻使能,提供连接到VCC。大多数Arduino板都以pinMode()的INPUT_PULLUP状态打开电阻。
3. 捕获引脚的状态
二维数组存储引脚的值。该引脚的上拉电阻关闭,循环递增。
这里的想法是捕获所有按下的按钮。矩阵扫描完成后,可以采取措施。就像使用常规按钮一样,您也可以创建一个“先前状态”矩阵来检测按钮状态的变化。
一旦读取,引脚的状态就会回到INPUT,通过关闭上拉电阻来禁用该行。
按位运算符会更好
我想指出,这种方法是非常存储效率低下的。问题是整个字节或8位正被用来存储每个按钮的状态。每个只需要1位。更有效的方法是使用按位运算符来跟踪每个关键点。
4. 禁用该列
检查每一行后,将列引脚恢复为INPUT状态将禁用它。
在扫描整个矩阵之后,处理新获得的按钮按压。在这个示例代码中,函数“printMatrix()”打印数组的内容。在示例中,您可以根据按钮的状态执行一些操作。
潜在的优化
正如前面提到的,你可以使用位操作符来存储每个按钮按一个位,而不是一个字节。
使用millis()将代码转换成状态机对于速度较慢的MCU或时间敏感的代码可能至关重要。事实上,大多数矩阵将被扫描得如此之快,阻塞时间并不重要。但是,我可以想象使用一个1 MHz的微控制器,大型矩阵读取所需的时间可能太多了。
替代代码
有一个Arduino键盘矩阵库可用。您可以从库管理器安装Keypad。
它简化了矩阵的编程。最大的努力是定义的关键。股票库的折衷是它不能处理多个按键。有一个代码示例,但这增加了复杂性。
结论
键盘矩阵是添加按钮的好方法,无需占用所有的I / O引脚。在这个键盘矩阵教程中,我展示了一个9键矩阵的工作原理。我在新项目中使用了这个相同的代码和电路。
本文翻译自:Arduino Keyboard Matrix Code and Hardware Tutorial |