📄 计算器.txt
字号:
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/111 4.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.2 KB
-=-=-=-=-=-=-=-=-=-=-=-= SOURCE CODE =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;;
;; 显示程序符号定义
;;
COM EQU 45H
DAT EQU 46H
CW_ADD EQU 5FFCH
CR_ADD EQU 5FFDH
DW_ADD EQU 5FFEH
DR_ADD EQU 5FFFH
; 子程序说明:
; 寄存器工作组使用
; WAITKEY, 00
; KEYPRESSED, MAKENUM, RES2RAW 等子程序, 01
; FLOATING LIB, 10
; CALCULATE使用01, 当调用浮点程序库时 10
; 显示子程序,11
; ======================================================================
; 工作区使用注意:
; -=-= 1. 位寻址区 =-=-=-
; 20H-21H 两个字用作位状态(位地址00H-0FH)
; 23H-27H ; NOT USE! ESP. 23H
; 28H-2FH为显示区域与记录输入数字所用,RAWIN
PNTB BIT 10H ; 小数点位指示(位地址, POINT BIT)
POSB BIT 11H ; 位置位,POSB=1表示创建BCD低位,POSB=0表示创建BCD高位
DPB BIT 12H ; 大于1的浮点数MAKENUM时,指示小数点是否已经加入
; 04H-08H, ARBITRARY USE
BYTE2 BIT 14H ; SEE BYTE2USAGE (CTRL+F, FIND IT.)
; STAT保存整个计算器的运行状态!
; BIT ASSIGNMENT:
; .7: ERROR
; .6: FLOAT
; .5 & .4: OPERATOR
; VALUE MEANING:
; 00: ADD
; 01: SUB
; 10: MUL
; 11: DIV
; .3: OPERATOR PRESSED
; .2: CONSTRUCTING NUM2
; .1: CONSTRUCTING NUM1
; .0: EQUAL SIGN PRESSED
STAT EQU 20H
DCOUNT EQU 21H ; 数字位数计数(DIGIT COUNT),只是RAWIN中有效字节
INPUT EQU 2AH ; 键盘输入暂存
RAWPTR EQU 2BH ; 显示缓冲区指针
NUMPTR EQU 2CH ; 当前组建数字的指针
TEMP1 EQU 2DH ; 临时存储,一般用于临时保存R0, R1
TEMP2 EQU 2EH ; 以切换寄存器组, UNUNSED... 07.25.NIGHT
; -=-= 2. IRAM =-=-
; 30H-33H为第一个操作数
; 34H-37H为第二个操作数
; 38H-3FH为显示区域
; 48H-4FH为临时存储区域
; 结果则存储在第一个操作数位置
NUM1 EQU 30H
NUM2 EQU 34H
RAWIN EQU 38H
TEMP EQU 48H
; -=-=-= 3. REGISTERS =-=-=-
; 键盘扫描使用寄存器组 0,浮点程序使用寄存器组 2
; 其余数字组合部分用寄存器组 1
; 测试程序使用寄存器组 3
; ASCII TABLE
; '.' -> 2EH
; '+' -> 2BH
; '-' -> 2DH
; '*' -> 2AH
; '/' -> 2FH
; '=' -> 3DH
;/////////////////////////////////////////////////////////////////////
;///
;/// T H E [ M A I N ] P R O G R A M
;/// ZEROX@2005.7.14
;////////////////////////////////////////////////////////////////////
ORG 0000H
LJMP MAIN
ORG 0030H
MAIN:
; 全局初始化
MOV SP, #60H ; 堆栈
MOV IE, #00H ; 禁止所有中断
; 寄存器组 00
CLR RS1
CLR RS0
; 工作区IRAM(20H-5FH)默认全为0
INIT20TO5F:
MOV R0, #20H ; START AT 20H
MOV R7, #40H ; 64 BYTES TO ZERO
LOOP20TO5F:
MOV @R0, #00H
INC R0
DJNZ R7, LOOP20TO5F
; ---------------------------------
SETB STAT.0 ; 初始状态为等号状态
MOV R7, #00H
MOV SCON, #00H ; 串行工作方式0
; -------------------------------------
;; DISPLAY INIT
; -------------------------------------
LCALL LCDINIT
MOV COM,#06H
LCALL PR1
MOV COM,#0C0H
LCALL PR1
MAIN_LOOP:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 键 盘 输 入
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 获取键盘输入,使用寄存器组00
CLR RS1
CLR RS0
; 获取输入
MOV A, #0F0H
WAITKEY:
NOP
CJNE A, #0F0H, WAITKEY_OK
LCALL KEY
SJMP WAITKEY
WAITKEY_OK:
MOV A, R7
MOV INPUT, A ; 保存键盘输入到INPUT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 按键响应(内部处理)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 处理键盘输入,使用寄存器组01
CLR RS1
SETB RS0
LCALL KEYPRESSED
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 显 示
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LCALL DISPLAY
SJMP MAIN_LOOP
; ========================================================
; ==
; == S U B R O U T I N E S
; ==
; ========================================================
KEYPRESSED:
; 键盘输入保存在 A 中,同时也保存在 INPUT 中
ANL A, #0F0H ; 屏闭低4位字节
JNZ NONDIGIT ; 高位非零,不是数字
; =========================================
; 按键为数字
; 否则按下的是数字,添加到显示缓冲区
MOV A, INPUT ; 取回数字,高位已经是0
; 如果之前处于“等号”状态,则此为NUM1
JB STAT.0, NEWNUM1
; 如果之前处于运算符状态,则此为NUM2
JB STAT.3, NEWNUM2
; 如果处于第一个数字状态
JB STAT.1, INNUM1
; 如果处于第二个数字状态
JB STAT.2, INNUM2
; 否则出错!!
SETB STAT.7 ; ERROR BIT
RET ; KEYPRESSED 直接结束
; --------------------------
NEWNUM1:
ANL STAT, #0F0H ; 操作状态清零(低4位)
SETB STAT.1 ; 计算器状态改为 NUM1
; 同时清除浮点运算状态
CLR STAT.6 ; STAT.6 -> FLOATING POINT
SJMP NEWNUM
NEWNUM2:
ANL STAT, #0F0H ; 操作状态清零(低4位)
SETB STAT.2 ; 计算器状态改为 NUM2
SJMP NEWNUM
NEWNUM:
; 准备开始一个新的操作数,首先清除显示缓冲区
MOV RAWPTR, #RAWIN ; 指向开始
; 清除小数点标志位
CLR PNTB
MOV DCOUNT, #00H ; 数字个数清零
; 判断数字是否为0,0则忽略
JZ IGNORE0
; 非零数字,保存
MOV RAWIN, A ; 此时RAWPTR单元的值,也就是地址
; 就是RAWIN
INC RAWPTR
; 数字位数增 1
INC DCOUNT
IGNORE0:
RET
; --------------------------
INNUM1:
INNUM2:
MOV R2, DCOUNT
CJNE R2, #08H, INNUM_OK
; 数字个数已经达到最大值,忽略本次输入
RET
INNUM_OK:
MOV R0, RAWPTR ; 使用R0间接寻址
MOV @R0, A
INC RAWPTR
INC DCOUNT
RET
; =========================================
; 按键非数字
NONDIGIT:
MOV A, INPUT ; 恢复A
INC A ; '.' 用 0FFH 表示,加1后为0
JZ DECPNT ; DECIMAL POINT PRESSED
DEC A ; 否则不是小数点,减一恢复之
; 非数字的情况:
; 1. 加, 减, 乘, 除: 高4位应该分别为 0001, 0010, 0011, 0100
; 2. 小数点: 代码为 0FFH, 前面已经考虑
; 3. 等号: 1000 0000 (80H)
; 首先检查是否是等号 (最高位为1)
JB ACC.7, KEYEQU
; 否则作为运算符对待
LJMP KEYOP
; ---------------------------------
ERRORND:; ERROR OF NON-DIGIT
; 出错…………
SETB STAT.7
LJMP EXIT
; =======================================================
DECPNT:
; 根据计算器状态进行操作
JB STAT.1, DP1
JB STAT.2, DP2
JB STAT.3, DP3
JB STAT.0, DP0
; ERROR
SETB STAT.7
LCALL EXIT
DP1:
DP2:
; 在数字输入状态下下按下了小数点,如果之前已经按过小数点
; 则忽略此次输入,计算器状态字无需修改
JB PNTB, DP_DONE ; 小数点已经按下
; 否则,这是本次操作数输入第一次按下小数点
; 首先设置小数点已经按下标志位
SETB PNTB
; 否则添加小数点到输入区,也就是显示区
; 首先判断第一个数字是不是0
MOV R0, RAWPTR ; 获取指针用于比较
; 如果当前输入位置不是开始(前面已有非零数字存储),直接添加小数点
CJNE R0, #RAWIN, DP_ADD
; 否则,RAWPTR还是指向RAWIN位置,第一个数字设置为0,
; 后再添加小数点
MOV R0, RAWPTR ; 用R0间接寻址
MOV @R0, #00H
INC RAWPTR
INC DCOUNT
DP_ADD:
MOV R0, RAWPTR ; R0间接寻址
MOV @R0, #0FFH ; DECIMAL POINT
INC RAWPTR
INC DCOUNT
DP_DONE:
RET
; --------------------
DP0:
; 之前的状态为等号,按下小数点后因该开始第一个操作数输入
; 设置状态
ANL STAT, #0F0H
SETB STAT.1
SJMP DP_NEW
; --------------------
DP3:
; 之前处于操作符状态,按下小数点则应该开始第二个操作数输入
; 设置状态
ANL STAT, #0F0H
SETB STAT.2
; 新的操作数开始
SJMP DP_NEW
; --------------------
DP_NEW:
; 小数点开始的新的操作数,添加'0' '.' 两个输入
; 初始化
MOV RAWPTR, #RAWIN
MOV DCOUNT, #00H
CLR PNTB
MOV RAWIN, #00H ; 第一个数字,0
INC RAWPTR
INC DCOUNT
MOV R0, RAWPTR ; 使用R0间接寻址来存储小数点
MOV @R0, #0FFH ; 小数点使用0FFH表示
INC RAWPTR
INC DCOUNT
RET
; =======================================================
KEYEQU:
; 保存原来的状态
MOV B, STAT
; 设置现在的状态为EQU
ANL STAT, #0F0H ; 清楚低4位状态
SETB STAT.0 ; EQU STAT BIT
; 根据原来的状态采取相应的操作
JB B.1, EQU1 ; 原来处于第一个数的输入状态
JB B.2, EQU2 ; 原来处于第二个数的输入状态 (NORMAL)
JB B.3, EQU3 ; 原来处于操作符状态
JB B.0, EQU0 ; 原来处于等号状态
; ERROR
SETB STAT.7
LJMP EXIT
EQU1:
; 在第一个数的状态下按了等号,那么第一个数不需要进行计算
; 直接作为结果显示,实际上RAWIN就是当前的操作数的显示格式
; 因此只需要将第一个数转换成浮点数存储于NUM1
SETB RS0
CLR RS1
MOV R0, #RAWIN ; SOURCE
MOV R1, #NUM1 ; DESTINATION
LCALL MAKENUM ; NOTE: MAKENUM!!!
RET
EQU2:
; 在第二个数的状态下按了等号,这是最普通的操作
; 首先转换操作数二到NUM2位置,然后计算结果
; 并将结果转化为RAWIN形式,供DISPLAY显示
MOV R0, #RAWIN
MOV R1, #NUM2 ; DESTINATION
LCALL MAKENUM ; NOTE: MAKENUM..
; 计算结果,结果放在NUM1
LCALL CALCULATE ; NOTE: CALCULATE!!!
; 结果转换为RAWIN,即从NUM1到RAWIN
LCALL RES2RAW ; NOTE: RES2RAW
NOP
RET
EQU3:
; 原来状态为操作符,然后直接按了等号
; 本程序采取的措施为:等1号覆盖前面的操作符
; 因此不需要采取任何措施,直接同按下了第一个
; 操作数后直接按等号相同,由于按下操作符的时候
; 已经处理了第一个操作数,因此这里直接返回
RET
EQU0: ; SYSTEM RESET
; 原来状态为等号,然后又按了等号
; 第一次按等号的时候已经处理好,这里只需返回
; 07.24 修改,连续两次等号相当于清0
; 首先清除错误状态位
CLR STAT.7
MOV DCOUNT, #00H ; DCOUNT设置为0,显示就为0
RET
; ========================================================
; 操作符处理,注意:前面已经判断不是等号
KEYOP:
; 保存操作符号: 给定的是 10H, 20H, 30H, 40H
; 转换为 00H, 10H, 20H, 30H. 即,减去 10H
;DEBUGHERE
CLR C
SUBB A, #10H
; INVARIANT: 除了4, 5位,其他位不可能为1
KEYOP_NE: ; KEY OPERATOR, NO ERROR
; 如果是第一个数之后按的运算符
JB STAT.1, KOP1
; 如果是第二个数之后按的运算符
JB STAT.2, KOP2
; 如果是一个运算符之后按的运算符
JB STAT.3, KOP3
; 如果是按了等号后按的运算符
JB STAT.0, KOP0
; ERROR
SETB STAT.7
LJMP EXIT
; ------------------
KO_NE_DONE: ; KEY OPERATOR, NOT EQUAL, DONE
; 完成相应的操作后,更新STAT到当前状态
; 取回 INPUT ,并减去 10H ,成为STAT要求的操作符表示
MOV A, INPUT
CLR C
SUBB A, #10H
; 运算符信息保存在 STAT 的 4,5位
; 先将4,5位清0
ANL STAT, #0CFH ; #1100 1111B
ORL STAT, A ; 设置4,5位
; 设置新的计算器状态
ANL STAT, #0F0H ; 清除状态(低4位)
SETB STAT.3 ; 操作符号状态
RET ; KEYPRESSED 返回
; --------
KOP1:
; 如果按了第一个数字之后按了操作符
; 则首先将当前显示缓冲区里的数字
; 拼合为3字节浮点数
; 保存在NUM1位置
; RAWIN 保持不变,因此计算器显示的仍然为
; 第一个操作数
MOV R0, #RAWIN ; 需要进行拼凑的数字
MOV R1, #NUM1 ; 目的
LCALL MAKENUM
SJMP KO_NE_DONE
KOP2:
; 如果按了第二个数字之后按了操作符,
; 首先计算前面的结果,然后结果作为
; 第一个数, 并设置状态为操作符
; 显示区域的数据为前面操作的结果
; 即:需要将3字节的浮点数结果转化
; 为可以显示的RAWIN格式。
; 创建第二个操作数
MOV R0, #RAWIN
MOV R1, #NUM2
LCALL MAKENUM
; 执行计算
LCALL CALCULATE
; 结果默认已经存储于到NUM1位置
; 但是需要将其转换为RAWIN形式用于DISPLAY子程序的显示
LCALL RES2RAW ; NOTE: RES2RAW
SJMP KO_NE_DONE
KOP3:
; 按了一个操作符后又按了另一个操作符,则直接忽略原来的
; 前面已经设置了操作符状态,此处无需进行任何操作
SJMP KO_NE_DONE
KOP0:
; 按了等号之后按的操作符,则运算结果直接作为第一个
; 操作数,因为按等号的时候已经进行了结果运算和显示
; 所需要的准备工作(将浮点数转换为RAWIN形式)
; 因此这里不需要再作其他工作
SJMP KO_NE_DONE
; =======================================================
EXIT:
SJMP $
; =======================================================
; =======================================================
MAKENUM: ; NOTEE!!!
; [R0] 为RAWIN
; [R1] 为目标,NUM1 OR NUM2
; 先决条件:见前面
; 后决条件:NUM1中为二进制浮点数(3字节,补码形式)
; 转换结果,RAWIN形式的数字被转换为3字节二进制浮点数
MOV A, R0
MOV R3, A ; 保存源位置
MOV A, R1
MOV R4, A ; 保存目的位置
MOV R2, #00H ; 小数点位置
; 保存 DCOUNT ,子程序结束后 DCOUNT
; 和 [R0] 内容应该不变
PUSH DCOUNT
; 如果 DCOUNT 为零,则就是0
MOV A, DCOUNT
JZ VALZERO
SJMP MN_DC_NZ ; DCOUNT NOT ZERO
; ------------------
; 结果为0
VALZERO:
; 三个字节均设置为0
MOV @R1, #00H
INC R1
MOV @R1, #00H
INC R1
MOV @R1, #00H
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -