; PIC 16F688 code for running an I2C master
;
; ### license
;
; Conforms to Version 2.1 of the I2C specification
;
; The external build script should set the following:
;
; SCL: the PortA/C pin number for the SCL bus
; SCL_PORT: which port (A or C) to use
; SDA: the PortA/C pin number for the SDA bus
; SDA_PORT: which port (A or C) to use
;
; ### should have optional code to look for multi-master interference. not
; ### all modules will need to deal with multi. many are the only master on
; ### their bus.

        list p = 16f688
        include p16f688.inc

        constant USING_WAIT_MACROS = 1
        include ../include/handy.inc

        constant PORTS_REQUIRED = 1
        include ../include/i2c.inc


        ; Define this module's variables
.gpr0   udata

; When sending data, which bit are we sending? And what is the current byte?
CUR_BIT res     1
CUR_BYTE res    1


; Cycle SCL and read the SDA value around mid-cycle.
; NOTE: SCL transitions during the SECOND cycle going in, and out of this
;       macro. (iow: cycle, then transit cycle; or transit, then cycle)
_read_bit:       macro MASK
        ; SCL is released during SECOND instruction cyle from now.
        release_wait_SCL_HIGH
        ; t(HIGH) is 4.0 µs minimum.
        ; SCL was noted as HIGH (2) instruction cycles ago.
        wait_1
        ; Read SDA about midway during the HIGH portion of the SCL cycle.
        setbit_SDA MASK         ; 2 cycles
        ; Tristate shifts to OUTPUT during THIRD instruction cycle from now
        ; SUM: 2 + 1 + 2 + 3 == 8 cycles
        assert_SCL_LOW
        ; SCL was asserted LOW during second instruction back.
        endm


master: code                    ; relocatable


; Send one byte (in W, on entry), then read the N/ACK response. On entry,
; SDA and SCL should be asserted LOW.
;
; On exit, the Z flag will be SET if the byte was properly ACK'd.
;
; This function may be used to send the initial address byte, and any
; following data bytes.
;
; NOTE: Z will also be CLEARed if this master loses arbitration. Regardless
;       of whether a failure to ACK or loss of arbitration, the application
;       should ignore all I2C activity until a STOP condition is seen and
;       the bus returns to the idle state.
 global I2C_M_send_one
I2C_M_send_one:
        ; Prepare to send the 8 bits of the (input) W register.
        movwf   CUR_BYTE
        movlw   0x08
        movwf   CUR_BIT

        ; ### this function needs timing delays
        ; ### we need to watch for arbitration loss

send_next_bit:
        ; Still sending bits. Move the next bit into the CARRY flag.
        rlf     CUR_BYTE, F

        ; If the CARRY flag is SET, then go prep SDA to send HIGH.
        bc      send_SET_bit
        ; High bit is LOW.
        assert_SDA_LOW
        goto    cycle_SCL
send_SET_bit:
        release_SDA
        ; ### what if the rise time is more than 0.5 µs ?
        ; ### t(r) is 1.0 µs maximum. ... and I have high-capacitance lines.
        ; ### for now, wait 1 µs. ###
        wait_2
        btfss   SDA_PORT, SDA
        ; Whoops. SDA is LOW, meaning somebody else has asserted a different
        ; value for SDA. By spec, they win, so let's get out of here. SCL
        ; remains LOW and SDA is LOW.
        goto    lost_arbitration
cycle_SCL:
        ; t(SU:DAT) is 0.25 µs. That is one instruction cycle, which is
        ; definitively provided between stabilizing SDA and rising SCL.
        release_wait_SCL_HIGH
        ; t(HIGH) is 4.0 µs minimum.
        wait_8                  ; ### shrink this a bit, given surrounding
        assert_SCL_LOW
        ; t(HD:DAT) is 0.3 µs minimum, which is provided.

        ; Now that SCL is LOW, we have t(VD:DAT) (maximum 3.45 µs) to
        ; prepare the next SDA value.
        ; ### this code cannot prepare it that quickly

        decfsz  CUR_BIT, F
        goto    send_next_bit

        ; Z flag is SET.

        ; t(VD:ACK) is 3.45 µs maximum, for slave to establish SDA for
        ; the N/ACK value. We need to ensure SDA is released, for the slave
        ; to assert/release the line.
        release_SDA
        ; ### count time above, while manipulating CUR_BIT
        ; ### some extra wait time?

        release_wait_SCL_HIGH

        ; Z flag is still SET/HIGH.
        ; If SDA has been asserted LOW, then an ACK has occurred. Signal,
        ; by leaving Z HIGH.
        btfsc   SDA_PORT, SDA
        clrz                    ; SDA is HIGH. NACK. set Z flag LOW.

        ; t(HIGH) is 4.0 µs minimum
        wait_8                  ; ### probably fewer cycles. examine surround.
        assert_SCL_LOW

        ; Exit: SCL is LOW. SDA is released (and nobody should be holding
        ; it LOW), so SDA will be SET/HIGH-Z
        ;
        ; The Z flag reflects the N/ACK state.
        return

lost_arbitration:
        ; Notify caller that we lost arbitration by CLEARing the Z flag.
        clrz
        ; ### is there any cleanup to perform?
        return


; Read one byte from the slave, then signal an ACK.
;
; On exit, the byte will be in W.
 global I2C_M_read_one
I2C_M_read_one:

        call _read_byte
        ; ### extra delay after above SCL transition to LOW?

        assert_SDA_LOW
        ; ### wait for setup time
        release_wait_SCL_HIGH
        wait_8                  ; ### likely too long
        assert_SCL_LOW

        return


; Read one byte from the slave, then signal an NACK to tell the slave to
; stop sending bytes.
;
; On exit, the byte will be in W.
 global I2C_M_read_last
I2C_M_read_last:

        call _read_byte
        ; ### extra delay after above SCL transition to LOW?

        release_SDA
        ; ### wait for setup time
        release_wait_SCL_HIGH
        wait_8                  ; ### likely too long
        assert_SCL_LOW

        return


; Signal a REPEATED START condition. On exit, SCL and SDA will be LOW.
;
; NOTE: on entry, SCL should be LOW (right after an ACK bit). SDA should
;       still be an INPUT/HIGH-Z from the ACK.
; NOTE: since SCL was pulled LOW (in I2C_M_send_one, I2C_M_read_one, or
;       I2C_M_read_last), at least a 'return' and a 'call' instruction
;       have been executed. That is 4 instructions, or 2.0 µs.
 global I2C_M_restart
I2C_M_restart:
        ; t(HD:DAT) is 0 µs. We can release SDA immediately.

        ; t(LOW) is minimum 4.7 µs. While waiting for that, we can
        ; release SDA to let it float HIGH. This is 3 instructions.
        ; Wait another 3 to ensure we hit 5 µs minimum for t(LOW).
        release_SDA

        ; +4 control flow
        ; +3 in release_SDA
        wait_2
        ; +1 in release_wait_SCL_HIGH

        ; Release SCL to float HIGH. Wait t(SU:STA) (4.7 µs minimum),
        ; then assert SDA to LOW to indicate REPEATED START.
        release_wait_SCL_HIGH
        ; +1 in release_wait_SCL_HIGH
        wait_7
        ; +2 in assert_SDA_LOW (below)

        ; FALLTHRU


; Establish the START condition on the bus, exiting with both lines LOW.
;
; NOTE: the bus SHOULD be idle. The application should have detected another
;       master sending data, and responded/ignored the transaction until the
;       bus returned to the idle state.
 global I2C_M_start
I2C_M_start:
        assert_SDA_LOW

        ; Wait t(HD:STA) (4 µs minimum), then assert SCL to LOW.
        ; +1 in assert_SDA_LOW
        wait_5
        ; +2 in assert_SCL_LOW
        assert_SCL_LOW

        ; SDA and SCL are now LOW.
        return


; Signal a STOP condition. On exit, the bus will be idle.
;
; NOTE: the application must leave the bus idle for t(BUF) (4.7 µs minimum).
;
; NOTE: on entry, SCL should be LOW (right after an ACK bit). SDA should
;       still be an INPUT/HIGH-Z from reading the slave's ACK.
; NOTE: since SCL was pulled LOW (in I2C_M_send_one, I2C_M_read_one, or
;       I2C_M_read_last), at least a 'return' and a 'call' instruction
;       have been executed. That is 4 instructions, or 2.0 µs.
 global I2C_M_stop
I2C_M_stop:
        ; t(HD:DAT) is 0, so we can modify SDA immediately.
        assert_SDA_LOW
        ; +4 control flow
        ; +4 in assert_SDA_LOW
        wait_1
        ; +1 in release_wait_SCL_HIGH
        ; t(LOW) (4.7 µs minimum) for SCL has been reached.

        ; Release SCL to float HIGH. Wait t(SU:STO) (4.0 µs minimum),
        ; then release SDA to float HIGH.
        release_wait_SCL_HIGH
        ; +1 in release_wait_SCL_HIGH
        wait_6
        ; +1 in release_SDA
        release_SDA

        ; Bus is now idle.
        return


; Read one byte from a SLAVE node, and return it in W.
;
; NOTE: SCL can transit HIGH during the 2nd instruction cycle of this
;       function. Add 2 instruction cycles for the CALL opcode to arrive here.
;       (clock stretching may delay the transit, but we're concerned with
;       the earliest possible transition, for timing purposes)
; 
; NOTE: SCL is pulled LOW, then one instruction cycle occurs, then the
;       RETURN opcode is executed for another 2 instruction cycles.
_read_byte:
        clrw
        ; Note: 1 instruction, then SCL *may* transit HIGH. (unless something
        ;       is clock-stretching)
        _read_bit 0x80
        ; SCL was pulled LOW, then 1 further instruction occurred. Given the
        ; lead-in on the next read-bit, we should wait (6) cycles. That will
        ; give us (8) cycles total between SCL transitions, to meet the spec:
        ;   t(LOW) is 4.0 µs minimum
        wait_6
        _read_bit 0x40
        wait_6
        _read_bit 0x20
        wait_6
        _read_bit 0x10
        wait_6
        _read_bit 0x08
        wait_6
        _read_bit 0x04
        wait_6
        _read_bit 0x02
        wait_6
        _read_bit 0x01

        return


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