简易计算器单片机实现

任务和要求:
1、用按键和液晶实现最简易计算器,可以完成整数的加减乘除运算;
2、加减乘除分别用上下左右键来代替,回车表示等于,ESC 表示归 0(也可以只用回车键,
第 1 次表示等于,第 2 次表示归 0,循环);
3、在 LCD1602 上显示操作数 1、操作数 2、操作符(+ -/)和运算结果。
扩展部分
1、考虑小数;
2、将操作数 1、操作数 2、操作符(±
/)和运算结果发给串口调试助手。
我的工程下有4个文件:
1.主函数.c

#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
#define slong signed long
#define schar signed char
uchar step = 0;//操作步骤
uchar oprt = 0;//运算类型
double num1 = 0;//操作数1
double num2 = 0;//操作数2
float result = 0;//运算结果
uchar T0RH = 0;//T0重载的高字节
uchar T0RL = 0;//T0重载的低字节
bit exchange = 0;//改变回车和Esc功能
bit flagdot = 0;//小数点记录
uchar div10 = 0;
uchar str2[12];
uchar count_str2=0;
void ConfigTimer0(uint ms);
extern void KeyScan();
extern void KeyDriver();
extern void InitLcd1602();
extern void LcdShowStr(uchar x, uchar y, uchar *str);
extern void LcdAreaClear(uchar x, uchar y, uchar len);
extern void LcdFullClear();
extern void UartRxMonitor(uchar ms);
extern void UartWrite(uchar *buf);
extern void UartDriver();
extern void ConfigUART(uint baud);
void process(uchar *str);
uchar LongToString(uchar *str, slong dat);
uchar FloatToString(uchar *str, double dat);
void main()
{
	EA = 1;//开关总中断
	ConfigTimer0(1);//配置T0定时1ms
	InitLcd1602();//初始化液晶
	LcdShowStr(15, 1, "0");//初始时显示一个数字0
	ConfigUART(9600);
	while(1)
	{
		KeyDriver();//按键驱动
		UartDriver();
	}
}
void copy(uchar *str,uchar length)
{
	uchar i,j,str4[3];
	for(i=count_str2;i<count_str2+length;i++)
	{
		j = i - count_str2;
		str2[i] = *str++;
		//str2[i] = '1';
	}
	count_str2 += length;
	//UartWrite(str2);
	//str4[0]='\r';
	//str4[1]='\n';
	//str4[2]='\0';
	//UartWrite(str4);
}
//长整形转换为字符串,str-字符串指针,dat-待转换数
uchar LongToString(uchar *str, slong dat)
{
	schar i = 0;
	uchar len = 0;
	uchar buf[12];

	if(dat < 0)	//如果时负数,首先取绝对值,并在指针上加上负号
	{
		dat = -dat;
		*str++ = '-';
		len++;
	}
	do {	//先转换为低位在前的十进制数组
		buf[i++] = dat%10;
		dat /= 10;
	}  while (dat > 0);
	len += i;	//i最后的值就是有效字符的个数
	while (i-- > 0)//将数组的ASCII值反向拷贝到接收指针上
	{
		*str++ = buf[i] + '0';
	}
	* str = '\0';//加上字符串结束符
	return len;//返回字符串长度
}
//长整形转换为字符串,str-字符串指针,dat-待转换数
uchar FloatToString(uchar *str, double dat)//*str是最终显示的结果
{
	schar i = 0;//总的部分
	schar i1 = 0;//整数部分
	uchar i2 = 0;//小数部分
	uchar len = 0;
	uchar buf[12];
	bit flag = 0;
	double tmp=0;
	unsigned long tmp2;
	unsigned long tmp3;

	if(dat < 0)	//如果时负数,首先取绝对值,并在指针上加上负号
	{
		dat = -dat;
		*str++ = '-';
		len++;					 
	}
	tmp3  = dat;//tmp3取dat的正的部分
	do {	//先转换为低位在前的十进制数组,因为是倒着显示的
		buf[i++] = tmp3%10;
		tmp3 /= 10;
	}  while (tmp3 > 0);
	i1 = i;
	if(dat - (unsigned long)dat > 0.0001)//判断是否存在小数部分
	{
		flag = 1;				        
		tmp = dat;
		tmp = tmp;
		do {								  
			tmp = 10 * tmp;
			tmp2 = tmp;
			buf[i++] = tmp2%10;
		} while(tmp - tmp2 > 0.0001 & i<4);//不能达到很精确
	}
	i2=i1;
	len += i;	//i最后的值就是有效字符的个数
	while (i1-- > 0)//将数组的ASCII值反向拷贝到接收指针上
	{
		*str++ = buf[i1] + '0';
	}
	
	if (flag != 0)
	{
		* str++ = '.';
		while (i2<i)
		{
			*str++ = buf[i2] + '0';	
			//*str++ = (uchar)tmp2%10 + '0';
			i2++;	
			
		}
		len++;
	}
	* str = '\0';//加上字符串结束符	
	return len;//返回字符串长度,加上小数点部分
}	
//显示运算符,显示位置y,运算符类型type
void ShowOprt(uchar y, uchar type)
{
	uchar str3;
	switch(type)
	{
		case 0:str3 = '+';break;//0代表+
		case 1:str3 = '-'; break;
		case 2:str3 = '*'; break;
		case 3:str3 = '/'; break;
		default:break;
	}
	LcdShowStr(0, y, &str3);
	if(y==1)
		copy(&str3,1);//复制操作符
}

//计算器复位,清零变量值,清除屏幕显示
void Reset()
{
	num1 = 0;
	num2 = 0;
	step = 0;
	count_str2 = 0;
	LcdFullClear();
}
//数字键动作函数,n-按键输入的数值
void NumKeyAction(float n)
{
	uchar len;
	uchar str[12];
	uchar i;

	if (step > 1)//如果已经计算完成,则重新开始新的运算
	{
		Reset();
		flagdot=0;
		div10=0;
           exchange = 0;
	}
	if (step == 0)//输入第一操作数
	{
		if(flagdot==0)
		{
			num1 = num1*10 + n;//输入数值累加到原操作数上
			len = LongToString(str, (slong)num1);//新数值转换为字符串
			LcdShowStr(16-len, 1, str);//显示到液晶第二行上
		}
		else
		{
			div10++;
			for(i=0;i<div10;i++)
			{
			 	n=n/10;//每次除以10的次数不一样
			}
			num1 = num1 + n;
			len = FloatToString(str, num1);//新数值转换为字符串
			LcdShowStr(16-len, 1, str);//显示到液晶第二行上
		}
	}
	else//输入第二操作数
	{
		if(flagdot==0)
		{
			num2 = num2*10 + n;//输入数值累加到原操作数上
			len = LongToString(str,(slong)num2);//新数值转换为字符串
			LcdShowStr(16-len, 1, str);//显示到液晶第二行上
		}
		else
		{
			div10++;
			for(i=0;i<div10;i++)
			 	n=n/10;//每次除以10的次数不一样
			num2 = num2 + n;
			len = FloatToString(str, num2);//新数值转换为字符串
			LcdShowStr(16-len, 1, str);//显示到液晶第二行上
		}
	}
}

//运算符按键动作函数,运算符类型为type
void OprtKeyAction(uchar type)
{
	uchar len;
	uchar str[12];

	if (step == 0)//第二操作数尚未输入时响应,即不支持连续操作
	{
		len = FloatToString(str, num1);//第一操作数转换为字符串

		copy(str,len);//复制第一操作数

		LcdAreaClear(0, 0, 16-len);//清除第一行左边的字符位
		LcdShowStr(16-len, 0, str);//字符串靠右显示在第一行
		ShowOprt(1, type);//第二行显示操作符
		LcdAreaClear(1, 1, 14);//清除第二行中间
		LcdShowStr(15, 1, "0");//在第二行最右端显示0
		oprt = type;//记录操作类型
		step = 1;
		flagdot=0;
		div10=0;
	}
}

//计算结果函数
void GetResult()
{
	uchar len;
	uchar str[12];     

	if (step == 1)//第二操作数已经输入时才执行计算
	{
		step = 2;
		switch(oprt)//根据运算符类型计算结果,未考虑溢出问题
		{
			case 0:result =num1 + num2; break;
			case 1:result =num1 - num2; break;
			case 2:result =num1 * num2; break;
			case 3:result =num1 / num2; break;
			default:break;
		}
		len = FloatToString(str, num2);//原第二操作数和运算符显示到第一行
		copy(str,len);	 //复制第二操作符
		copy("=",1);
		ShowOprt(0, oprt);
		LcdAreaClear(1, 0, 16-1-len);
		LcdShowStr(16-len,0,str);
		len = FloatToString(str, result);
		copy(str,len);
		LcdShowStr(0, 1, "=");
		LcdAreaClear(1, 1, 16-1-len);
		LcdShowStr(16-len, 1, str);

		str2[count_str2+1]='\r';
		str2[count_str2+2]='\n';

		//FloatToString(str, count_str2);
		str2[count_str2]='\0';
		UartWrite(str2);
		UartWrite("\r\n\0");
	}
}

//按键动作函数,根据按键码执行相应的操作,keycode-按键码
void KeyAction(uchar keycode)
{
	if ((keycode>='0') && (keycode<='9'))
	{
		NumKeyAction(keycode - '0');
	}
	else if(keycode == 0x26)//向上键表示+
	{
		OprtKeyAction(0);
	}
	else if(keycode == 0x28)//向下键表示-
	{
		OprtKeyAction(1);
	}
	else if(keycode == 0x25)//向左键 *
	{
		OprtKeyAction(2);
	}
	else if(keycode == 0x27)//向右键 /
	{
		OprtKeyAction(3);
	}
	else if(keycode == 0x0D  && exchange == 0 )//回车键,计算结果
	{
		GetResult();
		exchange = ~exchange;
	}
	else if(keycode == 0x0D && exchange == 1 )//Esc键,清除
	{
		Reset();
		LcdShowStr(15, 1, "0");
		exchange = ~exchange;
		flagdot=0;
		div10=0;
	}
	else if(keycode == 0x1B)
	{
		flagdot=1;
	}
}		

//配置并启动T0,ms-T0定时时间
void ConfigTimer0(uint ms)
{
	ulong tmp;
	tmp = 11059200 / 12;
	tmp = (tmp * ms) / 1000;
	tmp = 65536 - tmp;
	tmp = tmp + 28;
	T0RH = (uchar)(tmp >> 8);
	T0RL = (uchar)tmp;
	TMOD &= 0xf0;
	TMOD |= 0x01;
	TH0 = T0RH;
	TL0 = T0RL;
	ET0 = 1;//使能T0中断
	TR0 = 1;//启动T0
}

//T0中断服务函数,执行按键扫描
void InterruptTimer0() interrupt 1
{
	TH0 = T0RH;
	TL0 = T0RL;
	KeyScan();//按键扫描
	UartRxMonitor(1);
}

2.keyboard.c键盘驱动程序

#include <reg52.h>
#define uchar unsigned char 

sbit key_in_1 = P2^4;
sbit key_in_2 = P2^5;
sbit key_in_3 = P2^6;
sbit key_in_4 = P2^7;
sbit key_out_1 = P2^3;
sbit key_out_2 = P2^2;
sbit key_out_3 = P2^1;
sbit key_out_4 = P2^0;

uchar code KeyCodeMap[4][4] = {
	{'1', '2', '3', 0x26},//向上键
	{'4', '5', '6', 0x25},//向左键
	{'7', '8', '9', 0x28},//向下键
	{'0',0x1B, 0x0D, 0x27}//ESC键,回车键,向右键
};

uchar pdata KeySta[4][4] = {
	{1,1,1,1}, {1,1,1,1}, {1,1,1,1}, {1,1,1,1} 
};

extern void KeyAction(uchar keycode);

void KeyDriver()
{
	uchar i, j;
	static uchar pdata backup[4][4] = {
		{1,1,1,1}, {1,1,1,1}, {1,1,1,1}, {1,1,1,1}	
	};
	for (i=0; i<4; i++)
	{
		for (j=0; j<4; j++)
		{
			if(backup[i][j] != KeySta[i][j])
			{
				if (backup[i][j] != 0)//即按键按下时
				{
					KeyAction(KeyCodeMap[i][j]);//调用响应的动作函数
				}
				backup[i][j] = KeySta[i][j];//刷新备份
			}
		}
	}
}

//按键扫描函数
void KeyScan()
{
	uchar i;
	static uchar keyout = 0;//矩阵按键扫描输出索引
	static uchar keybuf[4][4] = {
		{0xff,0xff,0xff,0xff}, {0xff,0xff,0xff,0xff},
		{0xff,0xff,0xff,0xff}, {0xff,0xff,0xff,0xff}
	};
	//将一行的四个键值移入缓冲区
	keybuf[keyout][0] = (keybuf[keyout][0] << 1) | key_in_1;
	keybuf[keyout][1] = (keybuf[keyout][1] << 1) | key_in_2;
	keybuf[keyout][2] = (keybuf[keyout][2] << 1) | key_in_3;
	keybuf[keyout][3] = (keybuf[keyout][3] << 1) | key_in_4;
	//消抖后更新按键状态
	for (i=0; i<4; i++)
	{
		if((keybuf[keyout][i] & 0x0f) == 0x00)
		{
			KeySta[keyout][i] = 0;
		}
		else if((keybuf[keyout][i] & 0x0f) == 0x0f)
		{
			KeySta[keyout][i] = 1;
		}
	}
	//执行下一次的扫描输出
	keyout++;
	keyout &= 0x03;
	switch(keyout)
	{
		case 0:key_out_4 = 1;key_out_1 = 0;break;
		case 1:key_out_1 = 1;key_out_2 = 0;break;
		case 2:key_out_2 = 1;key_out_3 = 0;break;
		case 3:key_out_3 = 1;key_out_4 = 0;break;
		default: break;
	}
}

3.LCD1602.c屏幕显示程序:

#include <reg52.h>

#define uchar unsigned char
#define lcd1602_db P0

sbit lcd1602_rs = P1^0;
sbit lcd1602_rw = P1^1;
sbit lcd1602_e = P1^5;

void LcdWaitReady()
{
	uchar sta;
	lcd1602_db = 0xff;
	lcd1602_rs = 0;
	lcd1602_rw = 1;
	do {
		lcd1602_e = 1;
		sta = lcd1602_db;
		lcd1602_e = 0;
	} while(sta & 0x80);//只是检测是否忙没有执行任何操作,1表示在忙,0表示空闲
}

//向液晶写入一行命令,cmd-待写入命令值
void LcdWriteCmd(uchar cmd)
{
	LcdWaitReady();
	lcd1602_rs = 0;//0为命令
	lcd1602_rw = 0;//0表示写入
	lcd1602_db = cmd;
	lcd1602_e = 1;//产生高脉冲
	lcd1602_e = 0;
}

//向液晶写入一字节数据,dat-待写入数据值
void LcdWriteDat(uchar dat)
{
	LcdWaitReady();
	lcd1602_rs = 1;//1为数据
	lcd1602_rw = 0;//0表示写入
	lcd1602_db = dat;
	lcd1602_e = 1;//产生高脉冲
	lcd1602_e = 0;
}

//设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标
void LcdSetCursor(uchar x, uchar y)
{
	uchar addr;
	if(y==0)
		addr = 0x00 + x;//这意味着第一个显示字符x=0
	else
		addr = 0x40 + x;
	LcdWriteCmd(addr | 0x80);//设置RAM地址
}

/*
//在液晶上显示字符串,和设置函数的功能部分重复
void LcdShowStr(uchar x,uchar y,uchar *str,uchar len)
{
	LcdSetCursor(x, y);	
	while(len--)//通过长度来改变,而静态显示时是通过判断最后一个字符是否为/0,while(*str!='\0')
	{
		LcdWriteDat(*str++);
	}
}
*/

void LcdShowStr(uchar x,uchar y,uchar *str)
{
	LcdSetCursor(x, y);	
	while(*str != '\0')//通过长度来改变,而静态显示时是通过判断最后一个字符是否为/0,while(*str!='\0')
	{
		LcdWriteDat(*str++);
	}
}

//打开光标闪烁效果
void LcdOpenCursor()
{
	LcdWriteCmd(0x0f);
}

//关闭光标显示
void LcdCloseCursor()
{
	LcdWriteCmd(0x0c);
}

//区域清除,清除从(x,y)坐标起始的len个字符位
void LcdAreaClear(uchar x,uchar y,uchar len)
{
	LcdSetCursor(x, y);
	while(len--)
	{
		LcdWriteDat(' ');
	}
} 


//整屏清除
void LcdFullClear()
{
	LcdWriteCmd(0x01);
}


//初始化1602液晶
void InitLcd1602()
{
	LcdWriteCmd(0x38);//写入
	LcdWriteCmd(0x0c);//显示器开,光标关闭
	LcdWriteCmd(0x06);//文字不动,地址自动+1
	LcdWriteCmd(0x01);//清屏操作
}

4.Uart串口通信部分:

#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int

bit flagFrame = 0;//帧接收完成标志
bit flagTxd = 0;//单字节发送完成标志,用来代替TXD中断标志位
uchar cntRxd = 0;//接收字节计数器
uchar pdata bufRxd[64];//接收字节缓冲区

//extern void UartAction(uchar *buf, uchar len);

//串口配置函数,baud-通信波特率
void ConfigUART(uint baud)
{
	SCON = 0x50;//配置串口为模式1
	TMOD &= 0x0F;//清零T1的控制位
	TMOD |= 0x20;//配置T1为模式2
	TH1 = 256 - (11059200/12/32)/baud;//计算T1重载值
	TL1 = TH1;//初值等于重载值
	ET1 = 0;//禁止T1中断
	ES = 1;//使能串口中断
	TR1 = 1;//启动T1
} 

//串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定发送的长度
void UartWrite(uchar *buf)
{
	while(*buf != '\0')
	{
		flagTxd = 0;//清零发送标志
		SBUF = *buf++;//发送一个字节数据
		while(!flagTxd);//等待该字节发送完成
	}
	flagTxd = 0;//清零发送标志
	SBUF = '\n';//发送一个字节数据
	while(!flagTxd);//等待该字节发送完成
}
/*
void UartWrite(uchar *buf, uchar len)
{
	while(len--)
	{
		flagTxd = 0;//清零发送标志
		SBUF = *buf++;//发送一个字节数据
		while(!flagTxd);//等待该字节发送完成
	}
}
*/

//串口数据读取函数,buf-接收指针,len-指定读取长度,返回值实际读到的长度
uchar UartRead(uchar *buf, uchar len)
{
	uchar i;

	if(len > cntRxd)//指定读取长度实际接收到的数据长度
	{
		len = cntRxd;//读取长度设置为实际接收到的数据长度
	}
	for(i=0;i<len;i++)
	{
		*buf++=bufRxd[i];
	}
	cntRxd = 0;//接收计数器清零
	return len;//返回实际读取长度
}

//串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔
void UartRxMonitor(uchar ms)
{
	static uchar cntbkp = 0;
	static uchar idletmr = 0;

	if (cntRxd > 0)//接收计数器大于零时,监控总线空闲时间
	{
		if (cntbkp != cntRxd)//接收计数器发生改变,即刚接收到数据时,清零空闲计时
		{
			cntbkp = cntRxd;
			idletmr = 0;
		}
		else//空闲计时小于30ms时,持续累积
		{
			if (idletmr < 30)//空闲计时小于30ms时,持续累加
			{
				idletmr += ms;
				if (idletmr >= 30)//空闲时间达到30ms时,即判断一帧接收完毕
				{
					flagFrame = 1;//设置帧接收完成标志
				}
			}
		}
	}
	else
	{
		cntbkp = 0;
	}
}

//串口驱动函数,检测数据帧的接收,调度功能
void UartDriver()
{
	uchar len;
	uchar pdata buf[40];
	if(flagFrame)//有命令到达时,读取处理该命令
	{
		flagFrame = 0;
		len = UartRead(buf, sizeof(buf));
		//UartAction(buf, len);
	}
}

//串口中断服务函数
void InterruptUART() interrupt 4
{
	if (RI)//接收到新字节
	{
		RI = 0;//清零接收中断标志位
		if(cntRxd < sizeof(bufRxd))
		{
			bufRxd[cntRxd++] = SBUF;
		}
	}
	if(TI)
	{
		TI = 0;//清零发送中断标志位
		flagTxd = 1;//设置字节发送完成标志
	}
}

结果分析:
①可以实现基础计算功能,以及后面的扩展功能。
②在小数部分还有待改进,因为最多只能输出3位小数牺牲了准确性,应该有能够计算更加准确的算法。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值