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