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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容

  • 一、原理简介 键盘接口电路是单片机系统设计非常重要的一环,作为人机交互界面里最常用的输入设备。我们可以通过键盘输入...
    创客乌托邦阅读 4,247评论 0 1
  • 恩,正式开始我的DIY键盘了。。 为了防止之前项目都没有一个好名字,先起个还凑合的名字”剑盘“.... 目前没有很...
    剑山阅读 7,282评论 26 51
  • 本文同步首发外设天下键盘区、虎扑论坛数码区、什么值得买原创频道以及个人订阅号hzy3618,转载请注明作者宇师 文...
    乐音清和_宇阅读 20,013评论 40 63
  • ​​​本文主要介绍嵌入式系统的一些基础知识,希望对各位有帮助。 嵌入式系统基础 1、嵌入式系统的定义 (1)定义:...
    OpenJetson阅读 3,250评论 0 13
  • $.ajax({ type: "GET", //发送方法 url: "*****", //发送地址 da...
    Mr丶T阅读 170评论 0 0