.INCLUDE "tn44adef.inc" 	
.DEF minU = R16					; time keeping regs
.DEF minT = R17
.DEF hrsU = R18
.DEF hrsT = R19
.DEF shiftH  = R20
.DEF shiftM  = R21
.DEF param	 = R22
.DEF btState = R23

.EQU butHrs = 0					; button pins for time setup
.EQU butMin = 1
.EQU SDA = 1					; setup for SPI/I2C lines
.EQU SCL = 2
.EQU ENA = 4
.EQU pressTsch   = 0x3F+1     	; threscholds for button press and
.EQU releaseTsch = 0xFC      	;  release (for the shift register)

.CSEG
.ORG 0 							; interrupt vectors
	rjmp 	RESET 				; reset interrupt
	reti						; external IRQ0
	rjmp	PCINT0_ISR			; PCINT0 interrupt
	reti						; PCINT1 interrupt
	reti						; WDT interrupt
	reti						; TIM1_CAPT 
	reti						; TIM1 COMPA
	reti						; TIM1 COMPB
	reti						; TIM1_OVF
	reti						; TIM0_COMPA
	reti						; TIM0_COMPB
	reti						; TIM0_OVF
	reti						; ANA_COMP
	reti						; ADC conversion complete
	reti						; EE_RDY
	reti						; USI_STR
	reti						; USR_OVF
 
code7seg:	; pfabedgcpfabedgc	; 7-segment codes for digits 1,3,4
	.DW		0b0001000101111101	; codes for 1 and 0
	.DW		0b0011011100111110	; codes for 3 and 2
	.DW		0b0110011101010011	; codes for 5 and 4
	.DW		0b0011000101101111	; codes for 7 and 6
	.DW		0b0111011101111111	; codes for 9 and 8
	.DW		0b0100111101111011	; codes for B and A
	.DW		0b0110110001111010	; codes for P and C
	.DW		0b0110101001011011	; codes for F and H

code7seg2:	; pgfabecdpgfabecd	; 7-segment codes for digit 2
	.DW		0b0000101000111111	; codes for 1 and 0
	.DW		0b0101101101011101	; codes for 3 and 2
	.DW		0b0111001101101010	; codes for 5 and 4
	.DW		0b0001101001110111	; codes for 7 and 6
	.DW		0b0111101101111111	; codes for 9 and 8
	.DW		0b0110011101111110	; codes for B and A
	.DW		0b0011010101111100	; codes for P and C
	.DW		0b0111010001101110	; codes for F and H
	
RESET:
	ldi		R25, LOW(RAMEND)	; initilize stack pointer
	out		SPL, R25
	ldi		R25, HIGH(RAMEND)
	out		SPH, R25

	ldi		R25, 0b11110100		; PORTS SETUP
	out		DDRA, R25			; configure PORTA<0,3> for input
	ldi		R25, 0b1000			; enable pull-up resistor
	out		PORTA, R25			; on PORTA<0,3>
	ldi		R25, 0b100			
	out		DDRB, R25			; configure PORTB:2 for output
	ldi		R25, 0b11			; enable pull-up resistors
	out		PORTB, R25			; on PORTB<1:0>

	ldi		R25, 0x80			; CLOCK SETUP
	out		CLKPR, R25			; set master clock divider 1:16
	ldi		R25, 4				; thus the system clock will
	out		CLKPR, R25			; run @ 0.5 MHz

	ldi		R25, 1				; ADC SETUP  
	out		DIDR0, R25			; disable digital input buffer at ADC0
	clr		R25					; ADC0 channel, use Vcc as reference
	out		ADMUX, R25
	ldi		R25, 0b10001010		; ADC on, interrupt enabled 
	out		ADCSRA, R25			; 1:4 clock prescaler
	ldi		R25, 0b00010000
	out		ADCSRB, R25			; left-justify the result

	ldi		R25, 0b10110001		; TIMER0 SETUP
	out		TCCR0A, R25			; phase correct PWM
	clr		R25					; initialize timer counter
	out		TCNT0, R25			; while the timer is stopped
	
	ldi		R25, 0b10110010		; TIMER1 SETUP
	out		TCCR1A, R25			; phase correct PWM mode 10
	clr		R24
	ldi		R25, 128
	out		TCNT1H, R24			; preload the timer counter
	out		TCNT1L, R25			; while the timer is stopped
	ser		R25
	out		ICR1H, R24			; set UPPER limit for timer1
	out		ICR1L, R25			; counter to 255 as for timer0

 	sbi		PCMSK0, 3			; enable pin change interrupt
	ldi		R25, 0x10			; at PORTA:3
	out		GIMSK, R25
	sei							; global interrupts enable
	rcall	setBrightness		; set initial brightness

	clr		R25					; STARTING TIMERS
	ldi		R24, 0b00000010		; set 1:8 TIMER0 clock divider
	ldi 	R25, 0b00010010		; set 1:8 TIMER1 clock divider
	out		TCCR0B, R24			; start TIMER0
	out		TCCR1B, R25			; start TIMER1

	ldi		R25, HIGH(31250)	; short delay for DS1339C to catch up
	ldi		R24, LOW(31250)
	sbiw	R24, 1
	brne	PC-1

init_RTC:						; initialize the I2C interface
	sbic	PINA, SDA
	 rjmp  	I2C_OK         		; by toggling clock line
	sbi		PORTA, SCL       	; until the data line gets high
	rjmp 	PC+1
	cbi   	PORTA, SCL
	rjmp 	init_RTC
        
I2C_OK:
	rcall 	start_I2C          	; configure RTC
	ldi		R22, 0b11010000   	; slave address/write
	rcall	write_I2C
    ldi		R22, 0x0B         	; alarm setup
	rcall 	write_I2C
	ldi		R22, 128			; set A2M2 bit
	rcall 	write_I2C
	ldi		R22, 128			; set A2M3 bit
	rcall 	write_I2C
	ldi		R22, 128			; set A2M4 bit
	rcall 	write_I2C
	ldi		R22, 6          	; enable alarm 2 
	rcall 	write_I2C 
	clr		R22					; reset AF
	rcall 	write_I2C
	clr		R22					; disable trickle charger
	rcall 	write_I2C
	rcall   stop_I2C
	rcall	getTime				; load time from DS1339C

	ser		R25					; initialize regs for button debouncing
	mov		shiftH, R25
	mov		shiftM, R25
	ldi		btState, 3			; initial button state: all up
	clr		R27
	
loop:							; MAIN PROGRAM LOOP
	lsr		shiftH				; debouncing the hours setup button
	sbic	PINB, 0				; shift in the button value
	 subi	shiftH, -128		; into its debouncing register

	sbrs	btState, 0			; old state == "up" ?	
	 rjmp	debounce1			; NO - it is "down"

	cpi		shiftH, pressTsch	; check the shift reg against the
	brsh	debounceM			; press threschold: must have 00xxxxxx
	andi	btState, 0xFE		; accept the new state = "down"

	inc   	hrsU           		; update units of hours
	ldi		R25, 10
	cpi   	hrsT, 2
	brne 	PC+2
	ldi		R25, 4
	cp   	R25, hrsU
	brne	uh_end
	clr		hrsU               	; update tens of hours
	inc 	hrsT
	cpi 	hrsT, 3
	brne	uh_end
	clr		hrsT
uh_end:    
	rcall	setTime          	; load updated time into RTC

	rjmp	debounceM

debounce1:						; here old state = "down"
	cpi		shiftH, releaseTsch	; check the shift reg against the
	brlo	debounceM			; release threschold: must be 111111xx
	ori		btState, 1			; set new state = "up"	

debounceM:
	lsr		shiftM				; debouncing the minutes setup button
	sbic	PINB, 1				; shift in the button value
	 subi	shiftM, -128		; into its debouncing register

	sbrs	btState, 1			; old state == "up" ?	
	 rjmp	debounce2			; NO - it is "down"

	cpi		shiftM, pressTsch	; check the shift reg against the
	brsh	debDelay			; press threschold: must have 00xxxxxx
	andi	btState, 0xFD		; accept the new state = "down"

	inc		minU       			; update units of minutes
	cpi 	minU, 10
	brne	um_end
	clr 	minU     			; clear units
	inc		minT   				; update tens
	cpi 	minT, 6
	brne	um_end             
	clr		minT            	; clear tens
um_end:    
	rcall	setTime  			; load updated time into RTC
	rjmp	debDelay

debounce2:						; here old state = "down"
	cpi		shiftM, releaseTsch	; check the shift reg against the
	brlo	debDelay			; release threschold: must be 111111xx
	ori		btState, 2			; set new state = "up"	

debDelay:						; 10 msec debouncing delay
	ldi		R25, HIGH(1250)		; for uC running at 0.5 MHz
	ldi		R24, LOW(1250)
	sbiw	R24, 1
	brne	PC-1

	inc		R27
	cpi		R27, 100			; adjust display brightness
	brne	loop 
	rcall	setBrightness		; every 1000 msec (=1sec)
	clr		R27
	rjmp	loop


;########################### PROCEDURES ###########################
;       ___        
; DATA:    |_______
;       ______    
; SCK :       |___
;
start_I2C:						; I2C start condition
	cbi		DDRA, SDA			; start with SDA=1 and SCL=1
	cbi		PORTA, SDA			
	sbi		PORTA, SCL
	rjmp	PC+1
	rjmp	PC+1
	rjmp	PC+1
    sbi		DDRA, SDA	        ; set dala low
    rjmp	PC+1
    rjmp	PC+1
	rjmp	PC+1
    cbi   	PORTA, SCL	       	; set clock low
	ret
        
;---------------------------------------------------------------------------
;              ___        
; DATA: ______|   
;           ______    
; SCK : ___|
;
stop_I2C:						; I2C stop condition
	sbi		DDRA, SDA			; start with data low
	cbi		PORTA, SDA
	rjmp	PC+1
	rjmp	PC+1
	sbi		PORTA, SCL 	        ; set clock high
	rjmp	PC+1
	rjmp	PC+1
	nop
	cbi		DDRA, SDA	        ; set data high
	ret

;---------------------------------------------------------------------------
write_I2C:						; send a byte in R22 into I2C
	cbi		DDRA, SDA	        ; make sure that DATA=1 and CLK=0 
	cbi   	PORTA, SCL	
	ldi   	R26, 8 				; initialize bit counter

w_loop:  
	rol   	R22					; get the leftmost data bit
	brcs	PC+2				; set SDA line 
	sbi		DDRA, SDA
	brcc	PC+2
	cbi		DDRA, SDA
	rjmp	PC+1				; let the data line stabilize
	rjmp	PC+1
	sbi   	PORTA, SCL	        ; raise clock up
	rjmp    PC+1				; short delay
	rjmp	PC+1	
	nop
	cbi   	PORTA, SCL	        ; ... and low
	dec   	R26
	brne	w_loop		        ; repeat this 8 times

	cbi   	DDRA, SDA	        ; release the data line	
	rjmp    PC+1	     		; let slave respond for ACK
	sbi   	PORTA, SCL	        ; clock up
	rjmp    PC+1				; slave responds OK (hopefully)
	rjmp	PC+1
	nop
	cbi   	PORTA, SCL	        ; clock down
	ret

;---------------------------------------------------------------------------
read_I2C:						; read byte from I2C into R22
	cbi		DDRA, SDA	        ; make sure that DATA=1 and CLK=0 
	cbi   	PORTA, SCL	
	ldi   	R26, 8       		; initialize bit counter

r_loop:
	sbi		PORTA, SCL	        ; clock up
	clc
	sbic	PINA, SDA
	 sec						; C = received bit
	rol		R22					; shift it in	  
	cbi		PORTA, SCL	        ; clock down
	nop
	dec		R26
	brne	r_loop		        ; repeat this 8 times

	or		R12, R12 			; R12 = ACK
	brne 	PC+2
	sbi		DDRA, SDA        	; send ACK signal
	rjmp 	PC+1
	sbi		PORTA, SCL        	; raise clock up
	rjmp  	PC+1				; short delay
	rjmp	PC+1
	nop
	cbi		PORTA, SCL        	; ... and low           
	cbi		DDRA, SDA     		; release the data line
	ret
                
;---------------------------------------------------------------------------
getTime:						; get time from DS1339
	rcall 	start_I2C              
	ldi		R22, 0b11010000 	; slave address/write
	rcall  	write_I2C
	ldi		R22, 1   			; minutes register address
	rcall	write_I2C
	rcall	start_I2C
	ldi		R22, 0b11010001  	; slave address/read
	rcall  	write_I2C

	clr   	R12         		; ACK signal  
	rcall  	read_I2C     		; get minutes and save them
	mov		minU, R22
	andi   	minU, 0x0F			; extract units of minutes 

	swap	R22					; extract tens of minutes
	andi 	R22, 7
	mov		minT, R22
        
	inc   	R12            		; NAK signal
	rcall 	read_I2C           	; get hours and save them
	rcall  	stop_I2C
    mov		hrsU, R22			; extract units of hours
	andi   	hrsU, 0x0F

	swap	R22					; extract tens of hours
	andi   	R22, 3
	mov		hrsT, R22

	rcall 	start_I2C          	; reset AF2 in DS1339C
	ldi		R22, 0b11010000   	; slave address/write
	rcall	write_I2C
    ldi		R22, 0x0F         	; status register
	rcall 	write_I2C
	clr		R22					; clear all flags there
	rcall 	write_I2C
	rcall	stop_I2C

	clr		R25					; clear pin change IF
	out		GIFR, R25			; to prevent unneeded interrupt
	rcall	displayTime			; display time	
	ret

;---------------------------------------------------------------------------
setTime: 						; set time in DS1339
	rcall	start_I2C
	ldi		R22, 0b11010000  	; slave address/write
	rcall 	write_I2C
	ldi  	R22, 1       		; minutes register address
	rcall 	write_I2C
	mov		R22, minT
	swap	R22
	add		R22, minU
	rcall	write_I2C        	; set minutes 
        
	mov		R22, hrsT
	swap	R22
	add		R22, hrsU           
	rcall 	write_I2C			; set hours
	rcall	stop_I2C
	rcall	displayTime
	ret

;---------------------------------------------------------------------------
PCINT0_ISR:	
	in		R22, SREG			; save status reg
	push	R22					
	rcall	getTime
	push	R22					; restore it
	out		SREG, R22
	reti	

;---------------------------------------------------------------------------
setBrightness:					; set PWM duty cycle 
	ldi		R25, 0x21			; enter ADC noise reduction
	out		MCUCR, R25			; sleep mode
	sleep
	in		R25, ADCH			; get ADC conversion result

	lsr		R25					
	brne	PC+2				; R25 it must be between 1 and 127
	 ldi	R25, 1				; if R25=0 set R25=1
	sbrc	R25, 7				; if R25>127, set R25=127
	 ldi	R25, 127
	com		R25
	clr		R24
	out		OCR1AH, R24			
	out		OCR0A, R25			; duty cycle of the first channel
	out		OCR1AL, R25			; duty cycle of the first channel
	com		R25					; complementary value
	out		OCR1BH, R24			
	out		OCR0B, R25			; duty cycle of the second channel
	out		OCR1BL, R25			; duty cycle of the second channel
	ret

;---------------------------------------------------------------------------
encode:							; encode the value in R0
	clr		ZH					; by using 7-seg encoding table offset
	add		ZL, ZL				; passed in ZL
	add		ZL, R0
	lpm	
	com		R0					; return result in R0
	ret

;---------------------------------------------------------------------------
displayTime:
	mov		R0, hrsT			; load digit 1
	or		R0, R0				; turn off leading 0
	brne	PC+3				; or proceed if it is not
	com		R0
	rjmp	PC+3
	ldi		ZL, code7seg		; use this table to
	rcall	encode				; encode it into 7-seg code
	rcall	write_AHC595		; send byte in R0 to display

	mov		R0, hrsU			; load digit 2
	ldi		ZL, code7seg2		; use this table to
	rcall	encode				; encode it into 7-seg code
	lsl		R0					; turn on decimal point
	lsr		R0					; by clearing bit 7
	rcall	write_AHC595		; send byte in R0 to display

	mov		R0, minT			; load digit 3
	ldi		ZL, code7seg		; use this table to
	rcall	encode				; encode it into 7-seg code
	lsl		R0					; turn on decimal point
	lsr		R0
	rcall	write_AHC595		; send byte in R0 to display

	mov		R0, minU			; load digit 4
	ldi		ZL, code7seg		; use this table to
	rcall	encode				; encode it into 7-seg code
	rcall	write_AHC595		; send byte in R0 to display

	sbi		PORTA, ENA			; latch in received data 
	rjmp	PC+1
	rjmp	PC+1
	cbi		PORTA, ENA	
	ret

write_AHC595:					; write R0 to 74AHC595
	ldi		R26, 8				; bit counter
	cbi		PORTA, SCL

write_loop:
	ror		R0					; cycle-shift the data 
	brcs	PC+2				; set SDA line 
	 sbi	DDRA, SDA
	brcc	PC+2
	 cbi	DDRA, SDA
	rjmp	PC+1

	sbi		PORTA, SCL			; set clock high 
	rjmp	PC+1
	rjmp	PC+1
	cbi		PORTA, SCL			; ... and low

	dec		R26
	brne	write_loop
	ret
