51基于OLED的高精度计算器设计

首先,第一次发帖,请多多包含,有不妥的地方请指正。

51基于OLED计算器

这个是我一个实训课作业,我选的是矩阵键盘的应用最后选了计算器。然后有要求精度至少达到小数点后6位(因为int型最多可以到小数点后5位,老师为了给我们增加难度!),且不使用浮点型(我用的long int 占4个Byte范围0~+4 294 967 295),要求计算器有删除撤销删除清除等功能按键。并且要通过OLED显示实时操作

首先要用OLED,IIC协议必不可少,我也是边学边做,这里推荐看其他大佬的讲解(很详细的,侵删)IIC原理超详细讲解---值得一看_Z小旋-CSDN博客_iic原理

OLED驱动程序商家一般都给了,但是老师还是建议我们写一写,因为我们专业就是电子专业,本来就是做电路设计,看时序。这里也不详讲,放一张ssd1306的命令图自己去参透吧!

 矩阵键盘:

这个应该不陌生吧,采用的4x4矩阵的按键,按键对应值如图

#include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include"keyboard.h"
#include"delay.h"

#define KeyPort P2

/*------------------------------------------------
按键扫描函数,返回扫描键值
------------------------------------------------*/
unsigned char KeyScan(void)  //键盘扫描函数,使用行列反转扫描法
{
 unsigned char cord_h,cord_l;//行列值中间变量
 KeyPort=0x0f;            //行线输出全为0
 cord_h=KeyPort&0x0f;     //读入列线值
 if(cord_h!=0x0f)    //先检测有无按键按下
 {
  DelayMs(10);        //去抖
  if((KeyPort&0x0f)!=0x0f)
  {
    cord_h=KeyPort&0x0f;  //读入列线值
    KeyPort=cord_h|0xf0;  //输出当前列线值
    cord_l=KeyPort&0xf0;  //读入行线值

    while((KeyPort&0xf0)!=0xf0);//等待松开并输出

    return(cord_h+cord_l);//键盘最后组合码值
   }
  }return(0xff);     //返回该值
}
/*------------------------------------------------
          按键值处理函数,返回扫键值
           可以根据需要改变返回值

  			| 1 | 2 | 3 | + |  
  			| 4 | 5 | 6 | - |  
  			| 7 | 8 | 9 | * |  
  			| 0 | . | = | / | 
------------------------------------------------*/
unsigned char KeyPro(void)
{
 switch(KeyScan())
 {
  case 0x7e:return '1';break;//0 按下相应的键显示相对应的码值
  case 0x7d:return '2';break;//1
  case 0x7b:return '3';break;//2
  case 0x77:return '+';break;//3

  case 0xbe:return '4';break;//4
  case 0xbd:return '5';break;//5
  case 0xbb:return '6';break;//6
  case 0xb7:return '-';break;//7

  case 0xde:return '7';break;//8
  case 0xdd:return '8';break;//9
  case 0xdb:return '9';break;//a
  case 0xd7:return 'x';break;//b

  case 0xee:return '0';break;//c
  case 0xed:return '.';break;//d
  case 0xeb:return '=';break;//e
  case 0xe7:return '/';break;//f
  default:return 0xff;break;
 }
}

重点来了,本次主讲计算器常见加减乘除的思想

加法:

主要就是考虑小数部分,主要是将小数位数对齐。例如 a=4.52  b=8.9  我们在输入的时候只会记录小数点位置,并不会在存在临时数组内 。所以取的时候直接取的 452  和  89 ,但是会记录扩大的倍数,比如a扩大了100倍,b扩大了10倍。这是我们要将两个数的小数位对其只需做

 a*(100/100)+b*(100/10)   即  452+890=1342  ,之所以用100来除以100和10是因为100是这两个数扩大倍数的最小公倍数。目的就是为了对齐小数位置。

//先将a,b乘以最小取整公倍数,即a,b对其,Rsulat_z存整数部分,Rsulat_fp存小数部分
case '+': Resulat_z=(a*(sign_fp/fp_a) + b*(sign_fp/fp_b))/sign_fp;  
		  Resulat_fp=(a*(sign_fp/fp_a) + b*(sign_fp/fp_b))%sign_fp;
          break;
//fp_a和fp_b分别存放a,b扩大了多少倍

减法:

常见得需要考虑负数情况,负数我们就用b-a(原则就是大减小),不过这样还得考虑小数部分问题。比如当减数的整数部分小于被减数,而小数部分却大于被减数。直接用b-a的话小数部分就会出问题,因此在小数部分还得做一次大小的判断。

case '-':         
             //sign_fp存放的是小数位多的 那个数扩大的倍数  fp_a、fp_b分别存放a、b扩大的倍数                      
		if(a*(sign_fp/fp_a) < b*(sign_fp/fp_b))   //判断a,b大小
			{		
                  // Resulat_z取结果的整数部分,_fp取小数部分
				Resulat_z=(b*(sign_fp/fp_b)-a*(sign_fp/fp_a))/sign_fp;

				if(((a*(sign_fp/fp_a))%sign_fp) > (b*(sign_fp/fp_b)))//判断a , b的小数大小
					{
					Resulat_fp=(((a*(sign_fp/fp_a))%sign_fp) - 
                                (b*(sign_fp/fp_b))%sign_fp)%sign_fp;
					}
				else{
					Resulat_fp=((b*(sign_fp/fp_b))%sign_fp - (a*(sign_fp/fp_a))%sign_fp);
					OLED_ShowChar((lessk_Resulat+1)*8,OLED_Show_H+2,'-',16);//显示一个负号
					lessk_Resulat++;//光标+
					}
			}
		else{													
			Resulat_z=(a*(sign_fp/fp_a) - b*(sign_fp/fp_b))/sign_fp;
			Resulat_fp=(a*(sign_fp/fp_a) - b*(sign_fp/fp_b))%sign_fp;
			}
			break;

乘法:

乘法最简单,直接相乘,然后对这  两个数扩大倍数之积  取整取余即可

扫描二维码关注公众号,回复: 13656192 查看本文章
//a,b取的时候已经取整了,直接运算后对公倍数取整取余即可
case 'x': Resulat_z=(a * b)/(fp_a*fp_b); 
		  Resulat_fp=(a * b)%(fp_a*fp_b);
		  break;

除法:

首先整数相除,考虑除不尽得情况,整数可直接取 a/b,小数先取a%b,之后扩大10的6次方倍再除b  即:((a%b)*1000000)/b(精确到小数点后6位)。

这里要做一个判断,若除数小于被除数,结果直接就取小数部分即可。

带小数的除法:思路也是一样的,先扩大,把两个数的小数位对齐。例 :98.25   /   65.234  

先乘以最小公倍数1000(不知道这样说有没有问题),处理成 98250  /  65234剩余步骤和上面一样

//除法考虑除不尽情况,小数部分算法即将a%b后扩大百万倍在除b,精度即可达到小数点后6位
case '/': if(a>b){
				Resulat_z=a*(sign_fp/fp_a) / b*(sign_fp/fp_b) ; 
				Resulat_fp=((a*(sign_fp/fp_a) % b*(sign_fp/fp_b))*1000000)/b;
				}
		  else  {
				div_x=(a*(sign_fp/fp_a))*1000000/(b*(sign_fp/fp_b));
				}
				break;

结果

 总结:

        第一次写,瞎写,主要是分享一下心得,也不知有没有人愿意看两眼。计算器的话,主要就是精度,然后小数运算。涉及到小数就一个原则,小数对齐后再运算。其实也大可直接用double来做,可能就没有太多数据处理的步骤。但是  老师规定了用整型!

吐槽一下,其实本来是想做《简易计算器》,结果老师说不行,还夸我真会给自己省事^_^......!

工程源码即仿真:链接:https://pan.baidu.com/s/1aybJBOirx-WRb1bFwLkMsw 
                             提取码:brdn

猜你喜欢

转载自blog.csdn.net/cb2506334803/article/details/122383098