前言
系列文章中的四篇是我学习单片机以来写下的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
int key=0;//存放键值
int ans=0;//存放计算结果
int newnum=0;//存放第二个操作数
char op='';//存放运算符
int b[4]={10,10,10,10};//存放数字的个位、十位、百位、千位
int digit[11]={0x3f,0x06,0x5B,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f,0x71};//数字0~9对应的七段数码管字形码
int cs[4]={0x0E,0x0D,0x0B,0x07};//四位七段数码管的片选信号
int shownum=0;//存放需要显示的数
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);//当按键抬起结束一次键盘扫描
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;//按下数字0,保存数字0
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;//外部中断0开中断
IT0=0;//外部中断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
}
②跑马灯函数:
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='';
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!=''){//如果已经有了符号,即这是第2+次运算,前面已经保存了两个操作数,需要先进行计算
operat();
}
op=p;
newnum=0;
shownum=ans;//显示ans
}
⑦保存数字:
void savenum(int n){
if(op!=''){//如果已经有运算符保存,例如1+2,此时输入的数应该是2,则数字保存到newnum
newnum=newnum*10+n;
shownum=newnum;
}
else{//如果没有存有运算符,例如1+2的顺序,此时输入的应该是1,则保存到ans
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;//数字超过4位,输出‘F’
}
⑨输出函数:
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
int ans=0;
int newnum=0;
char op='';
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};//在数码管上显示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;//按下数字0,保存数字0
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
}
②跑马灯函数:
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='';
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!='')
operat();
op=p;
show(ans);
}
⑦保存数字:
void savenum(int n){
if(op==''){
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记录我的学习。