BlinkenArea - GitList
Repositories
Blog
Wiki
bluebox
Code
Commits
Branches
Tags
Search
Tree:
fd252ce
Branches
Tags
master
bluebox
BlueMiniModule
firmware
BlueMiniModule.asm
initial commit of files from bluebox project
Stefan Schuermans
commited
fd252ce
at 2015-12-19 20:16:38
BlueMiniModule.asm
Blame
History
Raw
; BlueMiniModule - LED matrix with 13x7 pixels ; version 0.3 date 2009-08-15 ; Copyright (C) 2007 Stefan Schuermans <stefan@blinkenarea.org> ; Copyleft GNU public license, version 2 ; a BlinkenArea project - http://www.blinkenarea.org/ ; clock frequency: 12 MHz (external crystal) ; PB0: column 11 (output, low) ; PB1: column 12 (output, low) ; PB2: column 13 (output, low) ; PB3: debug, MOSI (input, pull-up enabled) ; PB4: debug, MISO (output, low) ; PB5: debug, SCK (input, pull-up enabled) ; PB6: crystal (input, pull-up disabled) ; PB7: crystal (output, low) ; PC0: column 5 (output, low) ; PC1: column 6 (output, low) ; PC2: column 7 (output, low) ; PC3: column 8 (output, low) ; PC4: column 9 (output, low) ; PC5: column 10 (output, low) ; PC6: reset ; PD0: serial data, RXD (input, pull-up disabled) ; PD1: serial port, TXD (output, high) ; PD2: clock of row shift register (output, low) ; PD3: data of row shift register (output, low) ; PD4: column 1 (output, low) ; PD5: column 2 (output, low) ; PD6: column 3 (output, low) ; PD7: column 4 (output, low) ; serial input: 115200,8,N,1 ; - message format: ; - general: ; - first byte: 0x80 ... 0xFF ; - additional bytes: 0x00 ... 0x7F ; - 0x80 <pixel 1x1> <pixel 2x1> ... <pixel 13x7>: pixel data ; - pixel XxY: 0x00 ... 0x0F, value to show for pixel at position XxY .INCLUDE "m8def.inc" ; *** IO pins .equ PORT_ROW_SHIFT_CLOCK = PORTD ; clock of row shift register .equ BIT_ROW_SHIFT_CLOCK = 2 .equ PORT_ROW_SHIFT_DATA = PORTD ; data of row shift register .equ BIT_ROW_SHIFT_DATA = 3 ; *** constants ; dimensions .equ COLS = 13 .equ ROWS = 7 .equ PIXELS = COLS * ROWS ; idle time in about 100ms steps .equ IDLE_TIME = 100 ; *** registers ; general purpose registers .def DATA = r16 .def TMP = r17 .def CNT = r18 ; registers for interrupts .def I_SREG = r0 ; interrupt backup register for SREG .def I_TMP = r19 ; general purpose registers for interrupt ; status of output .def OUT_VAL = r1 ; current value of output (turn on all pixels with a higher value) .def OUT_VAL_CNT = r2 ; output value counter (number of steps until next real output) .def OUT_POS = r3 ; position in output buffer .def OUT_DATA_B = r4 ; bits to output at PORTB .def OUT_DATA_C = r5 ; bits to output at PORTC .def OUT_DATA_D = r6 ; bits to output at PORTD ; status of input .def IN_POS = r7 ; position in input buffer ; addresses of frame buffers ; - all frame buffers contain values 0..15 .def FRAME_ADDR_OUT_H = r8 ; address of frame buffer being used for output .def FRAME_ADDR_OUT_L = r9 .def FRAME_ADDR_IN_H = r10 ; address of frame buffer being used for input .def FRAME_ADDR_IN_L = r11 .def FRAME_ADDR_NEW_H = r12 ; address of frame buffer with new frame .def FRAME_ADDR_NEW_L = r13 .def FRAME_ADDR_FIXED_H = r14 ; address of frame buffer with nex fixed frame .def FRAME_ADDR_FIXED_L = r15 ; global bits .def BITS = r20 .equ BIT_NEW_FRAME = 0 ; new frame has been received ; subtick counter .def SUBTICKS = r21 ; idle time counter .def IDLE = r22 ; fixed movie .def FIXED_DELAY = r23 ; delay in 100ms steps .def FIXED_PTR_H = r24 ; pointer to next frame to play .def FIXED_PTR_L = r25 ; *** data .DSEG .ORG 0x060 ; frame buffers ; - all frame buffers contain values 0..15 .equ FRAME_BUF_LEN = PIXELS ; size of a single frame buffer FRAME_BUF_1: .BYTE FRAME_BUF_LEN ; frame buffer 1 FRAME_BUF_2: .BYTE FRAME_BUF_LEN ; frame buffer 2 FRAME_BUF_3: .BYTE FRAME_BUF_LEN ; frame buffer 3 FRAME_BUF_4: .BYTE FRAME_BUF_LEN ; frame buffer 4 ; *** non-volatile data (internal EEPROM) .ESEG .ORG 0x000 ; *** macros .MACRO brcsx brcc CC rjmp @0 CC: .ENDM .MACRO breqx brne NE rjmp @0 NE: .ENDM .MACRO brnex breq EQ rjmp @0 EQ: .ENDM .MACRO brlox brsh SH rjmp @0 SH: .ENDM .MACRO brshx brlo LO rjmp @0 LO: .ENDM .MACRO brvsx brvc VC rjmp @0 VC: .ENDM ; *** code .CSEG .ORG 0x0000 rjmp ENTRY ; RESET reti ; INT0 reti ; INT1 rjmp MATRIX_OUT ; TIMER2_COMP reti ; TIMER2_OVF reti ; TIMER1_CAPT reti ; TIMER1_COMPA reti ; TIMER1_COMPB reti ; TIMER1_OVF reti ; TIMER0_OVF reti ; SPI_STC reti ; USART_RXC reti ; USART_UDRE reti ; USART_TXC reti ; ADC reti ; EE_RDY reti ; ANA_COMP reti ; TWI reti ; SPM_RDY ; code entry point ENTRY: ; initialize output ports ldi TMP,0x28 ; PB[0-247] to output, low - PB[35] to input, pull-up enabled - PB6 to input, pull-up disabled out PORTB,TMP ldi TMP,0x97 out DDRB,TMP ldi TMP,0x40 ; PC[0-5] to output, low - PC6 to input, pull-up enabled out PORTC,TMP ldi TMP,0x3F out DDRC,TMP ldi TMP,0x00 ; PD0 to input, pull-up disabled - PD1 to output, high - PD[2-7] to output, low out PORTD,TMP ldi TMP,0xFE out DDRD,TMP ; initialize stack pointer ldi TMP,HIGH(RAMEND) out SPH,TMP ldi TMP,LOW(RAMEND) out SPL,TMP ; enable watchdog (64ms) wdr ldi TMP,1<<WDCE|1<<WDE out WDTCR,TMP ldi TMP,1<<WDE|1<<WDP1 out WDTCR,TMP wdr ; disable analog comparator ldi TMP,1<<ACD out ACSR,TMP ; set up timer 0 as timer tick generator ; prescaler 1:1024 => 11.72kHz ; overflow flag set about every 21.8ms (45.8Hz) ldi TMP,1<<CS02|1<<CS00 out TCCR0,TMP ; set up timer 2 as time base for matrix output interrupt ; prescaler 1:1 => 12MHz ; clear timer on compare match with value 149 => 12MHz/150=80kHz (12.5us) ldi TMP,149 ; OCR2 := 149 out OCR2,TMP ldi TMP,1<<WGM21|1<<CS20 ; clear timer on compare match to OCR2, prescaler 1:1 out TCCR2,TMP in TMP,TIMSK ; enable timer 2 compare match interrupt sbr TMP,1<<OCIE2 out TIMSK,TMP ; set up serial port ; transmitter disabled ; receiver enabled ; 115200 baud ; 8N1 ; enable usart ldi TMP,1<<U2X ; double speed out UCSRA,TMP ldi TMP,1<<RXEN ; only receiver enabled out UCSRB,TMP ldi TMP,1<<URSEL|1<<UCSZ1|1<<UCSZ0 ; asynchronous, 8N1: 8 data bits, no parity, 1 stop bit out UCSRC,TMP ldi TMP,0 ; speed = Fosc / (8 * (UBRR + 1)) out UBRRH,TMP ; = 12MHz / (8 * (12 + 1)) ldi TMP,12 ; = 115384bps (115200bps, 0.16% deviation) out UBRRL,TMP ; jump to main program rjmp MAIN ; matrix output interrupt: <= 133 cycles ; - called every 150 cycles (i.e. every 12.5 us) ; - changes: I_SREG, I_TMP, OUT_POS, OUT_VAL_CNT, OUT_DATA_* MATRIX_OUT: ; interrupt entry: 5 cycles in I_SREG,SREG push XH push XL ; decrement output value counter: 3..4 cycles dec OUT_VAL_CNT ; OUT_VAL_CNT-- brnex MATRIX_OUT_END ; if OUT_VAL_CNT > 0 then done ; get pointer to pixel data to output for current row: 5 cycles mov XH,FRAME_ADDR_OUT_H ; X := FRAME_ADDR_OUT + OUT_POS mov XL,FRAME_ADDR_OUT_L clr I_TMP add XL,OUT_POS adc XH,I_TMP ; calculate new states for pixels of current row: 59 cycles clr OUT_DATA_D ; columns 1..4 ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_D ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_D ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_D ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_D clr OUT_DATA_C ; columns 5..10 ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_C ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_C ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_C ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_C ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_C ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_C lsr OUT_DATA_C lsr OUT_DATA_C clr OUT_DATA_B ; columns 11..13 ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_B ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_B ld I_TMP,X+ cp OUT_VAL,I_TMP ; C := [X] > OUT_VAL ror OUT_DATA_B lsr OUT_DATA_B swap OUT_DATA_B ; turn off pixels of previous row: 8 cycles in I_TMP,PORTB andi I_TMP,0xF8 out PORTB,I_TMP clr I_TMP out PORTC,I_TMP in I_TMP,PORTD andi I_TMP,0x0F out PORTD,I_TMP ; switch to next row: 11..12 cycles clr I_TMP ; if OUT_VAL == 0 then switch to next row cp OUT_VAL,I_TMP brne MATRIX_OUT_NOSWITCHNEXT sbi PORT_ROW_SHIFT_DATA,BIT_ROW_SHIFT_DATA ; if OUT_POS == 0 then DATA := 1 else DATA := 0 cpse OUT_POS,I_TMP cbi PORT_ROW_SHIFT_DATA,BIT_ROW_SHIFT_DATA sbi PORT_ROW_SHIFT_CLOCK,BIT_ROW_SHIFT_CLOCK ; CLOCK := 1, CLOCK := 0 cbi PORT_ROW_SHIFT_CLOCK,BIT_ROW_SHIFT_CLOCK MATRIX_OUT_NOSWITCHNEXT: ; turn on pixels of new row: 7 cycles in I_TMP,PORTB or I_TMP,OUT_DATA_B out PORTB,I_TMP out PORTC,OUT_DATA_C in I_TMP,PORTD or I_TMP,OUT_DATA_D out PORTD,I_TMP ; get number of steps until next real output: 4 cycles ; - output with OUT_VAL = 0, 1, 2, 3, 4, ..., 14, 15==0 ; - output in step number 0, 1, 4, 9, 16, ..., 196, 225==0 ; - number of steps to next output: 1, 3, 5, 7, ..., 29 ---> OUT_VAL * 2 + 1 mov I_TMP,OUT_VAL add I_TMP,OUT_VAL inc I_TMP mov OUT_VAL_CNT,I_TMP ; advance value inc OUT_VAL ; OUT_VAL++ ldi I_TMP,15 cp OUT_VAL,I_TMP ; if OUT_VAL < 15 then done brlo MATRIX_OUT_OUT clr OUT_VAL ; OUT_VAL := 0 ; advance position, use next frame: 6 cycles ldi I_TMP,COLS ; OUT_POS += COLS add OUT_POS,I_TMP ldi I_TMP,PIXELS cp OUT_POS,I_TMP ; if OUT_POS < PIXELS then done brlo MATRIX_OUT_OUT clr OUT_POS ; OUT_POS := 0 ; go to next frame: 9 cycles sbrs BITS,BIT_NEW_FRAME ; if BIT_NEW_FRAME == 0 then done rjmp MATRIX_OUT_OUT mov I_TMP,FRAME_ADDR_OUT_H ; swap buffers: FRAME_ADDR_OUT <---> FRAME_ADDR_NEW mov FRAME_ADDR_OUT_H,FRAME_ADDR_NEW_H mov FRAME_ADDR_NEW_H,I_TMP mov I_TMP,FRAME_ADDR_OUT_L mov FRAME_ADDR_OUT_L,FRAME_ADDR_NEW_L mov FRAME_ADDR_NEW_L,I_TMP cbr BITS,1<<BIT_NEW_FRAME ; BIT_NEW_FRAME := 0 MATRIX_OUT_OUT: MATRIX_OUT_END: ; interrupt exit: 9 cycles pop XL pop XH out SREG,I_SREG reti ; receive data from serial port ; - changes: X, DATA, TMP SER_RECV: ; check for reception sbic UCSRA,RXC rjmp SER_RECV_RECV ; jump if something received (or error) ret SER_RECV_RECV: ; check for errors sbic UCSRA,FE rjmp SER_RECV_ERR ; frame error sbic UCSRA,DOR rjmp SER_RECV_ERR ; data overrun ; fetch received byte and interpret it in DATA,UDR cpi DATA,0x80 ; sync byte breq SER_RECV_SYNC cpi DATA,0x10 ; color byte brlo SER_RECV_COLOR ret ; error SER_RECV_ERR: in TMP,UDR ; clear error ldi TMP,0xFF ; invalidate position in frame mov IN_POS,TMP ret ; sync byte SER_RECV_SYNC: clr IN_POS ; begin of new frame ret ; color data byte SER_RECV_COLOR: ; check if current position is in frame ldi TMP,FRAME_BUF_LEN cp IN_POS,TMP brlo SER_RECV_COLOR_IN_FRAME ret SER_RECV_COLOR_IN_FRAME: ; get pointer to current position in frame mov XH,FRAME_ADDR_IN_H ; X := FRAME_ADDR_IN + IN_POS mov XL,FRAME_ADDR_IN_L clr TMP add XL,IN_POS adc XH,TMP ; store received color st X,DATA ; advance position inc IN_POS ; check for completed frame ldi TMP,FRAME_BUF_LEN cp IN_POS,TMP brsh SER_RECV_FRAME ret ; frame is completed SER_RECV_FRAME: ; reset idle time clr IDLE ; swap buffers cli mov TMP,FRAME_ADDR_IN_H ; FRAME_ADDR_IN <---> FRAME_ADDR_NEW mov FRAME_ADDR_IN_H,FRAME_ADDR_NEW_H mov FRAME_ADDR_NEW_H,TMP mov TMP,FRAME_ADDR_IN_L mov FRAME_ADDR_IN_L,FRAME_ADDR_NEW_L mov FRAME_ADDR_NEW_L,TMP sbr BITS,1<<BIT_NEW_FRAME ; set bit indicating that new frame has been received sei ret ; restart fixed movie ; - changes: TMP, FIXED_DELAY, FIXED_PTR_* RESTART_FIXED: clr FIXED_DELAY ldi TMP,HIGH(2*FIXED_MOVIE) mov FIXED_PTR_H,TMP ldi TMP,LOW(2*FIXED_MOVIE) mov FIXED_PTR_L,TMP ret ; play fixed movie ; - changes: TMP, CNT, X, Z, FIXED_DELAY, FIXED_PTR_* PLAY_FIXED: ; check if delay active clr TMP cp FIXED_DELAY,TMP breq PLAY_FIXED_NO_DELAY ; decrement delay dec FIXED_DELAY ret ; no delay active PLAY_FIXED_NO_DELAY: ; copy frame to buffer for new fixed frame ldi CNT,PIXELS mov ZH,FIXED_PTR_H mov ZL,FIXED_PTR_L mov XH,FRAME_ADDR_FIXED_H mov XL,FRAME_ADDR_FIXED_L PLAY_FIXED_LOOP: lpm TMP,Z+ st X+,TMP dec CNT brne PLAY_FIXED_LOOP ; swap buffers cli mov TMP,FRAME_ADDR_FIXED_H ; FRAME_ADDR_FIXED <---> FRAME_ADDR_NEW mov FRAME_ADDR_FIXED_H,FRAME_ADDR_NEW_H mov FRAME_ADDR_NEW_H,TMP mov TMP,FRAME_ADDR_FIXED_L mov FRAME_ADDR_FIXED_L,FRAME_ADDR_NEW_L mov FRAME_ADDR_NEW_L,TMP sbr BITS,1<<BIT_NEW_FRAME ; set bit indicating that new frame has been received sei ; load new delay lpm FIXED_DELAY,Z+ ; check if end of movie lpm TMP,Z+ cpi TMP,0 breq PLAY_FIXED_NOT_END ldi TMP,HIGH(2*FIXED_MOVIE) ; restart from beginning mov ZH,TMP ldi TMP,LOW(2*FIXED_MOVIE) mov ZL,TMP PLAY_FIXED_NOT_END: ; save new fixed movie pointer mov FIXED_PTR_H,ZH mov FIXED_PTR_L,ZL ret ; detect serial idle time ; - changes: TMP, CNT, X, Z, IDLE, FIXED_DELAY, FIXED_PTR_* IDLE_DETECT: ; check if idle time reached ldi TMP,IDLE_TIME cp IDLE,TMP brsh IDLE_DETECT_IDLE ; increment idle time counter inc IDLE ; check if idle time reached ldi TMP,IDLE_TIME cp IDLE,TMP brsh IDLE_DETECT_NOW_IDLE ; not idle ret ; just became idle IDLE_DETECT_NOW_IDLE: ; restart fixed movie rcall restart_fixed ; idle IDLE_DETECT_IDLE: ; play fixed movie rcall PLAY_FIXED ret ; main program MAIN: ; initialization of registers clr BITS clr SUBTICKS clr IDLE ; initialization of output state clr OUT_VAL ; begin with smallest value ldi TMP,1 ; do a real output in next step mov OUT_VAL_CNT,TMP clr OUT_POS ; begin at top of frame ; initialization of input state ldi TMP,0xFF ; not at a valid frame position mov IN_POS,TMP ; initialization of buffer addresses ldi TMP,HIGH(FRAME_BUF_1) ; buffer 1 for output mov FRAME_ADDR_OUT_H,TMP ldi TMP,LOW(FRAME_BUF_1) mov FRAME_ADDR_OUT_L,TMP ldi TMP,HIGH(FRAME_BUF_2) ; buffer 2 for input mov FRAME_ADDR_IN_H,TMP ldi TMP,LOW(FRAME_BUF_2) mov FRAME_ADDR_IN_L,TMP ldi TMP,HIGH(FRAME_BUF_3) ; buffer 3 for new frame mov FRAME_ADDR_NEW_H,TMP ldi TMP,LOW(FRAME_BUF_3) mov FRAME_ADDR_NEW_L,TMP ldi TMP,HIGH(FRAME_BUF_4) ; buffer 4 for new fixed frame mov FRAME_ADDR_FIXED_H,TMP ldi TMP,LOW(FRAME_BUF_4) mov FRAME_ADDR_FIXED_L,TMP ; clear output buffer mov XH,FRAME_ADDR_OUT_H mov XL,FRAME_ADDR_OUT_L clr TMP ldi CNT,PIXELS MAIN_INIT_CLEAR_OUT: st X+,TMP dec CNT brne MAIN_INIT_CLEAR_OUT ; clear row shift register cbi PORT_ROW_SHIFT_DATA,BIT_ROW_SHIFT_DATA ; DATA := 0 ldi CNT,8 MAIN_INIT_CLEAR_SHIFT: sbi PORT_ROW_SHIFT_CLOCK,BIT_ROW_SHIFT_CLOCK ; CLOCK := 1, CLOCK := 0 cbi PORT_ROW_SHIFT_CLOCK,BIT_ROW_SHIFT_CLOCK dec CNT brne MAIN_INIT_CLEAR_SHIFT ; enable interrupts sei ; main loop MAIN_LOOP: wdr ; receive data from serial port rcall SER_RECV ; check if timer tick is over in TMP,TIFR ; check timer 0 overflow flag sbrs TMP,TOV0 rjmp MAIN_NO_TICK ; jump if not set ldi TMP,1<<TOV0 ; clear timer 0 overflow flag out TIFR,TMP ; check if about 100ms are over inc SUBTICKS ; count subticks ldi TMP,5 ; check if 5 subticks elapsed cp SUBTICKS,TMP brlo MAIN_NO_TICK ; jump if not elapsed clr SUBTICKS ; clear subticks counter ; do idle detection rcall IDLE_DETECT MAIN_NO_TICK: ; bottom of main loop rjmp MAIN_LOOP ; fixed movie data ; - format: <pixel data> <delay in 100ms> <end flag> FIXED_MOVIE: .INCLUDE "movie.geninc"