;=======================================================================

; 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 LOOP

;=======================================================================

.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

       LOOP                              ; main loop forever

             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-4)  ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-4)

                           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-5)  ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-5)

                           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 LOOP register)

                    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-4)         ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-4)

                           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-5)         ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-5)

                           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-4)         ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-4)

                           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-5)         ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-5)

                           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-4)         ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-4)

                           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-5)         ; REPEAT loop counter (counting down)

                           ldi    CNTL,LOW(Array-5)

                           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 leadig 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 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