; Software PWM for a clone of Geir's RGB LED night light.
; Original is here: http://letsmakerobots.com/node/17532
;
; This clone is created with ATTiny2313 running at 8MHz (internal oscillator).
;
; $Id: multiled-sw-pwm.asm 282 2010-05-07 23:16:55Z nuumio $

.nolist
.include "tn2313def.inc"
.list

; Number of leds (1 RGB led = 3 leds)
.equ led_count = 15

; led ports
.equ lport_00 = PORTB
.equ lport_01 = PORTB
.equ lport_02 = PORTB
.equ lport_03 = PORTB
.equ lport_04 = PORTB
.equ lport_05 = PORTB
.equ lport_06 = PORTB
.equ lport_07 = PORTB
.equ lport_08 = PORTD
.equ lport_09 = PORTD
.equ lport_10 = PORTD
.equ lport_11 = PORTD
.equ lport_12 = PORTA
.equ lport_13 = PORTA
.equ lport_14 = PORTD

; led pins
.equ lpin_00 = PB7
.equ lpin_01 = PB6
.equ lpin_02 = PB5
.equ lpin_03 = PB4
.equ lpin_04 = PB3
.equ lpin_05 = PB2
.equ lpin_06 = PB1
.equ lpin_07 = PB0
.equ lpin_08 = PD6
.equ lpin_09 = PD5
.equ lpin_10 = PD4
.equ lpin_11 = PD3
.equ lpin_12 = PA0
.equ lpin_13 = PA1
.equ lpin_14 = PD1

; Zero reg (always zero, helps comparing things)
.def zero_reg = r0

; PWM counter
.def pwm_counter = r1

; Switch debounce registers
.def debounce_lo = r2
.def debounce_hi = r3

; Switch status bit (in GPIOR0)
.equ switch_status_bit = 1

; Delay counter
.def delay_counter = r7

; Delay reached bit (in GPIOR0)
.equ delay_reached_bit = 0

; Registers for Galois LFSR
; See http://en.wikipedia.org/wiki/Linear_feedback_shift_register#Galois_LFSRs
.def lfsr_lo = r4
.def lfsr_hi = r5

; LFSR seed and taps
.equ seed = $ace1
.equ galois_taps = $b4

; Timer1 CTC mode top
; Timer1 max value of 399 and no prescaling means 0,05ms isr intervals with 8Mhz resonator.
.equ t1_ctctop = 399

; "Interrupt" variables (used in interrupts [and in in initialization before interrupts are enabled])
; Not to be used outside interrupts! (Could be used between cli and sei commands though.)
.def ivar1 = r18
.def ivar2 = r19
.def ivar3 = r20

; "Non-interrupt" variables (not used in interrupts -> safe to use in main loop)
.def var1 = r21
.def var2 = r22
.def var3 = r23
.def var4 = r12

; How many measurements to take of 555 pulse times for average
.equ mcount_555 = 8

; 555 pulse time measurements counter
.def ptime_counter = r6

; 555 pulse timing
.def ptime_lo = r24
.def ptime_hi = r25

; 555 R2var measusement store (potentiometer position)
.def pot_pos = r10

; Switch closed counter
.def sw_closed = r9

; Modes
.def mode = r8
.equ mode_debug       = 0
.equ mode_random      = 1
.equ mode_random_fade = 2
.equ mode_fader       = 3
.equ mode_count       = 4

; SREG store register for storing status register during interrupts
.def sreg_store = r11

.DSEG
; PWM Duty cycle values for leds
pwm_dc_tab:.BYTE led_count
; Info tab for leds (usege depends on mode)
led_info_tab:.BYTE led_count
; Extra info tab for leds (usege depends on mode)
led_extra_tab:.BYTE (led_count*2)
; Random fade mode's counter
rfm_counter:.BYTE 1
; Random fade mode counter's value. Defines how often to get new duty cycle values.
.equ rfm_counter_value = 32

.CSEG
; Interrupt vectors
.org $0000
	rjmp main			; RESET External Pin, Power-on Reset, Brown-out Reset, and Watchdog Reset
	rjmp int0_isr		; INT0 External Interrupt Request 0
	reti				; INT1 External Interrupt Request 1
	reti				; TIMER1 CAPT Timer/Counter1 Capture Event
	rjmp timer1_cma_isr	; TIMER1 COMPA Timer/Counter1 Compare Match A
	reti				; TIMER1 OVF Timer/Counter1 Overflow
	rjmp timer0_ovf_isr	; TIMER0 OVF Timer/Counter0 Overflow
	reti				; USART0, RX USART0, Rx Complete
	reti				; USART0, UDRE USART0 Data Register Empty
	reti				; USART0, TX USART0, Tx Complete
	reti				; ANALOG COMP Analog Comparator
	reti				; PCINT Pin Change Interrupt
	reti				; TIMER1 COMPB Timer/Counter1 Compare Match B
	reti				; TIMER0 COMPA Timer/Counter0 Compare Match A
	reti				; TIMER0 COMPB Timer/Counter0 Compare Match B
	reti				; USI START USI Start Condition
	reti				; USI OVERFLOW USI Overflow
	reti				; EE READY EEPROM Ready
	reti				; WDT OVERFLOW Watchdog Timer Overflow

;
; TIMER0 overflow ISR
; PWM is done here.
; Worst case runtime: (counter != 0 & counter >= for every pwm) = 110 clocks
;
timer0_ovf_isr:
	in sreg_store, SREG   ; Store status register
	push xl               ; Push X register to stack so we won't mess it up.
	push xh               ; Not really needed with ATTiny 2313 because SRAM is $0060-$00DF
	inc pwm_counter

	; Load pwm_dc_tab address to X register
	ldi xl, low(pwm_dc_tab)
	ldi xh, high(pwm_dc_tab)

	; If pwm counter is 0 we set on leds that have pwm value greater then 0.
	cp pwm_counter, zero_reg
	brne counter_non_zero

	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_00, lpin_00  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_01, lpin_01  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_02, lpin_02  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_03, lpin_03  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_04, lpin_04  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_05, lpin_05  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_06, lpin_06  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_07, lpin_07  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_08, lpin_08  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_09, lpin_09  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_10, lpin_10  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_11, lpin_11  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_12, lpin_12  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_13, lpin_13  ; Set led on
	ld ivar2, X+           ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cpse ivar2, zero_reg   ; Compare, skip if equal
	sbi lport_14, lpin_14  ; Set led on
	rjmp timer0_ovf_isr_exit

counter_non_zero:
	; Turn leds off at mark
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l01
	cbi lport_00, lpin_00
timer0_ovf_isr_l01:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l02
	cbi lport_01, lpin_01
timer0_ovf_isr_l02:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l03
	cbi lport_02, lpin_02
timer0_ovf_isr_l03:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l04
	cbi lport_03, lpin_03
timer0_ovf_isr_l04:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l05
	cbi lport_04, lpin_04
timer0_ovf_isr_l05:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l06
	cbi lport_05, lpin_05
timer0_ovf_isr_l06:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l07
	cbi lport_06, lpin_06
timer0_ovf_isr_l07:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l08
	cbi lport_07, lpin_07
timer0_ovf_isr_l08:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l09
	cbi lport_08, lpin_08
timer0_ovf_isr_l09:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l10
	cbi lport_09, lpin_09
timer0_ovf_isr_l10:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l11
	cbi lport_10, lpin_10
timer0_ovf_isr_l11:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l12
	cbi lport_11, lpin_11
timer0_ovf_isr_l12:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l13
	cbi lport_12, lpin_12
timer0_ovf_isr_l13:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_l14
	cbi lport_13, lpin_13
timer0_ovf_isr_l14:
	ld ivar2, X+             ; Load duty cycle value from address pointed by X register to ivar2 and increment X register
	cp ivar2, pwm_counter
	brcc timer0_ovf_isr_exit
	cbi lport_14, lpin_14

timer0_ovf_isr_exit:
	pop xh                  ; Pop X register from stack
	pop xl
	out SREG, sreg_store    ; Restore status register
	reti

;
; TIMER1 compare match A (CTC mode timer) ISR
; Worst case runtime: 29 cycles
;
timer1_cma_isr:
	in sreg_store, SREG
	; Pulse time counter
	adiw ptime_hi:ptime_lo, 1

	; Debouncer. We want 16 ones/zeros in a row
	ldi ivar1, 1
	lsl debounce_lo
	rol debounce_hi
	sbic PIND, PD0
	eor debounce_lo, ivar1
	sbis GPIOR0, switch_status_bit
	rjmp timer1_cma_isr_set_sw_closed
	ldi ivar1, 255
	cp debounce_lo, ivar1
	brne timer1_cma_isr_sw_done
	cp debounce_hi, ivar1
	brne timer1_cma_isr_sw_done
	cbi GPIOR0, switch_status_bit
	rjmp timer1_cma_isr_sw_done
timer1_cma_isr_set_sw_closed:
	cp debounce_lo, zero_reg
	brne timer1_cma_isr_sw_done
	cp debounce_hi, zero_reg
	brne timer1_cma_isr_sw_done
	inc sw_closed
	sbi GPIOR0, switch_status_bit
	
timer1_cma_isr_sw_done:
	; Decrease delay counter if delay_reached_bit is clear.
	; Set delay reached bit if delay counter reaches zero
	sbic GPIOR0, delay_reached_bit
	rjmp timer1_cma_isr_exit
	dec delay_counter
	brne timer1_cma_isr_exit
	sbi GPIOR0, delay_reached_bit

timer1_cma_isr_exit:
	out SREG, sreg_store
	reti

;
; INT0 ISR
; Worst case runtime: 34 cycles
;
int0_isr:
	in sreg_store, SREG

	; Keep on reading measurements until ptime_counter hits zero (for getting an average)
	dec ptime_counter
	brne int0_isr_galois

	; Reset 555 measuments counter
	ldi ivar3, mcount_555
	mov ptime_counter, ivar3

	; Calculate average of measurements (8 measurements -> divide by 8)
	lsr ptime_hi
	ror ptime_lo
	lsr ptime_hi
	ror ptime_lo
	lsr ptime_hi
	ror ptime_lo

	; Pulse timing varies from 12,7983ms to 26,5696ms
	; (R1=4K7, R2=6K2, R2var=9K2, C1=1,08uF)
	; [R2var potentiometer is rated 10K but multimeter shows about 9K2]
	; [C1 is rated 1uF but when measuring it looks more like 1,08uF]
	; That is 255 - 531 "0,05ms" and that's 275 range of "0,05ms" (more with larger R2var).
	; That means ptime_hi should be 1 if 555 calculations are right and ptime_low should
	; have "the value of potentiometer (R2var)". Just in case if ptime_hi = 2 or 0 we set
	; ptime_lo to 255 or 0 accordingly.
	cp ptime_hi, zero_reg
	breq int0_isr_pt_set_zero
	ldi ivar1, 2
	cp ptime_hi, ivar1
	breq int0_isr_pt_set_max
	rjmp int0_isr_pt_store
int0_isr_pt_set_max:
	ldi ptime_lo,255
	rjmp int0_isr_pt_store
int0_isr_pt_set_zero:
	ldi ptime_lo,0

	; Store "potentiometer value"
int0_isr_pt_store:
	mov pot_pos, ptime_lo

	; Clear 555 measurements
	clr ptime_hi
	clr ptime_lo

	; Advance 16-bit Galois LFSR
int0_isr_galois:
	rcall _do_lfsr

	out SREG, sreg_store
	reti

;
; Subroutine to Advance 16-bit Galois LFSR (not "interrupt safe!")
; See http://en.wikipedia.org/wiki/Linear_feedback_shift_register#Galois_LFSRs
;
_do_lfsr:
	ldi ivar1, galois_taps
	clc
	ror lfsr_hi
	ror lfsr_lo
	brcc _do_lfsr_exit
	eor lfsr_hi, ivar1
_do_lfsr_exit:
	ret

;
; Subroutine to advance Galois LFSR 8 times ("interrupt safe")
;
do_lfsr:
	cli
	rcall _do_lfsr
	sei
	ret

;
; Subroutine to advance Galois LFSR 8 times ("interrupt safe")
;
do_lfsr_8:
	push var1
	ldi var1, 8
do_lfsr_8_loop:
	cli
	rcall _do_lfsr
	sei
	dec var1
	brne do_lfsr_8_loop
	pop var1
	ret

;
; Subroutine to advance Galois LFSR 16 times ("interrupt safe")
;
do_lfsr_16:
	push var1
	ldi var1, 16
do_lfsr_16_loop:
	cli
	rcall _do_lfsr
	sei
	dec var1
	brne do_lfsr_16_loop
	pop var1
	ret

;
; Subroutine to move duty cycle values towards target values
;
adjust_pwm_values_towards_targets:
	push var1
	push var2
	push var3
	; Load pwm_dc_tab address to Y register
	ldi yl, low(pwm_dc_tab)
	ldi yh, high(pwm_dc_tab)

	; Load led_info_tab address to Z register
	ldi zl, low(led_info_tab)
	ldi zh, high(led_info_tab)

	; Loop pwm values and targets
	ldi var1, led_count
pwm_adjust_loop:
	ld var2, y                ; Load duty cycle value from address pointed by Y register to var2
	ld var3, z+               ; Load duty cycle target value from address pointed by Z register to var3 and increment Z register
	cp var2, var3
	breq pwm_adjust_loop_end  ; If value and target are equal there's no need to change the value
	brcc pwm_adjust_down      ; If carry bit is cleared var2 (value) >= var3 (target) and we want to dec the value
	inc var2
	rjmp pwm_adjust_store
pwm_adjust_down:
	dec var2
pwm_adjust_store:
	st y, var2                ; Store new value
pwm_adjust_loop_end:
	inc yl                    ; Increment Y register
	dec var1
	brne pwm_adjust_loop
	pop var3
	pop var2
	pop var1
	ret

;
; Pause var3 ms in a busy loop.
; This is probably not the most accurate way to do pause but it works for now.
; delay_reached_bit and delay_counter are handled in timer1_cma_isr and it's
; called evely 0,05 milliseconds.
;
pause_ms:
	cp var3, zero_reg ; Check for no delay
	breq pause_ms_exit
	push var2
	ldi var2, 20
pause_loop1:
	mov delay_counter, var3
	cbi GPIOR0, delay_reached_bit
pause_loop2:
	sbis GPIOR0, delay_reached_bit
	rjmp pause_loop2
	dec var2
	brne pause_loop1
	pop var2
pause_ms_exit:
	ret

;
; Main
;
main:
	; Clear zero, mode, debounce and sw_closed registers
	clr zero_reg
	clr mode
	clr debounce_lo
	clr debounce_hi
	clr sw_closed

	; Set PORTB output pins
	ldi ivar3, (1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3)|(1<<PB4)|(1<<PB5)|(1<<PB6)|(1<<PB7)
	out DDRB, ivar3

	; Set PORTD output pins
	ldi ivar3, (1<<PD1)|(1<<PD3)|(1<<PD4)|(1<<PD5)|(1<<PD6)
	out DDRD, ivar3

	; Set PORTD output pins
	;sbi PORTD, PD0

	; Set PORTA output pins
	ldi ivar3, (1<<PA0)|(1<<PA1)
	out DDRA, ivar3

	; Set TIMER0: No prescaler
	ldi ivar3, (1<<CS00)
	out TCCR0B, ivar3

	; Set TIMER1 Preload
	ldi ivar2, high(t1_ctctop)
	ldi ivar1, low(t1_ctctop)
	out OCR1AH, ivar2
	out OCR1AL, ivar1

	; Set TIMER1 Prescale to None and Mode to CTC
	ldi ivar3, (1<<CS10) | (1<<WGM12);
	out TCCR1B, ivar3

	; Enable TIMER0 overflow and TIMER1 compare match (for CTC mode) interrupts
	ldi ivar3, (1<<TOIE0)|(1<<OCIE1A)
	out TIMSK, ivar3

	; Set INT0 to rising edge and PUD
	ldi ivar3, (1<<ISC00)|(1<<ISC01)
	out MCUCR, ivar3

	; Enable INT0 interrupt
	ldi ivar3, (1<<INT0)
	out GIMSK, ivar3

	; Seed LFSR
	ldi ivar1, low(seed)
	ldi ivar2, high(seed)
	mov lfsr_lo, ivar1
	mov lfsr_hi, ivar2

	; Set up pwm values
	ldi ivar1, led_count           ; Led count for loop
	ldi xl, low(pwm_dc_tab)        ; Load pwm_dc_tab address to X register
	ldi xh, high(pwm_dc_tab)
	ldi yl, low(led_info_tab)      ; Load led_info_tab address to Y register
	ldi yh, high(led_info_tab)
setup_loop:
	st x+, zero_reg                ; All PWM duty cycles to 0
	st y+, zero_reg                ; Set initial duty cycle target values to 0
	dec ivar1
	brne setup_loop

	; Reset 555 measuments counter
	ldi ivar1, mcount_555
	mov ptime_counter, ivar1

	; Initial coutner value for random fade mode
	ldi yl, low(rfm_counter)       ; Load rfm_counter address to Y register
	ldi yh, high(rfm_counter)
	ldi var1, rfm_counter_value
	st y, var1

	; Set global interrupt flag
	sei

main_loop:
	cp sw_closed, zero_reg
	breq main_loop_mode_check
	dec sw_closed
	rcall change_mode
main_loop_mode_check:
	mov var1, zero_reg
	cp mode, var1
	brne ml_check_random_mode
	rcall do_mode_debug
	rjmp main_loop_skip
ml_check_random_mode:
	inc var1
	cp mode, var1
	brne ml_check_random_mode_fade
	rcall do_mode_random
	rjmp main_loop_skip
ml_check_random_mode_fade:
	inc var1
	cp mode, var1
	brne ml_check_fader_mode
	rcall do_mode_random_fade
	rjmp main_loop_skip
ml_check_fader_mode:
	inc var1
	cp mode, var1
	brne main_loop_skip ; Should not happen
	rcall do_mode_fader
	;rjmp main_loop_skip
main_loop_skip:
	rjmp main_loop


;
; Change mode subroutine
;
change_mode:
	ldi var1, mode_count
	inc mode
	cp mode, var1         ; Reached max?
	brne change_mode_exit
	clr mode              ; Back to 0
change_mode_exit:
	rcall fade_to_black   ; Fade to black between modes to set duty cycle values and target values to 0
	rcall do_mode_init
	ret

;
; Init mode if needed
;
do_mode_init:
	mov var1, mode
	cpi var1, mode_fader
	brne dmi_skip
	rcall init_mode_fader
dmi_skip:
	ret

;
; Fade to black.
;
fade_to_black:
	; Set up pwm values
	ldi var2, 255                ; Loop counter
ftb_loop1:
	ldi yl, low(pwm_dc_tab)      ; Load pwm_dc_tab address to Y register
	ldi yh, high(pwm_dc_tab)
	ldi zl, low(led_info_tab)    ; Load led_info_tab address to Z register
	ldi zh, high(led_info_tab)
	ldi var1, led_count          ; Led count for loop
ftb_loop2:
	ld var3, y
	cp var3, zero_reg
	breq ftb_skip_change
	dec var3
ftb_skip_change:
	st y+, var3                  ; Set new PWM duty cycles
	st z+, zero_reg              ; Set duty cycle target value to 0
	dec var1
	brne ftb_loop2
	ldi var3, 1
	rcall pause_ms               ; Pause 1 ms
	dec var2
	brne ftb_loop1
	ret

;
; Debug mode
; Sets leds to show pot value
;
do_mode_debug:
	mov var1, pot_pos
	ldi var2, led_count
	ldi var3, 255
	ldi yl, low(pwm_dc_tab)   ; Load pwm_dc_tab address to Y register
	ldi yh, high(pwm_dc_tab)
dmd_loop:
	cp var1, zero_reg
	breq dmd_loop_set_to_zero
	cpi var1, 17
	brsh dmd_loop_set_to_max
	lsl var1                  ; Scale remainder to 0-255 range (multiply by 8)
	lsl var1
	lsl var1
	st y, var1
	clr var1
	rjmp dmd_loop_counter
dmd_loop_set_to_max:
	st y, var3
	subi var1, 17
	rjmp dmd_loop_counter
dmd_loop_set_to_zero:
	st y, zero_reg
dmd_loop_counter:
	inc yl
	dec var2
	brne dmd_loop
	ldi var3, 50              ; Pause for 50 ms
	rcall pause_ms
	ret

;
; Random mode
; Set pwm duty cycle randomly.
; Pot position defines speed.
;
do_mode_random:
	ldi var2, led_count
	ldi yl, low(pwm_dc_tab)  ; Load pwm_dc_tab address to Y register
	ldi yh, high(pwm_dc_tab)
dmr_loop:
	st y+, lfsr_lo
	rcall do_lfsr_8
	dec var2
	brne dmr_loop
	ldi var3, 10             ; Pause for 10 ms at least
	rcall pause_ms
	mov var3, pot_pos        ; Pause for some milliseconds depending on pot position.
	rcall pause_ms
	ret

;
; Random fade mode
; Set pwm duty cycle target randomly and adjust
; actual duty cycles towards targets. pot_pos
; defines speed.
;
do_mode_random_fade:
	; Adjust pwm duty cycles toward target values
	rcall adjust_pwm_values_towards_targets

	; New random target values for random leds every rfm_counter_value steps
	ldi yl, low(rfm_counter)         ; Load rfm_counter address to Y register
	ldi yh, high(rfm_counter)
	ld var1, y
	dec var1
	st y, var1                       ; Store new rfm_counter value (can do it here because ST doesn't affect SREG so BRNE works ok)
	brne dmrf_skip_new_values
	ldi var1, rfm_counter_value      ; Reset counter
	st y, var1                       ; Store reset rfm_counter value
	ldi var2, led_count
	ldi yl, low(pwm_dc_tab)          ; Load pwm_dc_tab address to Y register
	ldi yh, high(pwm_dc_tab)
	ldi zl, low(led_info_tab)        ; Load led_info_tab address to Z register
	ldi zh, high(led_info_tab)
dmrf_new_value_loop:
	ld var1, y
	ld var3, z
	cp var1, var3
	brne dmrf_new_value_loop_next    ; Only change target duty cycles if target has been already reached
	mov var3, lfsr_lo
	rcall do_lfsr_8                  ; Get 8 new random bits
	cpi var3, 246                    ; Top 10 -> Set duty cycle to max
	brsh dmrf_set_to_max
	cpi var3, 10                     ; Low 10 -> Set duty cycle to zero
	brlo dmrf_set_to_zero
	mov var3, lfsr_lo
	rcall do_lfsr_8                  ; Get new random bit (only one used in sbrc var3, 0)
	sbrc var3, 0
	rjmp dmrf_new_value_loop_next
	mov var3, lfsr_lo
	rcall do_lfsr_8                  ; Get 8 new random bits
	rjmp dmrf_store_value
dmrf_set_to_max:
	ldi var3, 255
	rjmp dmrf_store_value
dmrf_set_to_zero:
	ldi var3, 0
dmrf_store_value:
	st z, var3                       ; Store new target value
dmrf_new_value_loop_next:
	inc yl
	inc zl
	dec var2
	brne dmrf_new_value_loop
dmrf_skip_new_values:
	ldi var3, 1        ; Pause for 1 ms at least
	rcall pause_ms
	mov var3, pot_pos  ; Pause for some milliseconds depending on pot position.
	lsr var3           ; Max pot position (255) causes quite noticeable steps on low values so divide by 2
	rcall pause_ms
	ret
;
; Init fader mode
; Set initial led counter data (directions, speeds and counter values) for each led
; led_info_tab has the counter value and led_extra_tab has direction and speed/counter max value.
; led_extra_tab: b0-b6 = speed (counter max value), b7 = direction
; Speed and initial counter values range is [7,11] (5 different speeds)
; Initial direction is up (led_extra_tab b7 = 0) because modes start with duty cycle values of 0.
;
init_mode_fader:
	ldi var1, led_count
	ldi var2, 7
	ldi yl, low(led_info_tab)    ; Load led_info_tab address to Y register
	ldi yh, high(led_info_tab)
	ldi zl, low(led_extra_tab)   ; Load led_extra_tab address to Z register
	ldi zh, high(led_extra_tab)
imf_loop:
	mov var3, pot_pos            ; Let pot position/4 affect counter value
	lsr var3
	lsr var3
	add var3, var2
	st y+, var3
	st z+, var2
	inc var2
	cpi var2, 12
	brne imf_loop_next
	ldi var2, 7
imf_loop_next:
	dec var1
	brne imf_loop
	ret

;
; Fader mode.
; Fade each led in and out with different speed.
; led_info_tab is used to define direction and speed of individual led.
; Pot position defines total speed.
;
do_mode_fader:
	ldi var1, led_count
	ldi xl, low(pwm_dc_tab)      ; Load pwm_dc_tab address to X register
	ldi xh, high(pwm_dc_tab)
	ldi yl, low(led_info_tab)    ; Load led_info_tab address to Y register
	ldi yh, high(led_info_tab)
	ldi zl, low(led_extra_tab)   ; Load led_extra_tab address to Z register
	ldi zh, high(led_extra_tab)
dmf_loop:
	ld var2, y                   ; Load led counter data
	cp var2, zero_reg            ; Has counter reached zero?
	breq dmf_loop_reset_counter  ; If it has reset counter and adjust duty cycle
	dec var2                     ; Otherwise decrement counter
	rjmp dmf_loop_dc_done        ; And we are done
dmf_loop_reset_counter:
	ld var2, z                   ; Reset counter if zero reached
	mov var4, var2               ; Copy led extra info to var3 so we can check direction later
	andi var2, 0b01111111
	mov var3, pot_pos            ; Let pot position/4 affect counter value
	lsr var3
	lsr var3
	add var2, var3
	ld var3, x                   ; Load and adjust duty cycle value
	sbrs var4, 7                 ; Check Direction
	rjmp dmf_loop_inc_dc
	dec var3
	st x, var3
	brne dmf_loop_dc_done
	rjmp dmf_loop_change_direction
dmf_loop_inc_dc:
	inc var3
	st x, var3
	cpi var3, 255
	brne dmf_loop_dc_done
dmf_loop_change_direction:
	ldi var3, 0b10000000         ; Toggle and store direction bit
	eor var4, var3
	st z, var4
dmf_loop_dc_done:
	st y+, var2                  ; Store counter data
	inc xl
	inc zl
	dec var1
	brne dmf_loop
	ldi var3, 1                  ; Pause for 1 ms
	rcall pause_ms
	ret

