1. 矩陣鍵盤原理
通過矩陣連接的模式,原本需要16個引腳連接的按鈕只需要8個引腳就能連接好,減少了I/O口的占用。
矩陣按鈕是通過掃描來讀取狀態的。
2. 掃描的概念
輸出掃描示例:數碼管掃描
原理:顯示第1位→顯示第2位→顯示第2位→…,快速重復此過程實現所有數碼管同時顯示的效果
輸入掃描示例:矩陣鍵盤掃描
原理:讀取第1行(列)→讀取第2行(列)→讀取第3行(列)→…,快速循環此過程,最終實現所有按鍵同時檢測的效果
這種循環掃描的優勢在于節省I/O口
3. Templates的定義
Template實際上就是雙擊出來指定的內容,比如平時寫.h文件的時候,總是需要反復寫如下開頭結尾:
#ifndef __XXX_H__
#define __XXX_H__#endif
會比較麻煩,通過Template可以將這部分代碼定義存儲,再使用的時候雙擊即可,定義Template步驟如下:
第一步:點擊下方的【Templates】→【右鍵】→【Configure Templates】
第二步:新建,起一個好識別的名字,寫入常用的部分作為Text
第三步:在希望光標落入的地方打一個|
,最終Text部分輸入如下:
#ifndef __|_H__
#define#endif
在文件中雙擊,就會出現如圖所示的結果:
4. 矩陣鍵盤掃描方式1
建立了MatrixKeyboard.c
和MatrixKeyboard.h
,里面寫掃描的代碼
需要先理解矩陣鍵盤的按鍵按下是怎么被掃描到的,我這里直接復制了deepseek的回答:
在矩陣鍵盤中,檢測按鈕被按下的正確原理是:
- 行線(輸出端)需要主動置低
- 列線(輸入端)被按鍵下拉為低
- 檢測時需滿足:行線輸出低電平 + 列線檢測到低電平
基于此,我們需要給行線置低(根據上面的電路圖圖示,行線分別是P17,P16,P15,P14),同時檢測列線(P13,P12,P11,P10)。
我在MatrixKeyboard.c
中自己寫了一個不帶消抖的按行掃描:
#include <REGX52.H>
#include "Delay.h"// 按行掃描
unsigned char MatrixKeyboardScan()
{unsigned char keyNum = 0;P1 = 0xFF;P1_7 = 0;if (P1_3 == 0) keyNum = 1;if (P1_2 == 0) keyNum = 2;if (P1_1 == 0) keyNum = 3;if (P1_0 == 0) keyNum = 4;P1 = 0xFF;P1_6 = 0;if (P1_3 == 0) keyNum = 5;if (P1_2 == 0) keyNum = 6;if (P1_1 == 0) keyNum = 7;if (P1_0 == 0) keyNum = 8;P1 = 0xFF;P1_5 = 0;if (P1_3 == 0) keyNum = 9;if (P1_2 == 0) keyNum = 10;if (P1_1 == 0) keyNum = 11;if (P1_0 == 0) keyNum = 12;P1 = 0xFF;P1_4 = 0;if (P1_3 == 0) keyNum = 13;if (P1_2 == 0) keyNum = 14;if (P1_1 == 0) keyNum = 15;if (P1_0 == 0) keyNum = 16;return keyNum;
}
MartixKeyboard.h
如下:
#ifndef __MATRIXKEYBOARD_H__
#define __MATRIXKEYBOARD_H__unsigned char MatrixKeyboardScan();#endif
為了驗證不帶消抖按行掃描存在的問題,我寫了如下一段主函數:
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKeyboard.h"void main()
{unsigned int myCount = 0;unsigned char keyboardNum = 0;LCD_Init();// LCD_ShowNum(2,1,keyboardNum,2);while (1){keyboardNum = MatrixKeyboardScan();if (keyboardNum){myCount++;LCD_ShowNum(1,1,keyboardNum,2);LCD_ShowNum(2,1,myCount,3);}}
}
此時,LCD第一行顯示按下的按鍵,第二行顯示受抖動電壓影響,LCD_ShowNum語句執行的次數:
可以看到,不穩的電壓起碼抖動了11次,從而11次執行了LCD_ShowNum語句,為此,消抖語句的存在還是很必要的,故修改如下:
#include <REGX52.H>
#include "Delay.h"// 按行掃描
unsigned char MatrixKeyboardScan()
{unsigned char keyNum = 0;P1 = 0xFF;P1_7 = 0;if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 1;}if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 2;}if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 3;}if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 4;}P1 = 0xFF;P1_6 = 0;if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 5;}if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 6;}if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 7;}if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 8;}P1 = 0xFF;P1_5 = 0;if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 9;}if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 10;}if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 11;}if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 12;}P1 = 0xFF;P1_4 = 0;if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 13;}if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 14;}if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 15;}if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 16;}return keyNum;
}
修改后的執行結果如下,可以看到按下一次按鈕,相應的代碼只會執行一次:
5. 矩陣掃描方式2
先掃出按鍵的列,再掃出按鍵的行
#include <REGX52.H>
#include "Delay.h"// 先找列再找行
unsigned char MatrixKeyboardScan()
{// 先掃出按下按鍵所處列unsigned char keyNum = 0;P1 = 0x0F;switch(P1){case (0x07): keyNum = 1; break;case (0x0B): keyNum = 2; break;case (0x0D): keyNum = 3; break;case (0x0E): keyNum = 4; break;}// 再掃出按下按鍵所處行P1 = 0xF0;switch(P1){case (0x70): keyNum += 0; break;case (0xB0): keyNum += 4; break;case (0xD0): keyNum += 8; break;case (0xE0): keyNum += 12; break;}return keyNum;
}
原理很簡單,當第一步將P1
置為0x0F
時,通過判斷低位處于什么樣的狀態,就可以知道按下的是哪一列的按鈕:
同理,第二步將P1置為0xF0
時,判斷高位處于什么樣的狀態,就可以判斷出按下按鈕的所處行。
因為在第一步假設按下按鈕位于第1行,分別置了1、2、3、4;則第二行僅需根據行數,加上具體相隔的按鈕值即可。
但是我不太清楚消抖應該加在哪,我自己試了一下會很奇怪,所以這里的代碼只是簡單寫了一下,僅供參考,可能會出現抖動問題。
6. 主函數代碼
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKeyboard.h"void main()
{unsigned char keyboardNum = 0;LCD_Init();LCD_ShowString(1,1,"Press:");LCD_ShowNum(2,1,keyboardNum,2);while (1){keyboardNum = MatrixKeyboardScan();if (keyboardNum){LCD_ShowNum(2,1,keyboardNum,2);}}
}
結果如下:
7. 矩陣鍵盤密碼鎖
需求是由用戶輸入密碼,單片機檢測是否正確,正確則輸出“OK”,反之輸出“ERR”,除此之外,還需要有“刪除(回退一格)”和“取消(輸入密碼全部清空)”功能。
我的代碼實現如下,S1-S9代表數字1-9,S10是回退一格,S11是取消,S12是確認:
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKeyboard.h"void main()
{unsigned int password = 0;unsigned int truePassword = 1234;unsigned int passwordNum = 0;LCD_Init();LCD_ShowString(1,1,"Password:");LCD_ShowNum(2,1,password,4);password = password / 10;while(1){unsigned int currNum = MatrixKeyboardScan();if (currNum){// 非法輸入不允許,直接忽略if (currNum == 13 || currNum == 14 || currNum == 15 || currNum == 16) continue;// 功能鍵:10刪除,11取消,12確認檢查密碼// 刪除操作if (currNum == 10 && passwordNum <= 4){password /= 10; // 回退一位LCD_ShowNum(2,1,password,4); // 刷新密碼passwordNum--; // 位數-1continue;}// 取消操作else if (currNum == 11){passwordNum = 0;password = 0;LCD_ShowNum(2,1,password,4);continue;}// 確認操作else if (currNum == 12){if (password == truePassword) // 密碼正確顯示OK,程序結束{LCD_ShowString(1,15,"OK");} else // 密碼錯誤顯示ERR,password重置為0{LCD_ShowString(1,14,"ERR");passwordNum = 0;password = 0;LCD_ShowNum(2,1,password,4);}continue;}// 功能鍵以外的數字鍵處理if (passwordNum == 0){LCD_ShowString(1,14," ");if (currNum == 13) currNum = 0; // 用13作為數字0,允許用戶輸入0password = currNum;LCD_ShowNum(2,1,password,4);passwordNum++;}else if (passwordNum < 4){if (currNum == 13) currNum = 0; // 用13作為數字0,允許用戶輸入0password = password * 10 + currNum;LCD_ShowNum(2,1,password,4);passwordNum++;}}}
}
效果如下:
密碼鎖Demo
輸入密碼:
輸入密碼后按一下S10(回退一格):
按取消(密碼置0):
輸入錯誤密碼按確定(密碼清0,顯示ERR):
輸入正確密碼按確定: