?? 計算器.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 + -