美文网首页我爱编程
DIY 智能门控设备—入门篇01:矩阵键盘

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

作者: 禾灮 | 来源:发表于2018-07-03 22:08 被阅读0次

    矩阵键盘简介

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

    当设备所需按键数量较多时,为了减少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
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/hgajuftx.html