#include "msp430.h"                     ; the actual CPU model is set in
                                        ; the project options in IAR

pressTsch       EQU   	0x3F+1    	; threscholds for button press and
releaseTsch     EQU   	0xC0          	;  release (for the shift register)
TA_deb		EQU	120		; setup for 10 msec debouncing period
lightTsch	EQU	16		; light/dark ADC threschold
lightPeri	EQU	5		; light checking period in minutes
darkPeri	EQU	1		; dark checking period in minutes
hrsB		EQU	BIT0		; hours setup button on P1 
power		EQU	BIT1		; mains sensor on P1
minB		EQU	BIT4		; minutes setup button on P1
RTCint		EQU	BIT5		; RTC interrupt pin on P1
dots		EQU	BIT6		; colon blink request
loadTime	EQU	BIT7		; load time into LCD driver request

RTCaddr		EQU	0xD0		; DS3231  slave address
LCDaddr		EQU	0x70		; PCF8562 slave address

        RSEG    CSTACK                  ; STACK SEGMENT
        RSEG    DATA16_N                ; DATA SEGMENT    
i2cb:	DS	8			; I2C I/O buffer
i2csta:	DS	2			; I2C FSM state	
TXcnt:	DS	1			; I2C transmit bytes counter
RXcnt:	DS	1			; I2C receive bytes counter
flags:	DS	1			; button, RTC and other flags
shiftH: DS      1                       ; shift registers 
shiftM: DS      1			;   for buttons debouncing
btState:DS      1                       ; buttons state reg: 1=up, 0=down
light:	DS	1			; light period checking
dark:	DS	1			; dark indicator: 0=bright, 1=dark

	RSEG    CODE                    ; CODE SEGMENT
reset:  mov.w	#SFE(CSTACK), SP        ; set up stack
        mov.w	#WDTPW+WDTHOLD, &WDTCTL ; stop watchdog timer

        ; I/O ports setup		
	mov.b	#0x08, &P1DIR 		; input on button pins
        mov.b   #hrsB+minB+RTCint+power, &P1REN ; pull-up on button pins	
	mov.b	#hrsB+minB+RTCint+power, &P1OUT
        mov.b   #BIT6+BIT7, &P1SEL	; I2C interface pins P1
	mov.b	#hrsB+minB+RTCint, &P1IES ; high-to-low interrupt edge
	mov.b	#hrsB+minB+RTCint, &P1IE  ; enable RTC interrupt
	clr.b	&P1IFG			; clear P1 interrupt flag
	
        clr.b   &P2OUT   		; init port P2  
        clr.b   &P2DIR      	
	clr.b	&P2SEL			; I/O on XTAL pins

        ; clock setup
	mov.b   &CALDCO_8MHZ, &DCOCTL	
	mov.b   &CALBC1_8MHZ, &BCSCTL1 	; set 8 MHz clock 	
	mov.b   #DIVS_3, &BCSCTL2  	; SMCLK=MCLK/8	
	mov.b	#LFXT1S_2, &BCSCTL3	; set VLOCLK as ACLK

	; ADC setup
	mov.w	#INCH_2+ADC10SSEL_3, &ADC10CTL1	; set channel A2	
	mov.w   #SREF_1+ADC10SR+ADC10IE, &ADC10CTL0 ; 1.5V reference
	mov.b	#BIT2, &ADC10AE0	; analog operation on P1.2 

        ; I2C setup for USI        
	mov.b	#USIPE7+USIPE6+USIMST+USISWRST, &USICTL0 ; enable I2C pins 
	mov.b	#USII2C, &USICTL1 	; switch USI into I2C mode
	mov.b	#USIDIV_3+USISSEL_2+USICKPL, &USICKCTL ; 125 KHz I2C clock

        ; Timer_A0 setup for button debouncing
        mov.w   #TASSEL_1, &TACTL 	; ACLK
	mov.w	#0x3FFF, &TACCR0	; upper limit in the UP mode
        mov.w   #CCIS_2, &TACCTL1	; compare channel 1
        mov.w   #CCIS_2, &TACCTL2	; compare channel 2

	call	#InitSystem		; set up various system vars

loop:   				; MAIN LOOP
	bis.w   #LPM3+GIE, SR           ; enter LPM3 sleep mode
        nop					

checkH:	bit.b	#hrsB, &flags		; hours button is pressed?
	jz	checkM			; NO - check minutes button
	clrc
	dadd.w	#0x0100, R4		; update hours mod 24
	cmp.w	#0x2400, R4
	jlo	$+6			; jump over the next instruction
	bic.w	#0xFF00, R4		; reset hours to 00
	call	#SetTime		; set new time in RTC and display
	bic.b	#hrsB, &flags		; clear processed event

checkM:	bit.b	#minB, &flags		; minutes button is pressed?
	jz	RTC			; NO - check RTC event
	clrc
	dadd.w	#0x0001, R4		; update minutes mod 60
	cmp.b	#0x60, R4
	jlo	$+6
	bic.w	#0x00FF, R4		; reset minutes
	call	#SetTime		; set new time in RTC and display
	bic.b	#minB, &flags		; clear processed event

RTC:	bit.b	#RTCint, &flags		; RTC minutes change event?
	jz	colBlink		; NO - keep checking
	call	#CheckLight		; YES - turn LCD ON/OFF if bright/dark	
rtc1:	call	#GetTime		; read time from RTC and display it
	bic.b	#RTCint, &flags		; clear processed event
	bis.b	#loadTime, &flags	; time display request
	
colBlink:
	bit.b	#dots, &flags		; colon blink event?
	jz	more			; NO - keep checking
	bic.b	#dots, &flags		; YES - clear event
	bit.b	#loadTime, &flags	; load new time?
	jnz	cb1		
	call	#Blink			; NO  - just reload the middle digits
	jmp	more
cb1:	call	#DisplayTime		; YES - load new time
	bic.b	#loadTime, &flags	; clear time reload request

more:	bit.b	#RTCint, &P1IN		; misset RTC event?
	jz	rtc1			; YES - read time and clear flag
mo1:	bit.b	#hrsB+minB+RTCint+dots, &flags ; some unprocessed flags?
	jz	loop			; NO - wait for new event
	jmp	checkH			; YES - check them again
	
;PROCEDURES-----------------------------------------------------------------
InitSystem:
	clr.w	&i2csta			; clear I2C state  
	clr.b	&dark			; make sure that display is ON
	clr.w	R5
	dec.w	R5			; power-up delay
	jnz	$-2
	dec.w	R5	
	jnz	$-2
	
	mov.b	#lightPeri, &light	; init light checking counter
	mov.b	#0xFF, &shiftH		; init button shift registers
	mov.b	#0xFF, &shiftM
	mov.b	#0xFF, &btState

	mov.w	#0xE070, &i2cb		; select LCD driver device
	mov.w	#0x00C9, &i2cb+2	; set static mode, enable display
	clr.w	&i2cb+4			; clear display memory
	clr.w	&i2cb+6
	mov.b	#8, &TXcnt
	clr.b	&RXcnt
	call	#I2C			; send data to LCD driver

	mov.w	#0x0BD0, &i2cb		; enable RTC alarm every minute
	mov.w	#0x8080, &i2cb+2	; disable 32KHz square wave
	mov.w	#0x1E80, &i2cb+4	; clear alarm flag
	mov.b	#0x00,   &i2cb+6
	mov.b	#7, &TXcnt
	clr.b	&RXcnt
	call	#I2C			; send data to RTC

	call	#GetTime		; read time from RTC
	clr.b	R14			; colon setup
	mov.b	#loadTime, &flags	; request time loading	
	bis.w	#TAIE, &TACTL		; enable blink interrupt 
	bis.w	#MC_1, &TACTL		; start timer in UP mode
	ret

;---------------------------------------------------------------------------
GetTime:				; read HH:MM from RTC into R4
	mov.w	#0x0FD0, &i2cb		; clear RTC alarm flag
	mov.b	#0x00,   &i2cb+2
	mov.b	#3, &TXcnt		; write 3 bytes
	clr.b	&RXcnt
	call	#I2C

	mov.w	#0x01D0, &i2cb		; read 2 bytes from RTC
	mov.b	#0xD1,   &i2cb+2
	mov.b	#2, &TXcnt		; # of bytes to send before restart
	mov.b	#2, &RXcnt		; # of bytes to receive
	call	#I2C
	mov.w	&i2cb+2, R4		; read minutes
	and.w	#0xFF00, R4		; minutes = MSB
	clr.b	&i2cb+5
	add.w	&i2cb+4, R4		; add hours as LSB
	swpb	R4			; swap the bytes order
	ret
	
SetTime:				; write time in R4 to RTC
	mov.w	#0x01D0, &i2cb		
	mov.w	R4,      &i2cb+2
	mov.b	#4, &TXcnt
	clr.b	&RXcnt
	call	#I2C
	
DisplayTime:				; display time in R4
	mov.w	#0x0070, &i2cb		; set LCD driver RAM pointer
	mov.w	R4, R5			; copy time to R5 for processing
	rlc.w	R5
	rlc.w	R5
	rlc.w	R5
	rlc.w	R5
	rlc.w	R5
	and.b	#0x0F, R5		; R5 = tens of hours
	jz	dt1			; do not display leading 0
	mov.b	Table7seg(R5), R5	; get 7-seg code
dt1:	mov.b	R5, &i2cb+5		; load tens of hours data
	
	mov.w	R4, R5
	swpb	R5
	and.b	#0x0F, R5		; R5 = units of hours
	mov.b	Table7seg(R5), R5	; get 7-seg code
	mov.b	R5, R15			; save digit in R15
	swpb	R15	
	bis.b	R14, R5			; add colon bit	
	mov.b	R5, &i2cb+4		; load tens of hours data
	
	mov.b	R4, R5
	rlc.b	R5
	rlc.b	R5
	rlc.b	R5
	rlc.b	R5
	rlc.b	R5
	and.b	#0x0F, R5		; R5 = tens of hours
	mov.b	Table7seg(R5), R5	; get 7-seg code
	add.w	R5, R15			; save digit in R15	
	bis.b	R14, R5			; add colon bit
	mov.b	R5, &i2cb+3		; load units of minutes data

	mov.w	R4, R5
	and.b	#0x0F, R5		; R5 = units of hours
	mov.b	Table7seg(R5), R5	; get 7-seg code
	mov.b	R5, &i2cb+2		; load tens of hours data

	mov.b	#6, &TXcnt		; load data into LCD driver
	clr.b	&RXcnt
	call	#I2C
	ret

Blink:	
	mov.w	#0x0870, &i2cb		; set LCD driver RAM pointer
	mov.w	R15, &i2cb+2
	bis.w	R14, &i2cb+2		; add colon
	mov.b	#4, &TXcnt		; load data into LCD driver
	clr.b	&RXcnt
	call	#I2C
	ret	
	
Table7seg:;  	abcdefgpabcdefgp	; 7-segment display data
	DW	0110000011111100b	; codes for 1 and 0
	DW	1111001011011010b	; codes for 3 and 2
	DW	1011011001100110b	; codes for 5 and 4
	DW	1110000010111110b	; codes for 7 and 6
	DW	1111011011111110b	; codes for 9 and 8
	DW	0011111011101110b	; codes for B and A
	DW	0111101010011100b	; codes for d and C
	DW	1000111010011110b	; codes for F and E
	
;---------------------------------------------------------------------------
CheckLight:				; check light and set up variable dark
	dec.b	&light			; time to check ambient light?
	jz	cl0			; YES - do it
	ret				; NO  - exit

cl0:	bit.b	#power, &P1IN		; is mains power on?
	jz	cl1			; YES - pretend that is it light now
	bis.w   #REFON+ADC10ON, &ADC10CTL0 ; NO  - proceed with light checking
	mov.w	#80, R5			; 30 mksec delay to set up VREF
	dec.w	R5
	jnz	$-2

	bis.w   #ENC+ADC10SC, &ADC10CTL0; start temp sampling/conversion
        bis.w   #LPM0+GIE, SR          	; LPM0, ADC10_ISR will force exit
	nop
	bic.w	#ENC, &ADC10CTL0	; shut down ADC completely
	bic.w	#ADC10ON, &ADC10CTL0
	bic.w	#REFON, &ADC10CTL0	; turn off reference
	
	cmp.w	#lightTsch, &ADC10MEM	; is it dark now?
	jlo	cl3			; YES - proceed
cl1:	mov.b	#lightPeri, &light	; NO  - reset counter to light period
	tst.b	&dark			; was it dark before?
	jz	cl2			; NO  - exit
	mov.w	#0x4970, &i2cb		; YES - enable the LCD
	mov.b	#2, &TXcnt
	clr.b	&RXcnt
	call	#I2C			; send data to LCD driver
	bis.b	#loadTime, &flags	; request time loading
	bic.w	#TAIFG, &TACTL		; clear blink interrupt flag	
	bis.w	#TAIE, &TACTL		; enable blink interrupt 
	clr.b	&dark			; remember current light condition
cl2:	ret
	
cl3:	mov.b	#darkPeri, &light	; reset counter to dark period
	tst.b	&dark			; was it dark before?
	jnz	cl4			; YES - exit
	bic.w	#TAIE, &TACTL		; NO  - disable blink interrupt 
	bic.b	#dots, &flags		; prevent display update and blink
	mov.w	#0x4170, &i2cb		; disable the LCD
	mov.b	#2, &TXcnt
	clr.b	&RXcnt
	call	#I2C			; send data to LCD driver
	mov.b	#1, &dark		; remember current light condition	
cl4: 	ret

;---------------------------------------------------------------------------
I2C:					; send i2cb data via I2C interface
	mov.w	#i2cb, R11		; set up data pointer
	bic.b	#USISWRST, &USICTL0 	; enable I2C module	
	bis.b	#USIIFG+USIIE, &USICTL1	; force RTC ISR
	bis.w   #LPM0+GIE, SR 		; wait for completion	
	nop
	bis.b	#USISWRST, &USICTL0 	; disable I2C module
	ret
	
;INTERRUPT SERVICE ROUTINES-------------------------------------------------
I2C_ISR:				; I2C master transmit ISR
        add     &i2csta, PC 		; add offset to PC
	jmp	sendAddr		; State0: send START and slave address
	jmp	sendData		; State2: send data to slave
	jmp	prep2RX			; State4: prepare to receive data
	jmp	receiveData		; State6: receive data from slave
	
sendAddr:				; State0: send START and slave address
	clr.b	&USISRL			; set SDA=0 for START condition
	bis.b	#USIOE+USIGE, &USICTL0	; enable data output on SDA and latch
	bic.b	#USIGE, &USICTL0	; disable output latch
	mov.b	@R11+, &USISRL		; get the slave address 
	mov.b	#4, &i2csta		; transfer to receiving state
	bit.b	#BIT0, &USISRL
	jnz	$+6
	mov.b	#2, &i2csta		; transfer to sending state
	call	#delay5us
	mov.b	#8, &USICNT		; set counter to transmit 8 bits
	reti

sendData:				; State2: check ACK and send data byte
	bic.b	#USIOE, &USICTL0	; disable output, release SDA
	mov.b	#1, &USICNT		; set counter to transmit 1 bit
	bit.b	#USIIFG, &USICTL1	; wait for SCL=1
	jz	$-4
	bit.b	#BIT0, &USISRL		; was previous transmission ACKed by slave? 
	jnz	sendStop		; NO - send STOP and exit
	
	dec.b	&TXcnt			; all bytes sent?
	jnz	sd1			; NO - keep transmitting
	tst.b	&RXcnt			; pending bytes to receive?
	jz	sendStop		; NO - send STOP and exit
	mov.b	#0xFF, &USISRL		; YES - prepare to generate RESTART	
	bis.b	#USIOE, &USICTL0	; enable output	
	mov.b	#1, &USICNT		; set counter to transmit 1 bit
	bit.b	#USIIFG, &USICTL1	; wait for SCL=1
	jz	$-4	
	call	#delay5us
	jmp	sendAddr		; generate restart
	
sd1:	mov.b	@R11+, &USISRL		; send data pointed by R11
	bis.b	#USIOE, &USICTL0	; enable data output on SDA 
	mov.b	#8, &USICNT		; set counter to transmit 8 bits
	reti

prep2RX:				; State4: get slave address ACK, switch to RX mode
	call	#delay5us
	bic.b	#USIOE, &USICTL0	; disable output, release SDA
	mov.b	#1, &USICNT		; set counter to transmit 1 bit
	bit.b	#USIIFG, &USICTL1	; wait for SCL=1
	jz	$-4
	bit.b	#BIT0, &USISRL		; was address ACKed by slave? 
	jnz	sendStop		; NO - send STOP and exit
	mov.b	#6, &i2csta		; transfer to the receiving state
	jmp	rd1			; request the first byte from slave, if any

receiveData:				; State6: send ACK and receive data byte
	mov.b	&USISRL, 0(R11)		; get received byte
	inc.w	R11			; adjust buffer pointer
	mov.b	#0xFE, &USISRL		; prepare to send NACK
	add.b	&RXcnt, &USISRL		; turn NACK into ACK, if needed
	bis.b	#USIOE, &USICTL0	; enable data output on SDA
	mov.b	#1, &USICNT		; set counter to transmit 1 bit
	bit.b	#USIIFG, &USICTL1	; wait for SCL=1
	jz	$-4
	dec.b	&RXcnt			; all bytes received?
	jz	sendStop		; YES - send STOP and exit

rd1:	bic.b	#USIOE, &USICTL0	; release the SDA line
	mov.b	#8, &USICNT		; set counter to receive 8 bits
	reti

sendStop:				; generate STOP condition	
	clr.b	&USISRL			; prepare to pull SDA low
	bis.b	#USIOE, &USICTL0	; enable data output on SDA
	mov.b	#1, &USICNT		; set counter to transmit 1 bit
	bit.b	#USIIFG, &USICTL1	; wait for SCL=1
	jz	$-4
	call	#delay5us
	mov.b	#0xFF, &USISRL		; set SDA=1
	bis.b	#USIGE, &USICTL0	; enable transparent latch
	bic.b	#USIGE+USIOE, &USICTL0	; release SDA (SCK=1 now)
	clr.b	&i2csta			; pass to init state
	bic.w   #LPM0, 0(SP)       	; wake-up CPU
	bic.b	#USIIFG+USIIE, &USICTL1	; clear interrupt flag
	reti

delay5us:				; delay around the START,
	push.w	R5			; STOP, and RESTART conditions
	mov.b	#10, R5			; required by the I2C specs
	dec.w	R5
	jnz	$-2
	pop.w	R5
	ret

;---------------------------------------------------------------------------
TA0_ISR:				; buttons debouncing routine
        add.w   &TAIV, PC              	; add offset to jump table 
        reti                            ; 0: no interrupt 
        jmp     TCCR1                 	; 2: TACCR1
        jmp     TCCR2                 	; 4: TACCR2 
        reti                            ; 6: reserved 
        reti                            ; 8: reserved
					
Colon:					; 10: TAIFG
      	xor.w	#0x0101, R14		; invert colon bit
	bis.b	#dots, &flags		; register event
	bic.w   #LPM3, 0(SP)            ; wake-up CPU	
	reti
      
TCCR1:	clrc                            
        rrc.b   &shiftH                 ; debouncing the "H" button          
        bit.b   #hrsB, &P1IN            ; shift in the button value
        jz      $+8                     ; jump over the next instruction
        bis.b   #BIT7, &shiftH          ; the shift register is updated
        
        bit.b   #hrsB, &btState         ; old state == "up" ?
        jz      ccr1_1                  ; NO - it is "down"

        cmp.b   #pressTsch, &shiftH     ; has old "up" state changed?
        jhs     ccr1_2                  ; NO - setup another CCR event
        bic.b   #hrsB, &btState         ; YES - set new state="down"
        bis.b   #hrsB, &flags    	; set event    
        bic.w   #LPM3, 0(SP)            ; wake-up CPU
        jmp     ccr1_2                  ; setup new event time

ccr1_1: cmp.b   #releaseTsch, &shiftH   ; has old "down" state changed?
        jlo     ccr1_2                  ; NO - keep waiting
        bis.b   #hrsB, &btState         ; YES - set new state="up"
        bic.w   #CCIE, &TACCTL1		; disable CCR1 compare interrupt
        bic.b   #hrsB, &P1IFG           ; clear P1 interrupt flag
        bis.b   #hrsB, &P1IE            ; enable button interrupt
        reti

ccr1_2: push.w  R7                      ; save used reg
        mov.w   &TAR, R7        	; compute next CCR0 event time
        add.w   #TA_deb, R7
	bic.w	#BITF+BITE, R7		; *************
        mov.w   R7, &TACCR1
        pop.w   R7                      ; restore used reg
        reti
        
TCCR2: 	clrc                            
        rrc.b   &shiftM                 ; debouncing the "M" button          
        bit.b   #minB, &P1IN  		; shift in the button value
        jz      $+8          		; jump over the next instruction
        bis.b   #BIT7, &shiftM 		; the shift register is updated
        
        bit.b   #minB, &btState  	; old state == "up" ?
        jz      ccr2_1                  ; NO - it is "down"

        cmp.b   #pressTsch, &shiftM     ; has old "up" state changed?
        jhs     ccr2_2                  ; NO - setup another CCR event
        bic.b   #minB, &btState 	; YES - set new state="down"
        bis.b   #minB, &flags 		; set event    
        bic.w   #LPM3, 0(SP)            ; wake-up CPU
        jmp     ccr2_2                  ; setup new event time

ccr2_1: cmp.b   #releaseTsch, &shiftM   ; has old "down" state changed?
        jlo     ccr2_2                  ; NO - keep waiting
        bis.b   #minB, &btState  	; YES - set new state="up"
        bic.w   #CCIE, &TACCTL2 	; disable CCR1 compare interrupt
        bic.b   #minB, &P1IFG  		; clear P1 interrupt flag
        bis.b   #minB, &P1IE   		; enable button interrupt
        reti

ccr2_2: push.w  R7                      ; save used reg
        mov.w   &TAR, R7  		; compute next CCR0 event time
        add.w   #TA_deb, R7
	bic.w	#BITF+BITE, R7		; *************
        mov.w   R7, &TACCR2
        pop.w   R7                      ; restore used reg
        reti     
	
;---------------------------------------------------------------------------
P1_ISR: bit.b   #BIT5, &P1IFG         	; RTC interrupt?
	jnz	RTC_ISR			; YES - proceed with its ISR
	
	push.w  R7			; save used reg
	mov.w   &TAR, R7   		; compute next CC event time
        add.w   #TA_deb, R7
	bic.w	#BITF+BITE, R7		; *************
	bit.b   #BIT4, &P1IFG         	; determine the button
	jnz	p1M			; <0:"H", 0:"M"

p1H:	mov.w   R7, &TACCR1           	; debouncing the "H" button
        mov.w   #CCIS_2+CCIE, &TACCTL1 	; enable compare Timer_A interr.
        bic.b   #hrsB, &P1IE            ; disable port interrupt
	bic.b	#hrsB, &P1IFG		; clear interrupt flag
	pop.w   R7
        reti

p1M:  	mov.w   R7, &TACCR2   		; debouncing the "M" button
        mov.w   #CCIS_2+CCIE, &TACCTL2	; enable compare Timer_A interr.
        bic.b   #minB, &P1IE       	; disable port interrupt
	bic.b	#minB, &P1IFG		; clear interrupt flag
	pop.w   R7
        reti

RTC_ISR:				; RTC ISR
	bis.b	#RTCint, &flags		; declare the event
	bic.w   #LPM3, 0(SP)            ; wake-up CPU
	bic.b	#RTCint, &P1IFG		; clear interrupt flag
	reti

;--------------------------------------------------------------------------
ADC10_ISR:				; end of ADC conversion ISR
	bic.w   #LPM0, 0(SP) 		; wake-up CPU
	reti    

;---------------------------------------------------------------------------
        COMMON  INTVEC                  ; MSP430 interrupt vectors

        ORG     RESET_VECTOR
        DW      reset                   ; POR, ext. Reset, Watchdog
 
        ORG     TIMER0_A1_VECTOR        ; Timer_A1 vector
        DW      TA0_ISR
  
        ORG     USI_VECTOR          	; USI vector
        DW      I2C_ISR        

	ORG     ADC10_VECTOR            ; ADC10 vector
	DW      ADC10_ISR

	ORG	PORT1_VECTOR		; P1 vector
	DW	P1_ISR

        END
