矩阵键盘简介
矩阵键盘是单片机外部设备中所使用的排布类似于矩阵的键盘组。
当设备所需按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。
矩阵式结构的键盘,结构和识别上显然要复杂一些:在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。
这样,一个端口(如PA口)就可以构成4*4=16个按键,比之直接将端口线用于键盘多出了一倍,而且线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。
由此可见,在需要的按键数量比较多时,采用矩阵法来做键盘是合理的。(原理图如下所示)
4*4矩阵键盘原理图1
4*4矩阵键盘原理图2
矩阵键盘实物图
键盘识别方法
如上原理图1所示,当按键没有按下时,所有的输入端都是高电平,代表无键按下。
下面介绍一种常规的键盘识别方法——电平翻转法:
识别步骤如下:
- 判断键盘中有无键按下:将全部行线ROW_1-4设置为输出低电平,然后检测列线COL_1-4的输入状态。只要有一列的电平为低,则表示键盘中已有按键被按下,而且闭合的按键恰好位于低电平列线(COL_y)与4根行线(ROW)相交叉的4个按键之中。若所有列线均为高电平,则键盘中无键按下。
- 判断闭合按键的具体位置:执行步骤1,且确定有按键按下后,将全部列线COL_1-4设置为输出高电平,检测行线ROW_1-4的输入状态。由于步骤1中按下的按键未变更,此时闭合的按键必定位于低电平行线(ROW_x)与列线(COL_y)相交叉的按键位置。
按键识别完毕。
电平翻转法流程
当然键盘识别的方法有很多,比如扫描法。扫描法又可称为逐行(或列)扫描查询法,也是一种常用的按键识别方法。
具体项目中,开发者可根据自身实际进行方案选择。
Arduino演示例程:
下面基于Arduino UNO Rev3开发板给出一个具体的例子,当然例子来自网络。
Arduino Playground - Keypad Library
开源代码参考链接:Arduino Playground - Keypad Library
仿写代码如下:
/*
Name: Keypads.ino
Created: 2018/7/3 17:26:41
Author: 禾灮\HeGuang
*/
// Define User Types below here or use a .h file
#include <Keypad.h>
// Define Function Prototypes that use User Types below here or use a .h file
/定义矩阵键盘按键个数及对应值
const byte ROWS = 4; //Rows 四行四列
const byte COLS = 4; //Columns
char hexaKeys[ROWS][COLS] = { //定义按键值
{'1','2','3','U'},
{'4','5','6','D'},
{'7','8','9','*'},
{'d','0','#','O'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; //定义键盘行对应接口
byte colPins[COLS] = {5, 4, 3, 2}; //定义键盘列对应接口
//键盘程序初始化
Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
// The setup() function runs once each time the micro-controller starts
void setup(){
Serial.begin(9600); //定义串口波特率
}
// Add the main program code into the continuous loop() function
void loop(){
char customKey = customKeypad.getKey(); //获取键盘值
if (customKey){
Serial.println(customKey); //串口发送键盘值
}
}
代码运行结果
实物连接
关于上述函数中调用的库函数,现将源代码分享如下,有兴趣可以自行参考:
// <<constructor>> Allows custom keymap, pin configuration, and keypad sizes.
Keypad::Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols) {
rowPins = row;
columnPins = col;
sizeKpd.rows = numRows;
sizeKpd.columns = numCols;
begin(userKeymap);
setDebounceTime(10);
setHoldTime(500);
keypadEventListener = 0;
startTime = 0;
single_key = false;
}
// Let the user define a keymap - assume the same row/column count as defined in constructor
void Keypad::begin(char *userKeymap) {
keymap = userKeymap;
}
// Returns a single key only. Retained for backwards compatibility.
char Keypad::getKey() {
single_key = true;
if (getKeys() && key[0].stateChanged && (key[0].kstate==PRESSED))
return key[0].kchar;
single_key = false;
return NO_KEY;
}
// Populate the key list.
bool Keypad::getKeys() {
bool keyActivity = false;
// Limit how often the keypad is scanned. This makes the loop() run 10 times as fast.
if ( (millis()-startTime)>debounceTime ) {
scanKeys();
keyActivity = updateList();
startTime = millis();
}
return keyActivity;
}
// Private : Hardware scan
void Keypad::scanKeys() {
// Re-intialize the row pins. Allows sharing these pins with other hardware.
for (byte r=0; r<sizeKpd.rows; r++) {
pin_mode(rowPins[r],INPUT_PULLUP);
}
// bitMap stores ALL the keys that are being pressed.
for (byte c=0; c<sizeKpd.columns; c++) {
pin_mode(columnPins[c],OUTPUT);
pin_write(columnPins[c], LOW); // Begin column pulse output.
for (byte r=0; r<sizeKpd.rows; r++) {
bitWrite(bitMap[r], c, !pin_read(rowPins[r])); // keypress is active low so invert to high.
}
// Set pin to high impedance input. Effectively ends column pulse.
pin_write(columnPins[c],HIGH);
pin_mode(columnPins[c],INPUT);
}
}
// Manage the list without rearranging the keys. Returns true if any keys on the list changed state.
bool Keypad::updateList() {
bool anyActivity = false;
// Delete any IDLE keys
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kstate==IDLE) {
key[i].kchar = NO_KEY;
key[i].kcode = -1;
key[i].stateChanged = false;
}
}
// Add new keys to empty slots in the key list.
for (byte r=0; r<sizeKpd.rows; r++) {
for (byte c=0; c<sizeKpd.columns; c++) {
boolean button = bitRead(bitMap[r],c);
char keyChar = keymap[r * sizeKpd.columns + c];
int keyCode = r * sizeKpd.columns + c;
int idx = findInList (keyCode);
// Key is already on the list so set its next state.
if (idx > -1) {
nextKeyState(idx, button);
}
// Key is NOT on the list so add it.
if ((idx == -1) && button) {
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kchar==NO_KEY) { // Find an empty slot or don't add key to list.
key[i].kchar = keyChar;
key[i].kcode = keyCode;
key[i].kstate = IDLE; // Keys NOT on the list have an initial state of IDLE.
nextKeyState (i, button);
break; // Don't fill all the empty slots with the same key.
}
}
}
}
}
// Report if the user changed the state of any key.
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].stateChanged) anyActivity = true;
}
return anyActivity;
}
// Private
// This function is a state machine but is also used for debouncing the keys.
void Keypad::nextKeyState(byte idx, boolean button) {
key[idx].stateChanged = false;
switch (key[idx].kstate) {
case IDLE:
if (button==CLOSED) {
transitionTo (idx, PRESSED);
holdTimer = millis();
} // Get ready for next HOLD state.
break;
case PRESSED:
if ((millis()-holdTimer)>holdTime) // Waiting for a key HOLD...
transitionTo (idx, HOLD);
else if (button==OPEN) // or for a key to be RELEASED.
transitionTo (idx, RELEASED);
break;
case HOLD:
if (button==OPEN)
transitionTo (idx, RELEASED);
break;
case RELEASED:
transitionTo (idx, IDLE);
break;
}
}
// New in 2.1
bool Keypad::isPressed(char keyChar) {
for (byte i=0; i<LIST_MAX; i++) {
if ( key[i].kchar == keyChar ) {
if ( (key[i].kstate == PRESSED) && key[i].stateChanged )
return true;
}
}
return false; // Not pressed.
}
// Search by character for a key in the list of active keys.
// Returns -1 if not found or the index into the list of active keys.
int Keypad::findInList (char keyChar) {
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kchar == keyChar) {
return i;
}
}
return -1;
}
// Search by code for a key in the list of active keys.
// Returns -1 if not found or the index into the list of active keys.
int Keypad::findInList (int keyCode) {
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kcode == keyCode) {
return i;
}
}
return -1;
}
// New in 2.0
char Keypad::waitForKey() {
char waitKey = NO_KEY;
while( (waitKey = getKey()) == NO_KEY ); // Block everything while waiting for a keypress.
return waitKey;
}
// Backwards compatibility function.
KeyState Keypad::getState() {
return key[0].kstate;
}
// The end user can test for any changes in state before deciding
// if any variables, etc. needs to be updated in their code.
bool Keypad::keyStateChanged() {
return key[0].stateChanged;
}
// The number of keys on the key list, key[LIST_MAX], equals the number
// of bytes in the key list divided by the number of bytes in a Key object.
byte Keypad::numKeys() {
return sizeof(key)/sizeof(Key);
}
// Minimum debounceTime is 1 mS. Any lower *will* slow down the loop().
void Keypad::setDebounceTime(uint debounce) {
debounce<1 ? debounceTime=1 : debounceTime=debounce;
}
void Keypad::setHoldTime(uint hold) {
holdTime = hold;
}
void Keypad::addEventListener(void (*listener)(char)){
keypadEventListener = listener;
}
void Keypad::transitionTo(byte idx, KeyState nextState) {
key[idx].kstate = nextState;
key[idx].stateChanged = true;
// Sketch used the getKey() function.
// Calls keypadEventListener only when the first key in slot 0 changes state.
if (single_key) {
if ( (keypadEventListener!=NULL) && (idx==0) ) {
keypadEventListener(key[0].kchar);
}
}
// Sketch used the getKeys() function.
// Calls keypadEventListener on any key that changes state.
else {
if (keypadEventListener!=NULL) {
keypadEventListener(key[idx].kchar);
}
}
}
AVR单片机演示例程
下面,基于Arduino UNO Rev3开发板,进行AVR单片机C语言的键盘(使用如原理图2所示的实物键盘)扫描程序设计:
未完待续。。。
感谢一直关注着禾灮成长进步的朋友们。你们的信任、支持和鼓励,鞭策着我们一路走到了今天。
感谢所有的合作伙伴,我们相互促进,共同见证了彼此的成长。
感谢所有曾经在禾灮彼此倚靠、相互鼓励、携手同心、砥砺同行的兄弟姐妹。这里承载了我们的青春与热血。
禾灮,感谢有你。
未来,我们将一如既往,砥砺前行。
禾灮·小楊
2018.07.03
网友评论