#include "msp430.h"                     ; #define controlled include file

SCL           EQU   2                   ; I2C bus setup
SDA           EQU   1
pressTsch     EQU   03Fh + 1            ; threscholds for button press and
releaseTsch   EQU   0FCh                ;  release (for the shift register)

        RSEG    CSTACK                  ; stack segment
        RSEG    DATA16_N                ; data segment
btState DS      1                       ; buttons state
shiftH  DS      1                       ; shift reg for debouncing
shiftM  DS      1                       ; same for the set minutes button  
seconds DS      1                       ; counter for seconds  
hrsT    DS      1                       ; storage for hours   (tens)
hrsU    DS      1                       ;   - " -             (units)
minT    DS      1                       ; storage for minutes (tens)
minU    DS      1                       ;   - " -             (units)
            
        RSEG    CODE                    ; code segment
main    mov     #SFE(CSTACK), SP        ; set up stack
        mov.w   #WDTPW+WDTHOLD, &WDTCTL ; stop watch dog timer
        bic.b   #XCAP_3, &BCSCTL3       ; set up 6 pF crystal load caps
        bic.w   #OSCOFF, SR             ; enable crystal oscillator
        mov.b   #0FFh, &P1DIR           ; enable P1<7:0> for output
        mov.b   #01Ah, &P2DIR           ; enable input on P2.5, P2.2, P2.0
        mov.b   #BIT5+BIT2, &P2REN      ; enable pull up/down resistors
        bis.b   #BIT5+BIT2, &P2OUT      ; select pull-up resistors 

        clr.b   &P1OUT
        bic.b   #BIT3+BIT4, &P2OUT
        bis.b   #BIT3+BIT4, &P2OUT      ; turn off all digits
        bic.b   #BIT3+BIT4, &P2OUT
        
        clr.b   R8                      ; colon state
        mov.b   #1, R14                 ; active digit code (1,2,4,8)
        mov.b   R14, R13                ; active digit index (1 - 4)

        mov.w   #16, R5
        mov.w   R5, &TACCR1             ; initial brightness setup
        mov.w   #CCIE, &TACCTL1         ; enable CCR1 interrupt
        mov.w   #127, &TACCR0           ; set up Timer_A upper counting limit
        mov.w   #TASSEL_1+MC_1+TAIE, &TACTL  ; ACLK, up mode, interrupt
  
        mov.b   #0FFh, &shiftH          ; initialize shift regs 
        mov.b   #0FFh, &shiftM          ;  for buttons debouncing
        mov.b   #3, &btState            ; initial buttons state "all up"

init_RTC
        bit.b   #SDA, &P2IN             ; initialize the I2C interface 
        jnz     I2C_OK                  ; by toggling clock line
        bis.b   #SCL, &P2OUT            ; until the data line gets high
        jmp     $+2
        bic.b   #SCL, &P2OUT
        jmp     init_RTC
        
I2C_OK  call    #start_I2C              ; configure RTC
        mov.b   #11010000b, R10         ; slave address/write
        call    #write_I2C
        mov.b   #0Eh, R10               ; config reg pointer
        call    #write_I2C
        mov.b   #4, R10                 ; disable alarms and frequency
        call    #write_I2C              ; output
        call    #stop_I2C

loop    clr.w   R15                     ; clear time counter of 1-sec intervals
        call    #start_I2C              ; get time from RTC
        mov.b   #11010000b, R10         ; slave address/write
        call    #write_I2C
        clr.b   R10                     ; seconds register address
        call    #write_I2C
        call    #start_I2C
        mov.b   #11010001b, R10         ; slave address/read
        call    #write_I2C

        clr.b   R12                     ; ACK signal
        call    #read_I2C               ; get seconds in R10
        mov.b   R10, R15                ; convert seconds from BCD
        and.b   #0xF0, R15              ; into binary representation in R15
        rla     R15
        rla     R15                     ; since R15 counts the # of 1/256sec
        add.w   R10, R15                ; intervals, it must be multiplied
        rra     R15                     ; by 256 afterwards to be consistent
        rra     R15                     ; with the rest of the code
        rra     R15
        bic     #BIT0, R15
        and.b   #0x0F, R10
        add     R10, R15                ; conversion is completed here
        dec.b   R15                     ; make sure that RTC and CPU are in sync
        jge     $+6                     ; by decrementing R15 (mod 60)
        mov.b   #59, R15
        swpb    R15                     ; R15 *= 256
      
        call    #read_I2C               ; get minutes and save them
        mov.b   R10, &minU
        and.b   #0Fh, &minU
        rra.b   R10
        rra.b   R10
        rra.b   R10
        rra.b   R10
        and.b   #7, R10
        mov.b   R10, &minT
        
        inc.b   R12                     ; NAK signal
        call    #read_I2C               ; get hours and save them
        call    #stop_I2C
        mov.b   R10, &hrsU
        and.b   #0Fh, &hrsU
        rra.b   R10
        rra.b   R10
        rra.b   R10
        rra.b   R10
        and.b   #3, R10
        jne     $+6
         mov.b  #12, R10                ; do not display a leading 0
        mov.b   R10, &hrsT 

get_ADC call    #start_I2C              ; adjust LED brightness
        mov.b   #10100011b, R10         ; request ADC conversion
        call    #write_I2C
        clr.b   R12                     ; ACK signal
        call    #read_I2C
        mov.b   R10, R5                 ; get high-order byte
        swpb    R5
        inc.b   R12                     ; NACK signal
        call    #read_I2C
        call    #stop_I2C
        add.w   R10, R5                 ; add lower-order byte
        rra.w   R5                      ; format ADC core in R5 to load into
        rra.w   R5                      ; PWM brightness control register
        rra.w   R5                      ; TACCR1 = ADC/32 + 4
        rra.w   R5
        rra.w   R5
        add     #4, R5
        cmp.w   #128, R5                ; make sure that PWM duty cycle code
        jlo     $+6                     ; does not exceed 127 which corresponds
        mov.b   #127, R5                ; to 100% brightness
        mov.w   R5, &TACCR1             ; load brightness control register

wait_l  bis.w   #LPM2+GIE, SR           ; enter LPM2, interrupts enabled  

        bit.b   #BIT0, R15              ; debounce the buttons every other
        jne     checkSecond             ; interrupt, i.e. once in 8 msec
        
debounceH     
        clrc
        rrc.b   &shiftH                 ; debouncing the hours setup button          
        bit.b   #BIT5, &P2IN            ; shift in the button value
        jz      $+8
         bis.b  #BIT7, &shiftH
        
        bit.b   #BIT1, &btState         ; old state == "up" ?
        jz      debounce1               ; NO - it is "down"
         cmp.b  #pressTsch, &shiftH
         jhs    debounceM
          bic.b #BIT1, &btState         ; set new state = "down"
    
          inc.b   &hrsU                 ; update units of hours
          mov.b   #10, R10
          cmp.b   #2, &hrsT
           jne     $+4
          mov.b  #4, R10
          cmp.b   R10, &hrsU
           jne     uh_end
      
          clr.b &hrsU                   ; update tens
          inc.b &hrsT
          cmp.b #3, &hrsT
           jlo  uh_end
          jeq   exit2
          mov.b #1, &hrsT          
          jmp   uh_end
exit2     mov.b #12, &hrsT              ; empty digit
uh_end    call  #setTime                ; load updated time into RTC
          jmp   debounceM     
debounce1        
        cmp.b   #releaseTsch, &shiftH 
        jlo     debounceM
         bis.b  #BIT1, &btState         ; set new state = "up"
        
debounceM 
        clrc
        rrc.b   &shiftM                 ; debouncing the minutes setup button
        bit.b   #BIT2, &P2IN
        jz      $+8
         bis.b  #BIT7, &shiftM
        
        bit.b   #BIT0, &btState         ; old state == "up" ?
        jz      debounce2               ; NO - it is "down"
         cmp.b  #pressTsch, &shiftM
         jhs    checkSecond
          bic.b #BIT0, &btState         ; set new state = "down"
          inc.b &minU                   ; update units of minutes
          cmp.b #10, &minU
           jne  um_end
          clr.b &minU                   ; clear units
          inc.b &minT                   ; update tens
          cmp.b #6, &minT
           jne  um_end             
          clr.b &minT                   ; clear tens
um_end    call  #setTime                ; load updated time into RTC
          jmp   checkSecond 
debounce2        
        cmp.b   #releaseTsch, &shiftM 
        jlo     checkSecond
         bis.b  #BIT0, &btState         ; set new state = "up"
        
checkSecond        
        inc.w   R15                     ; counter of interrupt intervals
        bit.b   #0FFh, R15              ; is it a 1-sec time stamp?
        jne     wait_l                  ; NO - wait

        xor.b   #BIT7, R8               ; YES - flip colon every second
       
        cmp.w   #15360, R15             ; is it a 1 minute time stamp? (15360)
        jeq     loop                    ; YES -get time and brightness

        cmp.w   #3840, R15              ; NO - check for a 15-sec interval
        jeq     get_ADC                 ; adjust brightness
        cmp.w   #7680, R15              ; check for a 30-sec interval
        jeq     get_ADC
        cmp.w   #11520, R15             ; check for a 45-sec interval
        jeq     get_ADC
        jmp     wait_l                  ; no adjustment is needed - wait

;///////////////////// ISRs ////////////////////////////////////////////////
TA_ISR  add.w   &TAIV, PC               ; Add Timer_A offset vector
        reti                            ; CCR0 - no source
        jmp     CCR1_ISR                ; CCR1
        reti                            ; CCR2
        reti                            ; reserved
        reti                            ; reserved

        rla.b   R14                     ; update active digit code
        inc.b   R13                     ; update digit index
        bit.b   #BIT4, R14
        jz      $+6                     ; the upper limit is not reached yet
         mov.b  #1, R14                 ; reset the active digit # to 1
         mov.b  R14, R13
         
        mov.b   R14, &P1OUT             ; set new active digit
        bis.b   #BIT3, &P2OUT           ; latch it in
        bic.b   #BIT3, &P2OUT
        
        mov.b   seconds(R13), R10       ; get digit
        mov.b   code7(R10), &P1OUT      ; move new data to output reg
        bit.b   #BIT1, R13              ; colon processing for digit codes
        jz      $+6                     ; 2 and 3
        add.b   R8, &P1OUT

        bis.b   #BIT4, &P2OUT           ; latch it in
        bic.b   #BIT4, &P2OUT
        bic.w   #LPM2, 0(SP)            ; return from sleep
        reti

;---------------------------------------------------------------------------
CCR1_ISR
        clr.b   &P1OUT                  ; turn off all digits
        bis.b   #BIT4, &P2OUT
        bic.b   #BIT4, &P2OUT
        bic.w   #CCIFG, &TACCTL1        ; clear interrupt flag   
        reti
        
;///////////////////// PROCEDURES /////////////////////////////////////////
;       ___        
; DATA:    |_______
;       ______    
; SCK :       |___
;
start_I2C				; I2C start condition
        bic.b   #SDA, &P2OUT
        bic.b   #SDA, &P2DIR	        ; start with SDA=1 and SCL=1
        bis.b   #SCL, &P2OUT
        bis.b   #SDA, &P2DIR	        ; set dala low
        jmp     $+2
        jmp	$+2
	nop
        bic.b   #SCL, &P2OUT	        ; set clock low
        ret
        
;---------------------------------------------------------------------------
;              ___        
; DATA: ______|   
;           ______    
; SCK : ___|
;
stop_I2C				; I2C stop condition
        bis.b   #SCL, &P2OUT	        ; set clock high
        jmp     $+2
        jmp	$+2
	nop
        bic.b   #SDA, &P2DIR	        ; set data high
        ret

;---------------------------------------------------------------------------
write_I2C				; send a byte in R10 into I2C
	bic.b   #SDA, &P2DIR	        ; make sure that DATA=1 and CLK=0 
	bic.b   #SCL, &P2OUT	
	mov.b   #8, R11                 ; initialize bit counter

w_loop  rlc.b   R10			; get the leftmost data bit
	jc      $+8
	bis.b   #SDA, &P2DIR            ; set 0 on data line
	jmp     $+6
	bic.b   #SDA, &P2DIR	        ; set 1 on data line
	bis.b   #SCL, &P2OUT	        ; raise clock up
	jmp     $+2			; short delay
	jmp	$+2
	nop
	bic.b   #SCL, &P2OUT	        ; ... and low
	dec.b   R11
	jnz	w_loop		        ; repeat this 8 times

	bic.b   #SDA, &P2DIR	        ; release the data line	
	jmp     $+2	                ; let slave respond for ACK
	bis.b   #SCL, &P2OUT	        ; clock up
 	jmp     $+2			; slave responds OK (hopefully)
        jmp	$+2
        nop
	bic.b   #SCL, &P2OUT	        ; clock down
	ret

;---------------------------------------------------------------------------
read_I2C				; read byte from I2C into R10
        bic.b   #SDA, &P2DIR	        ; make sure that DATA=1 and CLK=0 
	bic.b   #SCL, &P2OUT	
	mov.b   #8, R11                 ; initialize bit counter

r_loop  bis.b   #SCL, &P2OUT	        ; clock up  
        bit.b   #SDA, &P2IN             ; received bit -> C-bit
        rlc.b   R10                     ; shift it into R10
        jmp     $+2
        nop
        bic.b   #SCL, &P2OUT	        ; clock down
        jmp     $+2
	dec.b   R11
	jnz	r_loop		        ; repeat this 8 times

        tst.b   R12                     ; R12 = ACK
        jnz     $+6
        bis.b   #SDA, &P2DIR            ; send ACK signal
        jmp     $+2
        bis.b   #SCL, &P2OUT	        ; raise clock up
	jmp     $+2			; short delay
	jmp	$+2
	nop
	bic.b   #SCL, &P2OUT	        ; ... and low           
        bic.b   #SDA, &P2DIR            ; release the data line
	ret
                
;---------------------------------------------------------------------------
setTime                                 ; set time in DS1337
        call    #start_I2C
        mov.b   #11010000b, R10         ; slave address/write
        call    #write_I2C
        mov.b   #1, R10                 ; minutes register address
        call    #write_I2C
        mov.b   &minT, R10
        and.b   #7, R10
        rla.b   R10 
        rla.b   R10
        rla.b   R10
        rla.b   R10
        add.b   &minU, R10
        call    #write_I2C               ; set minutes 
        
        mov.b   &hrsT, R10
        and.b   #3, R10
        rla.b   R10 
        rla.b   R10
        rla.b   R10
        rla.b   R10
        add.b   &hrsU, R10              ; set hours
        call    #write_I2C
        call    #stop_I2C
        ret
                
;---------------------------------------------------------------------------   
code7   ;	pabcdefg                ; 7-segment codes
        DB      01111110b	        ; code for 0
        DB      00110000b	        ; code for 1
        DB      01101101b	        ; code for 2
        DB      01111001b	        ; code for 3
        DB      00110011b	        ; code for 4
        DB      01011011b	        ; code for 5
        DB      01011111b	        ; code for 6
        DB      01110000b	        ; code for 7
        DB      01111111b	        ; code for 8
        DB      01111011b	        ; code for 9
        DB      00000001b               ; code for -
        DB      01000111b	        ; code for F
        DB      00000000b               ; empty

;---------------------------------------------------------------------------
        COMMON  INTVEC                  ; MSP430F2101 interrupt vectors
        ORG     RESET_VECTOR
        DC16    main                    ; set reset vector to 'main' label
  
        ORG     TIMERA1_VECTOR
        DC16    TA_ISR                  ; set timer_A interrupt vector


        END

