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