;Calculator based on 8049 microcontroller (128 Byte RAM, ext. EPROM) @ 10.7 MHz
;Assemble with asm48 v0.4.1
;Algorithms based on
;
;PORTS
; DB0~7 = BUS (EPROM output, latch inputs)
; P10~7 = Display common 1~8 (P10 = left), Keypad column 1~8 (active low)
; P20 = EPROM A8, LED "negative" (active low)
; P21 = EPROM A9, LED "overflow" (active low)
; P22 = EPROM A10
; P23 = EPROM A11, Switch "degree/radian" (through 1k to GND, closed=degree)
; P24~7 = Display common 9~12 (P27 = right), Keypad column 9~12 (active low)
; T0 = Keypad row 1 input
; T1 = Keypad row 2 input
; #INT = Keypad row 3 input
; ALE = Address latch enable
; #WR = Segment data latch clock
;
;ADDRESS LATCH (transparent when ALE high, e.g. 74xx373/573)
; Q0~7 = EPROM A0~7
;
;SEGMENT DATA LATCH (latch on rising edge, e.g. 74xx374/574)
; * active high or active low, see SEGINV below
; * bit to segment mapping is set below (SA...SG & DP)
;
;KEYPAD (with keycodes)
; ,----+----+----+----+----+----+----+----+----+----+----+----,
; #INT ----| 3 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 |
; +----+----+----+----+----+----+----+----+----+----+----+----+
; T1 ------| 2 | 5 | 8 | 11 | 14 | 17 | 20 | 23 | 26 | 29 | 32 | 35 |
; +----+----+----+----+----+----+----+----+----+----+----+----+
; T0 ------| 1 | 4 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 |
; '----+----+----+----+----+----+----+----+----+----+----+----'
; | | | | | | | | | | | |
; _|_ _|_ _|_ _|_ _|_ _|_ _|_ _|_ _|_ _|_ _|_ _|_
; \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / Diodes
; _V_ _V_ _V_ _V_ _V_ _V_ _V_ _V_ _V_ _V_ _V_ _V_ optional
; | | | | | | | | | | | |
; COMREV=0: P27 P26 P25 P24 P17 P16 P15 P14 P13 P12 P11 P10
; COMREV=1: P10 P11 P12 P13 P14 P15 P16 P17 P24 P25 P26 P27
; <<<<<<<<<<<<<<<<< MULTIPLEXING DIRECTION <<<<<<<<<<<<<<<<<
;
;VERSIONS
; 2022-09-12 Arne Rossius
; * first version
; 2022-11-13 Arne Rossius
; * updated KEYTBL for final keypad layout
; * added KDUP key function
; 2023-04-04 Arne Rossius
; * BUGFIX: wrong degree values for sin(+/-1), cos(0) and argument reduction
; * clean-up & some code size improvements
; 2024-05-03 Arne Rossius
; * BUGFIX: correctly initialize memory dp position at power-on
; * push previous result to ACCU when starting a new entry
; * normalize number when storing to memory
; * code size improvements
; 2024-09-08 Arne Rossius
; * BUGFIX: rounding into new MSD didn't decrement dp position
; * clean-up & minor code size improvements
;settings
.equ XTAL, 10700000 ;quartz crystal frequency (Hz) - used for mux delay
.equ DIGITS, 12 ;number of display digits (max. 12, must be even)
.equ SEGINV, 0 ;invert segment outputs: 0 = active high, 1 = active low
.equ COMREV, 1 ;reverse common pin order (0 = P10->P27, 1 = P27->P10)
;display segments (SA = a ... SG = g, DP = dp)
.equ SA, 1<<4 ;DB4
.equ SB, 1<<5 ;DB5
.equ SC, 1<<6 ;DB6
.equ SD, 1<<1 ;DB1
.equ SE, 1<<0 ;DB0
.equ SF, 1<<3 ;DB3
.equ SG, 1<<2 ;DB2
.equ DP, 1<<7 ;DB7
;===============================================================================
;RAM
;( 0~ 7 = Bank 0 Working Registers)
;( 8~23 = 8 Level Stack)
;[24~31 = Bank 1 Working Registers - unused]
.equ KEY, 24 ;1 byte
.equ KEYCNT, 25 ;1 byte
.equ KEYCOD, 26 ;1 byte
.equ ALTKEY, 27 ;1 byte - b0 = ARC (other bits unused)
.equ ENTRY, 28
.equ ACCU, ENTRY+DIGITS/2+1 ;2 digits/byte + 1 byte for dp position & sign
.equ CALC, ACCU+DIGITS/2+1
.equ MEMORY, CALC+(DIGITS+2)/2 ;CALC has 2 extra digits, but no dp/sign
.equ BACKUP, MEMORY+DIGITS/2+1
.equ VAL1, BACKUP+DIGITS/2+1
.equ VAL2, VAL1+DIGITS/2+1
.equ VAL3, VAL2+DIGITS/2+1
.equ RAMEND, VAL3+DIGITS/2+1
.if (RAMEND > 128)
.error "Out of RAM: ", RAMEND, " bytes used"
.else
.message "RAM usage: ", RAMEND, " bytes"
.endif
;number in RAM: DIGITS=8, -12345.678
; offset data
; 0 0x78
; 1 0x56
; 2 0x34
; 3 0x12
; 4 0x83 (decimal point = 3 digits from right, bit 7 set = negative)
;
; no dp entered: last byte = 0x3F (positive) or 0xBF (negative)
; TODO: change to 0x20 for no dp? Check which values are used for overflow.
;status flags
; F0 = Overflow (set on overflow/error, cleared by CLRALL or CE)
; F1 = Entry mode (number is being entered: set by 0~9,dp,RCL, cleared by op),
; also used for other purposes during some calculations
;======================================================================== PAGE 0
.org 0 ;reset
;init key counter
mov R0, KEYCNT
mov @R0, 0
;start timer (for multiplexing)
strt T
;clear memory register
mov R0, MEMORY
call CLEAR
mov @R0, DIGITS-1 ;dp at MSD (normalized)
;clear number registers
CLRALL: call CLEARE ;clear ENTRY
mov @R0, DIGITS-1 ;ENTRY dp at MSD (normalized)
call MOVEA ;clear ACCU
clr F0 ;clear overflow flag
;(fall through to RESULT)
;display a result (in ENTRY register)
RESULT: clr F1 ;clear entry flag
jmp UPDATE
;--------------------
;set ACCU = 1
SETA1: mov A, 1
;set ACCU to integer value between 0 and 99
SETA: mov R0, ACCU
jmp SETREG
;clear ENTRY register
CLEARE: clr A
;set ENTRY to integer value between 0 and 99
SETE: mov R0, ENTRY
jmp SETREG
;clear CALC register
CLEARC: mov R0, CALC
;clear number register
CLEAR: clr A
;set number register to integer value between 0 and 99 (in LSD+1 & LSD)
; clobbers R7
; returns with R0 = number + DIGITS/2, A = 0
SETREG: mov R7, DIGITS/2+1 ;extra byte is dp/sign (2 extra digits in CALC)
SETRL: mov @R0, A
inc R0
clr A ;clear all further bytes (MSD...LSD+2 = 0, dp=0, positive)
djnz R7, SETRL
dec R0
ret
;--------------------
;clear entry
; clobbers R7
; returns with R0 = ENTRY + DIGITS/2
CE: call CLEARE
mov @R0, 0x3F ;positive, no dp
clr F1
cpl F1 ;set entry flag
ret
;--------------------
;copy VAL2 to ENTRY
MOVV2E: mov R1, VAL2
jmp MOVXE
;copy VAL1 to ENTRY
MOVV1E: mov R1, VAL1
;copy @R1 to ENTRY
MOVXE: mov R0, ENTRY
jmp MOVREG
;copy ENTRY to VAL1
MOVEV1: mov R0, VAL1
;copy ENTRY to @R0
MOVEX: mov R1, ENTRY
jmp MOVREG
;copy VAL3 to ACCU
MOVV3A: mov R1, VAL3
jmp MOVXA
;copy ENTRY to ACCU
MOVEA: mov R1, ENTRY
;copy number to ACCU
MOVXA: mov R0, ACCU
jmp MOVREG
;copy ACCU to VAL2
MOVAV1: mov R0, VAL1
jmp MOVAX
;copy ACCU to VAL2
MOVAV2: mov R0, VAL2
jmp MOVAX
;copy ACCU to VAL3
MOVAV3: mov R0, VAL3
jmp MOVAX
;copy ACCU to BACKUP
MOVAB: mov R0, BACKUP
jmp MOVAX
;--------------------
;multiply ACCU by ENTRY and duplicate result into ENTRY
MULDUP: call MUL
;(fall through to MOVAE)
;--------------------
;copy ACCU to ENTRY
MOVAE: mov R0, ENTRY
;copy ACCU to number
MOVAX: mov R1, ACCU
;copy number from @R1 to @R0 (value, dp & sign)
; clobbers R7
; returns with R0 = destination+DIGITS/2, R1 = source+DIGITS/2
MOVREG: mov R7, DIGITS/2+1
MOVLOP: mov A, @R1
mov @R0, A
inc R0
inc R1
djnz R7, MOVLOP
dec R0
dec R1
ret
;--------------------
MOVEAN:
;move ENTRY to ACCU and normalize ACCU
call MOVEA
jmp NORMDP
;--------------------
;exchange ACCU and VAL2
XCHAV2: mov R0, VAL2
jmp XCHAX
;exchange ACCU and ENTRY
XCHAE: mov R0, ENTRY
;exchange ACCU with @R0
XCHAX: mov R1, ACCU
;exchange numbers @R0 and @R1 (value, dp & sign)
; clobbers R7
; returns with R0 = number2+DIGITS/2+1, R1 = number1+DIGITS/2+1
XCHREG: mov R7, DIGITS/2+1
XCHLOP: mov A, @R0
xch A, @R1
mov @R0, A
inc R0
inc R1
djnz R7, XCHLOP
ret
;--------------------
;toggle sign of ACCU
TGLSA: mov R0, ACCU+DIGITS/2
;toggle sign
; call with R0 = number + DIGITS/2
TGLSGN: mov A, @R0
xrl A, 0x80
mov @R0, A
RETP0: ret
;--------------------
;add 1 to number LSD (for rounding) - doesn't check for overflow
; call with R0 = number
; clobbers R0, R7
;--------------------
;check if CALC = 0
; clobbers R7
; returns with R0 = CALC + (DIGITS+2)/2
CALC0: mov R0, CALC
mov R7, (DIGITS+2)/2
jmp REG0S
;check if ENTRY = 0
ENTRY0: mov R0, ENTRY
;check if number = 0
; call with R0 = number
; clobbers R7
; returns with R0 = number + DIGITS/2
REG0: mov R7, DIGITS/2
REG0S: clr A
REG0L: orl A, @R0
inc R0
djnz R7, REG0L
ret
;--------------------
;compare mantissas of CALC and ENTRY
; clobbers R0, R1, R7
; returns C=1 if ENTRY > CALC, Z=1 if ENTRY = CALC
CMPMEC: mov R0, CALC+DIGITS/2
mov R1, ENTRY+DIGITS/2
mov A, @R0 ;check extra digits of CALC
jz CMPM
clr C
ret ;CALC > ENTRY (Z=0, C=0)
;compare ENTRY to ACCU (return C=1 if |ACCU| > |ENTRY|)
CMPEA: mov R0, ENTRY+DIGITS/2
mov R1, ACCU+DIGITS/2
;compare two numbers (mantissa & dp, assumes numbers are normalized)
; call with R0 = number1 + DIGITS/2, R1 = number2 + DIGITS/2
; clobbers R0, R1, R7
; returns C=1 if |number2| > |number1|, Z=1 if equal
CMP: mov A, @R1
anl A, 0x7F
cpl A
mov R7, A
mov A, @R0
anl A, 0x7F
add A, R7
inc A ;INC for 2's complement after ADD => avoid C=1 for equal
jc CMPRET ;carry: dp(R0) > dp(R1) => number1 < number2
jnz CMPRET ;not zero: dp(R0) < dp(R1) => number(R0) > number(R1)
CMPM: mov R7, DIGITS/2
CMPML: dec R0
dec R1
mov A, @R0 ;CALC
cpl A
clr C
cpl C ;C=1 (add 1 to make 2's complement)
addc A, @R1 ;ENTRY
jz CMPMEQ ;Z=1 & C=1 => equal
ret ;(Z=0, C=1 if ENTRY > CALC)
CMPMEQ: djnz R7, CMPML
clr C ;ENTRY = CALC (Z=1, C=0)
CMPRET: ret
;--------------------
;decimal shift left CALC:ACCU registers
; clobbers R6~R7
; returns with R0 = CALC + (DIGITS+2)/2
SHFLCA: clr A
ROTLCA: mov R0, ACCU
call ROTLA ;rotate ACCU left
SRLC: inc R0 ;R0 = CALC
mov R7, (DIGITS+2)/2
jmp ROTLAL ;rotate CALC left
;decimal shift left ENTRY register
SHFLE: mov R0, ENTRY
;decimal shift left (multiply by 10)
; call with R0 = number
; clobbers R6~R7
; returns with R0 = number + DIGITS/2, A = shifted-out digit
SHFL: clr A
;decimal rotate left through A (multiply by 10 and add A)
ROTLA: mov R7, DIGITS/2
ROTLAL: mov R6, A
mov A, @R0 ;swap BCD nibbles
swap A
mov @R0, A
mov A, R6 ;replace low nibble with lower byte's high nibble
xchd A, @R0
inc R0
djnz R7, ROTLAL
ret
;--------------------
;decimal shift right CALC:ACCU registers
; clobbers R6~R7
; returns with R0 = ACCU
SHFRCA: mov R0, CALC+(DIGITS+2)/2
clr A
mov R7, (DIGITS+2)/2
call ROTRAL
dec R0 ;R0 = ACCU + DIGITS/2
;(fall through to ROTRA)
;decimal rotate right through A (divide by 10 and insert A as MSD)
; call with R0 = number + DIGITS/2
; clobbers R6~R7
; returns with R0 = number, A = shifted-out digit
ROTRA: mov R7, DIGITS/2
ROTRAL: dec R0
xchd A, @R0 ;replace high nibble with higher byte's low nibble
mov R6, A
mov A, @R0 ;swap BCD nibbles
swap A
mov @R0, A
mov A, R6
djnz R7, ROTRAL
ret
;--------------------
;calculate 9's complement of @R1 (2 digits: result = 0x99 - @R1)
CPL9: mov A, @R1
cpl A
add A, 0x99 + 1 ;add 1 to make 2's complement
retr ;restore carry
;--------------------
.message "Page 0: ", 0x100-7-$, " bytes free"
;--------------------
.org (0x100-7)
;normalize ENTRY (and clear CALC)
; clobbers R0, R1, R6, R7
; returns with R0 = ENTRY + DIGITS/2 - 1
NORME: call CLEARC ;clear CALC
mov R0, ENTRY+DIGITS/2
;normalize number, add dp if not already set
NORMDP: mov A, @R0
xrl A, 0x3F
;--------------------
.if ($ < 0x100)
.error "Gap in NORMDP function"
.endif
.org 0x100 ;============================================================= PAGE 1
;--------------------
jb5 NORM ;dp entered
mov @R0, A ;no dp entered, set dp=0
;(fall through to NORM)
;normalize number (shift left as far as possible)
; call with R0 = number + DIGITS/2
; returns with R0 = number + DIGITS/2 - 1
NORM: mov A, @R0
dec R0
anl A, 0x7F
xrl A, DIGITS-1
jz RETP1 ;dp at MSD position
mov A, @R0
anl A, 0xF0
jnz RETP1 ;MSD nonzero
mov A, R0
add A, -(DIGITS/2-1)
mov R0, A
call SHFL ;shift left
inc @R0 ;move dp left
jmp NORM
;--------------------
;backup ACCU, normalize ENTRY, set overflow if negative, return dp
; clobbers R0, R6~R7
; returns with A = R5 = dp position (Carry set if negative)
CHKPOS: call NORME
inc R0 ;R0 = ENTRY + DIGITS/2
mov A, @R0 ;check sign
add A, 0x80
anl A, 0x7F ;copy dp position
mov R5, A ;to R5
jc SETOVF ;argument is negative
RETP1: ret
;--------------------
;logarithms: backup ACCU, check argument and separate into m * 10^p
; 1 <= m < 10, p = signed integer (2's complement)
; clobbers R5~R7
; returns with ENTRY = m, A = p, R0 = ENTRY+DIGITS/2
LOGSEP: call MOVAB ;backup accu
call CHKPOS ;normalize ENTRY, set overflow if neg., return dp in R5
call ENTRY0
jz SETOVF ;argument is 0 => overflow
LOGSHF: call SHFLE ;shift left until MSD nonzero
inc R5 ;increment dp
jz LOGSHF
call ROTRA ;undo last shift (shift nonzero digit back in)
dec R5
mov R0, ENTRY+DIGITS/2 ;set dp to MSD position (1 <= ENTRY < 10)
mov @R0, DIGITS-1
mov A, R5 ;integer part of result = (DIGITS-1) - dp
cpl A
add A, DIGITS-1 + 1 ;add 1 to make 2's complement
ret
;--------------------
;multiply ACCU by ENTRY
; clobbers R0, R1, R5~R7
MUL: call NORME ;normalize ENTRY (and clear CALC)
inc R0 ;R0 = ENTRY + DIGITS/2
mov R1, ACCU+DIGITS/2
mov A, @R0 ;result dp = ENTRY + ACCU, sign = ENTRY xor ACCU
add A, @R1
mov @R1, A
mov R5, DIGITS ;loop once per digit
MULDGT: mov R0, ACCU ;get ACCU LSD
mov A, @R0
anl A, 0x0F
jz MULNXT ;ACCU LSD zero: next digit
mov R6, A
MULLOP: call ADDEC ;add ENTRY to CALC
mov A, R6
djnz R6, MULLOP ;repeat until ACCU LSD zero
MULNXT: call SHFRCA ;next digit: shift right CALC:ACCU
djnz R5, MULDGT
jmp NORMCA ;normalize and return
;--------------------
;MUL/DIV for EXP
EXPMD: jf1 MUL ;argument positive: multiply (x * e^1)
;(fall through to DIV) - argument negative: divide (x * e^-1 = x / e)
;--------------------
;divide ACCU by ENTRY
; clobbers R0, R1, R4~R7
DIV: call NORME ;normalize ENTRY (and clear CALC)
call ENTRY0
jz SETOVF ;divisor is 0 => set overflow and return
mov R1, ACCU+DIGITS/2 ;R0 = ENTRY+DIGITS/2
mov A, @R0 ;result dp = ACCU - ENTRY, sign = ACCU xor ENTRY
cpl A ;inc (for 2's complement) done after DJNZ below
add A, @R1
mov @R1, A
mov R4, 0
mov R5, DIGITS ;loop once per digit
DIVDGT: mov A, R4
call ROTLCA ;rotate left CALC:ACCU (rotate A into LSD)
mov R4, 0 ;clear new result digit
DIVLOP: call CMPMEC ;compare ENTRY to CALC
jc DIVNXT ;ENTRY > CALC: next digit
call SUBEC ;CALC >= ENTRY: subtract ENTRY from CALC
inc R4 ;increment next result digit
jmp DIVLOP
DIVNXT: djnz R5, DIVDGT ;next digit
mov R1, ACCU+DIGITS/2 ;start incrementing dp after DIGITS loops
inc @R1 ;increment dp
mov R0, ACCU
call REG0 ;result=0?
orl A, R4 ;(include new result digit)
jnz DIVMSD ;no
call CALC0 ;dividend=0?
jz DIVEND ;yes => dividend = result = 0 => abort
DIVMSD: inc R5 ;continue until ACCU MSD nonzero
dec R1
mov A, @R1 ;check ACCU MSD
anl A, 0xF0
jz DIVDGT ;MSD is zero
DIVEND: call CLEARC ;clear CALC (remainder) for correct normalization
mov A, R4
call ROTLCA ;rotate final digit into result LSB (for rounding)
;(fall through to NORMCA)
;--------------------
;normalize CALC:ACCU
; clobbers R0, R1, R6~R7
NORMCA: mov R6, 0
NCALOP: call CALC0 ;check if CALC = 0
mov R0, ACCU+DIGITS/2
jnz NCASHF ;CALC is nonzero => shift CALC right into ACCU
mov A, @R0 ;check ACCU dp
jb6 NCAOVF ;dp negative => overflow
anl A, 0x7F
add A, -DIGITS
jc NCASHF ;dp too high => shift ACCU right
mov A, R6 ;rounding
add A, -5
jnc NCAEND ;shifted-out digit < 5 (or no right shift performed)
mov R0, ACCU ;shifted-out digit >= 5: round up
mov R7, DIGITS/2
NCARND: mov A, @R0
add A, 1
da A
mov @R0, A
inc R0
jnc NCAEND
djnz R7, NCARND
mov A, @R0 ;overflow into a new digit (e.g. 9.999 => 10.00)
dec A ;decrement dp (make room for new digit)
mov @R0, A
mov R5, A
mov A, 1 ;add new MSD = 1
call ROTRA
mov R6, 0 ;prevent any more rounding
jmp NCALOP ;check for overflow
NCAEND: mov R0, ACCU ;check if ACCU = 0
call REG0
jnz NORM ;ACCU nonzero: shift left as required
mov @R0, DIGITS-1 ;ACCU = 0: set dp at MSD, sign to positive
ret
NCAOVF: add A, DIGITS ;overflow: show (result / 10^DIGITS)
mov @R0, A
SETOVF: clr F0
cpl F0 ;set overflow flag
ret
NCASHF: mov A, @R0 ;decrement dp
dec A
mov @R0, A
call SHFRCA ;shift CALC:ACCU right
mov R6, A ;save shifted-out digit
jmp NCALOP
;--------------------
.message "Page 1: ", 0x200-34-$, " bytes free"
;--------------------
.org (0x200-34)
EXPOVF: jf1 SETOVF ;ENTRY >= +100: overflow
jmp CLEARE ;ENTRY <= -100: underflow (result = 0)
;exponential of ENTRY
; exp(x) = exp(int{x}) * exp(frac{x})
; exp(int{x}) = e^int{x} = (e^10)^(int{x} div 10) * e^(int{x} mod 10)
; exp(frac{x}) = sum(x^n/n!), n=0..inf (stop when x^n/n! = 0)
EXP: call NORME ;normalize ENTRY
;integer part: R2 = mask, R3 = integer part (0~99), F1 = sign
mov A, @R0 ;read the two MSD of ENTRY
mov R3, A
inc R0
mov A, @R0 ;read dp & sign
clr F1 ;backup sign to F1 (flag set = positive)
jb7 EXPDP
cpl F1
EXPDP: anl A, 0x7F ;remove sign
add A, -(DIGITS-2) ;check if dp is at MSD or MSD-1 position
jnc EXPOVF ;|ENTRY| >= 100 => overflow (arg. pos) / zero (arg. neg)
mov R2, 0x00
jz EXPIE ;dp at MSD-1: 10 <= |ENTRY| < 100 (int. part = 2 digits)
mov R2, 0x0F ;dp at MSD: |ENTRY| < 10 (integer part = 1 digit)
mov A, R3
swap A
anl A, R2 ;R2=0x0F
mov R3, A
EXPIE: dec R0 ;R0 = ENTRY + DIGITS/2 - 1
mov A, @R0 ;remove integer part from ENTRY
anl A, R2
mov @R0, A
;--------------------
.if ($ < 0x200)
.error "Gap within EXP function"
.endif
.org 0x200 ;============================================================= PAGE 2
;--------------------
;fractional part: R2 = n, ACCU = x^n/n!, VAL1 = x, VAL2 = sum
call MOVAB ;backup ACCU
call MOVEV1 ;copy x (fractional part of ENTRY) to VAL1
mov R2, 0 ;n = 0
call SETA1 ;x^0/0! = 1
call MOVAV2 ;sum = 1 (for n=0)
EXPSUM: call MOVV1E ;ACCU = ACCU * x (numerator: x^(n-1) * x = x^n)
call MUL
mov A, 1 ;increment n by 1
call SIDVAD ;increment R2 (n), ACCU = ACCU / n, add to sum
jnz EXPSUM ;loop
;multiply results for integer & fractional parts
call XCHAV2 ;ACCU = sum
mov R0, ENTRY+DIGITS/2
mov A, @R1 (ENTRY has more decimals) => shift ACCU left
cpl A ;@R1 > @R0 (ACCU has more decimals)
inc A
mov R5, A
call XCHAE ;exchange ACCU and ENTRY, then shift ACCU left
ADDSHF: call SHFLCA ;shift left CALC:ACCU (add a decimal)
mov R0, ACCU+DIGITS/2
inc @R0 ;increment ACCU dp
inc R5
mov A, R5
jb6 ADDSHF
ADDSGN: mov R0, ACCU+DIGITS/2 ;check operand signs
mov R1, ENTRY+DIGITS/2
mov A, @R0
xrl A, @R1
jb7 SUB ;signs differ => subtract
mov R6, 0 ;same sign => add
clr C
call ASECA ;add and normalize result
ASEND: jmp NORMCA
SUB: mov R0, ENTRY ;subtract
clr C
call CPL10 ;9's complement
cpl C ;set carry (add 1 to make 10's complement)
mov R6, 0x99 ;9's complement of 0
call ASECA ;subtract ENTRY from CALC:ACCU
jc SUBEND ;C=1 => CALC:ACCU didn't change sign, done
mov R0, ACCU
cpl C ;C=1
call CPL10 ;10's complement (C=1) of ACCU
mov A, @R0 ;toggle ACCU (result) sign
xrl A, 0x80
mov @R0, A
call CPL10C ;10's complement of CALC (with carry from ACCU)
SUBEND: jmp NORMCA ;normalize
;--------------------
.message "Page 2: ", 0x300-71-$, " bytes free"
;--------------------
.org (0x300-71)
;square root (TODO rounding) - doesn't backup ACCU
; clobbers R0, R1, R4~R7
SQRT: call CHKPOS
call MOVEA ;move argument to ACCU
call CLEARE ;clear ENTRY for result
mov R4, DIGITS ;loop once per digit
SQRDGT: mov A, R5
jb0 SQRSH2 ;argument dp odd: skip 1st shift (even after 2nd shift)
xrl A, DIGITS
jz SQRSH1 ;argument dp = DIGITS: don't increment it further
inc R5
SQRSH1: call SHFLCA ;shift left CALC:ACCU
SQRSH2: call SHFLCA ;shift left CALC:ACCU
call SHFLE ;shift left ENTRY
mov A, R5
xrl A, DIGITS
jz SQRRDP ;argument dp = DIGITS: increment result dp instead
inc R5
jmp SQRLOP
SQRRDP: inc @R0 ;increment result dp (R0 set to ENTRY+DIGITS/2 by SHFLE)
SQRLOP: call CMPMEC ;compare ENTRY to CALC
jc SQRNXT ;CALC < ENTRY: next digit
call SUBEC ;CALC >= ENTRY: subtract ENTRY from CALC
mov R0, ENTRY
inc @R0 ;increment ENTRY LSD
call CMPMEC
jc SQRADD ;CALC < ENTRY+1: add back ENTRY to CALC, then next digit
call SUBEC ;CALC >= ENTRY+1: subtract ENTRY+1 from CALC
jmp SQRLOP ;repeat
SQRADD: mov R0, ENTRY
mov A, @R0 ;decrement ENTRY LSD
dec A
mov @R0, A
call ADDEC ;add back ENTRY to CALC (undo first subtraction)
SQRNXT: djnz R4, SQRDGT
SQRNX2: inc R4 ;continue until result normalized
mov R0, ENTRY+DIGITS/2 - 1
mov A, @R0 ;check result MSD
anl A, 0xF0
jnz RETP2 ;MSD nonzero
inc R0
mov A, @R0 ;check result dp
xrl A, DIGITS-1
jnz SQRDGT ;dp not at MSD position
;--------------------
.if ($ < 0x300)
.error "Gap within SQRT function"
.endif
.org 0x300 ;============================================================= PAGE 3
;--------------------
ret
;key function table (map keycodes 1~36 to key function)
;must start at address 0x301 (accessed by movp3 at KEYDB, low byte = keycode)
;table entries: digits 0~9 or jump address (low byte of 0x30A~0x3FF)
.org 0x301
KEYTBL:
; 1 2 3 4 5 6 7 8 9 10
.db CLR F1)
clr F1 ;clear entry flag
KDUP: call MOVEAN ;copy & normalize ;duplicate ENTRY into ACCU
jmp UPDATE
KMC: mov R0, MEMORY ;clear memory
call CLEAR
jmp UPDATE
KRCL: jf1 RCL ;recall memory
call MOVEA ;push previous result to ACCU
cpl F1 ;set entry flag
RCL: mov R1, MEMORY
call MOVXE
jmp UPDATE
KSTO: mov R0, MEMORY ;store to memory
call MOVEX
call NORMDP
jmp UPDATE
KMP: call MOVEV1 ;backup ENTRY ;add to memory (M+)
MADD: call MOVAB ;backup ACCU
mov R1, MEMORY
call MOVXA ;load MEMORY to ACCU
call ADDEA ;add ENTRY to ACCU
mov R0, MEMORY
call MOVAX ;result to MEMORY
call MOVV1E ;restore ENTRY
call MOVBA ;restore ACCU
jmp UPDATE
KMM: call MOVEV1 ;backup ENTRY ;subtract from memory (M-)
mov R0, ENTRY+DIGITS/2
call TGLSGN ;toggle ENTRY sign
jmp MADD ;continue same as M+
KINV: call MOVAB ;backup ACCU ;inverse (1/x)
call SETA1 ;ACCU = 1
call DIV
call RESINA ;result in ACCU: move to ENTRY, restore ACCU
jmp RESULT
KSQR: call MOVAB ;backup ACCU ;square root
call SQRT
RESBA: call MOVBA ;restore ACCU
jmp RESULT
KPI: mov A, x = 1 => result = pi/2
call DIVATN ;ENTRY = arctan(ACCU/ENTRY)
jmp RESBA ;restore accu & show result
;--------------------
.message "Page 3: ", 0x400-6-$, " bytes free"
;--------------------
.org 0x400-6
;arccos(x) = arctan(sqrt(1 - x^2) / x), and add pi for x < 0
KACOS: call ARCSCR ;ACCU = sqrt(1 - x^2), (restore ENTRY = x)
call ENTRY0
jz ARCSC0 ;x = 0 => result = pi/2
;--------------------
.if ($ < 0x400)
.error "Gap within KACOS function"
.endif
.org 0x400 ;============================================================= PAGE 4
;--------------------
clr F1 ;save argument sign to F1 (flag set = positive)
mov A, @R0 ;R0 = ENTRY + DIGITS/2
jb7 KACOST
cpl F1
KACOST: call DIVATN ;ENTRY = ACCU = arctan(ACCU / ENTRY)
jf1 KACOSE ;x > 0: done
call LDDRPI ;x < 0: add pi to result
call ADDEA
call MOVAE
KACOSE: jmp RESBA ;restore accu & show result
;--------------------
;arcsin/arccos: (ARCSCR = ARC Sin/Cos Root)
; * backup ACCU and normalize ENTRY
; * ACCU = sqrt(1 - x^2)
ARCSCR: call MOVAB ;backup accu
call NORME
call MOVEV1 ;store ENTRY
call MOVEA
call MULDUP ;ENTRY = ACCU = x^2
call TGLSGN ;ENTRY = -(x^2) (R0 = ENTRY+DIGITS/2 set by MULDUP)
call SETA1 ;ACCU = 1
call ADDEA ;ACCU = 1 - x^2
call MOVAE
call SQRT ;ENTRY = sqrt(1 - x^2)
call MOVEA ;ACCU = sqrt(1 - x^2)
jmp MOVV1E ;recall ENTRY
;--------------------
;calculate 9's (C=0) or 10's (C=1) complement of CALC
; clobbers R0, R7
CPL10C: mov R0, CALC
mov R7, (DIGITS+2)/2
jmp CPL10L
;calculate 9's (C=0) or 10's (C=1) complement
; call with R0 = number
; returns with R0 = number + DIGITS/2
CPL10: mov R7, DIGITS/2
CPL10L: mov A, @R0
cpl A ;1's complement
addc A, 0x99+1 ;(add 1 to make 2's complement)
add A, 0 ;clear C and AC
da A
mov @R0, A
inc R0
djnz R7, CPL10L
RETP4: ret
;--------------------
;common logarithm (base 10)
; lg(x) = lg(m * 10^p) = lg(m) + p, 1 <= m < 10, p = integer
; lg(m): for each result decimal, perform
; * x = m^10
; * normalize x to m' * 10^p'
; * p' is the result decimal, m' becomes m for next iteration
; overflow results: lg(0) = 0, lg(-x) = lg(x)
LG: call LOGSEP ;backup ACCU, check argument and separate into m & p
mov R0, VAL2+DIGITS/2
mov @R0, A ;temporarily store integer part in result dp position
call MOVEA ;ACCU = ENTRY
;fractional part (lg(m)): VAL1 = temporary x^2, VAL2 = result
mov R3, DIGITS ;calculate DIGITS digits (one for rounding)
LGFRAC: call MULDUP ;ACCU = ENTRY^10: [(x*x)*(x*x)] * [(x*x)*(x*x)] * (x*x)
call MOVEV1 ;store x^2 for later
call MULDUP ;x^2 * x^2 = x^4
call MUL ;x^4 * x^4 = x^8
call MOVV1E ;restore x^2 to ENTRY
call MUL ;x^8 * x^2 = x^10
mov R0, ACCU+DIGITS/2 ;new result digit = DIGITS-1 - dp
mov A, @R0
mov @R0, DIGITS-1 ;reset dp to MSD position
cpl A
add A, DIGITS-1 + 1 ;add 1 to make 2's complement
mov R0, VAL2
call ROTLA ;rotate new result digit into result register
call MOVAE ;ENTRY = ACCU (dp-shifted result of ENTRY^10)
djnz R3, LGFRAC
mov R1, VAL2+DIGITS/2
clr A
mov R3, A ;clear negative flag
add A, @R1 ;use "ADD" to clear C & AC flags for "DA"
jb7 LGNEG ;negative
LGDA: da A
call SETE
mov A, R3
mov @R0, A ;set sign (dp=0)
call XCHAV2 ;copy fractional digits to ACCU
dec R1 ;R0 = ACCU+DIGITS/2
mov @R1, DIGITS ;set dp to number of fractional digits
call ADDEA ;add fractional & integer digits
jmp RESINA ;result in ACCU => copy to ENTRY & restore ACCU
LGNEG: cpl A ;integer part is negative
inc A
mov R3, 0x80 ;set negative flag
jmp LGDA
;--------------------
;natural logarithm (base e)
; ln(x) = ln(m * 10^p) = ln(m) + p*ln(10), 1 <= m < 10, p = integer
; ln(m) = 2*ln[sqrt(m)] = sum[4*y^(2n+1) / (2n+1)]
; y=(r-1)/(r+1), r=sqrt(m), n = 0...inf (stop when term=0)
; overflow results: ln(0) = 0, ln(-x) = m
;TODO: improve accuracy by multiplying (m-1) by 4 before div by (m+1),
; and dividing (4*y) by 4 to get VAL1=y
LN: call LOGSEP ;backup ACCU, check argument and separate into m & p
mov R3, A ;save p
call ENTRY0
jz RETP4 ;argument = 0: abort (would cause endless loop) TODO overflow: ln(0) = -inf
;calculate r = sqrt(m)
call SQRT
;calculate y = (r-1) / (r+1)
call SETA1 ;ACCU = 1
call ADDEA ;ACCU = r+1
call MOVAV1 ;VAL1 = r+1
call SETA1 ;ACCU = -1
mov @R0, 0x80
call ADDEA ;ACCU = r-1
call MOVV1E ;ENTRY = r+1
call DIV ;ACCU = (r-1) / (r+1)
;sum: R2 = 2n+1, ACCU = term, VAL1 = y, VAL2 = sum, VAL3 = 4 * y^(2n+1)
mov R2, 1 ;2n+1 = 1 (n=0)
call MOVAE
call MOVEV1 ;VAL1 = y (already have ACCU = y)
call ADDEA ;ACCU = y + y = 2*y
call MOVAE
call ADDEA ;ACCU = (2*y) + (2*y) = 4*y
call MOVAV2 ;VAL2 = 4*y (term for 2n+1 = 1)
LNSUM: call MOVV1E ;ACCU = ACCU * y * y (term numerator)
call MUL
call MUL
call MOVAV3 ;save 4 * y^(2n+1) for next iteration
call SI2DA ;increment 2n+1 by 2, ACCU = ACCU / (2n+1), add to sum
jz LNMULP
call MOVV3A ;restore ACCU = 4 * y^(2n+1) for next iteration
jmp LNSUM ;loop
;calculate p*ln(10) and add sum
LNMULP: clr A
xch A, R3 ;A=p, R3=0
add A, R3 ;(R3=0) clear flags for DA
jz LNADD ;p = 0
jb7 LNPNEG ;integer part negative
LNPDA: da A ;convert to BCD
call SETA ;ACCU = p
mov A, R3
mov @R0, A ;sign
mov A, copy to ENTRY & restore ACCU
LNPNEG: cpl A
inc A
mov R3, 0x80
jmp LNPDA
;--------------------
;sine
; sin(x) = sum[(-1)^n * x^(2n+1) / (2n+1)!], n = 0...infinity
SIN: call ARGRED ;argument reduction to |x| <= pi
call SINF
jmp RESULT
;sine function
;sum: R2 = 2n+1, ACCU = term (already set), VAL1 = x, VAL2 = sum
SINF: mov R2, 1 ;2n+1 = 1 (n=0)
SIN1ST: call MOVAV2 ;sum(n=0) = term(n=0)
call MOVEV1
SINSUM: call SINIMD ;increment 2n+1, ACCU = ACCU * x / (2n+1)
call SINIMD ;increment 2n+1, ACCU = ACCU * x / (2n+1)
call TGLSA ;toggle sign of ACCU ((-1)^n)
call SUMADD ;add term to sum, check if term is zero
jnz SINSUM
call MOVV2E ;ENTRY = sum (result)
jmp MOVBA ;restore ACCU and return
;--------------------
;Increment 2n+1 by 1, Multiply by x, Divide by 2n+1
SINIMD: call MOVV1E ;ACCU = ACCU * x
call MUL
mov A, R2
add A, 1 ;use ADD to set flags for DA
da A
mov R2, A
call SETE ;ACCU = ACCU / n
jmp DIV
;--------------------
.message "Page 4: ", 0x500-6-$, " bytes free"
;--------------------
.org 0x500-6
;argument reduction for sin/cos/tan
ARGRED: call MOVAB ;backup accu
call NORME
in A, P2 ;check mode
mov R3, A
;--------------------
.if ($ < 0x500)
.error "Gap within ARGRED function"
.endif
.org 0x500 ;============================================================= PAGE 5
;--------------------
mov R2, 0 ;p = DIGITS-1 (DIGITS-3 for degrees)
;argument reduction to -pi <= ENTRY <= pi: add/subtract (2*pi)*10^p
call MOVEA
ARCMP: call LDDRPI
mov A, R2
mov @R0, A ;set dp (R0 = ENTRY + DIGITS/2)
call CMPEA
jnc ARNXT ;|ACCU| <= pi * 10^p
mov A, = 0
mov A, R3
jb3 AREND ;radians => done
mov A, convert to radians (rad = deg * pi / 180)
call MOVKE
call MUL
call CLEARE
mov @R0, DIGITS-3
dec R0
mov @R0, 0x18 ;180 degrees
call DIV
AREND: jmp MOVAE
;--------------------
;cosine
; cos(x) = sum[(-1)^n * x^(2n) / (2n)!], n = 0...infinity
COS: call ARGRED ;argument reduction to |x| <= pi
call COSF
jmp RESULT
;cosine function
;sum: R2 = 2n, ACCU = term, VAL1 = x, VAL2 = sum
COSF: call SETA1 ;term(n=0) = 1
mov R2, A ;(A=0) 2n = 0 (n=0)
jmp SIN1ST
;--------------------
;arcsin/arccos: ENTRY = ACCU / ENTRY, arctan(ENTRY)
DIVATN: call DIV ;ACCU = ACCU / ENTRY
call MOVAE ;move ACCU to ENTRY
;(fall through to ATAN)
;--------------------
;arctangent - doesn't backup ACCU
; if |x| >= 1: atan(x) = pi/2 - atan(1/x)
; if |x| >= 2-sqrt(3): atan(x) = atan[(x-c)/(1+x*c)] + atan(c)
; c = 1/sqrt(3) => atan(x) = atan[(sqrt(3)*x-1)/(sqrt(3)+x)]+pi/6
; atan(x) = sum[(-1)^n * x^(2n+1) / (2n+1)]
ATAN: call NORME
;save & clear argument sign
ATANF: mov R3, 3
inc R0 ;R0 = ENTRY + DIGITS/2
mov A, @R0
jb7 ATANCS ;negative
mov R3, 7
ATANCS: anl A, 0x7F ;clear sign
mov @R0, A
;argument reduction
call MOVEA ;ACCU = x
dec R0 ;R0 = ACCU + DIGITS/2 - 1
mov A, @R0 ;check MSD
anl A, 0xF0
jz ATANL1 ;MSD=0 => x < 1
dec R3 ;set flag to subtract result from pi/2 later
call SETA1 ;ACCU = 1
call DIV ;ACCU = 1/x
ATANL1: mov R0, ACCU + DIGITS/2 - 1
mov A, @R0 ;check if x < 2-sqrt(3) = ~0.268 (TODO: x<0.3 good enough?)
add A, -0x02
jnc ATANL2 ;x < 0.2
jnz ATANG3 ;x >= 0.3
dec R0
mov A, @R0
add A, -0x68
jnc ATANL2 ;x < 0.268
ATANG3: dec R3 ;set flag to add pi/6 to result later
dec R3
call MOVAV2 ;VAL2 = x
mov A, done
call CLEARE ;degrees => degrees = radians * 180 / pi
mov @R0, DIGITS-3 ;3 digits before decimal point
dec R0
mov @R0, 0x18 ;180 degrees
call MUL
mov A, tan(x) = infinity
call MOVAB ;backup ACCU
call MOVV3A ;ACCU = sin(x)
call DIV ;ACCU = sin(x) / cos(x) = tan(x)
call RESINA
jmp RESULT
TANOVF: jmp SETOVF
;--------------------
.if (DIGITS == 12)
CONST: ;constants (rounded to 12 digits)
; LSD ......................... MSD, dp
EULER: .db 0x46, 0x28, 0x18, 0x28, 0x18, 0x27, 11 ;e = 2.7 18 28 18 28 46
EULR10: .db 0x48, 0x79, 0x65, 0x64, 0x02, 0x22, 7 ;e^10 = 22 02 6.4 65 79 48
LN10: .db 0x99, 0x92, 0x50, 0x58, 0x02, 0x23, 11 ;ln(10) = 2.3 02 58 50 92 99
PI6: .db 0x60, 0x75, 0x87, 0x59, 0x23, 0x05, 11 ;pi/6 = 0.5 23 59 87 75 60
HALFPI: .db 0x79, 0x26, 0x63, 0x79, 0x70, 0x15, 11 ;pi/2 = 1.5 70 79 63 26 79
PI: .db 0x59, 0x53, 0x26, 0x59, 0x41, 0x31, 11 ;pi = 3.1 41 59 26 53 59
TWOPI: .db 0x18, 0x07, 0x53, 0x18, 0x83, 0x62, 11 ;2*pi = 6.2 83 18 53 07 18
SQRT3: .db 0x57, 0x07, 0x08, 0x05, 0x32, 0x17, 11 ;sqrt(3)= 1.7 32 05 08 07 57
.else
.error "Constants not defined for DIGITS = ", DIGITS
.endif
;load constant from table to ENTRY
MOVKE: mov R0, ENTRY
;move constant to number (value, dp & sign)
; clobbers R6~R7
; returns with R0 = ENTRY + DIGITS/2
MOVK: mov R6, A
mov R7, DIGITS/2+1
MOVKL: mov A, R6
movp A, @A
mov @R0, A
inc R0
inc R6
djnz R7, MOVKL
dec R0
ret
.if (>CONST != >$)
.error "CONST table & MOVK function not in same page!"
.endif
;--------------------
;load constant PI in degrees (180) or radians
; clobbers R5~R7
LDDRPI: mov A, done
call CLEARE ;degrees => ENTRY = xx0000000000 * 10^(-R5)
mov A, R5 ;dp position
mov @R0, A
dec R0
mov A, R1
mov @R0, A ;set the two MSD of ENTRY (all other digits remain 0)
inc R0
LDDRKE: ret
;--------------------
.message "Page 6: ", 0x700-$, " bytes free"
.org 0x700 ;============================================================= PAGE 7
;--------------------
;mux placed in page 7 so P20/P21/P22 are high (LED off) when accessing EPROM
DSPTBL: ;7-segment encoding for digits 0~9 (active low)
.if (SEGINV)
;active low segment outputs
.db <~(SA | SB | SC | SD | SE | SF) ;0
.db <~(SB | SC) ;1
.db <~(SA | SB | SD | SE | SG) ;2
.db <~(SA | SB | SC | SD | SG) ;3
.db <~(SB | SC | SF | SG) ;4
.db <~(SA | SC | SD | SF | SG) ;5
.db <~(SA | SC | SD | SE | SF | SG) ;6
.db <~(SA | SB | SC | SF) ;7
.db <~(SA | SB | SC | SD | SE | SF | SG) ;8
.db <~(SA | SB | SC | SD | SF | SG) ;9
.else
;active high segment outputs
.db (SA | SB | SC | SD | SE | SF) ;0
.db (SB | SC) ;1
.db (SA | SB | SD | SE | SG) ;2
.db (SA | SB | SC | SD | SG) ;3
.db (SB | SC | SF | SG) ;4
.db (SA | SC | SD | SF | SG) ;5
.db (SA | SC | SD | SE | SF | SG) ;6
.db (SA | SB | SC | SF) ;7
.db (SA | SB | SC | SD | SE | SF | SG) ;8
.db (SA | SB | SC | SD | SF | SG) ;9
.endif
.if ( display is full
dec R0
mov A, @R0
anl A, 0xF0
jnz MUX ;MSD is nonzero => display is full
mov A, R2
mov R0, ENTRY
call ROTLA
mov A, @R0 ;R0 = ENTRY + DIGITS/2
jb5 UPDATE ;no dp entered
inc @R0 ;increment dp position
;update trailing zero suppression (number has changed)
UPDATE: mov R0, ALTKEY
clr A
mov @R0, A ;clear alternative key function flags
mov R6, A
jf1 MUX ;number is being entered, don't suppress trailing zeros
mov R0, ENTRY+DIGITS/2
mov A, @R0 ;get dp position
anl A, 0x7F
cpl A
add A, DIGITS + 1 ;add 1 to make 2's complement
mov R1, A ;R1 = DIGITS - dp
mov R0, ENTRY ;start from LSD
mov R5, 0x0F ;start with low nibble (LSD)
mov R7, DIGITS
TRL0L: mov A, R1 ;compare dp position
xrl A, R7 ;to digit position
jz MUX ;equal
mov A, @R0
anl A, R5
jnz MUX ;digit is nonzero
inc R6
inc R6
inc R6
mov A, R5
swap A ;toggle mask to other nibble
mov R5, A
jb7 TRL0E ;high nibble next: don't increment RAM address
inc R0
TRL0E: djnz R7, TRL0L
;multiplex display & keypad
; R0 = number RAM pointer
; R1 = segment data (BUS) / 2nd RAM pointer
; R2 = commons 7~0 (P1)
; R3 = commons 11~8 (P2)
; R4 = "stop zero suppression" flag (set on nonzero or digit with dp)
; R5 = dp position
; R6 = start position for trailing zero suppression
; R7 = loop/keycode counter
MUX: mov R0, KEYCOD
clr A
mov @R0, A ;clear current keycode
mov R4, A ;clear "stop zero suppression" flag
dec A ;A=0xFF
mov R2, A
mov R3, A
mov R0, ENTRY+DIGITS/2 ;start with MSD (on P10)
mov A, @R0 ;R5 = 3*dp + 3 (to match loop/keycode counter)
add A, @R0
add A, @R0
anl A, 0x7F ;remove sign
add A, 3 ;sets C=0
mov R5, A
mov R7, 3*DIGITS ;loop/keycode counter
.if (COMREV) ;calculate common mask
MUXLOP: mov A, R3 ;shift P3 mask right
rrc A ;(bit 7 taken from Carry)
clr C
cpl C ;C=1
jb3 MUXC1
add A, 0x08 ;sets C=0 (move bit3 to carry and set bit3)
MUXC1: anl A, 0xFC ;LEDs on: P20 & P21
jf0 MUXOVF ;overflow flag set
orl A, 0x02 ;no overflow: P21 high (LED off)
MUXOVF: mov R3, A
mov R1, ENTRY+DIGITS/2 ;read ENTRY sign
mov A, @R1
jb7 MUXNEG ;negative
inc R3 ;positive: P20 high (LED off)
MUXNEG: mov A, R2 ;shift P2 mask right
rrc A ;(bit 7 taken from Carry)
mov R2, A
.else
MUXLOP: mov A, R2 ;shift P1 mask left
rlc A
mov R2, A
mov A, R3 ;shift P2 mask left
orl A, 0x0F
jc MUXP2R
anl A, ~0x08 ;clear bit 3 (becomes bit 4 after RLC)
MUXP2R: rlc A ;Carry doesn't matter (bit 0 overwritten below)
anl A, 0xFC ;LEDs on: P20 & P21
jf0 MUXOVF ;overflow flag set
orl A, 0x02 ;no overflow: P21 high (LED off)
MUXOVF: mov R3, A
mov R1, ENTRY+DIGITS/2 ;read ENTRY sign
mov A, @R1
jb7 MUXTR0 ;negative
inc R3 ;positive: P20 high (LED off)
nop
.endif
MUXTR0: mov A, R6 ;trailing zero suppression
xrl A, R7
jnz MUXDGT
mov R4, A ;(A=0) restart zero suppression
MUXDGT: mov A, R7 ;load digit (1 nibble)
jb0 MUXODD ;odd digit
dec R0 ;even digit: decrement RAM address
MUXODD: mov A, @R0
swap A ;swap nibbles after every digit
mov @R0, A
anl A, 0x0F
mov R1, A
add A, R4 ;C=0 (dp on) ;zero suppression & dp
mov R4, A ;stop zero suppression if digit nonzero
mov A, R7 ;compare dp position
xrl A, R5 ;to current digit
jz MUXENC ;equal: don't suppress a zero (display 0.xxx)
cpl C ;C=1 (dp off)
mov A, R4
jnz MUXENC ;zero suppression stopped
mov A, R7
xrl A, 3
jnz MUXSUP ;not the LSD
jf1 MUXENC ;LSD in entry mode: never suppress
MUXSUP:
.if (SEGINV)
dec R1 ;R1=0xFF => blank (suppressed zero)
.else
nop ;R1=0 (already set) => blank (suppressed zero)
.endif
jmp MUXDLY
MUXENC: inc R4 ;stop zero suppression ;encode digit to 7-segment
mov A, R1
movp A, @A
jc MUXSEG
.if (SEGINV)
anl A, <~DP ;dp on
.else
orl A, DP ;dp on
.endif
MUXSEG: mov R1, A
MUXDLY: jtf MUXT ;delay (for >= 250 Hz)
jmp MUXDLY
MUXT: mov A, -(XTAL/15/32/250/DIGITS) ;Timer clock = XTAL/15/32
mov T, A ;reload timer
.if (SEGINV)
mov A, 0xFF ;update display
.else
mov A, 0x00
.endif
movx @R0, A ;latch segments off (pulse #WR, address ignored)
mov A, R2
outl P1, A ;set commons 7~0
mov A, R3
outl P2, A ;set commons 11~8
mov A, R1
movx @R0, A ;latch segments on (pulse #WR, address ignored)
mov R1, KEYCOD ;read keypad
jni KEY2
dec R7
jnt1 KEY1
dec R7
jt0 KEYEND
mov A, R7 ;T0 is low
KEYSTO: xch A, @R1
jz KEYEND
KEYERR: mov @R1, 0xFF ;more than 1 key held down
KEYEND:
clr C
cpl C ;C=1
djnz R7, MUXLOP ;mux loop end
mov R0, KEY ;check key press
mov A, @R1
xch A, @R0 ;store current keycode & compare last keycode ...
inc R0 ;R0 = KEYCNT
xrl A, @R1 ;... to current keycode
jz KEYDB ;no change => debounce
mov @R0, 0 ;keycode changed => clear key counter
jmp MUX
;--------------------
KEY1: mov A, R7 ;T1 is low
dec R7
jmp KEYT0
KEY2: mov A, R7 ;#INT is low
dec R7
dec R7
jnt1 KEYERR ;T1 also low
KEYT0: jnt0 KEYERR ;T0 also low
jmp KEYSTO
KEYDB: mov A, @R1 ;debounce key press
jz MUX ;no key pressed (keycode 0)
jb7 MUX ;error (keycode 0xFF)
mov A, @R0 ;increment key counter
inc A
jz MUX ;prevent overflow
mov @R0, A
xrl A, 5 ;compare counter to debounce value (5 / 250 Hz = 20 ms)
jnz MUX ;not equal
.if (SEGINV) ;execute key press
dec A ;A=0xFF => blank
.else
nop ;A=0 (already set) => blank
.endif
movx @R0, A ;latch segments => display off
dec R0 ;R0 = KEY
mov A, @R0
movp3 A, @A ;KEYTBL starts at 0x301 => keycode 1 = 1st entry
add A, -10
jnc DIGIT ;keys 0~9
jmp KEYJMP ;other keys
;=========================================================================== END
.message "Page 7: ", 0x800-$, " bytes free"
.if ($ < 0x800)
.org 0x7FF
nop ;ensure binary for EPROM is exactly 2048 bytes long
.endif
.if ($ > 0x800)
.error "Program exceeds 2048 bytes (", $, " bytes)"
.endif