|
51單片機(jī)8個(gè)IO口掃描檢測(cè)64個(gè)按鍵+數(shù)碼管顯示+程序+Proteus仿真+算法實(shí)現(xiàn)和心法要點(diǎn)講解 - 掃描原理想通了很簡(jiǎn)單,就是通過(guò)一個(gè)IO拉低,其檢測(cè)這一組其他7個(gè)IO口狀態(tài),如果檢測(cè)到有被拉低了,說(shuō)明就有按鍵按下了,因?yàn)闄z測(cè)到拉低的IO口,是被被用來(lái)檢測(cè)的IO口拉低的。相當(dāng)于我用一個(gè)接GND的探針去碰一個(gè)高電平的IO口,肯定是會(huì)拉低的。電路原理的話,利用二極管單向?qū)ㄌ匦裕跈z查過(guò)程中,如果IO口被拉低,只能構(gòu)成一個(gè)回路,依次不斷輪詢進(jìn)行。
- 知道了按鍵掃描原理然后去實(shí)現(xiàn)的過(guò)程并不是很艱難,我覺(jué)得最困難的是調(diào)試過(guò)程遇到的各種問(wèn)題。掌握其原理并不那么復(fù)雜,在寫(xiě)完之后,代碼并不是很順利按照自己的思路去運(yùn)行的,也許就是一個(gè)項(xiàng)目的學(xué)習(xí)過(guò)程。一個(gè)DIY創(chuàng)意可能很簡(jiǎn)單,真正讓其按照自己的想法運(yùn)行還是有很多細(xì)節(jié)點(diǎn)要打通的。
- 小bug折騰的時(shí)間比整個(gè)寫(xiě)代碼花費(fèi)的精力和時(shí)間多得多,想想一個(gè)穩(wěn)定好用的產(chǎn)品都需要幾個(gè)版本的迭代。
總結(jié)要領(lǐng) 最難的地方是,控制56-64最后一排的數(shù)碼管顯示,因?yàn)椋@個(gè)你是對(duì)P0總線端口自身的掃描,最容易出問(wèn)題的地方,也是卡在這個(gè)地方最長(zhǎng)時(shí)間。一定要了解其單片機(jī)運(yùn)行和按鍵掃描原理。在沒(méi)有延時(shí)或打斷的情況下,按鍵動(dòng)作的時(shí)間一定是快不過(guò)單片機(jī)運(yùn)行的速度。所以在處理最后一排按鍵時(shí),需要特別注意,顯示時(shí)要比其他行掃描處理的時(shí)間留長(zhǎng)一點(diǎn),不然就很容易跳數(shù),按下的按鍵,和顯示的數(shù)值不是你想要的結(jié)果,下面我會(huì)將經(jīng)驗(yàn)一一寫(xiě)下來(lái)。
仿真原理圖如下(proteus仿真工程文件可到本帖附件中下載)
- 在處理最后一排按鍵,我有想過(guò)兩個(gè)辦法來(lái)處理邏輯判斷問(wèn)題:
- 利用復(fù)合邏輯來(lái)寫(xiě),很直觀,但是代碼閱讀和可執(zhí)行性看起來(lái)相對(duì)很臃腫一樣,寫(xiě)的時(shí)候很爽,單片機(jī)處理邏輯,運(yùn)行的時(shí)間會(huì)多一些。是我最先想到的第一種辦法,寫(xiě)法如下:
- if(P0==0x7f||P0==0xbf||P0==0xdf||P0==0xef||P0==0xf7||P0==0xfb||P0==0xfd||P0==0xfe)
復(fù)制代碼
- 第二種辦法,通過(guò)二分查找的方式:(為什么可以采用二分查找算法來(lái)快速篩查對(duì)象,是有講究的):二分查找的條件就是注意事項(xiàng),定義的數(shù)組必須是有序序列才行。最優(yōu)方法看不懂不要緊,直接搜算法拿來(lái)用就行!寫(xiě)法如下:
- int Search(uchar arr[], int len, int flag)
- {
- int right = len - 1;
- int left = 0;
- while (left <= right)
- {
- int mid = (right + left) / 2;
- if (arr[mid] > flag)
- {
- right = mid - 1;
- }
- else if (arr[mid] < flag)
- {
- left = mid + 1;
- }
- else
- {
- return arr[mid];
- }
- }
- return 0;
- }
復(fù)制代碼
- 第三種遍歷方法,就不需要參照二分查找算法那樣考慮什么注意事項(xiàng)了,隨便寫(xiě)一個(gè)簡(jiǎn)單的遍歷程序即可,執(zhí)行效率雖然慢一點(diǎn),起碼實(shí)現(xiàn)起來(lái)簡(jiǎn)單,代碼可敲性強(qiáng),比起二分查找算法寫(xiě)起來(lái)。
- uchar libian(uchar a[], int value, int n)
- {
- int i;
- for (i = 0; i < n; i++)
- {
- if (value == a[i])
- {
- return a[i];
- }
- }
- return 0;
- }
復(fù)制代碼
0-64個(gè)按鍵,需要考慮消抖的只有最后一排56-64的8個(gè)按鍵的響應(yīng)。為什么這么說(shuō)呢?這是因?yàn)榘存I從設(shè)計(jì)原理和實(shí)現(xiàn)來(lái)看的。0-56的按鍵不管你怎么長(zhǎng)按還是短按,單片機(jī)給你的響應(yīng)數(shù)值都是一樣的不會(huì)變,但是在處理56-64這8個(gè)按鍵時(shí),是做了特殊處理,連接的是GND,如果敲代碼沒(méi)注意的話,就很容易造成跳數(shù)字,單片機(jī)掃描是通過(guò)按照規(guī)定先給指定的IO口拉點(diǎn),再去檢測(cè)其他7個(gè)IO的電平狀態(tài),所以在處理第56-64按鍵時(shí),如果你操作的按鍵按下時(shí),單片機(jī)掃描按鍵的速度已經(jīng)從你按下那一到彈起前已經(jīng)超過(guò)了你的速度,那么會(huì)造成,單片機(jī)讀取到錯(cuò)誤的響應(yīng)數(shù)據(jù),處理方式如下:- /****************自身端口讀取*********************/
- P0=0xff;//掃描第8行
- // delay(5);
- tmp=P0;
- if (Search(arr, 8, tmp))
- {
- //將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(Search(arr, 8, tmp))
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xfe:
- keynum=57;
- break;//第1行第1個(gè)按鍵按下
- case 0xfd:
- keynum=58;
- break;//第1行第2個(gè)按鍵按下
- case 0xfb:
- keynum=59;
- break;//第1行第3個(gè)按鍵按下
- case 0xf7:
- keynum=60;
- break;//第1行第4個(gè)按鍵按下
- case 0xef:
- keynum=61;
- break;//第1行第5個(gè)按鍵按下
- case 0xdf:
- keynum=62;
- break;//第1行第6個(gè)按鍵按下
- case 0xbf:
- keynum=63;
- break;//第1行第7個(gè)按鍵按下
- case 0x7f:
- keynum=64;
- break;//第1行第8個(gè)按鍵按下
- }
- display();//這里必須單獨(dú)處理P0的IO檢測(cè)和顯示,否則容易跳數(shù)
- delay(80);//阻塞按鍵掃描,防止數(shù)碼管跳變,其他行掃描不需要此處的延時(shí)。
- }
復(fù)制代碼 完整實(shí)現(xiàn)代碼:- #include <reg52.h>
- #include<intrins.h>
- #define uchar unsigned char
- #define uint unsigned int
- //共陰極數(shù)碼管0~9
- uchar code table[]= {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//0-9共陰數(shù)碼管
- uchar code arr[] = {0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe};
- uchar duanZhi[]= {0,0};//保存每段數(shù)碼管顯示位數(shù)的數(shù)值
- sbit P36=P3^6;//數(shù)碼管時(shí)能端
- sbit P37=P3^7;
- sbit ST=P3^0;//定義74HC595移位寄存器
- sbit SH=P3^2;
- sbit DS=P3^1;
- sbit P33=P3^3;
- sbit P34=P3^4;
- sbit P35=P3^5;
- uchar shi,ge;//數(shù)碼管個(gè)位和十位顯示
- uchar tmp;//暫存P0的值
- static uchar keynum=0;//按鍵值
- unsigned char Trg;
- unsigned char Cont;
- static char count=1;
- //毫秒級(jí)延時(shí)
- void delay(uint z)
- {
- uint x,y;
- for(x=z; x>>0; x--)
- for(y=110; y>>0; y--);
- }
- void SendTo595(uchar byteData);
- int Search(uchar arr[], int len, int flag)
- {
- int right = len - 1;
- int left = 0;
- while (left <= right)
- {
- int mid = (right + left) / 2;
- if (arr[mid] > flag)
- {
- right = mid - 1;
- }
- else if (arr[mid] < flag)
- {
- left = mid + 1;
- }
- else
- {
- return arr[mid];
- }
- }
- return 0;
- }
- /*----------------------------------------------------------------------------------
- 顯示
- void display2()
- {
- ge = keynum%10;
- shi = keynum/10;
- duanZhi[0]=table[ge];
- duanZhi[1]=table[shi];
- P34=0x00;
- SendTo595(duanZhi[0]); //
- delay(5);
- P34=0x01;//消隱
- P33=0x00;
- SendTo595(duanZhi[1]);//
- delay(5);
- P33=0x01;//消隱
- }
- ----------------------------------------------------------------------------------*/
- void display()
- {
- ge = keynum%10;
- shi = keynum/10;
- duanZhi[0]=table[ge];
- duanZhi[1]=table[shi];
- //顯示個(gè)位
- P37=0;
- SendTo595(duanZhi[0]); //
- delay(2);
- P37=1;//消隱
- //顯示十位
- P36=0;
- SendTo595(duanZhi[1]);//
- delay(2);
- P36=1;//消隱
- }
- /***********************************************************
- *函數(shù)名 :SendTo595
- *功能 :串行發(fā)送8個(gè)比特(一個(gè)字節(jié))的數(shù)據(jù)給595,再并行輸出
- *參數(shù) :byteData
- ************************************************************/
- void SendTo595(uchar byteData)
- {
- uchar i=0;
- ST = 0; //ST //先拉低,為后面的上升沿做準(zhǔn)備
- for(i; i<8; i++)
- {
- SH = 0;//先拉低,
- if(byteData&0x80)DS=1;
- else DS=0;
- // DS = (byteData&0x80)?1:0;
- byteData = byteData <<1; //該字節(jié)右移一位
- SH = 1;//上升沿,讓串行輸入時(shí)鐘變?yōu)楦唠娖剑⒀訒r(shí)2個(gè)時(shí)鐘周期
- _nop_();
- _nop_();
- SH = 0; //上升沿,讓串行輸入時(shí)鐘變?yōu)楦唠娖剑⒀訒r(shí)2個(gè)時(shí)鐘周期
- }
- /*位移寄存器數(shù)據(jù)準(zhǔn)備完畢,轉(zhuǎn)移到存儲(chǔ)寄存器*/
- ST =1;
- _nop_();
- _nop_();
- ST = 0;
- }
- void key_scan()
- {
- // P0=0xff;
- // delay(6);
- /********************第1行掃描**************************/
- P0=0x7F;//掃描第1行0111 1111
- delay(5);
- if (!Search(arr, 8, tmp))//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0x7e:
- keynum=50;
- break;//第1行第1個(gè)按鍵按下
- case 0x7d:
- keynum=51;
- break;//第1行第2個(gè)按鍵按下
- case 0x7b:
- keynum=52;
- break;//第1行第3個(gè)按鍵按下
- case 0x77:
- keynum=53;
- break;//第1行第4個(gè)按鍵按下
- case 0x6f:
- keynum=54;
- break;//第1行第5個(gè)按鍵按下
- case 0x5f:
- keynum=55;
- break;//第1行第6個(gè)按鍵按下
- case 0x3f:
- keynum=56;
- break;//第1行第7個(gè)按鍵按下
- }
- }
- /********************第2行掃描**************************/
- P0=0xbf;//掃描第2行
- delay(5);
- if(P0!=0xbf)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xbe:
- keynum=43;
- break;//第2行第1個(gè)按鍵按下
- case 0xbd:
- keynum=44;
- break;//第2行第2個(gè)按鍵按下
- case 0xbb:
- keynum=45;
- break;//第2行第3個(gè)按鍵按下
- case 0xb7:
- keynum=46;
- break;//第2行第4個(gè)按鍵按下
- case 0xaf:
- keynum=47;
- break;//第2行第5個(gè)按鍵按下
- case 0x9f:
- keynum=48;
- break;//第2行第6個(gè)按鍵按下
- case 0x3f:
- keynum=49;
- break;//第2行第7個(gè)按鍵按下
- }
- }
- /********************第3行掃描**************************/
- P0=0xdf;//掃描第3行
- delay(5);
- if(P0!=0xdf)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xde:
- keynum=36;
- break;//第3行第1個(gè)按鍵按下
- case 0xdd:
- keynum=37;
- break;//第3行第2個(gè)按鍵按下
- case 0xdb:
- keynum=38;
- break;//第3行第3個(gè)按鍵按下
- case 0xd7:
- keynum=39;
- break;//第3行第4個(gè)按鍵按下
- case 0xcf:
- keynum=40;
- break;//第3行第5個(gè)按鍵按下
- case 0x9f:
- keynum=41;
- break;//第3行第6個(gè)按鍵按下
- case 0x5f:
- keynum=42;
- break;//第3行第7個(gè)按鍵按下
- }
- }
- /********************第4行掃描**************************/
- P0=0xef;//掃描第4行
- delay(5);
- if(P0!=0xef)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xee:
- keynum=29;
- break;//第4行第1個(gè)按鍵按下
- case 0xed:
- keynum=30;
- break;//第4行第2個(gè)按鍵按下
- case 0xeb:
- keynum=31;
- break;//第4行第3個(gè)按鍵按下
- case 0xe7:
- keynum=32;
- break;//第3行第4個(gè)按鍵按下
- case 0xcf:
- keynum=33;
- break;//第4行第5個(gè)按鍵按下
- case 0xaf:
- keynum=34;
- break;//第4行第6個(gè)按鍵按下
- case 0x6f:
- keynum=35;
- break;//第4行第7個(gè)按鍵按下
- }
- }
- /********************第5行掃描**************************/
- P0=0xf7;//掃描第5行
- delay(5);
- if(P0!=0xf7)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xf6:
- keynum=22;
- break;//第5行第1個(gè)按鍵按下
- case 0xf5:
- keynum=23;
- break;//第5行第2個(gè)按鍵按下
- case 0xf3:
- keynum=24;
- break;//第5行第3個(gè)按鍵按下
- case 0xe7:
- keynum=25;
- break;//第5行第4個(gè)按鍵按下
- case 0xd7:
- keynum=26;
- break;//第5行第5個(gè)按鍵按下
- case 0xb7:
- keynum=27;
- break;//第5行第6個(gè)按鍵按下
- case 0x77:
- keynum=28;
- break;//第5行第7個(gè)按鍵按下
- }
- }
- /********************第6行掃描**************************/
- P0=0xfb;//掃描第6行
- delay(5);
- if(P0!=0xfb)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xfa:
- keynum=15;
- break;//第6行第1個(gè)按鍵按下
- case 0xf9:
- keynum=16;
- break;//第6行第2個(gè)按鍵按下
- case 0xf3:
- keynum=17;
- break;//第6行第3個(gè)按鍵按下
- case 0xeb:
- keynum=18;
- break;//第6行第4個(gè)按鍵按下
- case 0xdb:
- keynum=19;
- break;//第6行第5個(gè)按鍵按下
- case 0xbb:
- keynum=20;
- break;//第6行第6個(gè)按鍵按下
- case 0x7b:
- keynum=21;
- break;//第6行第7個(gè)按鍵按下
- }
- }
- /********************第7行掃描**************************/
- P0=0xfd;//掃描第7行
- delay(5);
- if(P0!=0xfd)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xfc:
- keynum=8;
- break;//第7行第1個(gè)按鍵按下
- case 0xf9:
- keynum=9;
- break;//第7行第2個(gè)按鍵按下
- case 0xf5:
- keynum=10;
- break;//第7行第3個(gè)按鍵按下
- case 0xed:
- keynum=11;
- break;//第7行第4個(gè)按鍵按下
- case 0xdd:
- keynum=12;
- break;//第7行第5個(gè)按鍵按下
- case 0xbd:
- keynum=13;
- break;//第7行第6個(gè)按鍵按下
- case 0x7d:
- keynum=14;
- break;//第7行第7個(gè)按鍵按下
- }
- }
- /********************第8行掃描**************************/
- P0=0xfe;//掃描第8行
- delay(5);
- if(P0!=0xfe)//有按鍵按下
- {
- tmp=P0;//將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(tmp)
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xfc:
- keynum=1;
- break;//第8行第1個(gè)按鍵按下
- case 0xfa:
- keynum=2;
- break;//第8行第2個(gè)按鍵按下
- case 0xf6:
- keynum=3;
- break;//第8行第3個(gè)按鍵按下
- case 0xee:
- keynum=4;
- break;//第8行第4個(gè)按鍵按下
- case 0xde:
- keynum=5;
- break;//第8行第5個(gè)按鍵按下
- case 0xbe:
- keynum=6;
- break;//第8行第6個(gè)按鍵按下
- case 0x7e:
- keynum=7;
- break;//第8行第7個(gè)按鍵按下
- }
- }
- /****************自身端口讀取*********************/
- P0=0xff;//掃描第8行
- // delay(5);
- tmp=P0;
- if (Search(arr, 8, tmp))
- {
- //將檢測(cè)到的P0狀態(tài)值賦值給臨時(shí)變量
- switch(Search(arr, 8, tmp))
- { //臨時(shí)變量對(duì)逐個(gè)IO口進(jìn)行查詢
- case 0xfe:
- keynum=57;
- break;//第1行第1個(gè)按鍵按下
- case 0xfd:
- keynum=58;
- break;//第1行第2個(gè)按鍵按下
- case 0xfb:
- keynum=59;
- break;//第1行第3個(gè)按鍵按下
- case 0xf7:
- keynum=60;
- break;//第1行第4個(gè)按鍵按下
- case 0xef:
- keynum=61;
- break;//第1行第5個(gè)按鍵按下
- case 0xdf:
- keynum=62;
- break;//第1行第6個(gè)按鍵按下
- case 0xbf:
- keynum=63;
- break;//第1行第7個(gè)按鍵按下
- case 0x7f:
- keynum=64;
- break;//第1行第8個(gè)按鍵按下
- }
- display();//這里必須單獨(dú)處理P0的IO檢測(cè)和顯示,否則容易跳數(shù)
- delay(80);//阻塞按鍵掃描,防止數(shù)碼管跳變,其他行掃描不需要此處的延時(shí)。
- }
- display();//這里的顯示是保存上一次的顯示值
- }
- void main()
- {
- keynum=0;
- P0=0xff;
- while(1)
- {
- key_scan();
- }
- }
復(fù)制代碼 仿真器件選選擇也是很有講究的。附上截圖
- 注意:二極管選型,不能選擇發(fā)光二極管,有些其他的二極管也不好使,我使用的是10A01
- 注意走線,線路連接中最好不要出現(xiàn)重復(fù)線或者點(diǎn),有多余的線或者連接點(diǎn)最好刪除,不然出問(wèn)題了排查問(wèn)題帶來(lái)難度,有可能也會(huì)影響仿真效果,電阻選擇不能過(guò)大。仿真時(shí),會(huì)比較占電腦內(nèi)存。按鍵操作和響應(yīng)速度可能會(huì)存在卡頓。
我會(huì)附上源碼,提供兩套不同實(shí)現(xiàn)方案的代碼。
8個(gè)IO口檢測(cè)64個(gè)按鍵.zip
(118.94 KB, 下載次數(shù): 42)
2021-9-11 23:57 上傳
點(diǎn)擊文件名下載附件
8個(gè)IO口檢測(cè)64個(gè)按鍵
- 創(chuàng)作不易,寫(xiě)出來(lái)調(diào)試代碼,折騰了我好幾天,對(duì)于有幫助的朋友,希望能用上,避免重復(fù)的道路上采坑。
|
評(píng)分
-
查看全部評(píng)分
|