公告:

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

作者:智凡单片机 / 时间:2年前 (2018/07/08) / 分类:AVR单片机 / 阅读:2012 / 评论:0

矩阵键盘简介

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

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

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

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

识别时,可预先将行线(或列线亦可)设置为输出低电平。一旦有键按下,则输入线就会被拉低,这样,通过读入输入线的状态就可得知是否有键按下了。

下面介绍一种常规的键盘识别方法——电平翻转法:

如上图所示原理图,识别步骤如下:

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开发板给出一个具体的例子,当然例子来自网络。

开源代码参考链接:http://playground.arduino.cc//Code/Keypad

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

代码如下:

// Visual Micro is in vMicro>General>Tutorial Mode

//

// 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);

// Define Functions below here or use other .ino or cpp files

// 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);          //发送键盘值

  }

}

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

 

代码运行结果如下

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

 

关于上述函数中调用的库函数,现将源代码分享如下,有兴趣可以自行参考:

// <<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 r++) {

        pin_mode(rowPins[r],INPUT_PULLUP);

    }

    // bitMap stores ALL the keys that are being pressed.

    for (byte c=0; c c++) {

        pin_mode(columnPins[c],OUTPUT);

        pin_write(columnPins[c], LOW);  // Begin column pulse output.

        for (byte r=0; r 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 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 r++) {

        for (byte c=0; c 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 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 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 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 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 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语言的键盘扫描程序设计:


没有评论,留下你的印记,证明你来过。


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。