系列文章目录
【51单片机】矩阵键盘逐行扫描法仿真实验+超详细Proteus仿真和Keil操作步骤
【51单片机】点阵LED的显示实验
【51单片机】七段数码管显示实验+详细讲解
【51单片机】矩阵键盘线反转法实验仿真
【51单片机】七段数码管和矩阵键盘的综合实验——计算器
前言
系列文章中的四篇是我学习单片机以来写下的4篇学习记录。在有了以上知识的了解后,我也掌握了部分80C51单片机的编程思想,当然80C51可以挂载很多不同的芯片和设备,还有很多内容需要学习的。就目前而言,对I/O设备的使用有了基础,平时也在学习中编写程序,这让我的小目标——做一个计算器,有了一定的基础。所以趁今天有时间,把这个计算器实现的过程记录下来。
一、程序思路
- 首先,要做一个计算器,并且实现连续运算,键盘的功能就应该有数字键和四则运算符号键,并且,连续按下多个数字键可以得到多位数,即有十位、百位、千位;
- 第二,进行连续运算的第二次符号输入时,即可输出上两个数字的运算结果。
- 第三,按下等号键,输出前两个数字的运算结果,并且可以继续输入符号和数字进行计算,而不代表结束。
- 第三,按下清零键会有跑马灯提示已经清空。
- 第四,数码管采用动态显示时,CPU被显示程序占用,无法在动态显示的同时扫描键盘。所以需要开中断,实现有键按下扫描键盘,无键按下动态显示的效果。
二、键盘线反转法+数码管动态显示
1、硬件仿真
首先应该放一个元件电路图,但是我做了两个,所以这里还是分成两个部分吧。在这一部分,我用的是矩阵键盘的线反转法和数码管的动态显示法来实现这个计算器的功能。当然,用到了数码管的动态显示,同时需要对键盘做出响应,就需要开中断了。于是电路图是这样的:

用到的元件有:80C51、BUTTON、7SEG-MPX4-CC-BLUE、RESPACK-8、4082(四输入的AND GATE),以及POWER。
有了第一次对元件库中的KEYPAD-SMALLCALC的了解,它在采用线反转法时,没法在线反转后正常获取键的位置,当然,这了说的是仿真时出现的问题,属于封装问题,真实硬件则不许考虑这一问题。所以我自己用BUTTON做了一个键盘。
2、软件程序
1)初始化
include头文件,定义全局变量。
| #include<reg51.h> |
| int key=0; |
| int ans=0; |
| int newnum=0; |
| char op='\0'; |
| int b[4]={10,10,10,10}; |
| int digit[11]={0x3f,0x06,0x5B,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f,0x71}; |
| int cs[4]={0x0E,0x0D,0x0B,0x07}; |
| int shownum=0; |
2)键盘扫描程序
| void keyscan(){ |
| int temp; |
| temp=P1&0x0f; |
| if(temp!=0x0f){ |
| delayms(1); |
| temp=P1&0x0f; |
| if(temp!=0x0f){ |
| P1=0xf0; |
| key=temp|(P1&0xf0); |
| } |
| } |
| while(P1!=0xf0); |
| 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; |
| case 0xD7:output();break; |
| 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)主函数,把主要框架搭起来
| void main(){ |
| int i; |
| EA=1; |
| EX0=1; |
| IT0=0; |
| P1=0x0f; |
| while(1){ |
| for(i=0;i<4;i++){ |
| todigit(shownum); |
| show(); |
| } |
| } |
| } |
5)中断服务程序
| void int0()interrupt 0{ |
| keyscan(); |
| act(); |
| } |
6)其他函数
主要的程序是上面的主程序、键盘扫描程序、中断服务程序和按键功能配置函数。其他的函数可以按照自己需要的功能修改。
①延时程序:
| void delayms(int n){ |
| int i,j; |
| for(i=0;i<n;i++) |
| for(j=0;j<120;j++); |
| } |
②跑马灯函数:
| void turnLight(){ |
| int light=0x03; |
| int i=24; |
| P2=0x00; |
| while(i){ |
| P0=light; |
| light=(light>>(8-1))|(light<<1); |
| i--; |
| delayms(50); |
| } |
| } |
③显示函数:
| void show(){ |
| int i; |
| for(i=0;i<4;i++){ |
| P2=cs[i]; |
| P0=digit[b[i]]; |
| delayms(15); |
| } |
| } |
④清零函数:
| void clear(){ |
| int i; |
| for(i=0;i<4;i++){ |
| b[i]=10; |
| } |
| key=0; |
| ans=0; |
| newnum=0; |
| op='\0'; |
| shownum=0; |
| } |
⑤运算函数:
| 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'){ |
| operat(); |
| } |
| op=p; |
| newnum=0; |
| shownum=ans; |
| } |
⑦保存数字:
| void savenum(int n){ |
| if(op!='\0'){ |
| newnum=newnum*10+n; |
| shownum=newnum; |
| } |
| else{ |
| ans=ans*10+n; |
| shownum=ans; |
| } |
| } |
⑧转化字形码:
| 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; |
| } |
⑨输出函数:
| void output(){ |
| operat(); |
| shownum=ans; |
| } |
3、效果

三、键盘线扫描法+数码管静态显示
1、硬件仿真
在采用逐行扫描法+静态显示的电路中,用到的元件有80C51、KEYPAD-SMALLCALC(键盘)、7SEG-MPX1-CC(七段数码管)×4、74LS273(锁存器)×4、RESPACK-8(电阻)以及GROUND和POWER。
连接的电路图如下:

具体连线方式参照【51单片机】矩阵键盘逐行扫描法仿真实验+超详细Proteus仿真和Keil操作步骤和【51单片机】七段数码管显示实验+详细讲解,这里不再赘述。
2、软件程序
大部分的程序与线反转法+动态显示的程序差不多,主要改变的是键盘扫描程序和显示的程序,并且对部分代码进行了优化。以下是改变的部分。
代码如下:
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}; |
| 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; |
| case 0xD7:output();break; |
| 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)其他函数
主要的程序是上面的主程序、键盘扫描程序、中断服务程序和按键功能配置函数。其他的函数可以按照自己需要的功能修改。
①延时程序:
| void delayms(int n){ |
| int i,j; |
| for(i=0;i<n;i++) |
| for(j=0;j<120;j++); |
| } |
②跑马灯函数:
| void turnlight(){ |
| int i=24; |
| int light=0x03; |
| while(i){ |
| P2=0x00; |
| P0=light; |
| P2=0xff; |
| light=(light>>7)|(light<<1); |
| delayms(50); |
| } |
| } |
③显示函数:
| void show(int num){ |
| int i; |
| todigit(num); |
| for(i=0;i<4;i++){ |
| P2=cs[i]; |
| P0=digit[b[i]]; |
| P2=0xff; |
| } |
| } |
④清零函数:
| void clear(){ |
| ans=newnum=0; |
| op='\0'; |
| turnlight(); |
| show(0); |
⑤运算函数:
| 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); |
| } |
⑦保存数字:
| void savenum(int n){ |
| if(op=='\0'){ |
| ans=ans*10+n; |
| show(ans); |
| } |
| else{ |
| newnum=newnum*10+n; |
| show(newnum); |
| } |
| } |
⑧转化字形码:
| 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; |
| for(j=i+1;j<4;j++){ |
| b[j]=12; |
| } |
| } |
| else b[0]=b[1]=b[2]=b[3]=10; |
| } |
⑨输出函数:
| void output(){ |
| operat(); |
| show(ans); |
| } |
3、效果

总结
此次的小目标已经完成,对单片机的I/O也有了了解,并对前面的代码做出优化,包括显示负数。后面将学习单片机的其它内容,并且在闲暇之余也会学习python,在原有的基础上更进一步。作为一名单片机小白,这段时间的收获颇丰,以后也会继续在CSDN记录我的学习。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」