【51單片機】七段數(shù)碼管和矩陣鍵盤的綜合實驗——計算器(思路+仿真電路+源代碼)
系列文章目錄
【51單片機】矩陣鍵盤逐行掃描法仿真實驗+超詳細(xì)Proteus仿真和Keil操作步驟
【51單片機】點陣LED的顯示實驗
【51單片機】七段數(shù)碼管顯示實驗+詳細(xì)講解
【51單片機】矩陣鍵盤線反轉(zhuǎn)法實驗仿真
【51單片機】七段數(shù)碼管和矩陣鍵盤的綜合實驗——計算器
前言
系列文章中的四篇是我學(xué)習(xí)單片機以來寫下的4篇學(xué)習(xí)記錄。在有了以上知識的了解后,我也掌握了部分80C51單片機的編程思想,當(dāng)然80C51可以掛載很多不同的芯片和設(shè)備,還有很多內(nèi)容需要學(xué)習(xí)的。就目前而言,對I/O設(shè)備的使用有了基礎(chǔ),平時也在學(xué)習(xí)中編寫程序,這讓我的小目標(biāo)——做一個計算器,有了一定的基礎(chǔ)。所以趁今天有時間,把這個計算器實現(xiàn)的過程記錄下來。
一、程序思路
- 首先,要做一個計算器,并且實現(xiàn)連續(xù)運算,鍵盤的功能就應(yīng)該有數(shù)字鍵和四則運算符號鍵,并且,連續(xù)按下多個數(shù)字鍵可以得到多位數(shù),即有十位、百位、千位;
- 第二,進行連續(xù)運算的第二次符號輸入時,即可輸出上兩個數(shù)字的運算結(jié)果。
- 第三,按下等號鍵,輸出前兩個數(shù)字的運算結(jié)果,并且可以繼續(xù)輸入符號和數(shù)字進行計算,而不代表結(jié)束。
- 第三,按下清零鍵會有跑馬燈提示已經(jīng)清空。
- 第四,數(shù)碼管采用動態(tài)顯示時,CPU被顯示程序占用,無法在動態(tài)顯示的同時掃描鍵盤。所以需要開中斷,實現(xiàn)有鍵按下掃描鍵盤,無鍵按下動態(tài)顯示的效果。
二、鍵盤線反轉(zhuǎn)法+數(shù)碼管動態(tài)顯示
1、硬件仿真
首先應(yīng)該放一個元件電路圖,但是我做了兩個,所以這里還是分成兩個部分吧。在這一部分,我用的是矩陣鍵盤的線反轉(zhuǎn)法和數(shù)碼管的動態(tài)顯示法來實現(xiàn)這個計算器的功能。當(dāng)然,用到了數(shù)碼管的動態(tài)顯示,同時需要對鍵盤做出響應(yīng),就需要開中斷了。于是電路圖是這樣的:

用到的元件有:80C51、BUTTON、7SEG-MPX4-CC-BLUE、RESPACK-8、4082(四輸入的AND GATE),以及POWER。
有了第一次對元件庫中的KEYPAD-SMALLCALC的了解,它在采用線反轉(zhuǎn)法時,沒法在線反轉(zhuǎn)后正常獲取鍵的位置,當(dāng)然,這了說的是仿真時出現(xiàn)的問題,屬于封裝問題,真實硬件則不許考慮這一問題。所以我自己用BUTTON做了一個鍵盤。
2、軟件程序
1)初始化
include頭文件,定義全局變量。
#include<reg51.h>
int key=0;//存放鍵值
int ans=0;//存放計算結(jié)果
int newnum=0;//存放第二個操作數(shù)
char op='\0';//存放運算符
int b[4]={10,10,10,10};//存放數(shù)字的個位、十位、百位、千位
int digit[11]={0x3f,0x06,0x5B,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f,0x71};//數(shù)字0~9對應(yīng)的七段數(shù)碼管字形碼
int cs[4]={0x0E,0x0D,0x0B,0x07};//四位七段數(shù)碼管的片選信號
int shownum=0;//存放需要顯示的數(shù)
2)鍵盤掃描程序
void keyscan(){
int temp;
temp=P1&0x0f;//P1口高四位給低電平后,讀入低四位
if(temp!=0x0f){//低四位不全為高電平表示有鍵按下
delayms(1);//軟件去抖
temp=P1&0x0f;//再讀一次低四位
if(temp!=0x0f){
P1=0xf0;//第四位給低電平,讀高四位
key=temp|(P1&0xf0);//獲得鍵值
}
}
while(P1!=0xf0);//當(dāng)按鍵抬起結(jié)束一次鍵盤掃描
b[3]=b[2]=b[1]=b[0]=10;
P1=0x0f;
}
3)定義按鍵的功能
void act(){
switch(key){
case 0x77:clear();turnLight();break;//清零鍵,清零+跑馬燈
case 0xB7:savenum(0);break;//按下數(shù)字0,保存數(shù)字0
case 0xD7:output();break;//按下等于號,輸出結(jié)果
case 0xE7:saveop('+');break;//按下運算符,保存符號
case 0x7B:savenum(1);break;
case 0xBB:savenum(2);break;
case 0xDB:savenum(3);break;
case 0xEB:saveop('-');break;
case 0x7D:savenum(4);break;
case 0xBD:savenum(5);break;
case 0xDD:savenum(6);break;
case 0xED:saveop('*');break;
case 0x7E:savenum(7);break;
case 0xBE:savenum(8);break;
case 0xDE:savenum(9);break;
case 0xEE:saveop('/');break;
}
}
4)主函數(shù),把主要框架搭起來
void main(){
int i;
EA=1;//開總中斷
EX0=1;//外部中斷0開中斷
IT0=0;//外部中斷0設(shè)置低電平觸發(fā)
P1=0x0f;
while(1){//顯示程序
for(i=0;i<4;i++){
todigit(shownum);
show();
}
}
}
5)中斷服務(wù)程序
void int0()interrupt 0{
keyscan();
act();
}
6)其他函數(shù)
主要的程序是上面的主程序、鍵盤掃描程序、中斷服務(wù)程序和按鍵功能配置函數(shù)。其他的函數(shù)可以按照自己需要的功能修改。
①延時程序:
void delayms(int n){
int i,j;
for(i=0;i<n;i++)
for(j=0;j<120;j++);
}
②跑馬燈函數(shù):
void turnLight(){
int light=0x03;
int i=24;
P2=0x00;
while(i){
P0=light;
light=(light>>(8-1))|(light<<1);
i--;
delayms(50);
}
}
③顯示函數(shù):
void show(){
int i;
for(i=0;i<4;i++){
P2=cs[i];
P0=digit[b[i]];
delayms(15);
}
}
④清零函數(shù):
void clear(){
int i;
for(i=0;i<4;i++){
b[i]=10;
}
key=0;
ans=0;
newnum=0;
op='\0';
shownum=0;
}
⑤運算函數(shù):
void operat(){
switch(op){
case '+':ans=ans+newnum;break;
case '-':ans=(ans-newnum<0)?10000:ans-newnum;break;
case '*':ans=ans*newnum;break;
case '/':ans=(newnum==0)?10000:ans/newnum;break;
}
newnum=0;
}
⑥保存符號:
void saveop(char p){
if(op!='\0'){//如果已經(jīng)有了符號,即這是第2+次運算,前面已經(jīng)保存了兩個操作數(shù),需要先進行計算
operat();
}
op=p;
newnum=0;
shownum=ans;//顯示ans
}
⑦保存數(shù)字:
void savenum(int n){
if(op!='\0'){//如果已經(jīng)有運算符保存,例如1+2,此時輸入的數(shù)應(yīng)該是2,則數(shù)字保存到newnum
newnum=newnum*10+n;
shownum=newnum;
}
else{//如果沒有存有運算符,例如1+2的順序,此時輸入的應(yīng)該是1,則保存到ans
ans=ans*10+n;
shownum=ans;
}
}
⑧轉(zhuǎn)化字形碼:
void todigit(int n){
int i;
if(n<10000){
for(i=0;i<4;i++){
b[3-i]=n%10;
n=(n-b[3-i])/10;
if(n==0) break;
}
}
else b[3]=b[2]=b[1]=b[0]=10;//數(shù)字超過4位,輸出‘F’
}
⑨輸出函數(shù):
void output(){
operat();
shownum=ans;
}
3、效果

三、鍵盤線掃描法+數(shù)碼管靜態(tài)顯示
1、硬件仿真
在采用逐行掃描法+靜態(tài)顯示的電路中,用到的元件有80C51、KEYPAD-SMALLCALC(鍵盤)、7SEG-MPX1-CC(七段數(shù)碼管)×4、74LS273(鎖存器)×4、RESPACK-8(電阻)以及GROUND和POWER。
連接的電路圖如下:

具體連線方式參照【51單片機】矩陣鍵盤逐行掃描法仿真實驗+超詳細(xì)Proteus仿真和Keil操作步驟和【51單片機】七段數(shù)碼管顯示實驗+詳細(xì)講解,這里不再贅述。
2、軟件程序
大部分的程序與線反轉(zhuǎn)法+動態(tài)顯示的程序差不多,主要改變的是鍵盤掃描程序和顯示的程序,并且對部分代碼進行了優(yōu)化。以下是改變的部分。
代碼如下:
1)初始化
#include <reg51.h>
int ans=0;
int newnum=0;
char op='\0';
int b[4]={12,12,12,0};
int cs[4]={0x07,0x0B,0x0D,0x0E};
int digit[13]={0x3f,0x06,0x5B,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f,0x71,0x40,0x00};//在數(shù)碼管上顯示0~9、'F'、'-'和不顯示
int p1line[4]={0xf7,0xfb,0xfd,0xfe};//逐行掃描法時給鍵盤的行值
2)主程序(鍵盤掃描程序)
void main(void){
unsigned int key1=0;
unsigned int key2=0;
unsigned int key=0;
int i;
show(0);
while (1){
key=key1=key2=0;
P1=0xf0;
key1=P1&0xf0;
if(key1!=0xf0){
delayms(1);
for(i=0;i<4;i++){
P1=p1line[i];
key2=P1&0xf0;
if(key2!=0xf0){
key=(p1line[i]&0x0f)|key1;
break;
}
}
while((P1&0xf0)!=0xf0);
act(key);
}
}
}
3)定義按鍵的功能
void act(){
switch(key){
case 0x77:clear();turnLight();break;//清零鍵,清零+跑馬燈
case 0xB7:savenum(0);break;//按下數(shù)字0,保存數(shù)字0
case 0xD7:output();break;//按下等于號,輸出結(jié)果
case 0xE7:saveop('+');break;//按下運算符,保存符號
case 0x7B:savenum(1);break;
case 0xBB:savenum(2);break;
case 0xDB:savenum(3);break;
case 0xEB:saveop('-');break;
case 0x7D:savenum(4);break;
case 0xBD:savenum(5);break;
case 0xDD:savenum(6);break;
case 0xED:saveop('*');break;
case 0x7E:savenum(7);break;
case 0xBE:savenum(8);break;
case 0xDE:savenum(9);break;
case 0xEE:saveop('/');break;
}
}
4)其他函數(shù)
主要的程序是上面的主程序、鍵盤掃描程序、中斷服務(wù)程序和按鍵功能配置函數(shù)。其他的函數(shù)可以按照自己需要的功能修改。
①延時程序:
void delayms(int n){
int i,j;
for(i=0;i<n;i++)
for(j=0;j<120;j++);
}
②跑馬燈函數(shù):
void turnlight(){
int i=24;
int light=0x03;
while(i){
P2=0x00;
P0=light;
P2=0xff;
light=(light>>7)|(light<<1);
delayms(50);
}
}
③顯示函數(shù):
void show(int num){
int i;
todigit(num);
for(i=0;i<4;i++){
P2=cs[i];
P0=digit[b[i]];
P2=0xff;
}
}
④清零函數(shù):
void clear(){
ans=newnum=0;
op='\0';
turnlight();
show(0);
⑤運算函數(shù):
void operat(){
switch(op){
case '+':ans=ans+newnum;break;
case '-':ans=ans-newnum;break;
case '*':ans=ans*newnum;break;
case '/':ans=(newnum==0)?10000:(ans/newnum);break;
}
newnum=0;
}
⑥保存符號:
void saveop(char p){
if(op!='\0')
operat();
op=p;
show(ans);
}
⑦保存數(shù)字:
void savenum(int n){
if(op=='\0'){
ans=ans*10+n;
show(ans);
}
else{
newnum=newnum*10+n;
show(newnum);
}
}
⑧轉(zhuǎn)化字形碼:
void todigit(int num){
int i;
int j;
if(num<10000&&num>= 0){
for(i=0;i<4;i++){
b[i]=num%10;
num=(num-b[i])/10;
if(num==0) break;
}
for(j=i+1;j<4;j++){//沒有的位上不顯示
b[j]=12;
}
}
else if(num>=-999&&num<0){
num=-num;
for(i=0;i<4;i++){
b[i]=num%10;
num=(num-b[i])/10;
if(num==0) break;
}
b[++i]=11;//顯示負(fù)號
for(j=i+1;j<4;j++){
b[j]=12;
}
}
else b[0]=b[1]=b[2]=b[3]=10;//超出顯示范圍
}
⑨輸出函數(shù):
void output(){
operat();
show(ans);
}
3、效果

總結(jié)
此次的小目標(biāo)已經(jīng)完成,對單片機的I/O也有了了解,并對前面的代碼做出優(yōu)化,包括顯示負(fù)數(shù)。后面將學(xué)習(xí)單片機的其它內(nèi)容,并且在閑暇之余也會學(xué)習(xí)python,在原有的基礎(chǔ)上更進一步。作為一名單片機小白,這段時間的收獲頗豐,以后也會繼續(xù)在CSDN記錄我的學(xué)習(xí)。
浙公網(wǎng)安備 33010602011771號