; PIC 16F688 code for interacting with a DS1302 chip
;
; ### license
;
; NOTE: code is not provided for the DS1302's nonvolatile RAM since the
;       PIC has its own 256 bytes of EEPROM.
;
; ### we only read/write the HOURS and MINUTES registers of the DS1302.
; ### the rest of the data is not needed for our application.
;

        list p = 16f688
        include p16f688.inc
        include ../include/handy.inc

; Our variables can just go into Bank 1
.gpr0   udata

; A holding buffer while we read/write bits
BUFFER  res     1

; The value to write to the DS1302.
W_VALUE res     1


 ifndef CE_PORT
   constant CE_PORT = PORTC
 endif
 ifndef CE_PIN
   constant CE_PIN = 5
 endif
 ifndef IO_PORT
   constant IO_PORT = PORTA
 endif
 ifndef IO_PIN
   constant IO_PIN = 0
 endif
 ifndef SCLK_PORT
   constant SCLK_PORT = PORTA
 endif
 ifndef SCLK_PIN
   constant SCLK_PIN = 1
 endif

; Derived constants
 if CE_PORT == PORTA
   constant CE_TRISTATE = TRISA
 else
   constant CE_TRISTATE = TRISC
 endif
 if IO_PORT == PORTA
   constant IO_TRISTATE = TRISA
 else
   constant IO_TRISTATE = TRISC
 endif
 if SCLK_PORT == PORTA
   constant SCLK_TRISTATE = TRISA
 else
   constant SCLK_TRISTATE = TRISC
 endif

; Various Address/Command Byte values
 constant W_HOUR = 0x84
 constant W_MINUTE = 0x82
 constant W_SECOND = 0x80
 constant W_CONTROL = 0x8e

 constant R_HOUR = 0x85
 constant R_MINUTE = 0x83

; Simple macros to flip the IO pin back/forth as an OUTPUT or INPUT.
io_as_OUTPUT:   macro
        bank1s
        bcf     IO_TRISTATE, IO_PIN
        bank0s
        endm
io_as_INPUT:   macro
        bank1s
        bsf     IO_TRISTATE, IO_PIN
        bank0s
        endm


ds1302: code                    ; relocatable

; Setup our communication with the DS1302, and tell it to start running.
;
; INPUTS: none
; ASSUME: Bank 0
; EXIT:   Bank 0
;
        global  ds1302_setup
ds1302_setup:
        ; Latch CE/SCLK LOW, before enabling them as OUTPUTs
        bcf     CE_PORT, CE_PIN
        bcf     SCLK_PORT, SCLK_PIN

        ; ANSEL and TRIS* are in Bank 1
        bank1s

        ; When IO is an input, it should be DIGITAL.
        disable_analog IO_PORT, IO_PIN

        ; CE/SCLK are always OUTPUTs. Start IO as an INPUT (high impedance)
        bcf     CE_TRISTATE, CE_PIN
        bcf     SCLK_TRISTATE, SCLK_PIN

        bsf     IO_TRISTATE, IO_PIN

        bank0s

        ; Clear the WRITE-PROTECT, so we can perform further writes.
        clrf    W_VALUE
        movlw   W_CONTROL
        call    write

        ; Write 0x00 (still in W_VALUE) to the SECONDS register to start
        ; the clock (clear the MSB, to be specific).
        movlw   W_SECOND
        goto    write           ; its return will perform ours


; Read the hour value from the DS1302, returned in W.
;
; INPUTS: none
; ASSUME: Bank 0
; EXIT:   Bank 0
;         W contains BCD hour value.
;
        global  ds1302_read_hour
ds1302_read_hour:
        movlw   R_HOUR
        goto    read            ; its return will perform ours


; Write the value in W to the DS1302's HOUR register.
;
; INPUTS: W contains BCD hour value.
; ASSUME: Bank 0
; EXIT:   Bank 0
;
        global  ds1302_write_hour
ds1302_write_hour:
        movwf   W_VALUE
        movlw   W_HOUR
        goto    write           ; its return will perform ours


; Read the minute value from the DS1302, returned in W.
;
; INPUTS: none
; ASSUME: Bank 0
; EXIT:   Bank 0
;         W contains BCD minutes value.
;
        global  ds1302_read_min
ds1302_read_min:
        movlw   R_MINUTE
        goto    read            ; its return will perform ours


; Write the value in W to the DS1302's MINUTE register.
;
; INPUTS: W contains BCD minutes value.
; ASSUME: Bank 0
; EXIT:   Bank 0
;
        global  ds1302_write_min
ds1302_write_min:
        movwf   W_VALUE
        movlw   W_MINUTE
        goto    write           ; its return will perform ours


; Write W_VALUE to the register (address) specified in W.
;
; NOTE: this function assumes 8 MHz operation, or 0.5 µs instruction cycles.
;
; INPUTS: W contains the register/address value
; ASSUME: Bank 0
; EXIT:   Bank 0
;
write:
        ; Make IO an OUPUT pin. (it's value does not matter)
        io_as_OUTPUT

        ; Set CE to HIGH, to enable communication.
        bsf     CE_PORT, CE_PIN
        ; t(CC) = 4.0 µs minimum, or 8 instruction cycles. We must not set
        ; SCLK HIGH for the next 8 cycles. No delay needed.

        ; Write the register/address contained in W.
        call    write_8

        ; Write W_VALUE to the DS1302.
        movfw   W_VALUE
        call    write_8
        ; t(CCH) = 0.240 µs minimum. No delay needed.

        ; Set CE to LOW, disabling communication.
        bcf     CE_PORT, CE_PIN
        ; t(CWH) = 4.0 µs minimum. No delay needed.
        ; t(CDZ) = 0.28 µs maximum. No delay needed.

        ; Return IO to an INPUT (high impedance) pin.
        io_as_INPUT

        ; Done! Some callers may use 'goto' so this 'return' will go
        ; back to the application.
        return


; Write 8 bits to IO, using SCLK to clock them through to the DS1302.
;
; INPUTS: W contains the 8 bits to transfer
; ASSUME: Bank 0
; EXIT:   Bank 0
; 
write_8:
        movwf   BUFFER

        ; Rise/Fall times are satisfied:
        ;   DS1302: t(R) = t(F) = 500 ns maximum.
        ;   PIC16F688: TioR = TioF = 40 ns maximum.

        ; Count the nubmer of bits to transfer, within W.
        movlw   8

write_bit:
        ; Set the IO pin's state based on BUFFER's LSB.
        bcf     IO_PORT, IO_PIN
        rrf     BUFFER, F
        skpnc
        bsf     IO_PORT, IO_PIN
        ; t(DC) = 0.2 µs minimum. No delay needed.

        ; Now toggle SCLK so that IO is clocked into the DS1302.
        bsf     SCLK_PORT, SCLK_PIN
        ; t(CDH) = 0.240 µs minimum. No delay needed.
        ; t(CCZ) = 0.280 µs maximum. 
        ; t(CH) = 1.0 µs minimum. One more instruction cycle needed.
        nop
        bcf     SCLK_PORT, SCLK_PIN
        ; t(CL) = 1.0 µs minimum. No delay needed.

        sublw   1
        skpz
        goto    write_bit

        return


; Read a value (returned in W) from the register (address) specified in W.
;
; NOTE: this function assumes 8 MHz operation, or 0.5 µs instruction cycles.
read:
        ; Make IO an OUPUT pin. (it's value does not matter)
        io_as_OUTPUT

        ; Set CE to HIGH, to enable communication.
        bsf     CE_PORT, CE_PIN
        ; t(CC) = 4.0 µs minimum, or 8 instruction cycles. We must not set
        ; SCLK HIGH for the next 8 cycles. No delay needed.

        ; The following is very similar to write_8, but we only want to
        ; write SEVEN (7) bits of the address. The last bit needs special
        ; handling: before releasing SCLK, the IO pin must be switched to
        ; an INPUT. The DS1302 will assert a value on IO at the trailing
        ; edge of SCLK; thus, we must avoid a conflicting assertion/output.

        ; NOTE: refer to 'write_8' for timing commentary/notes.

        ; Store the input register/address into BUFFER for delivery.
        movwf   BUFFER
        ; Transfer just the first seven bits (all but the MSB).
        movlw   7
        ; Write those bits to the DS1302.
        call    write_bit

        ; We just have the MSB to deliver. It is, by definition, HIGH.
        bsf     IO_PORT, IO_PIN
        bsf     SCLK_PORT, SCLK_PIN
        ; Switch IO to an INPUT, before we release SCLK. This also provides
        ; a needed instruction cycle delay, for the DS1302 to acquire the
        ; value we wrote.
        io_as_INPUT
        ; And the falling edge will trigger the DS1302 to assert the LSB
        ; of the requested register.
        bcf     SCLK_PORT, SCLK_PIN

        ; NEXT: Read the register's value

        ; Perform a little setup for the read.
        clrf    BUFFER
        movlw   8
read_bit:
        clrc
        btfsc   IO_PORT, IO_PIN
        setc
        rrf     BUFFER, F

        ; t(CDD) = 0.8 µs maximum. No delay needed.

        ; Now toggle SCLK so that IO is clocked into the DS1302.
        bsf     SCLK_PORT, SCLK_PIN
        ; t(CDH) = 0.240 µs minimum. No delay needed.
        ; t(CCZ) = 0.280 µs maximum. 
        ; t(CH) = 1.0 µs minimum. One more instruction cycle needed.
        nop
        bcf     SCLK_PORT, SCLK_PIN
        ; t(CL) = 1.0 µs minimum. No delay needed.

        sublw   1
        skpz
        goto    read_bit

        ; Set CE to LOW, disabling communication.
        bcf     CE_PORT, CE_PIN
        ; t(CWH) = 4.0 µs minimum. No delay needed.
        ; t(CDZ) = 0.28 µs maximum. No delay needed.

        ; Leave the IO pin as an INPUT (high impedance).

        ; Return the value read from the DS1302. Some callers may use 'goto'
        ; so this 'return' will go back to the application.
        movfw   BUFFER
        return


; stupid assemblers don't realize EOF means *end*
        end
