.list
.listmac
.include "tn44adef.inc"		; Definition file for Tiny44A

//  Standard thresholds
.equ inpThrRed = int(1.1*256/4.1)	; 0.2v scaled to ref
.equ inpThrGrn = int(1.2*256/4.1)	; 0.3v scaled to ref
.equ inpThrBlu = int(1.3*256/4.1)	; 0.4v scaled to ref

.equ inpThrDeltaRed = 2	; Threshold for diff betw LT and ST averages
.equ inpThrDeltaGrn = 2
.equ inpThrDeltaBlu = 2
						; used to trigger output LEDs

// S&H Settling time
.equ SHSettlingTime = 100
.equ T1CompareVal = SHSettlingTime*1000/1024
.equ T1CompareValH = T1CompareVal/256
.equ T1CompareValL = T1CompareVal-(T1CompareValH*256)

/*  Special case for reworked with wrong LEDs
.equ inputThresh1 = int(1.1*256/4.1)	; 0.2v scaled to ref
.equ inputThresh2 = int(1.25*256/4.1)	; 0.3v scaled to ref
.equ inputThresh3 = int(1.15*256/4.1)	; 0.4v scaled to ref
*/
.equ oneShotDelayRed = 3			; number of 250 mS periods outputs stay on
.equ oneShotDelayGrn = 3			; number of 250 mS periods outputs stay on
.equ oneShotDelayBlu = 3			; number of 250 mS periods outputs stay on

.equ redLED = PB0				; Output pins for LEDs
.equ grnLED = PB1
.equ bluLED = PB2

.equ redInput = PA0				; Input pins for LEDs
.equ grnInput = PA2
.equ bluInput = PA3

.equ ADC0Pin = PA0				; ADC pins defs
.equ ADC1Pin = PA1
.equ ADC2Pin = PA2
.equ ADC3Pin = PA3
.equ ADC4Pin = PA4
.equ ADC5Pin = PA5
.equ ADC6Pin = PA6
.equ ADC7Pin = PA7

.equ redInChan = 0				; ADC channels that correspond to sensor LEDs
.equ grnInChan = 2
.equ bluInChan = 3

.equ redInpDis = ADC0D			; Input disable for ADC pins in use
.equ grnInpDis = ADC2D
.equ bluInpDis = ADC3D

.equ ADCchanMask = 0x07			; Selects the channel setting
.equ ADCnonChanMask = 0xF8		; Selects all but channel setting

.equ ARefVcc = 0				; Use Vcc as ref for ADC
.equ ARefExt = 1				; Use AREF (PA0) as ref for ADC
.equ ARef11 = 2					; Use int 1.1v as ref for ADC


.cseg	
.org 0x0000						; Starts at location 0000
	rjmp	RESETisr			; Reset
	rjmp	EXT_INT0isr			; External Interrupt 0
	rjmp	PCINT0isr			; Pin change Interrupt Request 0
	rjmp	PCINT1isr			; Pin change Interrupt Request 1
	rjmp	WDTisr				; Watchdog Interrupt Handler
	rjmp	TIM1_CAPTisr		; Timer1 Capture Handler
	rjmp	TIM1_COMPAisr		; Timer/Counter1 Compare Match A
	rjmp	TIM1_COMPBisr		; Timer/Counter1 Compare Match B
	rjmp	TIM1_OVFisr			; Timer/Counter1 Overflow
	rjmp	TIM0_COMPAisr		; Timer/Counter0 Compare Match A
	rjmp	TIM0_COMPBisr		; Timer/Counter0 Compare Match B
	rjmp	TIM0_OVFisr			; Timer/Counter0 Overflow
	rjmp	ANA_COMPisr			; Analog comparator
	rjmp	ADCisr				; ADC Conversion ready
	rjmp	EE_RDYisr			; EEPROM Ready
	rjmp	USI_STRisr			; USI START
	rjmp	USI_OVFisr			; USI Overflow

RESETisr:
/*	Note clock default is 8 MHz, 14*CK+64ms start-up, CK/8 => System Clock = 1 MHz
***
***	Initialization Section
***
*/
	ldi		r16, high(RAMEND)			; Main program start
	out		SPH,r16						; Set Stack Pointer to top of RAM
	ldi		r16, low(RAMEND)
	out		SPL,r16							
;
; Turn off unused features
										; Shut down timer 1 and USI to save power
	ldi		r16,(1<<PRUSI)
	out 	PRR, r16
;
; Port A contains the inputs, which will be read as analog values

;	Set port A as input pins
	clr		r16
	out 	DDRA, r16

;	Set port A inputs to tri-state (no pull-ups)
	clr		r16
	out		PortA, r16

;  Port B controls the LED outputs

;	Set port B as output pins
	ldi		r16,(1<<redLED)+(1<<grnLED)+(1<<bluLED)
	out 	DDRB, r16

;	Set port B outputs high (LEDs off)
	out		PortB, r16
;
; Setup Timer 0 to control the LED one-shot on time

;	CTC mode 2 clears Timer 0 on match, outputs disconnected
	ldi		r16, (1<<WGM01)
	out		TCCR0A, r16

;	Set timer 0 prescale to 1024 (1 ms count)
	ldi		r16,(1<<CS02)+(1<<CS00)
	out		TCCR0B, r16

;	Set T0 compare reg A to 244 (4 Hz or 250 mS)
	ldi		r16, 244
	out		OCR0A, r16

;	Enable Timer 0 compare A interrupt to force update every 250 mS
	ldi		r16,(1<<OCIE0A)
	out		TIMSK0, r16

;	Set timer sync mode, start timer
	ldi		r16, (1<<PSR10)
	out		GTCCR, r16
;
; Setup Timer 1 to control the S&H setup time prior to ADC

;	CTC mode 4 clears Timer 0 on match, outputs disconnected
	clr		r16
	out		TCCR1A, r16

;	Set timer 0 to run from CPU clock/1024, CTC mode 0
	ldi		r16, (1<<CS12)+(1<<CS10)
	out		TCCR1B, r16

;	Set T1 compare reg A to S&H settling time
	ldi		r17, T1CompareValH
	ldi		r16, T1CompareValL
	out		OCR1AH, r17
	out		OCR1AL, r16

;	Enable Timer 0 compare A interrupt to force update every 250 mS
	ldi		r16,(1<<OCIE1A)
	out		TIMSK1, r16

;	Set timer sync mode, start timer
	ldi		r16, (1<<PSR10)
	out		GTCCR, r16
;
; Setup ADC

;	Set up ADC ref Vcc (cannot use int ref), chan Red input
	ldi		r16,ARefVcc+redInChan
	out 	ADMUX, r16

;	Left-adjust result, free running mode
	ldi		r16,(1<<ADLAR)
	out 	ADCSRB, r16

;	Disable digital inputs on ADC pins in use
//	ldi		r16,(1<<redInpDis)+(1<<grnInpDis)+(1<<bluInpDis)
//	out 	DIDR0, r16

;	Enable ADC, start first conv, enable int, clk/2 pre-scale
;	(ADPS0-2 = 0) to provide faster conversion for 8-bit res
;	Single sample mode
;	This means each ADC sample will take at most 104 clock cycles
.equ ADCSetting = (1<<ADEN)+(1<<ADSC)+(1<<ADIE)
	ldi		r16, ADCSetting
	out 	ADCSRA, r16

;	Initialize the average values with initial ADC reads
	ldi		r16, 0xFF
	sts		initFlag, r16

;
; End of initialization
;
	ldi		r16, (1<<SE)				; Allow SLEEP mode = Idle
	out		MCUCR, r16
	sei									; Enable interrupts

/*
		Core program: (currently does nothing)
*/
Main:
	sleep								; Main routine would go here
	rjmp 	Main						; Re-run loop
/*
		Interrupt Service Routines
*/
EXT_INT0isr:							; External Interrupt 0
	reti
PCINT0isr:								; Pin change Interrupt Request 0
	reti
PCINT1isr:								; Pin change Interrupt Request 1
	reti
WDTisr:									; Watchdog Time-out
	reti
TIM0_COMPAisr:							; Timer/Counter0 Compare Match A
/*
		Timer 0 is used to set the delay time between when an input is sensed and
		when the output is updated.  The prescaler is set for 1 ms ticks of the timer
		and the timeout is 250 mS, so the LED outputs will be updated 4 times a second

*/
	push	r16							; Save contents of temp regs
	in		r16,SREG					; Get status register
	push	r16							; Save on the stack
//
	lds		r16, countDown+redInChan	; Decrement the timers for each output
	tst		r16
	breq	countDownGrn				; If 0, skip to next output timer
	dec		r16
	sts		countDown+redInChan, r16	; Save new value
	brne	countDownGrn				; If just timed out, then update the output
turnRedOff:
	sbi		PortB, redLED				; Output high turns LED off
countDownGrn:
	lds		r16, countDown+grnInChan	; Decrement the timers for each output
	tst		r16
	breq	countDownBlu				; If 0, skip to next output timer
	dec		r16
	sts		countDown+grnInChan, r16	; Save new value
	brne	countDownBlu				; If just timed out, then update the output
turnGrnOff:
	sbi		PortB, grnLED				; Output high turns LED off
countDownBlu:
	lds		r16, countDown+bluInChan	; Decrement the timers for each output
	tst		r16
	breq	countDownEnd				; If 0, skip to next output timer
	dec		r16
	sts		CountDown+bluInChan, r16	; Save new value
	brne	countDownEnd				; If just timed out, then update the output
turnBluOff:
	sbi		PortB, bluLED				; Output high turns LED off
countDownEnd:
	pop		r16							; Restore status register
	out		SREG,r16
	pop		r16
	reti
;
TIM0_COMPBisr:							; Timer/Counter0 Compare Match B
	reti
TIM0_OVFisr:							; Timer/Counter0 Overflow
	reti
TIM1_CAPTisr:							; Timer1 Capture Handler
	reti
TIM1_COMPAisr:							; Timer/Counter1 Compare Match A
/*	This timer interrupt indicates that the time allowed for the ADC S&H has expired
	and the conversion should begin.
*/
	ldi		r30, ADCSetting
	out		ADCSRA, r30					; Start the conversion!
	reti

TIM1_COMPBisr:							; Timer/Counter1 Compare Match B
	reti
TIM1_OVFisr:							; Timer/Counter1 Overflow
	reti
ANA_COMPisr:							; Analog comparator
	reti
ADCisr:									; ADC Conversion ready
/*	Three of the ADC channels are in use.  The ADC is set for single conversion mode.
	This ISR gets the ADC sample value and stores it into the input array, then
	keeps track of pointers and updates the multiplexer for the next reading.  Note
	that because of the ADC timing, the multiplexer value set now will determine the
	channel for the reading after next.
	The ADC scale is 1 bit = 1/256 of Vcc.  Each color uses the same threshhold.
*/
// Get value from latest conversion
	in		r28, ADCH					; Get 8-bit left-justified value
	in		r29, ADMUX					; Get channel for this sample
	andi	r29, ADCchanMask

processSample:
	lds		r30, initFlag
	tst		r30
	breq	tryRed						; If the flag is clear, initialization was completed
	rjmp	finishInit
tryRed:
	cpi		r29, redInChan				; Determine source of current reading
	brne	tryGrn
	ldi		r31, grnInChan				; Set ADC channel for next color
	ldi		r30, ARefVcc
	add		r30, r31
	out		ADMUX, r30					; Set the MUX to the new channel
	; Bounce the inputs to clear the charge on the S&H
	clr		r30
	out 	DIDR0, r30
	ldi		r30,(1<<redInput)+(1<<grnInput)+(1<<bluInput)
	out 	DDRA, r30
	clr		r30
	out 	PortA, r30
	out 	DDRA, r30
	ldi		r30, 0xFF
	out 	DIDR0, r30
	sts		ADCvalue+redInChan, r28		; Save in value array
	lsr		r28							; Divide by 4
	lsr		r28
	lds		r30, ADCShrtAvg+redInChan	; Get current short term average value
	mov		r1, r30						; Put ST avg in r1
	lsr		r30
	lsr		r30
	sub		r1, r30						; Subtract 1/4 to leave 3/4
	add		r1, r28						; New ST average = 0.75 ST + 0.25 sample
	mov		r30, r1
	sts		ADCShrtAvg+redInChan, r30
	lds		r31, ADCLongAvg+redInChan
	mov		r1, r31						; Put ST avg in r1
	lsr		r31							; Calc 7/8 of last LT avg value
	lsr		r31
	lsr		r31
	sub		r1, r31						; Subtract 1/8 to leave 7/8
	lsr		r28							; Calc 1/8 of sample value
	add		r1, r28						; New LT average = 7/8 LT + 1/8 sample
	mov		r31, r1
	sts		ADCLongAvg+redInChan, r31
	sub		r30, r31					; Subtract ST from LT average
	cpi		r30, inpThrDeltaRed			; If difference is sufficiently large,
	brlt	redOff
redOn:
	cbi		PortB, redLED				; Output low turns LED on
	ldi		r31, oneShotDelayRed		; Set timeout for one-shot
	sts		countDown+redInChan, r31	
	rjmp	redDone
redOff:
	lds		r31, countDown+redInChan	; Make sure it's not on for a reason
	tst		r31
	brne	redDone
	sbi		PortB, redLED				; Output high turns LED off
redDone:
	ldi		r28, grninChan				; Set ADC channel for next color
	rjmp	startConv

tryGrn:
	cpi		r29, grnInChan				; Determine source of current reading
	brne	tryBlu
	ldi		r31, bluInChan				; Set ADC channel for next color
	ldi		r30, ARefVcc
	add		r30, r31
	out		ADMUX, r30					; Set the MUX to the new channel
	; Bounce the inputs to clear the charge on the S&H
	clr		r30
	out 	DIDR0, r30
	ldi		r30,(1<<redInput)+(1<<grnInput)+(1<<bluInput)
	out 	DDRA, r30
	clr		r30
	out 	PortA, r30
	out 	DDRA, r30
	ldi		r30, 0xFF
	out 	DIDR0, r30
	sts		ADCvalue+grnInChan, r28		; Save in value array
	lsr		r28							; Calc 1/4 of sample value
	lsr		r28
	lds		r30, ADCShrtAvg+grnInChan	; Get current short term average value
	mov		r1, r30						; Put ST avg in r1
	lsr		r30
	lsr		r30
	sub		r1, r30						; Subtract 1/4 to leave 3/4
	add		r1, r28					; New ST average = 0.75 ST + 0.25 sample
	mov		r30, r1
	sts		ADCShrtAvg+grnInChan, r30
	lds		r31, ADCLongAvg+grnInChan
	mov		r1, r31						; Put ST avg in r1
	lsr		r31							; Calc 7/8 of last LT avg value
	lsr		r31
	lsr		r31
	sub		r1, r31						; Subtract 1/8 to leave 7/8
	lsr		r28							; Calc 1/8 of sample value
	add		r1, r28						; New LT average = 7/8 LT + 1/8 sample
	mov		r31, r1
	sts		ADCLongAvg+grnInChan, r31
	sub		r30, r31					; Subtract ST from LT average
	cpi		r30, inpThrDeltaGrn			; If difference is sufficiently large,
	brlt	grnOff
grnOn:
	cbi		PortB, grnLED				; Output low turns LED on
	ldi		r31, oneShotDelayGrn		; Set timeout for one-shot
	sts		countDown+grnInChan, r31	
	rjmp	grnDone
grnOff:
	lds		r31, countDown+grnInChan	; Make sure it's not on for a reason
	tst		r31
	brne	grnDone
	sbi		PortB, grnLED				; Output high turns LED off
grnDone:
	ldi		r28, bluInChan				; Set ADC channel for next color
	rjmp	startConv

tryBlu:
	cpi		r29, bluInChan				; Determine source of current reading
	brne	invalidChan
	ldi		r31, redInChan				; Set ADC channel for next color
	ldi		r30, ARefVcc
	add		r30, r31
	out		ADMUX, r30					; Set the MUX to the new channel
	; Bounce the inputs to clear the charge on the S&H
	clr		r30
	out 	DIDR0, r30
	ldi		r30,(1<<redInput)+(1<<grnInput)+(1<<bluInput)
	out 	DDRA, r30
	clr		r30
	out 	PortA, r30
	out 	DDRA, r30
	ldi		r30, 0xFF
	out 	DIDR0, r30
	sts		ADCvalue+bluInChan, r28		; Save in value array
	lsr		r28							; Divide by 4
	lsr		r28
	lds		r30, ADCShrtAvg+bluInChan	; Get current short term average value
	mov		r1, r30						; Put ST avg in r1
	lsr		r30
	lsr		r30
	sub		r1, r30						; Subtract 1/4 to leave 3/4
	add		r1, r28					; New ST average = 0.75 ST + 0.25 sample
	mov		r30, r1
	sts		ADCShrtAvg+bluInChan, r30
	lds		r31, ADCLongAvg+bluInChan
	mov		r1, r31						; Put ST avg in r1
	lsr		r31							; Calc 7/8 of last LT avg value
	lsr		r31
	lsr		r31
	sub		r1, r31						; Subtract 1/8 to leave 7/8
	lsr		r28							; Calc 1/8 of sample value
	add		r1, r28						; New LT average = 7/8 LT + 1/8 sample
	mov		r31, r1
	sts		ADCLongAvg+bluInChan, r31
	sub		r30, r31					; Subtract ST from LT average
	cpi		r30, inpThrDeltaBlu			; If difference is sufficiently large,
	brlt	bluOff
bluOn:
	cbi		PortB, bluLED				; Output low turns LED on
	ldi		r31, oneShotDelayBlu		; Set timeout for one-shot
	sts		countDown+bluInChan, r31	
	rjmp	bluDone
bluOff:
	lds		r31, countDown+bluInChan	; Make sure it's not on for a reason
	tst		r31
	brne	bluDone
	sbi		PortB, bluLED				; Output high turns LED off
bluDone:
	ldi		r28, redInChan				; Set ADC channel for next color
	rjmp	startConv

invalidChan:
	ldi		r28, redInChan
	ldi		r30, ARefVcc
	add		r30, r28
	out		ADMUX, r30					; Set the MUX to the new channel
	rjmp	startConv

finishInit:
initRed:
	cpi		r29, redInChan				; Determine source of current reading
	brne	initGrn
	ldi		r31, grnInChan				; Set ADC channel for next color
	ldi		r30, ARefVcc
	add		r30, r31
	out		ADMUX, r30					; Set the MUX to the new channel
	sts		ADCShrtAvg+redInChan, r28
	sts		ADCLongAvg+redInChan, r28
	rjmp	startConv
initGrn:
	cpi		r29, grnInChan				; Determine source of current reading
	brne	initBlu
	ldi		r31, bluInChan				; Set ADC channel for next color
	ldi		r30, ARefVcc
	add		r30, r31
	out		ADMUX, r30					; Set the MUX to the new channel
	sts		ADCShrtAvg+grnInChan, r28
	sts		ADCLongAvg+grnInChan, r28
	rjmp	startConv
initBlu:
	cpi		r29, bluInChan				; Determine source of current reading
	brne	invalidChan
	ldi		r31, redInChan				; Set ADC channel for next color
	ldi		r30, ARefVcc
	add		r30, r31
	out		ADMUX, r30					; Set the MUX to the new channel
	sts		ADCShrtAvg+bluInChan, r28
	sts		ADCLongAvg+bluInChan, r28
	clr		r30							; Clear the initialization flag
	sts		initFlag, r30
;	Drop into starting timer

;	Start next conversion: this actually restarts timer 1 so the S&H can settle
startConv:
	clr		r30
	out		TCNT1H, r30
	out		TCNT1L, r30

restoreState:
	reti								; Done with ISR

EE_RDYisr:								; EEPROM Ready
	reti
USI_STRisr:								; USI START
	reti
USI_OVFisr:								; USI Overflow
	reti

.dseg							// Static RAM Area
.org		SRAM_START			; Skip over the memory-mapped registers
initFlag:	.byte 8
ADCValue:	.byte 8				; Array of input readings from the 8 ADC channels
ADCShrtAvg:	.byte 8				; Short term average of input values, by channel
ADCLongAvg:	.byte 8				; Long term average of input values, by channel
countDown:	.byte 8				; Array of counters for output changes

.eseg							// EEPROM Area
