单片机模块学习之键盘
轉(zhuǎn)載一位大神的,很全
一篇理解按鍵掃描的思想的博文。
理論
按鍵涉及到的重要知識點就是掃描和消抖了!
關(guān)于掃描,主要三種循環(huán)查詢,定時查詢,中斷響應(yīng),當(dāng)然各有優(yōu)缺點,這里來總結(jié)下先。
1、循環(huán)查詢
在一個循環(huán)函數(shù)里不斷地掃描按鍵值,獲取按下的按鍵。
優(yōu)點:實現(xiàn)簡單。
缺點:消抖需要浪費寶貴的CPU時間,且實時性不足(等待)。
2、定時查詢
在中斷服務(wù)函數(shù)里掃描按鍵活的按鍵值,根據(jù)按鍵按下的值然后存入緩沖區(qū),等主函數(shù)有需要再來處理按鍵消息。(關(guān)于消息機制其實是一個很有意思的東西,這里這樣稱不知道準(zhǔn)不準(zhǔn)確。。。)
優(yōu)點:避免消抖浪費時間,不會丟失捕捉按鍵按下,容易實現(xiàn)按鍵按下,長按,以及彈起等動作的識別。
缺點:需要使用定時器中斷。
3、中斷響應(yīng)
按鍵按下觸發(fā)中斷,獲取相應(yīng)的按鍵值,需要進行消抖處理。
優(yōu)點:實時性好。
缺點:需要微控制器支持中斷,并且消抖浪費CPU資源。
通過上面的分析我們也不難猜出,其實應(yīng)用比較好的還是定時查詢的方式,既可以識別多種按鍵狀態(tài),還不必消抖浪費CPU資源。
實驗
①、獨立按鍵
1-2短接實現(xiàn)矩陣按鍵。
2-3短接實現(xiàn)獨立按鍵。
1個獨立按鍵是每2ms掃描一次(進一次中斷保存一下當(dāng)前值),獲取連續(xù)8個當(dāng)前值,也就是耗費 2*8 = 16ms。
獨立按鍵,同時使用一個數(shù)碼管實現(xiàn)按一下+1的操作。(注意J5插針在右邊)
/* ******************************************************************************* * 文件名: * 描 述: * 作 者:CLAY * 版本號:v1.0.0 * 日 期: * 備 注:S4每次加1,S5每次加2,S6每次加3,S7每次加4 * ******************************************************************************* */#include <stc15.h>sbit KEY_IN_1 = P3^3; sbit KEY_IN_2 = P3^2; sbit KEY_IN_3 = P3^1; sbit KEY_IN_4 = P3^0;typedef unsigned char u8; typedef unsigned int u16; typedef unsigned long u32;u8 code LedChar[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; u8 LedBuff[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; u8 KeySta[4] = {1, 1, 1, 1}; u8 KeyCodeMap[4] = {'1', '2', '3', '4'};u8 T0RH; u8 T0RL; u16 cnt = 0;void CloseFucker(); void ConfigTimer0(u16 ms); void ShowNumber(u16 dat); void KeyDriver();void main() {CloseFucker();ConfigTimer0(2);//2ms一掃。EA = 1;ShowNumber(0);while(1){ KeyDriver();} }void KeyAction(u8 keycode) {if(keycode == '1'){cnt += 1;ShowNumber(cnt);} else if(keycode == '2'){cnt += 2;ShowNumber(cnt);} else if(keycode == '3'){cnt += 3;ShowNumber(cnt);} else if(keycode == '4'){cnt += 4;ShowNumber(cnt);} }void KeyDriver() {u8 i;static u8 backup[4] = {1, 1, 1, 1};for(i=0; i<4; i++){if(KeySta[i] != backup[i]){if(backup[i] != 0){KeyAction(KeyCodeMap[i]);}backup[i] = KeySta[i];}} }void CloseFucker() {P2 = (P2 & 0x1F) | 0x80;P0 = 0xFF;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xA0;P0 = 0xAF;P2 = P2 & 0x1F; }void ConfigTimer0(u16 ms) {u32 tmp;tmp = 11059200 / 12;tmp = (tmp * ms) / 1000;tmp = 65536 - tmp;T0RH = (u8)(tmp >> 8);T0RL = (u8)tmp;TMOD &= 0xF0;TMOD |= 0x01;TH0 = T0RH;TL0 = T0RL;ET0 = 1;TR0 = 1; }void ShowNumber(u16 dat) {char i;u8 buf[8];for(i=0; i<8; i++){buf[i] = dat % 10;dat /= 10;}for(i=7; i>0; i--){if(buf[i] == 0)LedBuff[i] = 0xFF;elsebreak;}for( ; i>=0; i--){LedBuff[i] = LedChar[buf[i]];} }void LedScan() {static u8 index = 0;P2 = (P2 & 0x1F) | 0xE0;P0 = 0xFF;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xC0;P0 = 0x80 >> index;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xE0;P0 = LedBuff[index];P2 = P2 & 0x1F;if(index < 7)index++;elseindex = 0; }void KeyScan() {u8 i;static u8 keybuff[4] = {0xFF, 0xFF, 0xFF, 0xFF};keybuff[0] = (keybuff[0] << 1) | KEY_IN_1;keybuff[1] = (keybuff[1] << 1) | KEY_IN_2;keybuff[2] = (keybuff[2] << 1) | KEY_IN_3;keybuff[3] = (keybuff[3] << 1) | KEY_IN_4;for(i=0; i<4; i++){if(keybuff[i] == 0xFF){KeySta[i] = 1;}else if(keybuff[i] == 0x00){KeySta[i] = 0;}else{}} } void interruptTimer0() interrupt 1 {TH0 = T0RH;TL0 = T0RL; LedScan();KeyScan(); }②、矩陣鍵盤
1個獨立按鍵的時候,一端是接地的。
同理,矩陣按鍵無非就是軟件設(shè)置分別接地而已。
關(guān)于掃描時間,這里如果還是2ms, 8個掃描值的話。那么4行按鍵,每個按鍵掃8次,也就是2*4*8 = 64ms……有點長了。我們改成,1ms一掃,每個按鍵掃4次。1*4*4 = 16ms 和 1個獨立按鍵的時間一樣!
矩陣按鍵映射關(guān)系
/* ******************************************************************************* * 文件名: * 描 述: * 作 者:CLAY * 版本號:v1.0.0 * 日 期: * 備 注:顯示對應(yīng)的0-9 * ******************************************************************************* */#include <stc15.h>typedef unsigned char u8; typedef unsigned int u16; typedef unsigned long u32;sbit KEY_OUT_1 = P3^0; sbit KEY_OUT_2 = P3^1; sbit KEY_OUT_3 = P3^2; sbit KEY_OUT_4 = P3^3; sbit KEY_IN_4 = P3^4; sbit KEY_IN_3 = P3^5; sbit KEY_IN_2 = P4^2; sbit KEY_IN_1 = P4^4;u8 code LedChar[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; u8 LedBuff[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };u8 KeySta[4][4] = {{1, 1, 1, 1}, {1, 1, 1, 1,}, {1, 1, 1, 1}, {1, 1, 1, 1} };u8 KeyCodeMap[4][4] = { {'1', '2', '3', 0x26},{'4', '5', '6', 0x25},{'7', '8', '9', 0x28},{'0', 0x1B, 0x0D, 0x27} };u8 T0RH; u8 T0RL;void CloseFucker(); void ConfigTimer0(u16 ms); void KeyDriver();void main() {CloseFucker();ConfigTimer0(1);EA = 1;while(1){KeyDriver();} }void ShowNumber(u8 dat) {char i;u8 buf[8];for(i=0; i<8; i++){buf[i] = dat % 10;dat /= 10;}for(i=7; i>0; i--){if(buf[i] == 0)LedBuff[i] = 0xFF;elsebreak;}for( ; i>=0; i--){LedBuff[i] = LedChar[buf[i]];} }void KeyAction(u8 keycode) {if((keycode >= '0') && (keycode <= '9')){ShowNumber(keycode - '0');} }void KeyDriver() {u8 i, j;static u8 backup[4][4] = {{1, 1, 1, 1}, {1, 1, 1, 1,}, {1, 1, 1, 1}, {1, 1, 1, 1}};for(i=0; i<4; i++){for(j=0; j<4; j++){if(KeySta[i][j] != backup[i][j]){if(backup[i][j] != 0){KeyAction(KeyCodeMap[i][j]);}backup[i][j] = KeySta[i][j];}}} }void CloseFucker() {P2 = (P2 & 0x1F) | 0x80;P0 = 0xFF;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xA0;P0 = 0xAF;P2 = P2 & 0x1F; }void ConfigTimer0(u16 ms) {u32 tmp;tmp = 11059200 / 12;tmp = (tmp * ms) / 1000;tmp = 65536 - tmp;T0RH = (u8)(tmp >> 8);T0RL = (u8)tmp;TMOD &= 0xF0;TMOD |= 0x01;TH0 = T0RH;TL0 = T0RL;ET0 = 1;TR0 = 1; }void LedScan() {static u8 index = 0;P2 = (P2 & 0x1F) | 0xE0;P0 = 0xFF;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xC0;P0 = 0x80 >> index;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xE0;P0 = LedBuff[index];P2 = P2 & 0x1F;if(index < 7)index++;elseindex = 0; }void KeyScan() {u8 i;static u8 keyout = 0;static u8 keybuff[4][4] = {{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}};switch(keyout){case 0: KEY_OUT_1 = 0; KEY_OUT_4 = 1; break;case 1: KEY_OUT_2 = 0; KEY_OUT_1 = 1; break;case 2: KEY_OUT_3 = 0; KEY_OUT_2 = 1; break;case 3: KEY_OUT_4 = 0; KEY_OUT_3 = 1; break;default : break;}keybuff[keyout][0] = (keybuff[keyout][0] << 1) | KEY_IN_1;keybuff[keyout][1] = (keybuff[keyout][1] << 1) | KEY_IN_2;keybuff[keyout][2] = (keybuff[keyout][2] << 1) | KEY_IN_3;keybuff[keyout][3] = (keybuff[keyout][3] << 1) | KEY_IN_4;for(i=0; i<4; i++){if((keybuff[keyout][i] & 0x0F) == 0x0F)KeySta[keyout][i] = 1;else if((keybuff[keyout][i] & 0x0F) == 0x00)KeySta[keyout][i] = 0;else{}}keyout++;keyout &= 0x03; }void interruptTimer0() interrupt 1 {TH0 = T0RH;TL0 = T0RL;LedScan();KeyScan();}③、長按鍵
如果上面所介紹的都沒有問題了的話,就可以在其上的基礎(chǔ)上再來了解一下長按鍵的實現(xiàn)了! 拿獨立按鍵來說,短按下只加一回,長按一直加,思路也很簡單,用到了閾值的思路,這個要特別注意長按的加入不能影響到短按!
設(shè)置一個像KeySta的全局變量KeyDownTime,用來保存每個按鍵按下的時間累加,只要彈起就清零,這個是在KeyScan()里面進行操作的,也是和KeySta狀態(tài)再一起進行判斷的!
然后還需要個TimeThr這個在KeyDriver()里面,初始值為1000。如果檢測到按下,執(zhí)行按鍵動作函數(shù),繼續(xù)往下執(zhí)行,如果檢測到某個按鍵的KeyDownTime不為0,再判斷是否大于閾值,大于閾值也要執(zhí)行按鍵動作函數(shù),然后讓閾值增大,調(diào)節(jié)閾值增量可以控制增長速度。一旦KeyDownTime等于0,就是按鍵彈起來了,讓閾值回歸1000。
看下怎么實現(xiàn)吧!
以獨立按鍵的實驗為例,矩陣按鍵同理
/* ******************************************************************************* * 文件名: * 描 述: * 作 者:CLAY * 版本號:v1.0.0 * 日 期: * 備 注:S4每次加1,S5每次加2,S6每次加3,S7每次加4 * ******************************************************************************* */#include <stc15.h>sbit KEY_IN_1 = P3^3; sbit KEY_IN_2 = P3^2; sbit KEY_IN_3 = P3^1; sbit KEY_IN_4 = P3^0;typedef unsigned char u8; typedef unsigned int u16; typedef unsigned long u32;u8 code LedChar[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; u8 LedBuff[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; u8 KeySta[4] = {1, 1, 1, 1}; u16 KeyDownTime[4] = {0, 0, 0, 0}; u8 KeyCodeMap[4] = {'1', '2', '3', '4'};u8 T0RH; u8 T0RL; u16 cnt = 0;void CloseFucker(); void ConfigTimer0(u16 ms); void ShowNumber(u16 dat); void KeyDriver();void main() {CloseFucker();ConfigTimer0(2);//2ms一掃。EA = 1;ShowNumber(0);while(1){ KeyDriver();} }void KeyAction(u8 keycode) {if(keycode == '1'){cnt += 1;ShowNumber(cnt);} else if(keycode == '2'){cnt += 2;ShowNumber(cnt);} else if(keycode == '3'){cnt += 3;ShowNumber(cnt);} else if(keycode == '4'){cnt += 4;ShowNumber(cnt);} }void KeyDriver() {u8 i;static u8 backup[4] = {1, 1, 1, 1};static u16 TimeThr[4] = {1000, 1000, 1000, 1000};for(i=0; i<4; i++){if(KeySta[i] != backup[i]){if(backup[i] != 0){KeyAction(KeyCodeMap[i]);}backup[i] = KeySta[i];}if(KeyDownTime[i] > 0){if(KeyDownTime[i] > TimeThr[i]){KeyAction(KeyCodeMap[i]);TimeThr[i] += 200;} }else{TimeThr[i] = 1000;}} }void CloseFucker() {P2 = (P2 & 0x1F) | 0x80;P0 = 0xFF;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xA0;P0 = 0xAF;P2 = P2 & 0x1F; }void ConfigTimer0(u16 ms) {u32 tmp;tmp = 11059200 / 12;tmp = (tmp * ms) / 1000;tmp = 65536 - tmp;T0RH = (u8)(tmp >> 8);T0RL = (u8)tmp;TMOD &= 0xF0;TMOD |= 0x01;TH0 = T0RH;TL0 = T0RL;ET0 = 1;TR0 = 1; }void ShowNumber(u16 dat) {char i;u8 buf[8];for(i=0; i<8; i++){buf[i] = dat % 10;dat /= 10;}for(i=7; i>0; i--){if(buf[i] == 0)LedBuff[i] = 0xFF;elsebreak;}for( ; i>=0; i--){LedBuff[i] = LedChar[buf[i]];} }void LedScan() {static u8 index = 0;P2 = (P2 & 0x1F) | 0xE0;P0 = 0xFF;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xC0;P0 = 0x80 >> index;P2 = P2 & 0x1F;P2 = (P2 & 0x1F) | 0xE0;P0 = LedBuff[index];P2 = P2 & 0x1F;if(index < 7)index++;elseindex = 0; }void KeyScan() {u8 i;static u8 keybuff[4] = {0xFF, 0xFF, 0xFF, 0xFF};keybuff[0] = (keybuff[0] << 1) | KEY_IN_1;keybuff[1] = (keybuff[1] << 1) | KEY_IN_2;keybuff[2] = (keybuff[2] << 1) | KEY_IN_3;keybuff[3] = (keybuff[3] << 1) | KEY_IN_4;for(i=0; i<4; i++){if(keybuff[i] == 0xFF){KeySta[i] = 1;KeyDownTime[i] = 0;}else if(keybuff[i] == 0x00){KeySta[i] = 0;KeyDownTime[i] += 4;}else{}} } void interruptTimer0() interrupt 1 {TH0 = T0RH;TL0 = T0RL; LedScan();KeyScan(); }小結(jié)
1、充分利用獨立按鍵和矩陣按鍵再次感受模塊化編程的便利,應(yīng)用層和底層分離,維護修改記憶都方便,一石好幾鳥。
2、注意程序中KEY_IN和KEY_OUT引腳定義以及KeyCodeMap的定義。
總結(jié)
以上是生活随笔為你收集整理的单片机模块学习之键盘的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单片机模块学习之数码管
- 下一篇: 单片机程序框架