居家隔离,闲来无事,手上刚好有一块51单片机和一些显示屏,就想着复现一下当时的计算器。
首先,51单片机的原理就不细讲了,这些网上一搜一大堆,随便跟一个学几天就能会的八九不离十,毕竟是几乎所有这行人的入门板子。
这里就讲一下lcd1602的原理以及引脚的连接方法,先上图:
有刚入门的同学可能会问:不是51单片机嘛,这是个神马玩意儿。
这个叫做Arduino,通俗来讲算是单片机的一种,既然都是单片机,那么他们和lcd1602的连线方式想必差不了多少,上图是我玩儿arduino时练手的截图,其中引脚怎么连接都有说明,把他移到51单片机上,也就实现了lcd1602的显示。这里还要说明一点,有的单片机上边有已经模块化的lcd1602,这时就不用我们自己连线了,直接插上去就行,但我们给它下载的程序必须要和板子上的接线方式对应起来。所以上帝给你打开一扇窗,也会给你关闭一扇门。 这里我没有去找我的51板子的原理图,直接用面包板自己连线,放一张仿真软件里的连线:
如图所示,计算器的功能可以应付大多数计算问题了,其中还有对程序员非常友好的进制转换。
好了,现在线接好了,程序怎么写呢?直接上源码:
/*------------------------------------------------
| 1 | 2 | 3 | + | | c | ^ |十进制转换2进制|删除|
| 4 | 5 | 6 | - |
| 7 | 8 | 9 | * |
| 0 | . | = | / |
------------------------------------------------*/
#include //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include
#include
#define uint unsigned int
#define uchar unsigned char
#define keyport1 P3 //矩阵1
#define keyport2 P1 //矩阵2
sbit rs = P2^4;
sbit rw = P2^5;
sbit en = P2^6;
uchar num,num1,i,sign; //i计数|num,num1记按键|sign记标志位
uchar temp[16]; //最大输入16个
bit firstflag; //等号判断 标志位置
float a=0,b=0; //a第一个数,b第二个数
uchar s; //计数
void delayms(uint xms) //延时
{
uint i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
void zhiling(uchar com) //写指令
{
rs=0;rw=0;en=0;P0=com;
delayms(5);
en=1;
delayms(5);
en=0;
}
void shuju(uchar dat) //写数据
{
rs=1;rw=0;en=0;P0=dat;
delayms(5);
en=1;
delayms(5);
en=0;
}
void init() //初始化
{
zhiling(0x38); //8总线2行显示
delayms(5);
zhiling(0x06); //左移
delayms(5);
zhiling(0x0f);
delayms(5);
zhiling(0x01); //清屏
delayms(5);
}
void Clear(void) // 清屏函数
{
zhiling(0x01);
delayms(5);
}
void shanchu() //删除函数
{
zhiling(0x10);
shuju(26);
zhiling(0x10);
}
void zifu(uchar x,uchar y,uchar Data) // 写入字符函数
{
if(y==0)
{
zhiling(0x80+x);
}
else
{
zhiling(0xc0+x);
}
shuju( Data);
}
void zifuchuan(uchar x,uchar y,uchar *s) // 写入字符串函数
{
while (*s)
{
zifu(x,y,*s);
s++;x++;
}
}
uchar keyscan1(void) //键盘扫描函数,使用行列反转扫描法
{
uchar hang,lie; // 行`列值中间变量
keyport1=0x0f; //行线输出全为0
hang=keyport1&0x0f; //读入列线值
if(hang!=0x0f) //先检测有无按键按下
{
delayms(10); //去抖
if((keyport1&0x0f)!=0x0f)
{
hang=keyport1&0x0f; //读入列线值
keyport1=hang|0xf0; //输出当前列线值
lie=keyport1&0xf0; //读入行线值
while((keyport1&0xf0)!=0xf0); //等待松开并输出
return(hang+lie); //键盘最后组合码值
}
}return(0xff); //返回该值
}
uchar anjian1(void)
{
switch(keyscan1())
{
case 0x7e:return '/';break;/// 按下相应的键显示相对应的码值
case 0x7d:return '*';break;//*
case 0x7b:return '-';break;//-
case 0x77:return '+';break;//+
case 0xbe:return '9';break;//9
case 0xbd:return '6';break;//6
case 0xbb:return '3';break;//3
case 0xb7:return '=';break;//=
case 0xde:return '8';break;//8
case 0xdd:return '5';break;//5
case 0xdb:return '2';break;//2
case 0xd7:return '0';break;//0
case 0xee:return '7';break;//7
case 0xed:return '4';break;//4
case 0xeb:return '1';break;//1
case 0xe7:return '.';break;//.
default:return 0xff;break;
}
}
uchar KeyScan2(void) //键盘扫描函数,使用行列反转扫描法
{
uchar hang1,lie1; // 行`列值中间变量
keyport2=0x0f; //行线输出全为0
hang1=keyport2&0x0f; //读入列线值
if(hang1!=0x0f) //先检测有无按键按下
{
delayms(10); //去抖
if((keyport2&0x0f)!=0x0f)
{
hang1=keyport2&0x0f; //读入列线值
keyport2=hang1|0xf0; //输出当前列线值
lie1=keyport2&0xf0; //读入行线值
while((keyport2&0xf0)!=0xf0); //等待松开并输出
return(hang1+lie1); //键盘最后组合码值
}
}return(0xff); //返回该值
}
uchar anjian2(void)
{
switch(KeyScan2())
{
case 0xee:return' ';break;//清屏 按下相应的键显示相对应的码值
case 0xde:return'^';break;//次幂
case 0xbe:return '&';break;//&
case 0x7e:return 'x';break;//删除
default:return 0xff;break;
}
}
void main()
{
init(); //初始化液晶屏
delayms(2); //延时用于稳定,可以去掉
Clear();
while (1) //主循环
{
num=anjian1();num1=anjian2(); //扫描键盘
/*************************************************************************
第一块按键板P3
*********************************************************************************/
if(num!=0xff) //如果扫描是第一块矩阵有效值则进行处理
{
if(i==0) //输入是第一个字符的时候需要把改行清空,方便观看
Clear();
if((i==16)||('+'==num)||('-'==num)||('*'==num)||('/'==num)||('='==num)) //输入数字最大值16,输入符号表示输入结束
{
i=0; //计数器复位
if(firstflag==0)
{
sscanf(temp,"%f",&a); //如果是输入的第一个数据,赋值给a,
firstflag=1; //并标志位置1,
}
else
{
sscanf(temp,"%f",&b);
} //到下一个数据输入时可以跳转赋值给b
for(s=0;s<16;s++) //赋值完成后把缓冲区清零,防止下次输入影响结果
temp[s]=0;
zifu(0,1,num);
/***********************************************************************************/
/****************************************************************************************/
if(num!='=') //如果不是等号记下标志位
{sign=num;} //
else
{
firstflag=0; //检测到输入=号,判断上次读入的符号
switch(sign)
{
case '+':a=a+b;
break;
case '-':a=a-b;
break;
case '*':a=a*b;
break;
case '/':a=a/b;
break;
case'^':a=pow(a,b); //math函数:a等于a的b次方
break;
default:break;
}
sprintf(temp,"%g",a); //输出浮点型,无用的0不输出
zifuchuan(1,1,temp); //显示到液晶屏
sign=0;a=b=0; //用完后所有数据清零
for(s=0;s<16;s++)
temp[s]=0;
}
}
else if(i<16)
{
if((1==i)&& (temp[0]=='0') ) //如果第一个字符是0,判读第二个字符
{
if(num=='.') //如果是小数点则正常输入,光标位置加1
{
temp[1]='.';
zifu(1,0,num); //输出数据
i++;
} //这里没有判断连续按小数点,如0.0.0
else
{
temp[0]=num; //如果是1-9数字,说明0没有用,则直接替换第一位0
zifu(0,0,num); //输出数据
}
}
else
{
temp[i]=num;
zifu(i,0,num); //输出数据
i++; //输入数值累加
}
}
}
/***********************************************************************
第二块按键P1
**************************************************************************/
if(num1!=0xff) //如果扫描的是第二矩阵有效值则进行处理
{
if(i==0) //输入是第一个字符的时候需要把改行清空,方便观看
Clear();
if(' '==num1) //清屏
{ Clear();
a=b=0; //数据清零
for(s=0;s<16;s++)
temp[s]=0;
temp[i]=num;
i=0;
}
if('x'==num1) //删除键
{
shanchu(); //不能删除符号
temp[i]=num;
i--;i--;
}
if((i==16) || ('^'==num1) || ('&'==num1) || ('='==num))//输入数字最大值16,输入符号表示输入结束
{
i=0; //计数器复位
if(firstflag==0) //如果是输入的第一个数据,赋值给a,并把标志位置1,到下一个数据输入时可以跳转赋值给b
{
sscanf(temp,"%f",&a);
firstflag=1;
}
else
{
sscanf(temp,"%f",&b);
}
for(s=0;s<16;s++) //赋值完成后把缓冲区清零,防止下次输入影响结果
temp[s]=0; //可以去掉
zifu(0,1,num1);
if(num!='=') //判断当前符号位并做相应处理
sign=num1; //如果不是等号记下标志位
}
}
}
}
把源码ctrl+v到你的keil里边,编译一下,输出.hex文件,然后用烧录软件把代码下载到你的单片机里就大功告成了!
上视频效果:
居家隔离闲来无事,花一天时间复现大一计算器
因为板子上只有4X4个按键,所以只展示了一部分功能,后面上的仿真效果。
有不足的地方还请大佬提出改正!