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