;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