;Light Box with 2x 8 LEDs, controlled by LPD8806 (data order: red, green, blue) ;ATtiny13 @ 1.2288 MHz (adj. int. osc.), Fuses: low = 0x62, high = 0xEB ; ;LPD8806 LED Strip Protocol: ; https://github.com/adafruit/LPD8806/blob/master/LPD8806.cpp ; * MSB first, sampled on rising edge of clock ; * reset LEDs: 0x00 (at least once per 32 LEDs) ; * set first LED: 0x80 = off ... 0xFF = full on, 3 bytes (R, G, B) ; * set second LED ; * ... ; * send at least one dummy byte (LEDs latch as /next/ byte starts) ; Colour order depends on strip type: RGB or BRG or GRB or ... ; ;PORTS: ; PB0 = UART Tx (outgoing data) - inverted logic level (idle low) ; PB1 = UART Rx (incoming data) - inverted logic level (idle low) [INT0] ; PB2 = [unused] ; PB3 = LED Strip Data ; PB4 = LED Strip Clock ; PB5 = [Reset] ; ;VERSIONS ; 2018-06-10 Arne Rossius ; * first version ;clock frequency (set ENABLE_CAL below and adjust for 1.2288 MHz) .equ OSCCAL_VAL = 118 ;box #1 ;.equ OSCCAL_VAL = 70 ;box #2 ;enable calibration mode (output CPU clock / 10 on UART_TX) .equ ENABLE_CAL = 0 ;number of RGB LEDs .equ N_LED = 16 ;=============================================================================== ;Port pins .equ UART_TX = 0 ;(must be 0) .equ UART_RX = 1 ;(must be on INT0 pin) .equ UNUSED_PIN = 2 .equ LED_DATA = 3 .equ LED_CLOCK = 4 .include "../include/tn13def.inc" .undef XH .undef YH ;R0 = SPM data / general purpose scratch register ;R1 = SPM data / general purpose scratch register .def anistartL = R2 ;[init required] .equ REG_anistartL = 2 .def anistartH = R3 ;[init required] .equ REG_anistartH = 3 .def n_steps = R7 .def repeatL = R8 .def repeatH = R9 .def n_repeat = R10 .def oldval = R11 .def newval = R12 .def rxbyte = R13 .def rxbyte_buf = R14 .def sreg_backup = R15 .def temp = R16 .def temp2 = R17 .def count = R18 .def inttmp = R19 .def bitcnt = R20 ;[init to 0 required] .equ REG_bitcnt = 20 .def repeat_nested = R21 ;[init to 0 required] .equ REG_repeat_nested = 21 .def opL = R22 .def opH = R23 .def timeoutL = R24 ;accessible by sbiw .def timeoutH = R25 ;accessible by sbiw, also used as zero register [init=0] .equ REG_timeoutH = 25 ;R26 = XL (secondary RAM pointer) .def cmd = R27 ;must directly follow XL (used in movw) ;R28 = YL [RAM pointer] [init required] .def flags = R29 ;also used to set GTCCR=(1< 16) .error "LED address table must be extended for more than 16 LEDs" .endif ;-------------------- .org (EEPROMEND+1 - 32) EE_Init: ;SFR & register initialisation values ;(must be aligned to end of EEPROM) ;init ports .db 0x20+DDRB, 1< wrap around to 0 subi inttmp, 64 ;no overflow: was <191 => sub 64 (increment by 1) ;(written back to TCNT0 below ...) sbrs bitcnt, 3 ;last data bit received (bitcnt == 8)? rjmp oc0b_return ;no => return ;all bits received brcs oc0b_complete ;ignore byte if startbit = 1 (low) mov rxbyte, rxbyte_buf ori flags, 1< repeat complete mov YL, repeat_nested ld n_repeat, Y+ ld repeatL, Y+ ld repeatH, Y+ mov repeat_nested, YL rjmp animation_loop repeat_end_inf: ;end of repeated block: repeat infinitely movw ZH:ZL, repeatH:repeatL rjmp animation_loop ;=============================================================================== reset: ;init SFR and registers: ; * stackpointer ; * ports ; * oscillator calibration ; * timer 0 ; * external interrupt ; * registers: anistartH:anistartL, repeat_nested, bitcnt, timeoutH, ; flags .if (EE_Init & 1) .error "EE_Init must be aligned to even address (bit 0 == 0)" .endif ldi opL, (~EEPROMEND) | EE_Init ;set unused bits high init_sfr_reg: rcall eeprom_read ;XL = EEPROM[opL] = even: SFR addr, odd: SFR value sbrc opL, 0 st Z, XL ;odd: store SFR value to previously read SFR address mov ZL, XL inc opL ;overflows after end of EEPROM reached brne init_sfr_reg .if (ENABLE_CAL) cli cal: ;calibration loop: output f_CPU / 10 on UART Tx pin (target: 122.88 kHz) sbi PORTB, UART_TX nop rjmp PC+1 cbi PORTB, UART_TX nop rjmp cal .endif ;clear RAM (all LEDs off) ldi YL, RAM_LEDs clear_ram: st Y+, timeoutH ;(timeoutH initialized to 0 in init_sfr_reg) cpi YL, RAM_LEDs_end brlo clear_ram ;enable interrupts sei ;------------------------------------------------------------------------------- loop: movw ZH:ZL, anistartH:anistartL animation_loop: sbrc flags, fRxComplete rjmp rx_complete lpm cmd, Z+ cpi cmd, 0x7F breq loop ;0111.1111 = end of animation brlo delay ;0xxx.xxxx = delay [x<<8 + param] * 10 ms (1~32511) cpi cmd, 0xFE breq repeat_end ;1111.1110 = repeat end brsh repeat_end_inf ;1111.1111 = repeat end infinite cpi cmd, 0xC0 brsh repeat ;11xx.xxxx = repeat 64-x times (x=3~64) ;copy LED values to RAM_LEDs_old ldi YL, RAM_LEDs_end led_copy_loop: ld temp, -Y std Y+(RAM_LEDs_old - RAM_LEDs), temp cpi YL, RAM_LEDs brne led_copy_loop cpi cmd, 0xA0 brsh set_leds ;101x.xxxx = set x LEDs (0~N_LED-1) and fade [C=0] ;(fall through) ;100x.xxxx = rotate x places (1~N_LED-1), set, fade ;rotate/set/fade byte sequence ; A) 100*.pppp : rotate LEDs p places (0~15) ; B) 101n.nnnn : set n LEDs (0~N_LED) ; (if n == 0: go to E) ; (if n >= 16: a = 0, c = 0, go to D) ; C) 0caa.aaaa : a = 16 + 1st LED to set (0~N_LED-1), c = continued ; D) LED data, n times: ; D.1) gggg.rrrr, 0000.bbbb : LED colour for even LED (0, 2, 4, ...) ; D.2) rrrr.0000, bbbb.gggg : LED colour for odd LED (1, 3, 5, ...) ; (if c == 1, go back to B) ; E) ssss.ssss - number of fading steps (1~255) ; ;set/fade byte sequence: ; same as above, but omit A) .if (RAM_LEDs_old - RAM_LEDs != N_LED * 3 / 2) .error "RAM_LEDs_old must directly follow RAM_LEDs" .endif ;rotate LEDs p places to the left (towards first LED) ; even number of places: ; RAM[addr] = RAM[addr + (p/2 * 3)] ; odd number of places: ; RAM[addr] = (RAM[addr + (p/2 * 3) + 1] & 0xF0) >> 4 | ; (RAM[addr + (p/2 * 3) + 2] & 0x0F) << 4 ; LED values previously copied to RAM_LEDs_old directly following ; RAM_LEDs, so reading beyond RAM_LEDs_end gives identical data to ; wrapping around. ;XL = p/2 * 3 + (odd: 1, even: 0) ; = (p >> 1) * 3 + (p & 1) = p + (p >> 1) mov XL, cmd lsr XL add XL, cmd subi XL, -(RAM_LEDs - 0xC0) ;0xC0 = 1000.0000 + (1000.0000 >> 1) ldi YL, RAM_LEDs rotate_loop: ld temp, X+ ;odd: X = addr + p/2 * 3 + 1, even: X = addr + p/2 * 3 sbrs cmd, 0 rjmp rotate_loop_even ld temp2, X ;odd: X = addr + p/2 * 3 + 2 andi temp, 0xF0 andi temp2, 0x0F or temp, temp2 swap temp rotate_loop_even: st Y+, temp cpi YL, RAM_LEDs_end brlo rotate_loop ;(C=0 here) set_continued: lpm cmd, Z+ ;read number of LEDs to set .if (EE_LED_Address & 1) .error "EE_LED_Address must be aligned to even address" .endif set_leds: ;set LEDs ;cmd = 111n.nnnn (n = number of LEDs to set) ; single LED, even address: ; Flash: 0xGR, 0x0B ; || | ; RAM: 0xGR, 0x*B (* unchanged) ; single LED, odd address: ; Flash: 0xr0, 0xbg ; | || ; RAM: 0xr*, 0xbg (* unchanged) ; 2 LEDs, even address: ; flash: 0xGR, 0xrB, 0xbg ; || || || ; RAM: 0xGR, 0xrB, 0xbg andi cmd, 0x1F breq set_end ;set 0 LEDs: skip ldi opL, EE_LED_Address sbrs cmd, 4 ;set >= 16 LEDs: first LED = LED 0, bit 6 = cont'd = 0 lpm opL, Z+ ;first LED to set (bit 6 = cont'd, unused in EEAR) set_loop: rcall eeprom_read ;XL = EEPROM[opL] = RAM addr of 1st nibble ;C=0 here: ; * 111x.xxxx => ensured by 'brsh set_leds' ; * 110x.xxxx => ensured by 'brlo rotate_loop' above ; * looped from below: carry cleared in 'sbiw' or 2nd iteration 'subi' ldi temp2, 0x0F set_led_iter: adc opL, timeoutH ;add carry (timeoutH is zero), 1st: C=0, 2nd: C=1 lpm R0, Z+ ld temp, X and temp, temp2 sbrc opL, 0 or R0, temp ;odd: preserve previous B, even: preserve next r st X+, R0 subi temp2, LOW(-0xE1) ;add 0xE1: 0x0F->0xF0 (C=1), 0xF0->0xD1 (C=0) brcs set_led_iter ;C=1: do 2nd iteration, C=0: done dec cmd breq set_led_end sbrc opL, 0 sbiw ZH:ZL, 1 ;even LED, not last: share byte with following odd LED rjmp set_loop set_led_end: sbrc opL, 6 ;bit 6 set: continued (set another group of LEDs) rjmp set_continued set_end: .if (N_LED & 1) .error "N_LED must be even for fading function" .endif fade: ;fade from current to new values lpm n_steps, Z+ ;read number of fading steps ;start fading ldi cmd, 1 ;using 'cmd' as step counter fade_loop: ldi YL, RAM_LEDs ;calculate intermediate values and update LEDs fade_update: ldd oldval, Y+(RAM_LEDs_old - RAM_LEDs) ;get 2 old values ld newval, Y+ ;get 2 new values rcall fade_led ;1st value (low nibble) swap newval swap oldval rcall fade_led ;2nd value (high nibble) cpi YL, RAM_LEDs_end brlo fade_update ;send zero byte: latch last data byte and reset LEDs ;(after fade_led -> spi_send: XL = 0, temp2 = 16) rcall spi_send ;delay (10ms from end of last delay) ldi timeoutL, 1 fade_wait: cpi timeoutL, 0 brne fade_wait inc cmd cp n_steps, cmd sbrs flags, fRxComplete ;abort if data received on UART brsh fade_loop ;loop while n_steps >= step rjmp animation_loop ;=============================================================================== ;uart_putc: .macro uart_putc ;transmit one byte at 9600 baud (128 cycles/bit @ 1.2288 MHz) .if (UART_TX != 0) .error "This function expects UART_TX to be 0" .endif clc ;first bit = start bit = 0 (high) ldi bitcnt, 10 uart_putc_loop: cli ;125 ldi temp2, 1< 0x03 = 1< 0x05 = 1< divisor cp opH, n_steps brlo divide_next ;remainder < divisor: don't subtract divide_sub: sub opH, n_steps ori opL, 0x01 ;set result LSB divide_next: dec temp2 brne divide_loop ;rounding lsr opL ;shift out extra bit (value 1/2) into carry adc opL, temp2 ;add carry bit (temp2 == 0) ;send value to LED (91 cycles) .if (EE_Gamma != 0) .error "Gamma table must start at EEPROM address 0" .endif rcall eeprom_read ;XL = EEPROM[opL] = LED PWM value ;(temp2 = 0 from division loop) spi_send: ;send one byte (XL) to LPD8806, MSB first -- 80 cycles (+ rcall/ret) ;expects (temp2 % 16 == 0) ldi temp, 0x00 spi_send_loop: bst XL, 7 bld temp, LED_DATA out PORTB, temp lsl XL sbi PORTB, LED_CLOCK subi temp2, -2 ;add 2 out PORTB, temp brhs spi_send_loop ;loop until temp2 % 16 == 0 ret ;-------------------- multiply: ;opH:opL = temp * opL, with opL <= 15 (4 bits) ;(37 cycles incl. rcall/ret) andi opL, 0x0F sub opH, opH ;clear opH and carry ldi temp2, 4 multiply_loop: sbrc opL, 0 ;test multiplier LSB add opH, temp ror opH ;shift in carry from addition (carry is 0 if add skipped) ror opL ;shift out used bit of multiplier subi temp2, 1 ;decrement temp2 and clear carry brne multiply_loop ;shift removed here and before division ; ;shift result right by 4 ; swap opL ; swap opH ; mov temp, opH ; andi temp, 0xF0 ; or opL, temp ; andi opH, 0x0F ret ;=============================================================================== .org 0x100 ;animation must start on a 256 byte boundary animation: ; .if (ENABLE_CAL == 0) ; ; .if ((animation*2 != 0x200) || ((FLASHEND+1)*2 != 0x400)) ; .error "Expected animation == 0x100, FLASHEND+1 == 0x200" ; .endif ; ; ;rainbow: red, orange-red, orange-yellow, yellow, ; ; yellow-green, green-yellow, green, green-cyan, ; ; cyan-green, cyan, cyan-blue, blue-cyan, ; ; blue, blue-violet, violet-blue, violet ; ; ;Set 16 LEDs + Fade (from black) ; ; F00 F50 ; .db 0xA0+16, 0x0F, 0xF0, 0x05 ; ; FA0 FF0 AF0 5F0 ; .db 0xAF, 0xF0, 0x0F, 0xFA, 0x50, 0x0F ; ; 0F0 0F5 0FA 0FF ; .db 0xF0, 0x00, 0x5F, 0xF0, 0x0A, 0xFF ; ; 0AF 05F 00F 50F ; .db 0xA0, 0x0F, 0xF5, 0x00, 0x5F, 0xF0 ; ; A0F F0F ; .db 0x0A, 0xFF, 0xF0, 100 ;100 fade steps = 1 second ; ; ;repeat 15x: rotate 1 place, set 0 LEDs, fade 25 steps ; .db 0xC0+(64-15), 0x80+1, 0xA0+0, 25 ; ;end repeat infinite, end of animation ; .db 0xFF, 0x7F ; ; .endif