;; LED Matrix driver
;; vim: set ft=avr:
.dseg
;; I/O addresses
; Control port
.set MATRIX_DDR = DDRB
.set MATRIX_PORT = PORTB
; Pin for matrix driver Output Enable
.set MATRIX_OE = PORTB1 ; Output Enable, active low, externally pulled high
;; Matrix properties
; Matrix width in columns
.set MATRIX_COLS = 8 ; physical columns
; Framebuffer width in columns
.set MATRIX_BUF_COLS = 16 ; framebuffer columns
;; SPI addresses
; row drivers (8 bits)
.set MATRIX_SPI_ROW = 0 ; row mask source
; column sinks (8 bits)
.set MATRIX_SPI_COL = 1 ; column scan sink
;; Matrix state
; Matrix framebuffer
; this holds the columns data as a 1 byte bitmask of row data per column (8 bits -> 8 rows)
matrix_colbuf: .byte MATRIX_BUF_COLS ; framebuffer (row data by column)
; Column scan bit
; in the matrix refresh loop, we push out each column's row data in turn
; this bit tracks the currently refreshing column
matrix_colbit: .byte 1 ; column scan bit
; Matrix viewport offset
; the visible matrix data is taken directly from the framebuffer, but it can be taken at an arbitrary offset
; this determines the starting offset for the visible viewport's left edge from the start of the framebuffer in columns
matrix_colshift: .byte 1 ; viewport left column offset
;; Text
; Maximum length of message
.set TEXT_MAXLENGTH = 64
; Scrolling speed (kiloticks per frame)
.set TEXT_SPEED = 1
text_buffer: .byte TEXT_MAXLENGTH ; display message buffer
text_offset: .byte 1 ; current offset in text
.cseg
;; Normalize the outputs, enable the matrix, and set up buffers
Matrix_Init:
; Setup ENable port
sbi MATRIX_PORT, MATRIX_OE ; high -> disabled
sbi MATRIX_DDR, MATRIX_OE ; out
; blank hardware
ldi r16, 0
sts spi_outbuf + 0, r16 ; column sinks
sts spi_outbuf + 1, r16 ; row drivers
; write out
rcall SPI_SendRecv
; enable
cbi MATRIX_PORT, MATRIX_OE ; low -> enabled
; init buffers
ldi r16, 0b1
sts matrix_colbit, r16
ldi r16, 0
sts matrix_colshift, r16
ldi r16, 0
ldi r17, MATRIX_BUF_COLS
ldi YL, low(matrix_colbuf)
ldi YH, high(matrix_colbuf)
m_init_mzero:
st Y+, r16
; loop until zero
dec r17
brne m_init_mzero
; Use Timer0, 32k cycles -> 500Hz scan rate
ldi r16, 32
rcall Timer0_Start
; done
ret
;; Scan the matrix's next column from the viewport
;; Interrupt-driven
Matrix_ScanCol:
; Save registers
push r16
push r17
; Column bit
; load
lds r16, matrix_colbit
; start packet
cbi SPI_PORT, SPI_SS
; output single column-enable bit
out SPDR, r16
; Compute col index
ldi r17, 0
m_sc_colidx:
; shift
lsr r16
; done if we shifted the bit out
brcs m_sc_row
; count shifts
inc r17
rjmp m_sc_colidx
m_sc_row:
; Column shift
; load
lds r16, matrix_colshift
; add to col index
add r17, r16
; Row mask
; base
ldi XL, low(matrix_colbuf)
ldi XH, high(matrix_colbuf)
; offset
ldi r16, 0
add XL, r17
adc XH, r16
; load
ld r16, X
; output full row-enable bitmask
rcall SPI_Wait
out SPDR, r16
; Update col bit
lds r16, matrix_colbit
; shift left
lsl r16
brcc m_sc_colout
; overflow, take bit from C
rol r16
m_sc_colout:
; store
sts matrix_colbit, r16
; End of packet
rcall SPI_Wait
sbi SPI_PORT, SPI_SS
; Done
pop r17
pop r16
ret
;; Scan the matrix once in one go
;; XXX: doesn't support colshift
Matrix_ScanFull:
; Row index
ldi ZL, low(matrix_colbuf)
ldi ZH, high(matrix_colbuf)
; Column bit
ldi r25, 0
sec ; set C
m_pulse_col:
; rotate bit left from C
rol r25
; overflow
brcs m_pulse_end
; store in output buffer
sts spi_outbuf + 1, r25
; Row mask
ld r16, Z+
sts spi_outbuf + 0, r16
; Display
rcall SPI_SendRecv
; Next column
rjmp m_pulse_col
m_pulse_end:
; Done
ret
;; Reset the viewport to the start (left edge) of the framebuffer
Matrix_ShiftZero:
; Constant offset
ldi r16, 0
; Set
rjmp Matrix_ShiftSet
;; Shift the viewport one column to the left in the framebuffer, looping around to the end of the framebuffer
; This moves the visible output one column to the right
Matrix_ShiftLeft:
; Decrement-loop current value
; current value
lds r16, matrix_colshift
; shift window left
dec r16
; test for underflow (MSB/N set) -> don't skip reset
brpl Matrix_ShiftSet
; reset window to right edge
ldi r16, MATRIX_BUF_COLS - MATRIX_COLS
; Set
rjmp Matrix_ShiftSet
;; Shift the viewport one column to the right in the framebuffer, looping around to the start of the FB
; This moves the visible output one column to the left
Matrix_ShiftRight:
; Increment-loop current value
; current value
lds r16, matrix_colshift
; shift window right
inc r16
; test for overflow -> don't skip reset
cpi r16, MATRIX_BUF_COLS - MATRIX_COLS
brlt Matrix_ShiftSet
; reset window to left edge
ldi r16, 0
; Set
rjmp Matrix_ShiftSet
;; Set the matrix viewport offset
;; Input: r16
Matrix_ShiftSet:
; store new value
sts matrix_colshift, r16
; done
ret
;; Rewinds the currently visible viewport to the beginning of the framebuffer
; This copies the currently visible viewport data to the beginning of the framebuffer and resets the offset
Matrix_ShiftRewind:
; current view offset
ldi XL, low(matrix_colbuf)
ldi XH, high(matrix_colbuf)
; offset
lds r16, matrix_colshift
; add
ldi r17, 0
add XL, r16
adc XH, r17
; start of framebuffer
ldi YL, low(matrix_colbuf + 0)
ldi YH, high(matrix_colbuf + 0)
; viewport width
ldi r17, MATRIX_COLS
matrix_shiftrew_loop:
; copy
ld r16, X+
st Y+, r16
; count
dec r17
brne matrix_shiftrew_loop
; done, reset offset
rjmp MAtrix_ShiftZero
;; Load a NUL-terminated ASCII string from PGM into the text buffer
; Input: Z - Address of NUL-terminated ASCII string in PGM
Text_LoadString:
; Setup
; storage buffer
ldi YL, low(text_buffer)
ldi YH, high(text_buffer)
; max. length
ldi r18, TEXT_MAXLENGTH
text_loads_loop:
; Test max length
; count and check for overflow
dec r18
brne text_loads_char
; Load char
; force NUL
ldi r16, 0x00
text_loads_char:
; load next char
lpm r16, Z+
text_loads_store:
; Store and test NUL
; store it
st Y+, r16
; test for NUL
tst r16
brne text_loads_loop
; Update scroll offset
; reset offset
ldi r17, 0
sts text_offset, r17
; done
ret
;; Shows the loaded string of ASCII text on the display, scrolling it horizontally
; Uses font.inc for rendering
; XXX: uses blocking timer sleeps and doesn't return until done
Text_ShowString:
; Load initial char
ldi XL, low(text_buffer + 0)
ldi XH, high(text_buffer + 0)
; load char
ld r16, X+
push XL
push XH
; one column spacing
ldi YL, low(matrix_colbuf + 1)
ldi YH, high(matrix_colbuf + 1)
; render to framebuffer
rcall Font_Render
; reset viewport
rcall Matrix_ShiftZero
; Load next char
text_shows_next:
; next char
pop XH
pop XL
ld r16, X+
push XL
push XH
; test NUL
tst r16
breq text_shows_end
; offscreen
ldi YL, low(matrix_colbuf + 1 + 6 + 1)
ldi YH, high(matrix_colbuf + 1 + 6 + 1)
; render
rcall Font_Render
; Animate to next char
ldi r20, 7
text_shows_animloop:
; sleep
ldi XH, high(TEXT_SPEED * 1024)
ldi XL, low(TEXT_SPEED * 1024)
rcall Timer_Sleep
; shift
rcall Matrix_ShiftRight
; count
dec r20
brne text_shows_animloop
; Rewind to next char
rcall Matrix_ShiftRewind
sbi PIND, PIND7
; load next char and animate it in
rjmp text_shows_next
text_shows_end:
; Done
pop XH
pop XL
; Clear second frame
ldi YL, low(matrix_colbuf + 16)
ldi YH, high(matrix_colbuf + 16)
ldi r16, MATRIX_COLS
ldi r17, 0
text_shows_end2:
st -Y, r17
dec r16
brne text_shows_end2
; Blink
text_shows_end3:
; on
ldi r16, 0
rcall Matrix_ShiftSet
; sleep
ldi XH, high(TEXT_SPEED * 6 * 1024)
ldi XL, low(TEXT_SPEED * 6 * 1024)
rcall Timer_Sleep
; off
ldi r16, 8
rcall Matrix_ShiftSet
; sleep
ldi XH, high(TEXT_SPEED * 6 * 1024)
ldi XL, low(TEXT_SPEED * 6 * 1024)
rcall Timer_Sleep
; loop
rjmp text_shows_end3
; XXX: end
ret