DIY 智能门控设备—入门篇01:矩阵键盘


矩阵键盘简介

    矩阵键盘是单片机外部设备中所使用的排布类似于矩阵的键盘组。

当设备所需按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。
矩阵式结构的键盘,结构和识别上显然要复杂一些:在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。
这样,一个端口(如PA口)就可以构成4*4=16个按键,比之直接将端口线用于键盘多出了一倍,而且线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。
由此可见,在需要的按键数量比较多时,采用矩阵法来做键盘是合理的。(原理图如下所示)


4*4矩阵键盘原理图1

4*4矩阵键盘原理图2

矩阵键盘实物图

键盘识别方法

如上原理图1所示,当按键没有按下时,所有的输入端都是高电平,代表无键按下。

下面介绍一种常规的键盘识别方法——电平翻转法:
    识别步骤如下:
  1. 判断键盘中有无键按下:将全部行线ROW_1-4设置为输出低电平,然后检测列线COL_1-4的输入状态。只要有一列的电平为低,则表示键盘中已有按键被按下,而且闭合的按键恰好位于低电平列线(COL_y)与4根行线(ROW)相交叉的4个按键之中。若所有列线均为高电平,则键盘中无键按下。
  2. 判断闭合按键的具体位置:执行步骤1,且确定有按键按下后,将全部列线COL_1-4设置为输出高电平,检测行线ROW_1-4的输入状态。由于步骤1中按下的按键未变更,此时闭合的按键必定位于低电平行线(ROW_x)与列线(COL_y)相交叉的按键位置。
  3. 按键识别完毕。


    电平翻转法流程

    当然键盘识别的方法有很多,比如扫描法。扫描法又可称为逐行(或列)扫描查询法,也是一种常用的按键识别方法。

具体项目中,开发者可根据自身实际进行方案选择。

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

推荐阅读更多精彩内容

  • 一、原理简介 键盘接口电路是单片机系统设计非常重要的一环,作为人机交互界面里最常用的输入设备。我们可以通过键盘输入...
    创客乌托邦阅读 1,651评论 0 1
  • 恩,正式开始我的DIY键盘了。。 为了防止之前项目都没有一个好名字,先起个还凑合的名字”剑盘“.... 目前没有很...
    剑山阅读 5,982评论 24 51
  • ​​​本文主要介绍嵌入式系统的一些基础知识,希望对各位有帮助。 嵌入式系统基础 1、嵌入式系统的定义 (1)定义:...
    OpenJetson阅读 2,169评论 0 12
  • $.ajax({ type: "GET", //发送方法 url: "*****", //发送地址 da...
    Mr丶T阅读 37评论 0 0
  • 人们经常说习惯成自然。那到底什么是习惯呢?《现代汉语词典》中,对习惯是这样解释的:一种解释是,经常接触某种新的情况...
    杨金社阅读 268评论 3 1