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

; TM1638cc.inc - s'AVR version and TM1638_RCV by EH

; major change when using KEYPAD interrupts (currently not implemented):

; use cli/sei at precedure TM1638_SEND_DATA

;

; Library to drive the special circuit for LED control TM1638

; from Titan Micro Electronics (TM)

;

; written by Ralf Jardon (xxxxxxxx@xxx.xxx), May-July 2017

;

; Comments related to the datasheet refer to version 1.3 (EN)

;

; License: GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007

;

; 24-AUG-2017 V1.0

; 05-SEP-2017 V1.2 PRINT_CLOCK/TIME no longer in library (moved to main)

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

 

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

; TM1638_INIT:

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

 

TM1638_INIT:

                                     ; initialize Ports

    ldi    AKKU, (1<<STB_PIN) | (1<<CLK_PIN) | (1<<DATA_PIN)

    out    DDR_TM1638, AKKU

 

    ldi    AKKU, (1<<STB_PIN) | (1<<CLK_PIN)

    out    PORT_TM1638, AKKU

    rcall  delay1us

 

#if !defined(BITBANGING)             ; initialize SPI-engine

    ldi    AKKU, (0<<SPIE)|(1<<SPE)|(1<<MSTR)|(1<<DORD)|(1<<CPOL)|(1<<CPHA)|(1<<SPR1)|(1<<SPR0)

    out    SPCR, AKKU

    ldi    AKKU, (1<<SPI2X)

    out    SPSR, AKKU

#endif

 

    ; initialize TM1638 - EH: init code moved to TM1638_CLEAR

    ; ldi      TM1638_DATA_BYTE, DATA_CMD | WRITE_DATA

    ; rcall TM1638_SEND_COMMAND

    ; ldi      TM1638_DATA_BYTE, DISP_CTRL_CMD | DISP_ON | DISP_PWM_MASK

    ; rcall TM1638_SEND_COMMAND

 

    rcall TM1638_CLEAR              ; clear Display Memory

    ret

 

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

; TM1638_SEND: generates CLOCK signal and send DATA

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

 

#ifdef BITBANGING                ; BITBANGING

 

TM1638_SEND:

    push   COUNT

    FOR COUNT := #8

       rcall  delay1us

       ror TM1638_DATA_BYTE      ; put lowest bit into carry flag

       IF NC

           TM1638_CLK_LOW_DATA_LOW   ; carry is not set -> DATA pin LOW

       ELSE

           TM1638_CLK_LOW_DATA_HIGH  ; if carry set  -> DATA pin HIGH

       ENDI

       rcall  delay1us

       TM1638_CLK_HIGH               ; CLOCK pin HIGH

    ENDF                          ; next bit

    pop    COUNT

    ret

#else                            ; HARDWARE SPI

 

TM1638_SEND:                     ; start SPI transfer

    push   AKKU

    out    SPDR, TM1638_DATA_BYTE ; write Data into SPI engine

    REPEAT

       in  AKKU, SPSR

    UNTIL  AKKU, SPIF            ; Wait for transmission complete

    pop    AKKU

    ret

 

#endif

 

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

; TM1638_RCV: generates CLOCK signal and receive DATA in AKKU2

; HW SPI: MOSI and MISO are connected at the TM1638 side = DIO

; HW SPI: MOSI must be switched to input before TM1638_RCV is called

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

 

#ifdef BITBANGING                ; BITBANGING

 

TM1638_RCV:

    push   COUNT

    FOR COUNT := #8                  ; 8 Bits each Byte

       TM1638_CLK_LOW            ; send CLK from Master to Slave

       rcall  Delay1us           ; Twait

       IF %PIN_TM1638, DATA_PIN  ; if slave sends high

           sbr    AKKU2, 0b10000000  ; -> set bit

       ENDI

       IF COUNT <> #1

           lsr    AKKU2          ; shift bit

       ENDI

       TM1638_CLK_HIGH               ; CLOCK pin HIGH

       rcall  Delay1us

    ENDF                          ; next bit

    pop    COUNT

    ret

 

#else                            ; HARDWARE SPI

 

TM1638_RCV:        ; MOSI must be switched to input before TM1638_RCV is called

    in     AKKU2, SPSR               ; dummy read to clear SPIF bit in SPSR

    ldi    AKKU2, 0xff

    out    SPDR, AKKU2               ; dummy write to generate the SPI CLK

    REPEAT

       in  AKKU2, SPSR

    UNTIL  AKKU2, SPIF               ; Wait for transmission complete

    in     AKKU2, SPDR               ; finally receive data from SPI engine

    ret

 

#endif

 

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

; TM1638_SEND_COMMAND: Sends Command to TM1638

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

 

TM1638_SEND_COMMAND:

    TM1638_STB_LOW                ; set STROBE-output LOW, 

                                 ; ready to send commands or data

    rcall  TM1638_SEND               ; send 1 Byte command

    rcall  delay1us

    TM1638_STB_HIGH                  ; set STROBE-output HIGH, 

                                 ; command received

    rcall  delay1us

    ret

 

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

; TM1638_SEND_DATA: Sends DATA to TM1638

; (see datasheet V1.3, page 10 )

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

 

TM1638_SEND_DATA:

    ;cli                                 ; prevent an interrupt

    ldi    TM1638_DATA_BYTE, DATA_CMD | FIXED_ADDR  ; = 0x44

    rcall  TM1638_SEND_COMMAND           ; send Command 1

    rcall  delay1us

    TM1638_STB_LOW

    ori    TM1638_GRID_BYTE, ADDR_CMD    ; build segment address (0xC0)

    mov    TM1638_DATA_BYTE, TM1638_GRID_BYTE

    rcall  TM1638_SEND                   ; send Command 2 + address

    mov    TM1638_DATA_BYTE, TM1638_SEGM_BYTE

    rcall  TM1638_SEND                   ; send data

    TM1638_STB_HIGH

    ;sei

    ret

 

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

; TM1638_CLEAR: clears the Display memory and the Grid memory

; to prevent garbage after startup

; EH: include the init TM1638 code here, which still allows

; to interface to TM1638 if TM1638 has been disconnected in-between

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

 

TM1638_CLEAR:

    push   COUNT

       ; initialize TM1638

    ldi    TM1638_DATA_BYTE, DATA_CMD | WRITE_DATA

    rcall  TM1638_SEND_COMMAND

    ldi    TM1638_DATA_BYTE, DISP_CTRL_CMD | DISP_ON | DISP_PWM_MASK

    rcall  TM1638_SEND_COMMAND

    clr    COUNT

    REPEAT

       mov    TM1638_GRID_BYTE, COUNT   ; Grid

       ldi    TM1638_SEGM_BYTE, 0x00 ; Segment

       rcall  TM1638_SEND_DATA

       subi   COUNT, -2             ; next address

                                     ; (+2; SEG9 and SEG10 not used)

    UNTIL COUNT > #REG_MAX           ; REG_MAX = 0x0F

    pop    COUNT

    ret

 

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

; TM1638_PRINT_DEC 16 bit - leading zeros are suppressed

; major modifications by EH:

; - register LEADZ is used to mark leading zeros

; - leadings zeros are printed as space character

; AKKU2 (low byte) / AKKU3 (high byte)

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

 

TM1638_PRINT_DEC:

    push   AKKU

    push   AKKU2

    push   AKKU3

    mov    LEADZ,TRUE     ; prepare decimal place is '0'

 

; ** 10000 **

    ldi    AKKU, '0'

    REPEAT

       inc    AKKU

       subi   AKKU2, low(10000)

       sbci   AKKU3, high(10000)

    UNTIL C

    subi   AKKU2, low(-10000)

    sbci   AKKU3, high(-10000)

    dec    AKKU           ; correct last inc

    IF  AKKU == #'0'       ; LEADZ is TRUE

       ldi    TM1638_SEGM_BYTE,' '

    ELSE

       mov    TM1638_SEGM_BYTE, AKKU

       clr    LEADZ      ; decimal place was 1-9

    ENDI

    ldi    TM1638_GRID_BYTE, 0x06          

    rcall  TM1638_PRINT_CHAR

 

; ** 1000 **

    ldi    AKKU, '0'      ; prepare AKKU = '0'

    REPEAT

       inc    AKKU

       subi   AKKU2, low(1000)

       sbci   AKKU3, high(1000)

    UNTIL C

    subi   AKKU2, low(-1000)

    sbci   AKKU3, high(-1000)

    dec    AKKU           ; correct the last inc

    IF AKKU <> #'0'

       clr    LEADZ      ; no longer leading zero,

    ENDI                  ; otherwise LEADZ stays TRUE

    IF LEADZ == TRUE

       IF  AKKU == #'0'

           ldi    TM1638_SEGM_BYTE,' '

       ELSE

           clr    LEADZ  ; decimal place was 1-9

       ENDI

    ELSE

       mov    TM1638_SEGM_BYTE, AKKU

       ori    TM1638_SEGM_BYTE, 0x80 ; add DP

       clr    LEADZ  ; decimal place was 1-9

    ENDI

    ldi    TM1638_GRID_BYTE, 0x08          

    rcall  TM1638_PRINT_CHAR

 

; ** 100 **

    ldi    AKKU, '0'      ; prepare AKKU = '0'

    REPEAT

       inc    AKKU

       subi   AKKU2, low(100)

       sbci   AKKU3, high(100)

    UNTIL C

    subi   AKKU2, -100

    dec    AKKU           ; correct the last inc

    IF AKKU <> #'0'

       clr    LEADZ      ; no longer leading zero,

    ENDI                  ; otherwise LEADZ stays TRUE

    IF LEADZ == TRUE

       IF  AKKU == #'0'

           ldi    TM1638_SEGM_BYTE,' '

       ELSE

           clr    LEADZ  ; decimal place was 1-9

       ENDI

    ELSE

       mov    TM1638_SEGM_BYTE, AKKU

       clr    LEADZ      ; decimal place was 1-9

    ENDI

    ldi    TM1638_GRID_BYTE, 0x0A         

    rcall  TM1638_PRINT_CHAR

 

; ** 10 **

    ldi    AKKU, '0'      ; prepare AKKU = '0'

    REPEAT

       inc    AKKU

       subi   AKKU2, 10

    UNTIL C

    subi   AKKU2, -10

    dec    AKKU           ; correct last inc

    IF AKKU <> #'0'

       clr    LEADZ      ; no longer leading zero,

    ENDI                  ; otherwise LEADZ stays TRUE

    IF LEADZ == TRUE

       IF  AKKU == #'0'

           ldi    TM1638_SEGM_BYTE,' '

       ELSE

           clr    LEADZ  ; decimal place was 1-9

       ENDI

    ELSE

       mov    TM1638_SEGM_BYTE, AKKU

       clr    LEADZ      ; decimal place was 1-9

    ENDI

    ldi    TM1638_GRID_BYTE, 0x0C          

    rcall  TM1638_PRINT_CHAR

 

; ** 1 **

    ldi    AKKU, '0'      ; prepare AKKU = '0'

    add    AKKU, AKKU2        ; add remainder

    mov    TM1638_SEGM_BYTE, AKKU

    ldi    TM1638_GRID_BYTE, 0x0E

    rcall  TM1638_PRINT_CHAR

    pop    AKKU3

    pop    AKKU2

    pop    AKKU

    ret

 

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

; TM1638_PRINT_HEX 16 bit from AKKU2 and AKKU

; EH: fully structured 16 bit version replacing original 8 bit version

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

 

TM1638_PRINT_HEX:

    push   AKKU3

    push   AKKU2

    push   AKKU

    swap   AKKU2                 ; higher nibble

    ldi    TM1638_GRID_BYTE, 0x08 ; digit position 4

    rcall  hex_digith

    swap   AKKU2                 ; lower nibble

    ldi    TM1638_GRID_BYTE, 0x0A ; digit position 3

    rcall  hex_digith

    swap   AKKU                  ; higher nibble

    ldi    TM1638_GRID_BYTE, 0x0C ; digit position 2

    rcall  hex_digit

    swap   AKKU                  ; lower nibble

    ldi    TM1638_GRID_BYTE, 0x0E ; digit position 1

    rcall  hex_digit

    pop    AKKU

    pop    AKKU2

    pop    AKKU3

    ret

 

hex_digith:

    push   AKKU2

    andi   AKKU2, $0F            ; mask off nibble

    IF  AKKU2 >= #10

       subi   AKKU2, -('A'-'9'-1)

    ENDI

    subi   AKKU2, -'0'

    mov    TM1638_SEGM_BYTE, AKKU2

    rcall  TM1638_PRINT_CHAR

    pop    AKKU2

    ret

 

hex_digit:

    push   AKKU

    andi   AKKU, $0F             ; mask off nibble

    IF  AKKU >= #10

       subi   AKKU, -('A'-'9'-1)

    ENDI

    subi   AKKU, -'0'

    mov    TM1638_SEGM_BYTE, AKKU

    rcall  TM1638_PRINT_CHAR

    pop    AKKU

    ret

 

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

; TM1638_PRINT_BIN 8 bit from AKKU

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

; s'AVR version is more clear and efficient compared to original version

; EH: DP between position 4 and 3 added to split the nibbles

; REPEAT loop replaced by FOR loop

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

 

TM1638_PRINT_BIN:

    push   AKKU

    push   AKKU2

    push   COUNT

    mov    AKKU2, AKKU;

    ldi    TM1638_GRID_BYTE, 0x00    ; segment start address   

    FOR COUNT := #8

       rol    AKKU2;

       IF NC

           ldi    AKKU, '0'

       ELSE

           ldi    AKKU, '1'

       ENDI

       mov    TM1638_SEGM_BYTE, AKKU

       IF COUNT == #5

           ori    TM1638_SEGM_BYTE,0x80 ; add DP to split nibbles

       ENDI

       rcall  TM1638_PRINT_CHAR

       subi   TM1638_GRID_BYTE, -2  ; next segment address

    ENDF

    pop    COUNT

    pop    AKKU2

    pop    AKKU

    ret

 

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

; TM1638_PRINT_CHAR: prints a single character using the FONT table.

; Char is given by TM1638_SEGM_BYTE, Position is given by TM1638_GRID_BYTE

; DP support added by EH: Bit 7 is marking a DP to be printed as well

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

 

TM1638_PRINT_CHAR:

    push   ZH

    push   ZL

    push   AKKU

    push   TM1638_SEGM_BYTE

    ldi    ZL, LOW(2*FONTS)          ; start address of FONTS

    ldi    ZH, HIGH(2*FONTS)

    IF  TM1638_SEGM_BYTE,7              ; DP marking?

       andi   TM1638_SEGM_BYTE, 0x7f ; mask off DP

       ldi    AKKU,1                ; remember the DP

    ELSE

       clr AKKU

    ENDI

    subi   TM1638_SEGM_BYTE, ASCII_OFFSET; - 0x30

    IF TM1638_SEGM_BYTE >= #0x1A

       subi   TM1638_SEGM_BYTE, CHAR_OFFSET ; if char, sub 7

    ENDI

    add    ZL, TM1638_SEGM_BYTE      ; select char address

    IF C                             ; EH: added carry

       inc ZH

    ENDI

    lpm    TM1638_SEGM_BYTE,Z        ; simplified by EH

    IF AKKU == #1

       ori TM1638_SEGM_BYTE, 0x80    ; add DP segment to char

    ENDI

    rcall  TM1638_SEND_DATA          ; send data

    pop    TM1638_SEGM_BYTE

    pop    AKKU

    pop    ZL

    pop    ZH

    ret

 

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

; TM1638_PRINT_TXT8, print 8 characters, block size is 10 (due to EOT)

; AKKU3 = Textblock Number, 0x00 = EOT

; subroutine added by EH

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

 

TM1638_PRINT_TXT8:

    push   COUNT

    push   ZL

    push   ZH

    ldi    ZL, LOW(TEXT10*2)     ; load text start address

    ldi    ZH, HIGH(TEXT10*2)

    ldi    TM1638_GRID_BYTE, 0x00 ; first segment (left most)

    WHILE AKKU3 <> #0

       adiw   ZL, TEXT_BLK10     ; each TEXT_BLOCK has 10 chars

                                 ; calculate address of current block

       dec    AKKU3

    ENDW

    FOR COUNT := #8

       lpm    TM1638_SEGM_BYTE, Z+  ; load first char of current block

       EXITIF TM1638_SEGM_BYTE == #0 ; early EOT?

       rcall  TM1638_PRINT_CHAR     ; print char

       subi   TM1638_GRID_BYTE, -2  ; go to next segment address

    ENDF

    pop    ZH

    pop    ZL

    pop    COUNT

    ret

 

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

; TM1638_PRINT_TXT35, print 35 characters, block size is 36 (due to EOT)

; AKKU3 = Text block Number, 0x00 = EOT

; EH: replaced LOOP/ENDL by FOR/ENF

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

 

TM1638_PRINT_TXT35:

    push   AKKU

    push   ZL

    push   ZH

    push   COUNT

    ldi    ZL, LOW(MOVETEXT*2)       ; load text start address

    ldi    ZH, HIGH(MOVETEXT*2)

    ldi    TM1638_GRID_BYTE, 0x00 ; first segment

    WHILE AKKU3 <> #0

       adiw   ZL, TEXT_BLK36     ; each TEXT_BLOCK has 36 chars

                                 ; calculate address of current block

       dec    AKKU3

    ENDW

    FOR COUNT := #35

       lpm    AKKU, Z+           ; load first char of current block

       EXITIF AKKU == #0         ; early EOT ?

       mov    TM1638_SEGM_BYTE, AKKU

       rcall  TM1638_PRINT_CHAR  ; print char

       subi   TM1638_GRID_BYTE, -2  ; go to next segment address

    ENDF

    pop    COUNT

    pop    ZH

    pop    ZL

    pop    AKKU

    ret

 

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

; TM1638_PRINT_MOVETEXT, move 35 characters, block size is 36 (due to EOT)

; AKKU3 = Text block Number, 0x00 = EOT

; REPEAT loop replaced by FOR loop

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

 

TM1638_PRINT_MOVETEXT:

    push   ZL

    push   ZH

    push   COUNT

    push   AKKU2

    push   AKKU

    ldi    ZL, LOW(MOVETEXT*2)       ; load text start address

    ldi    ZH, HIGH(MOVETEXT*2)

    WHILE AKKU3 <> #0

       adiw   ZL, TEXT_BLK36     ; each TEXT_BLOCK has 36 chars

                                 ; calculate address of current block

       dec    AKKU3

    ENDW

    clr    TM1638_GRID_BYTE   

    clr    COUNT

    clr    AKKU2

    REPEAT

       FOR COUNT := #8

           lpm    AKKU, Z+       ; load first char of current block

           EXITIF AKKU == #0     ; EH: earlier EOT

           mov    TM1638_SEGM_BYTE, AKKU

           rcall  TM1638_PRINT_CHAR  ; print current char

           subi   TM1638_GRID_BYTE, -2  ; go to next segment address

       ENDF

       EXITIF AKKU == #0         ; EH: another EXITIF required

       rcall  Delay100ms         ; text loop

       rcall  Delay100ms

       sbiw   ZL, 7              ; add 7 (8-1) to shift display

       inc    AKKU2

    UNTIL AKKU2 >= #TEXT_BLK36-8

    pop    AKKU

    pop    AKKU2

    pop    COUNT

    pop    ZH

    pop    ZL

    ret

 

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

; TM1638_BRIGHTNESS

; AKKU = value between 0-7

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

 

TM1638_BRIGHTNESS:

    ldi    TM1638_DATA_BYTE, (DISP_CTRL_CMD | DISP_ON)

    add    TM1638_DATA_BYTE, AKKU

    rcall  TM1638_SEND_COMMAND

    ret

 

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

; TM1638_KEYPAD

; Register BUTTONS = Keys being pushed in the order 56781234 (MSB to LSB)

; BUTTONS should be cleared (if wanted) before TM1638_KEYPAD is called,

; otherwise new buttons are OR'ed together

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

 

TM1638_KEYPAD:

    push   AKKU2

    push   AKKU3

    push   COUNT

    TM1638_STB_LOW                       ; set strobe low to start action

    ldi    TM1638_DATA_BYTE, (DATA_CMD | READ_KEYS); send DATA_READ COMMAND

    rcall  TM1638_SEND

    rcall  Delay1us                      ; Twait = 2us

    rcall  Delay1us

                                         ; configure DATA_PIN/MOSI as INPUT

    ldi    AKKU3, (1<<STB_PIN) | (1<<CLK_PIN) | (0<<DATA_PIN)

    out    DDR_TM1638, AKKU3             ; to receive DATA from Slave

    TM1638_DATA_HIGH                     ; release internal pullup

    FOR AKKU3 := #4                          ; 4 Bytes to read

       clr    AKKU2                     ; collects pressed buttons bitwise

       rcall  TM1638_RCV                ; receive one byte in AKKU2

       andi   AKKU2,0b0001_0001         ; keep buttons 1 through 8 only

       mov    COUNT,AKKU3                   ; from outer FOR counter

       dec    COUNT                     ; shift by 3, 2, 1, 0

       WHILE COUNT > #0                 ; dynamically shift button bits

           lsl AKKU2

           dec COUNT

       ENDW

       or  BUTTONS,AKKU2                 ; mark buttons 1 through 8 only

    ENDF                                 ; next byte

    ; finally all 8 Buttuons (if pressed) are marked in following order: 5678_1234

    ; don't add a SWAP BUTTONS here, otherwise additional buttons cannot be OR'ed

    TM1638_STB_HIGH                          ; set strobe high to terminate action

    ldi    AKKU3, (1<<STB_PIN) | (1<<CLK_PIN) | (1<<DATA_PIN)

    out    DDR_TM1638, AKKU3         ; reconfigure DATA_PIN/MOSI as OUTPUT

    pop    COUNT

    pop    AKKU3

    pop    AKKU2

    ret