;=======================================================================
; Factorial_TM1640.s
;
; Created: 22.05.2019
; Author : EH
;
; Calculate 1000 Factorial (1000!) on ATmega1284 (due to
large SRAM)
;
; by Eberhard Haug, 22-MAY-2019
;
; Definition:
;
; 1000! = 1 * 2 * 3 * ... * 1000
;
; Note: when using ATmega1284, JTAG should be disabled
or any port other than PORTC must be used
; For the given code no delays should be called while
CNTH:CNTL is used outside of Delay.s
;
; License: GNU GENERAL PUBLIC LICENSE, Version 3, 29
June 2007
;=======================================================================
.NOLIST
.INCLUDE
"tm1640.def" ; various definitions, including processor def.inc
.INCLUDE
"tm1640.mac" ; TM1640 macros
.INCLUDE
"MOD_DIV10.mac" ; macro to generate MOD 10 and DIV 10 from AKKU
.LIST
.LISTMAC
.CSEG
.ORG $0000 ; Start of program code after Reset
rjmp init ; External Pin,
Power-on Reset,
nop ; Brown-out Reset
and Watchdog System Reset
;
all other interrupt vectors are not
used in this application
;=======================================================================
; INIT and MAIN
;=======================================================================
.ORG $100 ; alternately .ORG INT_VECTORS_SIZE
init:
ldi AKKU,
HIGH(RAMEND) ; Initialize stack pointer
out SPH, AKKU
ldi AKKU,
LOW(RAMEND)
out SPL, AKKU
; prepare various constants in lower AVR registers R2..R15
; registers R0 and R1 are used
by MULT
clr ZERO ; ZERO = 0
clr TRUE
inc TRUE ; TRUE = 1
ldi AKKU,
0b11111000 ; bit pattern for MOD_DIV10
mov BITP, AKKU
ldi AKKU, 205 ; constant 205 for MOD_DIV10
mov C205, AKKU
rcall TM1640_INIT ; Initialize Ports, SPI & TM1640, also LED pin PORTC0
clr AKKU3 ; "CALCULATE 1000 FACTORIAL", Text block 0
rcall TM1640_PRINT_MOVETEXT
rcall TM1640_CLEAR
clr AKKU3
rcall TM1640_PRINT_TXT8 ; display "--------" while calculating ...
rcall factorial ; calculate FACT! which takes 14 sec
@ 18 MHz
rcall print_factorial_f8 ; print 1st 8 digits of factorial result once,
; 1000! starts with 40238726,
1230! starts with 22138400
rcall cdigits ; count # of digits and print on TM1640 display line 2
rcall czeros ; count # of zeros and print on TM1640 display line 2
sbi PORTC,0 ; pin/LED ON
rcall Delay500ms
cbi PORTC,0 ; pin/LED OFF
rcall factorial
sbi PORTC,0 ; pin/LED ON
rcall Delay500ms
ENDL
;=======================================================================
; Subroutines
;=======================================================================
.UNDEF TM1640_GRID_BYTE ; redefine registers for (slightly)
faster factorial
.UNDEF COUNT
.UNDEF DTMP
.DEF DD2 =
r21 ; DD1 = r20 is defined already
.DEF DD3 =
r22
.DEF DD4 =
r23
;=======================================================================
factorial: ; calculate factorial in the huge array FArray,
; which includes the result and the interlaced partial
products PP:
; result_1 | PP1_1 | 0 | 0 | 0 | result_2 | PP1_2 |
PP2_1 | 0 | 0 | ...
; this arrangement allows to
use register offset addressing
ldi zh,HIGH(FArray) ;
Z register is pointer to FArray (LSB)
ldi zl,LOW(FArray)
st z, TRUE ; starting with number 1 (LSB), being multiplied by DD = 2,
etc.
std z+1, ZERO ; clear PP[1], right most
std z+2, ZERO ; clear PP[2], right most
std z+3, ZERO ; clear PP[3], right most
std z+4, ZERO ; clear PP[4], right most
adiw zh:zl, 5 ; now point to 2nd location of FArray
ldi CNTH,HIGH(5*(Array-1)) ;
REPEAT loop counter (counting down)
ldi CNTL,LOW(5*(Array-1))
REPEAT
st z+, ZERO ; clear all other entries of FArray
(sets of 5) at once
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
; now calculate factorial *2,
*3, ..., * FACT
;
for x := 2 to FACT do ;
must be incremental due to limited arrays (smaller size possible)
; variable x handled separately
digit wise
ldi DD1,2 ; decimal digit 1 (LSD), starting with number 2
clr DD2 ; decimal digit 2
clr DD3 ; decimal digit 3
clr DD4 ; decimal digit 4 (MSD)
ldi xh,HIGH(FACT-1) ; X register is the big decreasing loop counter for
FACT!
ldi xl,LOW(FACT-1)
REPEAT.m ; the 16-bit FOR loop handled by REPEAT-UNTIL, CARRY is
last carry
ldi zh,HIGH(FArray) ;
Z register is increasing pointer to FArray
ldi zl,LOW(FArray)
IF
DD1 == #0 ; if digit DD1 is 0 ...
ldi CNTH,HIGH(Array-1) ; REPEAT loop
counter (counting down to 1)
ldi CNTL,LOW(Array-1)
REPEAT
std z+1, ZERO ;
... delete complete PP1 line
adiw ZH:ZL,5 ;
point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
ELSEIF
DD1 == #1 ; if digit DD1 is 1 ...
ldi CNTH,HIGH(Array-3) ; REPEAT loop
counter (counting down)
ldi CNTL,LOW(Array-3)
REPEAT
ld AKKU, z ;
... copy complete line from FArray
std z+1, AKKU ;
to partial product 1
adiw ZH:ZL,5 ;
point to next FArray entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+1, ZERO ; clear upper 3 entries
std z+1+5, ZERO
std z+1+10, ZERO
ELSE ; if digit DD1 is > 1 ...
clr CARRY ; clear carry
ldi CNTH,HIGH(Array-4) ; REPEAT loop
counter (counting down)
ldi CNTL,LOW(Array-4)
REPEAT
ld AKKU3, z
IF
AKKU3 == #0 ; saves plenty of time
std z+1, CARRY ;
for 1000! carry is always <=2
clr CARRY ; no additional carry can happen
ELSEIF
AKKU3 == #1
mov AKKU, CARRY
add AKKU, DD1
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+1, AKKU
ELSE ; true multiply + carry
mul AKKU3, DD1 ;
result must be 8 bit --> PLO only
mov AKKU, PLO
add AKKU, CARRY ; add carry
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+1, AKKU
ENDI
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+1, CARRY
std z+1+5, ZERO
ENDI
; now do the same for DD2, DD3
and DD4
;
(unrolled because DD registers cannot be addressed by a
ldi zh,HIGH(FArray) ; Z register is increasing pointer to FArray
ldi zl,LOW(FArray)
IF
DD2 == #0 ; if digit DD2 is 0 ...
ldi CNTH,HIGH(Array-1) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-1)
REPEAT
std z+2, ZERO
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
ELSEIF
DD2 == #1 ; if digit DD2 is 1 ...
ldi CNTH,HIGH(Array-3) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-3)
REPEAT
ld AKKU, z
std z+2+5, AKKU ; copy FArray
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+2+5, ZERO ; clear upper entries
std z+2+10, ZERO
std z+2+15, ZERO
ELSE ; if digit DD2 is > 1 ...
clr CARRY ; carry
ldi CNTH,HIGH(Array-4) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-4)
REPEAT
ld AKKU3, z
IF
AKKU3 == #0
std z+2+5, CARRY
clr CARRY
ELSEIF
AKKU3 == #1
mov AKKU, CARRY
add AKKU, DD2
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+2+5, AKKU
ELSE
mul AKKU3, DD2 ;
result must be 8 bit --> PLO only
mov AKKU, PLO
add AKKU, CARRY ; add carry
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+2+5, AKKU
ENDI
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+2+5, CARRY
std z+2+10, ZERO
ENDI
; now do the same for DD3 ...
ldi zh,HIGH(FArray) ; Z register is increasing pointer to FArray
ldi zl,LOW(FArray)
IF
DD3 == #0 ; if digit DD3 is 0 ...
ldi CNTH,HIGH(Array-1) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-1)
REPEAT
std z+3, ZERO
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
ELSEIF
DD3 == #1 ; if digit DD3 is 1 ...
ldi CNTH,HIGH(Array-3) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-3)
REPEAT
ld AKKU, z
std z+3+10, AKKU ; copy FArray
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+3+10, ZERO ; clear upper entries
std z+3+15, ZERO
std z+3+25, ZERO
ELSE ; if digit DD3 is > 1 ...
clr CARRY ; carry
ldi CNTH,HIGH(Array-4) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-4)
REPEAT
ld AKKU3, z
IF
AKKU3 == #0
std z+3+10, CARRY
clr CARRY
ELSEIF
AKKU3 == #1
mov AKKU, CARRY
add AKKU, DD3
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+3+10, AKKU
ELSE
mul AKKU3, DD3 ;
result must be 8 bit --> PLO only
mov AKKU, PLO
add AKKU, CARRY ; add carry
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+3+10, AKKU
ENDI
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+3+10, CARRY
std z+3+15, ZERO
ENDI
; now do the same for DD4 ...
ldi zh,HIGH(FArray) ; Z register is increasing pointer to FArray
ldi zl,LOW(FArray)
IF
DD4 == #0 ; if digit DD4 is 0 ...
ldi CNTH,HIGH(Array-1) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-1)
REPEAT
std z+4, ZERO
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
ELSEIF
DD4 == #1 ; if digit DD4 is 1 ...
ldi CNTH,HIGH(Array-3) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-3)
REPEAT
ld AKKU, z
std z+4+15, AKKU ; copy FArray
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+4+15, ZERO ; clear upper entries
std z+4+20, ZERO
std z+4+25, ZERO
ELSE ; if digit DD4 is > 1 ...
clr CARRY ; carry
ldi CNTH,HIGH(Array-4) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-4)
REPEAT
ld AKKU3, z
IF
AKKU3 == #0
std z+4+15, CARRY
clr CARRY
ELSEIF
AKKU3 == #1
mov AKKU, CARRY
add AKKU, DD1
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+4+15, AKKU
ELSE
mul AKKU3, DD4 ;
result must be 8 bit --> PLO only
mov AKKU, PLO
add AKKU, CARRY ; add carry
MOD_DIV10 ;
gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
std z+4+15, AKKU
ENDI
adiw ZH:ZL,5 ; point to next entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
std z+4+15, CARRY
std z+4+20, ZERO
ENDI
; finally add up all partial
products PP1..PP4 (must be incremental)
; 16-bit FOR loop is not supported by s'AVR, replaced by
REPEAT-UNTIL
ldi zh,HIGH(FArray) ; Z register is increasing pointer to FArray
ldi zl,LOW(FArray)
ldi CNTH,HIGH(Array-1) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array-1)
clr CARRY ; start with no carry
REPEAT
; partial sum := carry + PP[1,j] + PP[2,j] + PP[3,j] +
PP[4,j]
mov AKKU, CARRY ; get last carry
ldd CARRY, z+1 ; get PP[1,j]
add AKKU, CARRY ; add to last carry
ldd CARRY, z+2 ; get PP[2,j]
add AKKU, CARRY ; sum up
ldd CARRY, z+3 ; get PP[3,j]
add AKKU, CARRY ; sum up
ldd CARRY, z+4 ; get PP[4,j]
add AKKU, CARRY ; sum up
MOD_DIV10 ; gets both AKKU = AKKU MOD 10 and CARRY = AKKU DIV 10
st z, AKKU ; store result = MOD 10 to FArray
adiw ZH:ZL,5 ; point to next entry, CARRY is carry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL
Z
st z, CARRY ; store last carry from DIV 10 (should be 0) to FArray
inc DD1 ; DD := DD + 1, get next DD number
IF DD1
> #9
clr DD1
inc DD2
IF
DD2 > #9
clr DD2
inc DD3
IF
DD3 > #9
clr DD3
inc DD4 ;
DD4 will never be more than 1 to calculate up to 1230!
ENDI
ENDI
ENDI
sbiw XH:XL,1 ;
X = big loop counter to calculate factorial 2 through FACT
UNTIL Z
ret
;=======================================================================
; other subroutines
;=======================================================================
; redefine registers to original set
.UNDEF DD2
.UNDEF DD3
.UNDEF DD4
.DEF TM1640_GRID_BYTE = r21 ; LED Grid (a-g + dp)
.DEF COUNT = r22 ; 8-bit counter for loops
.DEF DTMP = r23 ; delay timer, also used for SPI (BB only)
;=======================================================================
cdigits: ; count # of digits and print in line 2
ldi yh,HIGH(Array)
ldi yl,LOW(Array)
ldi zh,HIGH(FArray+5*(Array-1)) ; Z register is decreasing pointer to end of FArray
ldi zl,LOW(FArray+5*(Array-1))
ldi CNTH,HIGH(Array) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array)
REPEAT
ld AKKU, z
EXITIF
AKKU <> #0 ; first leading digit <> 0 found
sbiw YH:YL,1
sbiw ZH:ZL,5 ; point to next lower entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL Z
mov AKKU3,yh
mov AKKU2,yl
rcall TM1640_PRINT_DEC_1 ; print DEC, 1st 4 digits in 2nd line
ret
;=======================================================================
czeros: ; count # of zeros and print in line 2
clr yh ; count # of trailing zeros in yh:yl
clr yl
ldi zh,HIGH(FArray) ; Z register is increasing pointer to FArray
ldi zl,LOW(FArray)
ldi CNTH,HIGH(Array) ; REPEAT loop counter (counting down)
ldi CNTL,LOW(Array)
REPEAT
ld AKKU, z
EXITIF
AKKU <> #0 ; first digit <> 0 found
adiw YH:YL,1 ; trailing 0 detected
adiw ZH:ZL,5 ; point to next higher entry
sbiw CNTH:CNTL,1 ; loop counter
UNTIL Z
mov AKKU3,yh
mov AKKU2,yl
rcall TM1640_PRINT_DEC_2 ; print decimal, 4 digits, 2nd line, 2nd half
ret
;=======================================================================
print_factorial_f8: ;
print 1st 8 digits of factorial from SRAM
; 1000! should begin with
40238726
push ZL
push ZH
push COUNT
push AKKU4
push AKKU
ldi ZL, LOW(FArray + 5*(ExpLen-1)) ; load text start address = upper end of FArray
ldi ZH, HIGH(FArray + 5*(ExpLen-1))
clr TM1640_GRID_BYTE
FOR COUNT := #8
ld AKKU, Z ; load digit from FArray
mov AKKU4, AKKU
andi AKKU4, 0x0F ; mask nibble
subi AKKU4,-'0' ; add ASCII-0
rcall TM1640_PRINT_CHAR ;
print current char
subi TM1640_GRID_BYTE,
-1 ; go to next segment address
sbiw ZH:ZL, 5 ; next lower FArray entry
ENDF
pop AKKU
pop AKKU4
pop COUNT
pop ZH
pop ZL
ret
;=======================================================================
rjmp init ; in case something goes weird
;=======================================================================
; other subroutines and tm1640
character font:
; NOTE: in general all included .asm files being
generated by s'AVR
; must use DIFFERENT label prefixes (could be any from
_A through _Z)!
; delay subroutines, compiled from "Delay.s", label prefix _D:
.INCLUDE "Delay.asm" ; various delay routines
; tm1640 library, compiled from
"tm1640_inc.s", label prefix _I:
.INCLUDE
"tm1640_inc.asm"
.INCLUDE
"tm1640_font.inc" ; tm1640 character font
;=======================================================================
; constant text in upper case
letters (part from tm1640 demo):
MOVETEXT: ; 35+1 characters each
.if FACT==100
.db " CALCULATING 100 FACTORIAL ",0 ;
TEXT_BLOCK 0
.elif FACT==500
.db " CALCULATING 500 FACTORIAL ",0 ;
TEXT_BLOCK 0
.elif FACT==1000
.db " CALCULATING 1000 FACTORIAL ",0 ;
TEXT_BLOCK 0
.elif FACT==1230
.db " CALCULATING 1230 FACTORIAL ",0 ;
TEXT_BLOCK 0
.endif
TEXT10: ; 8+2 characters each
.db "--------",0,0 ;
TEXT10, Block 0
; no other text used in this
program
.DSEG
.ORG SRAM_START ; 0x0100 for ATmega1284P, the major part of SRAM is used
for factorial calculation
FArray: .byte 5 * Array ; FArray LSB first +
interlaced PP (partial products),
;
PPs addressed by register offsets 1, 2, 3 and 4, from
Index 1 through Array
.EXIT