【51單片機】矩陣鍵盤逐行掃描法仿真實驗+超詳細Proteus仿真和Keil操作步驟
【51單片機】矩陣鍵盤逐行掃描法仿真實驗+超詳細Proteus仿真和Keil操作步驟
一、環境
我用的是Keil5做編譯工具,用proteus仿真。除了Keil5不知道有沒有其他好用的能生成.hex文件的軟件(要單片機運行是需要生成.hex文件的),Proteus則是一款很好用的仿真軟件,原件很多。當然,之前有試過multisim14,也是非常不錯的軟件,自帶有可以編寫代碼的文本編輯器,但沒找到我想要的原件。所以選擇了Proteus。
二、硬件部分
我們可以先打開Proteus:

1. Proteus新建工程
點擊開始界面的創建工程,先創建一個Proteus的工程。

(注意:最好每個項目單獨一個文件夾,后期的文件很亂很雜)
工程名寫好,選擇好文件夾,后面的可以一直下一步。
2. 添加元件
可以直接點紅色箭頭或者先點擊“元件模式”然后點擊“P”進入元件庫。

可以輸入80C51進行篩選,我用的是第一個80C51。
再找到篩選keypad,我用的是keypad-smallcalc。
接著找LED,選擇的是LED-BARGRAPH-GRN,作為輸出,也方便調試。

選好的元件就在這了。然后點擊就能放置元件。

3.連接線路

4、硬件效果
當然,紅色和藍色的點不是接上線就有的,這是仿真之后的效果。其中,紅色是高電平,藍色是低電平,無色是無電平或脈沖不穩定,黃色為短路。
注意:Proteus的部分原件默認接了電源和接地,所以找不到電源和接地管腳。比如T80C51就是默認了接電源接地,所以沒有20、40管腳。
三、軟件部分
剛剛完成了硬件部分,和真實的硬件一樣,我們都需要有程序才能讓單片機工作?,F在我們來用Keil5編寫程序。雖然課程是用的匯編,但由于個人不太習慣匯編的程序,所以我嘗試的是C語言。目標是做成一個簡單的計算器。
1、Keil5新建工程
菜單欄的project下的new uVision project,選擇好芯片**T80C51,**選擇好地址 (可以和Proteus的工程放一個文件夾)。
然后新建一個.c源文件,并把源文件添加到工程的 Source Group內。
2、代碼:
(1 思路分析
想要做一個計算器,其中有“+、-、*、/ ” 。最開始我想到的是數字和運算符分開存放,然后再處理。后來發現無法預測輸入的數字位數(因為每次只能輸入一位,也不能像在黑窗口那樣回車)。于是我決定把數字變成字符,跟運算符存放在一個char數列里,再分析處理數列,找出數字和運算符。
(2 添加頭文件
添加頭文件,并設置全局變量。
#include<reg51.h>
int cro[4] = {0xFE,0xFD,0xFB,0xF7};//存放行值。分別表示是P2.0口低電平,P2.1低電平…………
char indata[50];//用于存放鍵盤輸入的字符數列
int len=0;//數列的長度
int fnum=0;//用于存放第一個操作數(處理數列得到的數字)
int lnum=0;//用于存放第二個操作數(處理數字得到的數字)
int ans=0;//存放計算結果
char op;//存放運算符
這是51單片機的頭文件,里面包含了51單片機的存儲器、端口等
(3 延時程序
在單片機中延時程序經常用到,延時的方法也很多,有硬件延時、軟件延時,匯編中可能會用nop,或者
MOV R0,100
DJNZ R0,$
在C語言中可以通過空循環來延時,就像下面這樣。當然也有其他方法。
void delay_ms(int n){
int i,j;
for(i = 0; i < n; i++)
for(j = 0; j < 1000; j++);
}//這里的值只是大概寫的,n==1時不一定真是1ms。也可以算準確值。
如果一個循環夠用或者容易控制時間的話,可以不用嵌套。
(4 鍵盤掃描程序
最先嘗試的是矩陣鍵盤的線路反轉法,但是中間出了些問題,暫時放棄了,改用 逐行掃描法。
根據書上的原理,結合以上面的電路圖 寫出程序。

可見,我們的鍵盤連接了P2端口,低4位為行,高四位為列,LED則連接的P1端口。以此為例。
先使行端口(即P2端口低四位)輸出低電平,讀列值。若讀入的列值不為0FFH,則有鍵按下
int keyscan(){
int temp;
P2 = 0xf0;//給P12口送入11110000B
temp = P2 & 0xf0;//讀取列值
}
注意:在C語言里,二進制是前面加0b或0B,八進制是加0,十六進制加0x或0X。如十進制123,二進制0b123或0B123,八進制0123,十六進制0x123或0X123。
這樣就能完成 行輸出低電平,讀取列值的操作。如果列值不為0xf0,表示有鍵按下,有一位變成低電平。
當有鍵按下時,可以加10毫秒延時去抖。再進行逐行掃描。
掃描的過程是第四位逐位輸出低電平,讀不為0xff 的列值
int keyscan(){
int n;
int row;
int temp;
P2 = 0xf0;
temp = P2 & 0xf0;
if(temp != 0xf0){
delay_ms(10)//這里需要加10毫秒延時去抖,
for(n = 0; n < 4; n++ ){//遍歷,低4位逐位輸出0,直到找到按下鍵的列值
P2 = cro[n];//P2口低位輸出0
rol = P2 & 0xf0;//讀高四維
if(rol != 0xf0){
return (row|(cro[n]&0x0f));//找到按下鍵的列值后合成鍵碼,并返回
break;
}
}
}
}
這就是逐行掃描的主要程序。
(5 配置按鍵功能
這時候我們發現,鍵碼找對了,該怎么讓它執行特定的程序呢?比如現在按"1",它并不代表"1"這個數。這個時候我們就需要給鍵配置一個功能或者含義。
我是這樣定義的:
void act(int key){
switch(key){
case 0x77:indata[len++] = '+';P1 = 0x80;break;//輸入的是運算符,輸出運算符按鍵對應的行。并存放到前面定義的數組里,長度+1
case 0xB7: show(); break;//”=“鍵的功能是展示運算結果
case 0xD7:indata[len++] = '0';P1 = 0x00;break;//輸入的是數字,輸出對應的二進制數
case 0xE7:clear(); break;//清零鍵的功能是清零
case 0x7B:indata[len++] = '-';P1 = 0x40;break;
case 0xBB:indata[len++] = '3';P1 = 0x03;break;
case 0xDB:indata[len++] = '2';P1 = 0x02;break;
case 0xEB:indata[len++] = '1';P1 = 0x01;break;
case 0x7D:indata[len++] = '*';P1 = 0x20;break;
case 0xBD:indata[len++] = '6';P1 = 0x06;break;
case 0xDD:indata[len++] = '5';P1 = 0x05;break;
case 0xED:indata[len++] = '4';P1 = 0x04;break;
case 0x7E:indata[len++] = '/';P1 = 0x10;break;
case 0xBE:indata[len++] = '9';P1 = 0x09;break;
case 0xDE:indata[len++] = '8';P1 = 0x08;break;
case 0xEE:indata[len++] = '7';P1 = 0x07;break;
default:break;
}
}
到這里我們的基本要求已經完成。接下來是完善的部分。
(6 補坑
剛才用到而沒有定義的函數,show()clear()?,F在就讓我們來把這兩個函數寫出來。
首先是"="號鍵的功能函數show()
void show(){
decode();//這是一個把存放按鍵的字符數組變成可以運算的數字的函數。
operat();//把字符型的數字變成int型的數字后,就該計算了。這是運算函數。
P1 = ans;//最終要把運算結果輸出到LED。由于之前定義的是全局變量,所以不需要傳參數
}
接下來是清零按鍵的功能,清零。
void clear(){
int i;
for(i=0;i<len;i++){//清空數組。數組沒清空可能會影響第二次計算。
indata[i] = '\0';
}
len=0;
fnum=0;
lnum=0;
ans=0;
op = '\0';
for(i=0;i<3;i++){//LED閃爍三次,作為提醒。
P1 = 0xff;
delay_ms(20);
P1 = 0x00;
delay_ms(20);
}
}
(7 深度補坑
前面的確做到了按哪個鍵輸出對應的值。但是!??!我們怎么會滿足于此呢?說好的計算器呢?
對!下面就來把運算功能的坑填一填。
首先是把字符數組的數據變成可以數學運算的數字。
void decode(){
int i;
int j;
for(i = 0; i<len;i++){//先找到第一個數
if(indata[i] >= '0' && indata[i] <= '9')
fnum = fnum*10 + (indata[i]-'0');
else break;
}
if(indata[i] == '+' || indata[i]=='-'||indata[i]=='*'||indata[i]=='/'){//然后找到中間的運算符
op = indata[i];i++;
}
for(j = i; j<len;j++){//再找到第二個操作數。
lnum = lnum*10 + (indata[j]-'0');
}
}
是不是so easy?確實,都是基礎??赡芪业乃惴ㄟ€不夠好。暫且這么看著吧,提供一個思路。
然后,然后的然后就是該運算了。這個也非常簡單,用一個多分支語句就能搞定了。
void operat(){
switch(op){
case '+':ans = fnum + lnum;break;
case '-':ans = fnum - lnum;break;
case '*':ans = fnum * lnum;break;
case '/':ans = fnum / lnum;break;
}
}
這樣就能實現運算功能了。
(8 程序入口
最后我們只差一個程序入口函數了。把main() 函數加進去我們的程序就大功告成了。
void main(){
while(1)
{
act(keyscan());
delay_ms(70);
}
}
以上就是我編寫時的思路和實現。
四、讓程序跑起來
現在我們硬件搭建好了,軟件也實現了,接下來就是讓軟件在硬件中跑起來。
1、生成.hex文件
先點擊這個"Option for target"。

然后找到"Output"里的"Creat HEX File",把復選框勾上,然后編譯的時候就能生成.hex文件。
例如這個
當然,這個文件也可能生成在"Object"文件夾里。
記住.hex文件的地址。
2、單片機添加程序文件
在Proteus中雙擊51單片機,進入單片機的屬性窗口

點擊"ProgramFile"后面的框選擇文件,把剛才得到的.hex文件添加進去。點擊左下角的開始仿真按鍵就可以試試自己寫的程序的運行效果了。

輸入了12 * 2 ,輸出的是11000B,即24D;按下清零,LED閃爍提醒。功能基本實現。
五、 總結
這次時第一次用C寫單片機,先前學的C似乎又能發揮作用了。但是沒目前這個程序還是比較粗糙,可以再改進一下,比如可以做個連續運算的計算器,或者增加其他不一樣的功能,包括多義鍵,以及輸出設備LED也可以優化。
在這個過程中,我也發現自己的算法比較樸實無華,都是些常規的邏輯。希望有其他的做法的小伙伴一起討論。
第一篇文章,還有點小興奮……
浙公網安備 33010602011771號