51计算器.docx
《51计算器.docx》由会员分享,可在线阅读,更多相关《51计算器.docx(45页珍藏版)》请在冰豆网上搜索。
![51计算器.docx](https://file1.bdocx.com/fileroot1/2023-6/23/ad685d95-2e46-4f9d-b460-a7c6ea3e06f5/ad685d95-2e46-4f9d-b460-a7c6ea3e06f51.gif)
51计算器
51单片机实现的简易计算器
1.4X4键盘输入,点阵字符型液晶显示。
2.由于所采用的浮点程序库的限制(MCU平台只找到这个……),浮点运算采用3字节二进制补码表示,有效数字6位。
对于输入输出,采用3字节BCD码浮点数格式,有效数字只有4位,因此最终有效数字只有4位。
3.可进行连续输入,例如:
1.23+4.56*8.23/234.8,但是运算结果为从左到右,这也是8位简易计算器的方式。
4.可进行错误判断,溢出、除零等错误将显示一个字符E。
5.由于键盘只有16个按键,安排如下:
+---------------+
|7|8|9|+|
|4|5|6|-|
|1|2|3|*|
|0|.|=|/|
+---------------+
6.按键的缺少导致取消了一些特殊函数,即开根号,三角函数(sin,cos,tan,ctg)的实现,由于这些函数在浮点程序库中均已提供,如果硬件允许,在原来的框架上添加这些附加功能是很容易的(可以看作和+,-,*,/等价的按键操作,调用不同的子程序进行运算即可)
7.按两次=等于清灵。
因为按键实在太少,才采用了这个做法。
8.相应举例:
按键结果说明
--------------------------------------
123+=123按下等号而没有第二个操作数,保留第一个操作数并认为此次运算结束(等号的功能)
123+321/1114.0等价于(123+321)/111
2.3+5.4=/0.1+77等号后直接按/,则将前面的运算结果作为第一个操作数
1/0=E错误显示
9.不足
使用3字节的浮点数表示,不可避免的带来了数表示的不精确,加上有效数字比较少,因此计算结果很容易产生误差,尤其是进行连续多次运算后,结果和精度较高的科学计算器的误差会很快达到0.01以上,当然这个差距和所测试的用例也有关系,4位有效数字导致了数字123456只能表示为123400,最后两位有效数字被摒弃了。
同时,虽然纯整数可以进行较为高精度的运算,实现也较为容易,但是考虑到要和浮点数混合在一起处理,如果在算法上分别考虑整数和浮点数,整个程序框架代码将会膨胀不少,因此将其简化为统一作为浮点数对待。
10.源代码
2000行左右(含注释、空行),其中浮点程序库约900行。
其余为键盘输入扫描、液晶输出显示和按键处理程序。
文件大小47.2KB
-=-=-=-=-=-=-=-=-=-=-=-=SOURCECODE=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;;
;;显示程序符号定义
;;
COMEQU45H
DATEQU46H
CW_ADDEQU5FFCH
CR_ADDEQU5FFDH
DW_ADDEQU5FFEH
DR_ADDEQU5FFFH
;子程序说明:
;寄存器工作组使用
;WAITKEY,00
;KEYPRESSED,MAKENUM,RES2RAW等子程序,01
;FLOATINGLIB,10
;CALCULATE使用01,当调用浮点程序库时10
;显示子程序,11
;======================================================================
;工作区使用注意:
;-=-=1.位寻址区=-=-=-
;20H-21H两个字用作位状态(位地址00H-0FH)
;23H-27H;NOTUSE!
ESP.23H
;28H-2FH为显示区域与记录输入数字所用,RAWIN
PNTBBIT10H;小数点位指示(位地址,POINTBIT)
POSBBIT11H;位置位,POSB=1表示创建BCD低位,POSB=0表示创建BCD高位
DPBBIT12H;大于1的浮点数MAKENUM时,指示小数点是否已经加入
;04H-08H,ARBITRARYUSE
BYTE2BIT14H;SEEBYTE2USAGE(CTRL+F,FINDIT.)
;STAT保存整个计算器的运行状态!
;BITASSIGNMENT:
;.7:
ERROR
;.6:
FLOAT
;.5&.4:
OPERATOR
;VALUEMEANING:
;00:
ADD
;01:
SUB
;10:
MUL
;11:
DIV
;.3:
OPERATORPRESSED
;.2:
CONSTRUCTINGNUM2
;.1:
CONSTRUCTINGNUM1
;.0:
EQUALSIGNPRESSED
STATEQU20H
DCOUNTEQU21H;数字位数计数(DIGITCOUNT),只是RAWIN中有效字节
INPUTEQU2AH;键盘输入暂存
RAWPTREQU2BH;显示缓冲区指针
NUMPTREQU2CH;当前组建数字的指针
TEMP1EQU2DH;临时存储,一般用于临时保存R0,R1
TEMP2EQU2EH;以切换寄存器组,UNUNSED...07.25.NIGHT
;-=-=2.IRAM=-=-
;30H-33H为第一个操作数
;34H-37H为第二个操作数
;38H-3FH为显示区域
;48H-4FH为临时存储区域
;结果则存储在第一个操作数位置
NUM1EQU30H
NUM2EQU34H
RAWINEQU38H
TEMPEQU48H
;-=-=-=3.REGISTERS=-=-=-
;键盘扫描使用寄存器组0,浮点程序使用寄存器组2
;其余数字组合部分用寄存器组1
;测试程序使用寄存器组3
;ASCIITABLE
;'.'->2EH
;'+'->2BH
;'-'->2DH
;'*'->2AH
;'/'->2FH
;'='->3DH
;/////////////////////////////////////////////////////////////////////;///;///THE[MAIN]PROGRAM
;///ZEROX@2005.7.14
;////////////////////////////////////////////////////////////////////ORG0000H
LJMPMAIN
ORG0030H
MAIN:
;全局初始化
MOVSP,#60H;堆栈
MOVIE,#00H;禁止所有中断
;寄存器组00
CLRRS1
CLRRS0
;工作区IRAM(20H-5FH)默认全为0
INIT20TO5F:
MOVR0,#20H;STARTAT20H
MOVR7,#40H;64BYTESTOZERO
LOOP20TO5F:
MOV@R0,#00H
INCR0
DJNZR7,LOOP20TO5F
;---------------------------------
SETBSTAT.0;初始状态为等号状态
MOVR7,#00H
MOVSCON,#00H;串行工作方式0
;-------------------------------------
;;DISPLAYINIT
;-------------------------------------
LCALLLCDINIT
MOVCOM,#06H
LCALLPR1
MOVCOM,#0C0H
LCALLPR1
MAIN_LOOP:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;键盘输入
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;获取键盘输入,使用寄存器组00
CLRRS1
CLRRS0
;获取输入
MOVA,#0F0H
WAITKEY:
NOP
CJNEA,#0F0H,WAITKEY_OK
LCALLKEY
SJMPWAITKEY
WAITKEY_OK:
MOVA,R7
MOVINPUT,A;保存键盘输入到INPUT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;按键响应(内部处理)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;处理键盘输入,使用寄存器组01
CLRRS1
SETBRS0
LCALLKEYPRESSED
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;显示
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LCALLDISPLAY
SJMPMAIN_LOOP
;========================================================
;==
;==SUBROUTINES
;==
;========================================================
KEYPRESSED:
;键盘输入保存在A中,同时也保存在INPUT中
ANLA,#0F0H;屏闭低4位字节
JNZNONDIGIT;高位非零,不是数字
;=========================================
;按键为数字
;否则按下的是数字,添加到显示缓冲区
MOVA,INPUT;取回数字,高位已经是0
;如果之前处于“等号”状态,则此为NUM1
JBSTAT.0,NEWNUM1
;如果之前处于运算符状态,则此为NUM2
JBSTAT.3,NEWNUM2
;如果处于第一个数字状态
JBSTAT.1,INNUM1
;如果处于第二个数字状态
JBSTAT.2,INNUM2
;否则出错!
!
SETBSTAT.7;ERRORBIT
RET;KEYPRESSED直接结束
;--------------------------
NEWNUM1:
ANLSTAT,#0F0H;操作状态清零(低4位)
SETBSTAT.1;计算器状态改为NUM1
;同时清除浮点运算状态
CLRSTAT.6;STAT.6->FLOATINGPOINT
SJMPNEWNUM
NEWNUM2:
ANLSTAT,#0F0H;操作状态清零(低4位)
SETBSTAT.2;计算器状态改为NUM2
SJMPNEWNUM
NEWNUM:
;准备开始一个新的操作数,首先清除显示缓冲区
MOVRAWPTR,#RAWIN;指向开始
;清除小数点标志位
CLRPNTB
MOVDCOUNT,#00H;数字个数清零
;判断数字是否为0,0则忽略
JZIGNORE0
;非零数字,保存
MOVRAWIN,A;此时RAWPTR单元的值,也就是地址
;就是RAWIN
INCRAWPTR
;数字位数增1
INCDCOUNT
IGNORE0:
RET
;--------------------------
INNUM1:
INNUM2:
MOVR2,DCOUNT
CJNER2,#08H,INNUM_OK
;数字个数已经达到最大值,忽略本次输入
RET
INNUM_OK:
MOVR0,RAWPTR;使用R0间接寻址
MOV@R0,A
INCRAWPTR
INCDCOUNT
RET
;=========================================
;按键非数字
NONDIGIT:
MOVA,INPUT;恢复A
INCA;'.'用0FFH表示,加1后为0
JZDECPNT;DECIMALPOINTPRESSED
DECA;否则不是小数点,减一恢复之
;非数字的情况:
;1.加,减,乘,除:
高4位应该分别为0001,0010,0011,0100
;2.小数点:
代码为0FFH,前面已经考虑
;3.等号:
10000000(80H)
;首先检查是否是等号(最高位为1)
JBACC.7,KEYEQU
;否则作为运算符对待
LJMPKEYOP
;---------------------------------
ERRORND:
;ERROROFNON-DIGIT
;出错…………
SETBSTAT.7
LJMPEXIT
;=======================================================
DECPNT:
;根据计算器状态进行操作
JBSTAT.1,DP1
JBSTAT.2,DP2
JBSTAT.3,DP3
JBSTAT.0,DP0
;ERROR
SETBSTAT.7
LCALLEXIT
DP1:
DP2:
;在数字输入状态下下按下了小数点,如果之前已经按过小数点
;则忽略此次输入,计算器状态字无需修改
JBPNTB,DP_DONE;小数点已经按下
;否则,这是本次操作数输入第一次按下小数点
;首先设置小数点已经按下标志位
SETBPNTB
;否则添加小数点到输入区,也就是显示区
;首先判断第一个数字是不是0
MOVR0,RAWPTR;获取指针用于比较
;如果当前输入位置不是开始(前面已有非零数字存储),直接添加小数点
CJNER0,#RAWIN,DP_ADD
;否则,RAWPTR还是指向RAWIN位置,第一个数字设置为0,
;后再添加小数点
MOVR0,RAWPTR;用R0间接寻址
MOV@R0,#00H
INCRAWPTR
INCDCOUNT
DP_ADD:
MOVR0,RAWPTR;R0间接寻址
MOV@R0,#0FFH;DECIMALPOINT
INCRAWPTR
INCDCOUNT
DP_DONE:
RET
;--------------------
DP0:
;之前的状态为等号,按下小数点后因该开始第一个操作数输入
;设置状态
ANLSTAT,#0F0H
SETBSTAT.1
SJMPDP_NEW
;--------------------
DP3:
;之前处于操作符状态,按下小数点则应该开始第二个操作数输入
;设置状态
ANLSTAT,#0F0H
SETBSTAT.2
;新的操作数开始
SJMPDP_NEW
;--------------------
DP_NEW:
;小数点开始的新的操作数,添加'0''.'两个输入
;初始化
MOVRAWPTR,#RAWIN
MOVDCOUNT,#00H
CLRPNTB
MOVRAWIN,#00H;第一个数字,0
INCRAWPTR
INCDCOUNT
MOVR0,RAWPTR;使用R0间接寻址来存储小数点
MOV@R0,#0FFH;小数点使用0FFH表示
INCRAWPTR
INCDCOUNT
RET
;=======================================================
KEYEQU:
;保存原来的状态
MOVB,STAT
;设置现在的状态为EQU
ANLSTAT,#0F0H;清楚低4位状态
SETBSTAT.0;EQUSTATBIT
;根据原来的状态采取相应的操作
JBB.1,EQU1;原来处于第一个数的输入状态
JBB.2,EQU2;原来处于第二个数的输入状态(NORMAL)
JBB.3,EQU3;原来处于操作符状态
JBB.0,EQU0;原来处于等号状态
;ERROR
SETBSTAT.7
LJMPEXIT
EQU1:
;在第一个数的状态下按了等号,那么第一个数不需要进行计算
;直接作为结果显示,实际上RAWIN就是当前的操作数的显示格式
;因此只需要将第一个数转换成浮点数存储于NUM1
SETBRS0
CLRRS1
MOVR0,#RAWIN;SOURCE
MOVR1,#NUM1;DESTINATION
LCALLMAKENUM;NOTE:
MAKENUM!
!
!
RET
EQU2:
;在第二个数的状态下按了等号,这是最普通的操作
;首先转换操作数二到NUM2位置,然后计算结果
;并将结果转化为RAWIN形式,供DISPLAY显示
MOVR0,#RAWIN
MOVR1,#NUM2;DESTINATION
LCALLMAKENUM;NOTE:
MAKENUM..
;计算结果,结果放在NUM1
LCALLCALCULATE;NOTE:
CALCULATE!
!
!
;结果转换为RAWIN,即从NUM1到RAWIN
LCALLRES2RAW;NOTE:
RES2RAW
NOP
RET
EQU3:
;原来状态为操作符,然后直接按了等号
;本程序采取的措施为:
等1号覆盖前面的操作符
;因此不需要采取任何措施,直接同按下了第一个
;操作数后直接按等号相同,由于按下操作符的时候
;已经处理了第一个操作数,因此这里直接返回
RET
EQU0:
;SYSTEMRESET
;原来状态为等号,然后又按了等号
;第一次按等号的时候已经处理好,这里只需返回
;07.24修改,连续两次等号相当于清0
;首先清除错误状态位
CLRSTAT.7
MOVDCOUNT,#00H;DCOUNT设置为0,显示就为0
RET
;========================================================
;操作符处理,注意:
前面已经判断不是等号
KEYOP:
;保存操作符号:
给定的是10H,20H,30H,40H
;转换为00H,10H,20H,30H.即,减去10H
;DEBUGHERE
CLRC
SUBBA,#10H
;INVARIANT:
除了4,5位,其他位不可能为1
KEYOP_NE:
;KEYOPERATOR,NOERROR
;如果是第一个数之后按的运算符
JBSTAT.1,KOP1
;如果是第二个数之后按的运算符
JBSTAT.2,KOP2
;如果是一个运算符之后按的运算符
JBSTAT.3,KOP3
;如果是按了等号后按的运算符
JBSTAT.0,KOP0
;ERROR
SETBSTAT.7
LJMPEXIT
;------------------
KO_NE_DONE:
;KEYOPERATOR,NOTEQUAL,DONE
;完成相应的操作后,更新STAT到当前状态
;取回INPUT,并减去10H,成为STAT要求的操作符表示
MOVA,INPUT
CLRC
SUBBA,#10H
;运算符信息保存在STAT的4,5位
;先将4,5位清0
ANLSTAT,#0CFH;#11001111B
ORLSTAT,A;设置4,5位
;设置新的计算器状态
ANLSTAT,#0F0H;清除状态(低4位)
SETBSTAT.3;操作符号状态
RET;KEYPRESSED返回
;--------
KOP1:
;如果按了第一个数字之后按了操作符
;则首先将当前显示缓冲区里的数字
;拼合为3字节浮点数
;保存在NUM1位置
;RAWIN保持不变,因此计算器显示的仍然为
;第一个操作数
MOVR0,#RAWIN;需要进行拼凑的数字
MOVR1,#NUM1;目的
LCALLMAKENUM
SJMPKO_NE_DONE
KOP2:
;如果按了第二个数字之后按了操作符,
;首先计算前面的结果,然后结果作为
;第一个数,并设置状态为操作符
;显示区域的数据为前面操作的结果
;即:
需要将3字节的浮点数结果转化
;为可以显示的RAWIN格式。
;创建第二个操作数
MOVR0,#RAWIN
MOVR1,#NUM2
LCALLMAKENUM
;执行计算
LCALLCALCULATE
;结果默认已经存储于到NUM1位置
;但是需要将其转换为RAWIN形式用于DISPLAY子程序的显示
LCALLRES2RAW;NOTE:
RES2RAW
SJMPKO_NE_DONE
KOP3:
;按了一个操作符后又按了另一个操作符,则直接忽略原来的
;前面已经设置了操作符状态,此处无需进行任何操作
SJMPKO_NE_DONE
KOP0:
;按了等号之后按的操作符,则运算结果直接作为第一个
;操作数,因为按等号的时候已经进行了结果运算和显示
;所需要的准备工作(将浮点数转换为RAWIN形式)
;因此这里不需要再作其他工作
SJMPKO_NE_DONE
;=======================================================
EXIT:
SJMP$
;=======================================================
;=======================================================
MAKENUM:
;NOTEE!
!
!
;[R0]为RAWIN
;[R1]为目标,NUM1ORNUM2
;先决条件:
见前面
;后决条件:
NUM1中为二进制浮点数(3字节,补码形式)
;转换结果,RAWIN形式的数字被转换为3字节二进制浮点数
MOVA,R0
MOVR3,A;保存源位置
MOVA,R1
MOVR4,A;保存目的位置
MOVR2,#00H;小数点位置
;保存DCOUNT,子