; bulb - BlinkenArea ultimate logo board
; version 1.0.0 date 2011-07-30
; Copyright (C) 2011-2012 Stefan Schuermans <stefan@blinkenarea.org>
; Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html

; ATtiny2313A, clock frequency: 8 MHz (internal oscillator)

; PA0: unused
; PA1: unused
; PA2: reset
; PB0: column 0 output (output, low)
; PB1: column 1 output (output, low)
; PB2: column 2 output (output, low)
; PB3: column 3 output (output, low)
; PB4: column 4 output (output, low)
; PB5: column 5 output (output, low)
; PB6: column 6 output (output, low)
; PB7: push button input (input, pull-up enabled)
; PD0: row 0 output (output, high)
; PD1: row 1 output (output, high)
; PD2: row 2 output (output, high)
; PD3: row 3 output (output, high)
; PD4: row 4 output (output, high)
; PD5: row 5 output (output, high)
; PD6: unused (output, low)



.INCLUDE        "tn2313def.inc"



; IO pins
.equ    COL_PORT                =       PORTB   ; column outputs
.equ    ROW_PORT                =       PORTD   ; row outputs
.equ    BTN_PIN                 =       PINB
.equ    BTN_BIT                 =       7



; general purpose registers
.def    TMP                     =       r16
.def    CNT                     =       r17
.def    DATA                    =       r18



.DSEG
.ORG    0x060



; current frame
FRAME:                  .BYTE   42



.MACRO breqx
        brne    NE
        rjmp    @0
NE:
.ENDM



; multiple rcalls in a row
; parameters: label, N
.MACRO rcall_N
.IF @1 > 0
        rcall   @0
        rcall_N @0, @1 - 1
.ENDIF
.ENDM



; check if to turn on pixel in subframe
; parameter: grayscale value (1..15)
; input: X = ptr to pixel data (0..7),
;        DATA[7-1] = other pixels
; output: X = ptr to pixel data of next pixel,
;         DATA.7 = if _not_ to turn on pixel, DATA.[6-0] = old DATA[7-1]
; changes: TMP, DATA, X
; cycles: 4
.MACRO PIXEL
        ld      TMP,X+
        cpi     TMP,@0
        ror     DATA
.ENDM



; output row (black/white)
; parameter: grayscale value (1..15)
; input: X = ptr to pixel data (0..15)
; output: -
; changes: TMP, DATA
; cycles: 32
.MACRO ROW_BW
        PIXEL   @0                      ; pixel 0
        PIXEL   @0                      ; pixel 1
        PIXEL   @0                      ; pixel 2
        PIXEL   @0                      ; pixel 3
        PIXEL   @0                      ; pixel 4
        PIXEL   @0                      ; pixel 5
        PIXEL   @0                      ; pixel 6
        subi    XL,7                    ;   XH not there on ATtiny2313
        lsr     DATA                    ; ensure remaining bit stays
                                        ;   high (pull-up for button)
        com     DATA
        out     COL_PORT,DATA
.ENDM



; wait n * 64 cycles
; parameter: n
; input: -
; output: -
; changes: TMP
; cycles: n * 64
.MACRO WAIT64_N
.IF @0 > 0
        rcall           WAIT64
        WAIT64_N        @0 - 1
.ENDIF
.ENDM



; output row for grayscale (black/white) and wait
; parameter: grayscale value (1..15)
; input: X = ptr to pixel data (0..15)
; output: -
; changes: TMP, DATA
; cycles: 32 + (grayscale - 1) * 64
.MACRO ROW_BW_WAIT
        ROW_BW          @0
        WAIT64_N        @0 - 1
.ENDM



; turn off row (with same timing as ROW_BW)
; input: -
; output: -
; changes: TMP
; cycles: 32
.MACRO ROW_OFF
        ldi     TMP,10
ROW_OFF_LOOP:
        dec     TMP
        brne    ROW_OFF_LOOP
        ldi     TMP,0x80                ; ensure remaining bit stays
                                        ;   high (pull-up for button)
        out     COL_PORT,TMP
.ENDM



.CSEG
.ORG    0x000
        rjmp    ENTRY                   ; RESET
        reti                            ; INT0
        reti                            ; INT1
        reti                            ; TIMER1_CAPT
        reti                            ; TIMER1_COMPA
        reti                            ; TIMER1_OVF
        reti                            ; TIMER0_OVF
        reti                            ; USART0_RX
        reti                            ; USART0_UDRE
        reti                            ; USART0_TX
        reti                            ; ANALOG_COMP
        reti                            ; PC_INT0
        reti                            ; TIMER1_COMPB
        reti                            ; TIMER0_COMPA
        reti                            ; TIMER0_COMPB
        reti                            ; USI_START
        reti                            ; USI_OVERFLOW
        reti                            ; EE_READY
        reti                            ; WDT
        reti                            ; PC_INT1
        reti                            ; PC_INT2



; code entry point
ENTRY:
; set system clock prescaler to 1:1
        ldi     TMP,1<<CLKPCE
        out     CLKPR,TMP
        ldi     TMP,0
        out     CLKPR,TMP
; initialize output ports
        ldi     TMP,0x00                ; PA[01] to output, low
        out     PORTA,TMP
        ldi     TMP,0x03
        out     DDRA,TMP
        ldi     TMP,0x80                ; PB[0-6] to output, low - PB7 to input, pull-up enabled
        out     PORTB,TMP
        ldi     TMP,0x7F
        out     DDRB,TMP
        ldi     TMP,0x3F                ; PD[0-5] to output, high - PD6 to output, low
        out     PORTD,TMP
        ldi     TMP,0x7F
        out     DDRD,TMP
; initialize stack pointer
        ldi     TMP,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
; enable pin change interrupt for button
;   to wake up from sleep
        in      TMP,GIMSK
        ori     TMP,1<<PCIE
        out     GIMSK,TMP
        ldi     TMP,1<<PCINT7
        out     PCMSK,TMP
; jump to main program
        rjmp    MAIN



; wait 64 cycles
; input: -
; output: -
; changes: TMP
; cycles: 64 (including rcall and ret)
WAIT64:
        ldi     TMP,19
WAIT64_LOOP:
        dec     TMP
        brne    WAIT64_LOOP
; done
        ret



; output row (grayscales)
; input: X = ptr to pixel data (0..15)
; output: -
; changes: TMP, DATA
; cycles: 7269 (including rcall and ret)
ROW_GRAY:
        ROW_BW_WAIT     1
        ROW_BW_WAIT     2
        ROW_BW_WAIT     3
        ROW_BW_WAIT     4
        ROW_BW_WAIT     5
        ROW_BW_WAIT     6
        ROW_BW_WAIT     7
        ROW_BW_WAIT     8
        ROW_BW_WAIT     9
        ROW_BW_WAIT     10
        ROW_BW_WAIT     11
        ROW_BW_WAIT     12
        ROW_BW_WAIT     13
        ROW_BW_WAIT     14
        ROW_BW_WAIT     15
        ROW_OFF
; done
        ret



; output a frame
; input: FRAME = pixel data (0..15)
; output: -
; changes: TMP, CNT, DATA, X
; cycles: 43676 (including rcall and ret)
; time: 5.5ms
OUT_FRAME:
        wdr
        ldi     XL,low(FRAME)           ; ptr to pixel data
                                        ;   XH not there on ATtiny2313
        ldi     CNT,0x01                ; bitmask loop over rows
OUT_FRAME_LOOP:
        mov     TMP,CNT                 ; select row
        com     TMP
        andi    TMP,0x3F
        out     ROW_PORT,TMP
        rcall   ROW_GRAY                ; display row
        subi    XL,-7                   ; ptr to next row
                                        ;   XH not there on ATtiny2313
        lsl     CNT                     ; bitmask loop over rows
        cpi     CNT,0x40
        brne    OUT_FRAME_LOOP
; done
        ret



; handle on/off button
; input: -
; output: -
; changes: TMP, CNT
; time: usually very short, but may suspend execution
BUTTON:
; exit if button not pressed
;   require stable press for about 50 ms
        clr     CNT                     ; loop 65536 times
        clr     TMP
BUTTON_CHECK_PRESS_LOOP:
        wdr
        sbic    BTN_PIN,BTN_BIT         ; not pressed -> leave
        ret
        dec     TMP
        brne    BUTTON_CHECK_PRESS_LOOP
        dec     CNT
        brne    BUTTON_CHECK_PRESS_LOOP
; wait for button to be released
;   require stable release for about 50 ms
BUTTON_WAIT_RELEASE:
        clr     CNT                     ; loop 65536 times
        clr     TMP
BUTTON_WAIT_RELEASE_LOOP:
        wdr
        sbis    BTN_PIN,BTN_BIT         ; pressed -> start over
        rjmp    BUTTON_WAIT_RELEASE
        dec     TMP
        brne    BUTTON_WAIT_RELEASE_LOOP
        dec     CNT
        brne    BUTTON_WAIT_RELEASE_LOOP
; disable watchdog
        cli
        wdr
        in      TMP,WDTCR
        ori     TMP,1<<WDCE|1<<WDE
        out     WDTCR,TMP
        andi    TMP,~(1<<WDCE|1<<WDE)
        out     WDTCR,TMP
        wdr
        sei
; sleep
        in      TMP,MCUCR               ; configure power-down sleep
        andi    TMP,~(1<<SM1)
        ori     TMP,1<<SE|1<<SM0
        out     MCUCR,TMP
        sleep                           ; go to sleep
        in      TMP,MCUCR               ; configure no sleep mode
        andi    TMP,~(1<<SE)
        out     MCUCR,TMP
; enable watchdog
        cli
        wdr
        in      TMP,WDTCR
        ori     TMP,1<<WDCE|1<<WDE
        out     WDTCR,TMP
        andi    TMP,~(1<<WDCE)
        out     WDTCR,TMP
        wdr
        sei
; wait for button to be released
;   require stable release for about 50 ms
BUTTON_WAIT2_RELEASE:
        clr     CNT                     ; loop 65536 times
        clr     TMP
BUTTON_WAIT2_RELEASE_LOOP:
        wdr
        sbis    BTN_PIN,BTN_BIT         ; pressed -> start over
        rjmp    BUTTON_WAIT2_RELEASE
        dec     TMP
        brne    BUTTON_WAIT2_RELEASE_LOOP
        dec     CNT
        brne    BUTTON_WAIT2_RELEASE_LOOP
; done
        ret



; output a frame for some time
;   also handles on/off button
; input: FRAME = pixel data (0..15)
;        TMP = time to show frame (1..255, in 5.5 ms steps)
; output: -
; changes: X, TMP
; time: TMP * 5.5 ms
OUT_FRAME_TIME:
; output frame
        push    TMP
        push    CNT
        push    DATA
        rcall   OUT_FRAME               ; 5.5 ms
        rcall   BUTTON
        pop     DATA
        pop     CNT
        pop     TMP
; loop
        dec     TMP
        brne    OUT_FRAME_TIME
; done
        ret



; clear frame
; input: -
; output: -
; changes: X, FRAME, TMP
; time: short
CLEAR:
        ldi     XL,low(FRAME)           ; ptr to pixel data
                                        ;   XH not there on ATtiny2313
        clr     TMP
CLEAR_LOOP:
        st      X+,TMP                  ; clear pixel
        cpi     XL,low(FRAME)+42        ; bottom of loop
                                        ;   XH not there on ATtiny2313
        brne    CLEAR_LOOP
; done
        ret



; set frame to solid color
; input: DATA = value (0..15)
; output: -
; changes: X, FRAME
; time: short
SET_COLOR:
        ldi     XL,low(FRAME)           ; ptr to pixel data
                                        ;   XH not there on ATtiny2313
SET_COLOR_LOOP:
        st      X+,DATA                 ; set pixel value
        cpi     XL,low(FRAME)+42        ; bottom of loop
                                        ;   XH not there on ATtiny2313
        brne    SET_COLOR_LOOP
; done
        ret



; set pixel
; input: CNT = pixel number (0..41, nothing is done for 42..255)
;        DATA = value (0..15)
; output: -
; changes: X, FRAME, TMP
; time: short
SET_PIXEL:
        cpi     CNT,42                  ; invalid pixel number -> done
        brsh    SET_PIXEL_END
        ldi     XL,low(FRAME)           ; ptr to pixel (base + offset)
        add     XL,CNT                  ;   XH not there on ATtiny2313
        st      X,DATA                  ; set pixel
SET_PIXEL_END:
; done
        ret



; draw worm
; input: CNT = head of worm (0..55)
; output: -
; changes: X, FRAME, TMP, DATA
; time: short
DRAW_WORM:
        cpi     CNT,56                  ; invalid head pos -> done
        brsh    DRAW_WORM_END
        ldi     XL,low(FRAME)+1         ; ptr to before head
        add     XL,CNT                  ;   XH not there on ATtiny2313
        ldi     DATA,15                 ; head is full on
        cpi     CNT,42                  ; head pos in frame -> go
        brlo    DRAW_WORM_LOOP
        mov     TMP,CNT                 ; TMP := invisible pixels
        subi    TMP,41
        sub     XL,TMP                  ; skip invisible pixels
        sub     DATA,TMP                ;   XH not there on ATtiny2313
DRAW_WORM_LOOP:
        st      -X,DATA                 ; set pixel, go back
        cpi     XL,low(FRAME)           ; 1st pixel -> done
        breq    DRAW_WORM_END           ;   XH not there on ATtiny2313
        dec     DATA                    ; next pixel darker
        brne    DRAW_WORM_LOOP          ; loop
DRAW_WORM_END:
; done
        ret



; draw backwards worm
; input: CNT = tail of worm (0..55)
; output: -
; changes: X, FRAME, TMP, DATA
; time: short
DRAW_BW_WORM:
        cpi     CNT,56                  ; invalid tail pos -> done
        brsh    DRAW_BW_WORM_END
        ldi     XL,low(FRAME)+1         ; ptr to before tail
        add     XL,CNT                  ;   XH not there on ATtiny2313
        ldi     DATA,1                  ; tail is minimum on
        cpi     CNT,42                  ; tail pos in frame -> go
        brlo    DRAW_BW_WORM_LOOP
        mov     TMP,CNT                 ; TMP := invisible pixels
        subi    TMP,41
        sub     XL,TMP                  ; skip invisible pixels
        add     DATA,TMP                ;   XH not there on ATtiny2313
DRAW_BW_WORM_LOOP:
        st      -X,DATA                 ; set pixel, go back
        cpi     XL,low(FRAME)           ; 1st pixel -> done
        breq    DRAW_BW_WORM_END        ;   XH not there on ATtiny2313
        inc     DATA                    ; next pixel brighter
        cpi     DATA,16                 ; loop
        brne    DRAW_BW_WORM_LOOP
DRAW_BW_WORM_END:
; done
        ret



; blink animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_BLINK:
; off
        ldi     DATA,0                  ; minimum color
        rcall   SET_COLOR               ; paint
        ldi     TMP,100                 ; show frame 550 ms
        rcall   OUT_FRAME_TIME
; on
        ldi     DATA,15                 ; maximum color
        rcall   SET_COLOR               ; paint
        ldi     TMP,100                 ; show frame 550 ms
        rcall   OUT_FRAME_TIME
; done
        ret



; fade up and down animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_FADE:
; fade up
        ldi     DATA,0                  ; start dark
ANIM_FADE_UP:
        rcall   SET_COLOR               ; paint
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        inc     DATA                    ; fade up
        cpi     DATA,15                 ; loop until almost full on
        brne    ANIM_FADE_UP
; fade down
ANIM_FADE_DOWN:
        rcall   SET_COLOR               ; paint
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        dec     DATA                    ; fade up
        cpi     DATA,255                ; loop until full off
        brne    ANIM_FADE_DOWN
; done
        ret



; flicker animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_FLICKER:
; even pixels
        rcall   CLEAR                   ; clear
        ldi     DATA,15                 ; even pixels to maximum
        ldi     CNT,0
ANIM_FLICKER_EVEN:
        rcall   SET_PIXEL
        subi    CNT,-2                  ; move two pixels
        cpi     CNT,42                  ; loop
        brlo    ANIM_FLICKER_EVEN
        ldi     TMP,40                  ; show frame 220 ms
        rcall   OUT_FRAME_TIME
; odd pixels
        rcall   CLEAR                   ; clear
        ldi     DATA,15                 ; odd pixels to maximum
        ldi     CNT,1
ANIM_FLICKER_ODD:
        rcall   SET_PIXEL
        subi    CNT,-2                  ; move two pixels
        cpi     CNT,42                  ; loop
        brlo    ANIM_FLICKER_ODD
        ldi     TMP,40                  ; show frame 220 ms
        rcall   OUT_FRAME_TIME
; done
        ret



; wobble animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_WOBBLE:
; even pixels up, odd pixels down
        ldi     DATA,0                  ; even pixels start dark
ANIM_WOBBLE_UP:
        ldi     CNT,0
ANIM_WOBBLE_UP_DRAW:
        rcall   SET_PIXEL
        inc     CNT                     ; next pixel
        ldi     TMP,0x0F                ; invert color
        eor     DATA,TMP
        cpi     CNT,42                  ; loop
        brlo    ANIM_WOBBLE_UP_DRAW
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        inc     DATA                    ; next color: brighter
        cpi     DATA,16
        brlo    ANIM_WOBBLE_UP
; even pixels down, odd pixels up
        ldi     DATA,15                 ; even pixels start full
ANIM_WOBBLE_DOWN:
        ldi     CNT,0
ANIM_WOBBLE_DOWN_DRAW:
        rcall   SET_PIXEL
        inc     CNT                     ; next pixel
        ldi     TMP,0x0F                ; invert color
        eor     DATA,TMP
        cpi     CNT,42                  ; loop
        brlo    ANIM_WOBBLE_DOWN_DRAW
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        dec     DATA                    ; next color: darker
        cpi     DATA,16
        brlo    ANIM_WOBBLE_DOWN
; done
        ret



; run animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_RUN:
        ldi     CNT,255                 ; start before 1st pixel
ANIM_RUN_LOOP:
        rcall   CLEAR                   ; clear
        ldi     DATA,15                 ; current pixel full on
        rcall   SET_PIXEL
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        inc     CNT                     ; next pixel
        cpi     CNT,43                  ; loop until after last pixel
        brne    ANIM_RUN_LOOP
; done
        ret



; backwards run animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_BW_RUN:
        ldi     CNT,42                  ; start after last pixel
ANIM_BW_RUN_LOOP:
        rcall   CLEAR                   ; clear
        ldi     DATA,15                 ; current pixel full on
        rcall   SET_PIXEL
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        dec     CNT                     ; previous pixel
        cpi     CNT,255                 ; loop until before 1st pixel
        brne    ANIM_BW_RUN_LOOP
; done
        ret



; worm animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_WORM:
        ldi     CNT,255                 ; worm starts before 1st pixel
ANIM_WORM_LOOP:
        rcall   CLEAR                   ; draw worm
        rcall   DRAW_WORM
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        inc     CNT                     ; advance worm
        cpi     CNT,57                  ; loop until has exits
        brne    ANIM_WORM_LOOP
; done
        ret



; backwards worm animation
; input: -
; output: -
; changes: X, FRAME, CNT, DATA, TMP
ANIM_BW_WORM:
        ldi     CNT,56                  ; worm starts behind frame
                                        ;   head not yet visible
ANIM_BW_WORM_LOOP:
        rcall   CLEAR                   ; draw backwards worm
        rcall   DRAW_BW_WORM
        ldi     TMP,10                  ; show frame 55 ms
        rcall   OUT_FRAME_TIME
        dec     CNT                     ; advance worm backwards
        cpi     CNT,254                 ; loop until worm has exited
        brne    ANIM_BW_WORM_LOOP
; done
        ret



; main program
MAIN:
        wdr

; initialization

; enable interrupts
        sei

MAIN_LOOP:
        wdr

; main loop

        rcall_N ANIM_BLINK,3
        rcall_N ANIM_WORM,3
        rcall_N ANIM_FLICKER,10
        rcall_N ANIM_BW_RUN,3
        rcall_N ANIM_FADE,2
        rcall_N ANIM_BW_WORM,3
        rcall_N ANIM_WOBBLE,5
        rcall_N ANIM_RUN,3

; bottom of main loop
        
        rjmp     MAIN_LOOP

