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