不積跬步無以至千里,不積小流無以成江海。
?導(dǎo)讀:《藍橋杯單片機組》專欄文章是博主2018年參加藍橋杯的單片機組比賽所做的學(xué)習(xí)筆記,在當年的比賽中,博主是獲得了省賽一等獎,國賽二等獎的成績。成績雖談不上最好,但至少問心無愧。如今2021年回頭再看該系列文章,仍然感觸頗多。為了能更好地幫助到單片機初學(xué)者,今年特地抽出時間對當年的文章邏輯和結(jié)構(gòu)進行重構(gòu),以達到初學(xué)者快速上手的目的。需要指出的是,由于本人水平有限,如有錯誤還請讀者指出,非常感謝。那么,接下來讓我們一起開始愉快的學(xué)習(xí)吧。
通過前面幾節(jié)的學(xué)習(xí),相信對CT107D的外設(shè)驅(qū)動套路都已經(jīng)熟悉的差不多了,今天我們來繼續(xù)學(xué)習(xí)更難一點的外設(shè):按鍵。這里再附一個博主當時學(xué)習(xí)金沙灘51單片機時,所寫的一篇理解按鍵掃描思想的博文:https://blog.csdn.net/ReCclay/article/details/75453278。建議搭配本文一起使用,這樣理解起來會更方便!
一、基礎(chǔ)理論
按鍵涉及到的重要知識點就是掃描和消抖了!
關(guān)于掃描,主要三種循環(huán)查詢,定時查詢,中斷響應(yīng),當然各有優(yōu)缺點,這里來總結(jié)下先。
1.1、循環(huán)查詢
思想:在一個循環(huán)函數(shù)里不斷地掃描按鍵值,獲取按下的按鍵。
優(yōu)點:實現(xiàn)簡單。
缺點:消抖需要浪費寶貴的CPU時間,且實時性不足(等待)。
1.2、定時查詢
思想:在中斷服務(wù)函數(shù)里掃描按鍵“活”的按鍵值,根據(jù)按鍵按下的值然后存入緩沖區(qū),等主函數(shù)有需要時,再來處理按鍵消息。(關(guān)于消息機制其實是一個很有意思的東西,這里這樣稱不知道準不準確。)
優(yōu)點:避免消抖浪費時間,不會丟失捕捉按鍵按下,容易實現(xiàn)按鍵按下,長按,以及彈起等動作的識別。
缺點:需要使用定時器中斷,要知道單片機最寶貴的資源莫過于定時器。
1.3、中斷響應(yīng)
思想:按鍵按下觸發(fā)中斷,獲取相應(yīng)的按鍵值,需要進行消抖處理。
優(yōu)點:實時性好。
缺點:需要微控制器支持中斷,并且消抖浪費CPU資源。
通過上面的分析我們也不難猜出,其實應(yīng)用比較好的還是定時查詢的方式,既可以識別多種按鍵狀態(tài),還不必消抖浪費CPU資源。
二、動手實驗
2.1、獨立按鍵
這里寫圖片描述
1-2短接實現(xiàn)矩陣按鍵;2-3短接實現(xiàn)獨立按鍵。
1個獨立按鍵是每2ms掃描一次(進一次中斷保存一下當前值),獲取連續(xù)8個當前值,也就是耗費 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;
else
break;
}
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++;
else
index = 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();
}
2.2、矩陣鍵盤
1個獨立按鍵的時候,一端是接地的。
同理,矩陣按鍵無非就是軟件設(shè)置分別接地而已。
關(guān)于掃描時間,這里如果還是2ms, 8個掃描值的話。那么4行按鍵,每個按鍵掃8次,也就是248 = 64ms......有點長了。我們改成,1ms一掃,每個按鍵掃4次。144 = 16ms 和 1個獨立按鍵的時間一樣!
矩陣按鍵映射關(guān)系
這里寫圖片描述
程序功能:按鍵對應(yīng)顯示 0-9
/*
*******************************************************************************
* 文件名:
* 描 述:
* 作 者: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;
else
break;
}
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++;
else
index = 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();
}
2.3、長按鍵
如果上面所介紹的都沒有問題了的話,就可以在其上的基礎(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)吧!以獨立按鍵的實驗為例,矩陣按鍵同理。
程序功能:S4每次加1,S5每次加2,S6每次加3,S7每次加4
/*
*******************************************************************************
* 文件名:
* 描 述:
* 作 者: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;
else
break;
}
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++;
else
index = 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é):本篇文章主要介紹了單片機學(xué)習(xí)中的一個重頭戲:按鍵操作,并結(jié)合了常見的按鍵操作方式:獨立按鍵、矩陣按鍵和長按鍵進行了詳細的介紹。在該部分學(xué)習(xí)中比較困難的可能無外乎兩點:一個是中斷思想的理解,另一個則是按鍵掃描思想的理解。記住,這是擺在每個初學(xué)者面前的兩座大山,大家都是這樣慢慢啃過來的。好好琢磨,再假以時日相信你一定會融會貫通的。
希望大家多多支持我的原創(chuàng)文章。如有錯誤,請大家及時指正,非常感謝。