merge
authorTero Marttila <terom@fixme.fi>
Tue, 26 Jul 2011 22:43:59 +0300
changeset 41 fe9f354dddd4
parent 40 3803c0b40a9c (current diff)
parent 39 d7eac199d323 (diff)
child 44 e6571a8040ae
merge
--- a/Makefile	Tue Jul 26 22:43:41 2011 +0300
+++ b/Makefile	Tue Jul 26 22:43:59 2011 +0300
@@ -4,7 +4,7 @@
 AD_PART = m328p
 AD_PROG = arduino
 AD_BAUD = 57600
-AD_PORT = /dev/ttyUSB0
+AD_PORT = /dev/ttyUSB1
 
 AD = avrdude
 ADFLAGS = -p $(AD_PART) -c $(AD_PROG) -b $(AD_BAUD) -P $(AD_PORT)
@@ -13,14 +13,19 @@
 
 all: $(PROG).hex
 
-matrix.hex: spi.inc matrix.inc timer.inc delay.inc macros.inc
+matrix.hex: spi.inc matrix.inc timer.inc delay.inc macros.inc font.inc font.def
 led7seg.hex: spi.inc led7seg.inc adc.inc timer.inc delay.inc macros.inc
 timer.hex: timer.inc macros.inc
 
+font.inc: font.def
+
 %.hex: %.s
 	$(AS) $(ASFLAGS) $<
 	mv $<.hex $@
 
+font.def: font.txt font-compile.py
+	python font-compile.py $< $@ > /dev/null
+
 upload: $(PROG).hex
 	$(AD) $(ADFLAGS) -U flash:w:$<
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/font-compile.py	Tue Jul 26 22:43:59 2011 +0300
@@ -0,0 +1,193 @@
+def read_block (fh) :
+    """
+        Yield a series of non-empty lines from the given file, ignoring any leading empty lines, and stopping after the first empty line
+    """
+
+    leading = True
+
+    for line in fh :
+        if line.strip() :
+            leading = False
+
+            # yield non-empty
+            yield line
+
+        elif leading :
+            # skip leading empty
+            continue
+
+        else :
+            # stop on empty
+            return
+
+    else :
+        # EOF
+        return
+
+def read_charblock (lines) :
+    """
+        Read in a char from the given lines, returning an
+            (ascii, rows)
+
+        tuple, or None, if there weren't any more blocks
+    """
+
+    # the ascii code as a char
+    ascii = ''
+
+    # row data as ints
+    rows = []
+
+    for line in lines :
+        line = line.strip()
+
+        if line.startswith(';') :
+            # set ascii code
+            ascii = line.replace(';', '').strip()
+
+            if not ascii :
+                print 'read_charblock', 'empty'
+
+                # skip
+                return None
+
+            elif len(ascii) == 1 :
+                print 'read_charblock', 'simplechar', ascii
+
+            else :
+                ascii = ascii.decode('string_escape')
+        
+                print 'read_charblock', 'decodechar', ascii
+
+            assert len(ascii) == 1
+        
+        else :
+            # convert
+            row = line.replace('#', '1').replace('-', '0')
+
+            print 'read_charblock', 'row', row
+
+            # 6 columns
+            assert len(row) == 6
+
+            # from binary
+            row = int(row, 2)
+
+            rows.append(row)
+    
+    # got data?
+    if ascii and rows :
+        # 8 rows
+        assert len(rows) == 8
+
+        return ascii, rows
+
+    else :
+        # nope, empty block, EOF
+        return None
+
+def read_charblocks (fh) :
+    """
+        Read in all char blocks as (ascii, rows) tuples from given file
+    """
+
+    while True :
+        out = read_charblock(read_block(fh))
+
+        if out :
+            yield out
+
+        else :
+            break
+
+def decode_rows (inrows) :
+    """
+        Decode char def data from its 6x8 row format into the format we need (6x8 col format)
+    """
+
+    outcols = [0x00] * 6
+
+    for rowidx, row in enumerate(inrows) :
+        
+        for colidx, col in enumerate(outcols) :
+            # get bit from row
+            bit = (row >> (5 - colidx)) & 1
+
+            # set bit into column
+            outcols[colidx] |= (bit << rowidx)
+
+    # ok...
+    return outcols
+
+def write_chardef (fh, ascii, cols) :
+    """
+        Write out character definition block to given .def file, using given char code and column data
+    """
+
+    fh.write(
+            ("; %r\n" % ascii)
+        +   (".db %s\n" % (', '.join(bin(col) for col in cols)))
+        +   ("\n")
+    )
+
+def compile_fonts (infh, outf) :
+    """
+        Compile char blocks from infh, writing out definitions to outf
+    """
+
+    charmap = dict()
+
+    # decode in
+    for charblock in read_charblocks(infh) :
+        # unpack
+        ascii, rows = charblock
+
+        # convert
+        cols = decode_rows(rows)
+
+        # map
+        charmap[ascii] = cols
+
+        print 'compile_fonts', 'read', ascii
+
+    # detect min/max syms
+    syms = charmap.keys()
+    font_start = min(syms)
+    font_end = max(syms)
+
+    assert(ord(font_start) < ord(font_end))
+    
+    # write out
+    outf.write(
+            ";; AUTOMATICALLY GENERATED - Do not edit!\n"
+            ";; 8x6 font, '0' - '1', rows-by-col format\n"
+        +  (".equ FONT_8x6_START = %d ; %r\n" % (ord(font_start), font_start))
+        +  (".equ FONT_8x6_END = %d ; %r\n" % (ord(font_end), font_end))
+        +  (".equ FONT_8x6_COLS = %d\n" % (6, ))
+        +  (".equ FONT_8x6_ROWS = %d\n" % (8, ))
+        +   "FONT_8x6:\n"
+            "\n"
+    )
+
+    # default symbol for unknown chars
+    defsym = charmap['\0']
+
+    for char in xrange(ord(font_start), ord(font_end) + 1) :
+        ascii = chr(char)
+        cols = charmap.get(ascii, defsym)
+
+        write_chardef(outf, ascii, cols)
+
+def main () :
+    import sys, getopt
+
+    opts, args = getopt.getopt(sys.argv[1:], '')
+
+    inpath, outpath = args
+
+    # run
+    compile_fonts(open(inpath, 'r'), open(outpath, 'w'))
+
+if __name__ == '__main__' :
+    main()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/font.inc	Tue Jul 26 22:43:59 2011 +0300
@@ -0,0 +1,73 @@
+;; Basic LED matrix fonts
+;; vim: set ft=avr:
+
+.cseg
+
+;; Font definition
+; Defines FONT_8x6 symbol
+.include "font.def"
+
+; Font to use
+.set FONT_TABLE     = FONT_8x6
+.set FONT_START     = FONT_8x6_START
+.set FONT_END       = FONT_8x6_END
+.set FONT_COLS      = FONT_8x6_COLS
+.set FONT_ROWS      = FONT_8x6_ROWS     ; XXX: fixed to 8
+
+;; Render the given ASCII char into the given buffer
+; Input:    r16 - ASCII char code
+;           Y   - dest buf (Wx8 column data)
+Font_Render:
+    ; Test char index
+
+        ; test under-range
+        ldi     r17, FONT_START
+        cp      r16, r17
+        brlt    font_r_invalid
+
+        ; test over-range
+        ldi     r17, FONT_END
+        cp      r17, r16
+        brlt    font_r_invalid
+
+        ; compute offset in chars (r16)
+        subi    r16, FONT_START
+
+        ; ok
+        rjmp    font_r_render
+
+font_r_invalid:
+        ; use first sym
+        ldi     r16, 0
+
+    cbi         PORTD, PIND7
+
+font_r_render:
+        ; compute offset in bytes (r1:r0)
+        ldi     r17, FONT_COLS
+        mul     r16, r17
+
+        ; font table start offset from words
+        ldi     ZL, low(FONT_TABLE * 2)
+        ldi     ZH, high(FONT_TABLE * 2)
+
+        ; apply offset
+        add     ZL, r0
+        adc     ZH, r1
+
+    ; Copy column pixel data
+        ; count columns
+        ldi     r16, FONT_COLS
+font_r_cpy:
+        
+        ; copy via r17
+        lpm     r17, Z+
+        st      Y+, r17
+
+        dec     r16
+        brne    font_r_cpy
+
+
+    ; Done
+        ret
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/font.txt	Tue Jul 26 22:43:59 2011 +0300
@@ -0,0 +1,210 @@
+; \x00
+------
+-####-
+-#--#-
+-#--#-
+-#--#-
+-#--#-
+-####-
+------
+
+; \x20
+------
+------
+------
+------
+------
+------
+------
+------
+
+; !
+--##--
+--##--
+--##--
+--##--
+--##--
+------
+--##--
+--##--
+
+; 0
+-####-
+#----#
+#----#
+#----#
+#----#
+#----#
+#----#
+-####-
+
+; 1
+---#--
+--##--
+-#-#--
+#--#--
+---#--
+---#--
+---#--
+-#####
+
+; 2
+--###-
+-#---#
+-#---#
+----#-
+---#--
+--#---
+-#----
+######
+
+; 3
+-###--
+#---#-
+-----#
+----#-
+---##-
+-----#
+#---#-
+-###--
+
+; 4
+----#-
+-#--#-
+##--#-
+#---#-
+######
+----#-
+----#-
+----#-
+
+; 5
+######
+#-----
+#-----
+#####-
+-----#
+-----#
+#----#
+-####-
+
+; 6
+------
+-####-
+##---#
+#-----
+#####-
+#----#
+#----#
+-####-
+
+; 7
+-#####
+#----#
+----#-
+---#--
+--#---
+-#----
+#-----
+------
+
+; 8
+------
+-####-
+#----#
+#----#
+-####-
+#----#
+#----#
+-####-
+
+; 9
+-####-
+#----#
+#----#
+#----#
+-#####
+-----#
+-----#
+----#-
+
+; h
+#-----
+#-----
+#-----
+#-----
+#-###-
+##---#
+#----#
+#----#
+
+; e
+--##--
+-#--#-
+#----#
+######
+#-----
+#---#-
+-###--
+------
+
+; l
+--##--
+---#--
+---#--
+---#--
+---#--
+---#--
+---#--
+--###-
+
+; o
+------
+------
+------
+-####-
+#----#
+#----#
+#----#
+-####-
+
+; w
+------
+------
+------
+#---#-
+#---#-
+#---#-
+#-#-#-
+-#-#--
+
+; r
+------
+------
+#-----
+#-##--
+##----
+#-----
+#-----
+#-----
+
+; d
+----#-
+----#-
+----#-
+-####-
+#---#-
+#---#-
+#---#-
+-###--
+
+; 
+------
+------
+------
+------
+------
+------
+------
+------
+
--- a/matrix.inc	Tue Jul 26 22:43:41 2011 +0300
+++ b/matrix.inc	Tue Jul 26 22:43:59 2011 +0300
@@ -1,19 +1,58 @@
 ;; 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
 
-.dseg
-.set MATRIX_COLS = 8		; number of columns
+;; Matrix properties
+; Matrix width in columns
+.set MATRIX_COLS = 8                        ; physical columns
 
-matrix_colbit:	.byte 1					; column bit
-matrix_rowbuf:	.byte MATRIX_COLS		; row bitmask by column
+; 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, an set up buffers
+;; Normalize the outputs, enable the matrix, and set up buffers
 Matrix_Init:
     ; Setup ENable port
         sbi         MATRIX_PORT, MATRIX_OE	; high -> disabled
@@ -35,10 +74,13 @@
 		ldi			r16, 0b1
 		sts			matrix_colbit, r16
 
+        ldi         r16, 0
+        sts         matrix_colshift, r16
+
 		ldi			r16, 0
-		ldi			r17, MATRIX_COLS
-		ldi			YL, low(matrix_rowbuf)
-		ldi			YH, high(matrix_rowbuf)
+		ldi			r17, MATRIX_BUF_COLS
+		ldi			YL, low(matrix_colbuf)
+		ldi			YH, high(matrix_colbuf)
 
 m_init_mzero:
 		st			Y+, r16
@@ -54,9 +96,13 @@
 	; done
 		ret
 
-;; Scan the next column
+;; 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
@@ -64,7 +110,7 @@
 		; start packet
         cbi         SPI_PORT, SPI_SS
 
-		; output
+		; output single column-enable bit
 		out			SPDR, r16
 
 	; Compute col index
@@ -82,21 +128,28 @@
 		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_rowbuf)
-		ldi			XH, high(matrix_rowbuf)
+		ldi			XL, low(matrix_colbuf)
+		ldi			XH, high(matrix_colbuf)
 
 		; offset
-		ldi			r18, 0
+		ldi			r16, 0
 
 		add			XL, r17
-		adc			XH, r18
+		adc			XH, r16
 
 		; load
 		ld			r16, X
 
-		; output
+		; output full row-enable bitmask
 		rcall		SPI_Wait
 		out			SPDR, r16
 
@@ -119,13 +172,17 @@
         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_rowbuf)
-		ldi			ZH, high(matrix_rowbuf)
+		ldi			ZL, low(matrix_colbuf)
+		ldi			ZH, high(matrix_colbuf)
 
 	; Column bit
 		ldi			r25, 0
@@ -156,3 +213,211 @@
 	; 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
+
+        ret
+
--- a/matrix.s	Tue Jul 26 22:43:41 2011 +0300
+++ b/matrix.s	Tue Jul 26 22:43:59 2011 +0300
@@ -1,3 +1,5 @@
+;; vim: set ft=avr:
+
 .nolist
 .include "m168def.inc"      ; Same family as 328P
 .list
@@ -18,7 +20,8 @@
 .org SPIaddr
         rjmp        SPI_Interrupt
 
-.org 0x40
+
+cseg0:         .org 0x40
 
 ;; Syntax
 .include "macros.inc"
@@ -37,6 +40,9 @@
 ;; Utils
 .include "delay.inc"
 
+;; Font rendering
+.include "font.inc"
+
 ;; Scan through each pixel
 Main_ScanRaw:
 	; init
@@ -73,15 +79,15 @@
 Main_ScanTest:
 	; Generate pattern
 		; end of buffer
-		ldi			r17, MATRIX_COLS
-		ldi			XL, low(matrix_rowbuf + MATRIX_COLS)
-		ldi			XH, high(matrix_rowbuf + MATRIX_COLS)
+		ldi			r17, MATRIX_BUF_COLS
+		ldi			XL, low(matrix_colbuf + MATRIX_BUF_COLS)
+		ldi			XH, high(matrix_colbuf + MATRIX_BUF_COLS)
 		
 		; bit pattern
 		ldi			r16, 0b11
 
 st_loop:
-		; put
+		; put, pre-decrement
 		st			-X, r16
 
 		; flip
@@ -91,10 +97,128 @@
 		dec			r17
 		brne		st_loop
 
-st_scan:
-	; Scan repeatedly
-		;rcall		Matrix_ScanCol
-		rjmp		st_scan
+st_animate:
+	; Animate
+        ; shift right
+        rcall       Matrix_ShiftLeft
+
+        ; wait for X/16th of a second
+        ldi         XH, high(8 * 1024)
+        ldi         XL, low(8 * 1024)
+
+        rcall       Timer_Sleep
+
+        ; loop
+        rjmp        st_animate
+
+
+;; Display device code memory
+Main_ScanCode:
+    ; Code start
+        ldi         ZL, low(cseg0 * 2)      ; word addr
+        ldi         ZH, high(cseg0 * 2)     ; word addr
+    
+    ; Pause refresh
+        cli
+
+    ; Load initial frame
+        ; to first frame, starting from right edge
+        ldi         r17, 8
+		ldi			XL, low(matrix_colbuf + 16)
+		ldi			XH, high(matrix_colbuf + 16)
+
+sc_load_initial:
+        ; one byte
+        lpm         r16, Z+
+        st          -X, r16
+
+        ; loop until zero
+        dec         r17
+        brne        sc_load_initial
+
+        ; the first ShiftLeft below will jump to the end of the framebuffer
+
+sc_next:
+    ; Show this frame
+        rcall       Matrix_ShiftLeft
+
+    ; Load next frame
+        ldi         r17, 8
+        ldi         XL, low(matrix_colbuf + 8)
+        ldi         XH, high(matrix_colbuf + 8)
+
+sc_load_next:
+        ; one byte
+        lpm         r16, Z+
+        st          -X, r16
+
+        ; loop until zero
+        dec         r17
+        brne        sc_load_next
+
+    ; Enable refresh
+        sei
+
+    ; Animate from this -> next frame
+        ldi         r17, 8      ; 8 columns
+
+sc_anim:
+        ; wait for X/16th of a second
+        ldi         XH, high(2 * 1024)
+        ldi         XL, low(2 * 1024)
+
+        rcall       Timer_Sleep
+
+        ; shift
+        rcall       Matrix_ShiftLeft
+
+        ; loop until zero
+        dec         r17
+        brne        sc_anim
+
+    ; Pause refresh
+        cli
+
+    ; Move next -> this
+        ldi         r17, 8
+        ldi         XL, low(matrix_colbuf + 16)
+        ldi         XH, high(matrix_colbuf + 16)
+        ldi         YL, low(matrix_colbuf + 8)
+        ldi         YH, high(matrix_colbuf + 8)
+
+sc_move_this:
+        ; one byte
+        ld          r16, -Y
+        st          -X, r16
+
+        ; loop until zero
+        dec         r17
+        brne        sc_move_this
+
+sbi         PIND, PIND7
+
+    ; Load next frame, and animate
+        rjmp        sc_next
+
+Main_ScanText:
+
+    ; Constants
+stxt_message:       ; text to render
+        .db         0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0 ; 'hello world!'
+
+    ; Load into buffer
+        ldi         ZL, low(stxt_message * 2)
+        ldi         ZH, high(stxt_message * 2)
+
+        rcall       Text_LoadString
+
+sbi         PORTD, PIND7
+
+    ; Display
+        rcall       Text_ShowString
+
+    ; Done
+        ret
 
 Main:
 init:
@@ -122,8 +246,9 @@
 	
     ; Run
 		; rcall		Main_ScanRaw
-
-		rcall		Main_ScanTest
+		; rcall		Main_ScanTest
+        ; rcall       Main_ScanCode
+        rcall       Main_ScanText
 
 end:
         rjmp        end
--- a/spi.inc	Tue Jul 26 22:43:41 2011 +0300
+++ b/spi.inc	Tue Jul 26 22:43:59 2011 +0300
@@ -106,6 +106,7 @@
         rjmp        spi_sr_wait
 
     ; Read
+        ; XXX: wrong, should be head byte?
         ; read+store tail byte
         in          r1, SPDR
         st          -X, r1