; Supporting "functions" for I2C communication.
;
; ### license
;

        list p = 16f688
        include p16f688.inc

        include ../include/handy.inc

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

I2C_support:    code


; Skip the current I2C transaction(s) until a STOP/REPEAT condition is seen.
;
; NOTE: this is needed if we are not the target address, after we lose
;       arbitration, or get a NACK.
;
; On exit: Z will be SET when a STOP condition is seen, and the bus is idle.
;             NOTE: the application must leave the bus idle for
;                   t(BUF) (4.7 µs minimum).
;          Z will be CLEARED if a REPEATED START is seen, and the next
;             transaction should be examined.
;
; EXPECT: SCL is LOW.
;         SDA is NOT-CARE.
;         Bank 0
; EXIT: SCL is HIGH
;       Z flag is set, per above
;       Bank 0
;
 global I2C_skip
I2C_skip:
        ; ### need to fix entry conditions??

        ; To skip a transaction, we want to look for SDA transitions while
        ; SCL is HIGH. For the regular parts of an I2C transaction, SDA will
        ; be stable while SCL is HIGH. Only during STOP/RESTART will the SDA
        ; line transition while SCL is HIGH.
        ;
        ; THUS: wait for SCL to become HIGH, then we look for STOP/RESTART.
        wait_SCL_HIGH

        ; If SDA is HIGH, then we should watch for REPEATED START.
        ; If SDA is LOW, then we should watch for STOP.

        btfsc   SDA_PORT, SDA
        ; SDA is HIGH
        goto    both_HIGH       ; inside I2C_skip_unknown

look_for_STOP:
        ; SCL is HIGH. SDA is LOW. Watch for one of two conditions:
        ; If SCL transitions LOW, then SDA never transitioned while SCL
        ;   was HIGH. Thus, we saw a typical bit transmission.
        ; If SDA transitions HIGH, then we saw a STOP condition
        btfss   SCL_PORT, SCL
        ; SCL just transitioned LOW, with no change to SDA. Random bit.
        ; Continue skipping transitions.
        goto    I2C_skip

        ; SCL remains HIGH. Has SDA changed?
        btfss   SDA_PORT, SDA
        ; SDA remains LOW. Keep looking for transitions.
        goto    look_for_STOP

        ; SCL is HIGH. SDA just transitioned HIGH, indicating STOP.
        ; The bus is now IDLE. Notify caller by SETTING the Z flag.
        setz
        return


; Wait for the I2C bus to become IDLE, from an unknown state. This is
; particularly useful after the processor has been busy, and finally
; returns to examining activity on the I2C bus.
;
; If either pin is LOW, then the bus is busy, and we need to wait for
; it to return to IDLE.
;
; If SDA and SCL are HIGH, then we may be looking at a bit transmission.
; Waiting t(HIGH) for SCL to return to LOW indicates which state we are
; looking at.
; ### t(HIGH) is either 4.0 µs or 8.0 µs. I2C_S_saw_START coded it as
; ### 8.0, but the spec indicates 4.0. a change in spec? or maybe read
; ### or coded it wrong?
;
; EXPECT: Bank 0
; ON EXIT: this function matches I2C_skip. See that function.
;
 global I2C_skip_unknown
I2C_skip_unknown:

        btfss   SCL_PORT, SCL
        ; SCL is LOW, so we're mid-transmission. Just skip the transaction.
        goto    I2C_skip

        btfss   SDA_PORT, SDA
        ; SDA is LOW, and SCL is HIGH. Seeing: sending a bit, or we are
        ; about to see a STOP condition.
        goto    look_for_STOP   ; this is within I2C_skip

both_HIGH:
        ; SCL is HIGH, and SDA is HIGH. This condition implies three
        ; possible conditions:
        ;   1) sending a bit (SCL transition to LOW indicates this)
        ;   2) we are about to see a REPEATED START condition (SDA
        ;      transition to LOW indicates this)
        ;   3) the bus is IDLE (nothing transitions for t(HIGH) minimum)

        ; Loop (5) times looking for transitions. If nothing happens, then
        ; we'll declare the bus IDLE.
        ;
        ; NOTE: this is a total 20.0 µs wait for activity, which is longer
        ;       than t(HIGH) if we are mid-transmission.
        movlw   5

        ; Each loop is (8) instruction cycles (aka 4.0 µs).
detect_or_timeout:
        btfss   SCL_PORT, SCL
        ; SCL just transitioned LOW, so we just saw a bit transmission.
        ; Skip the rest of this transaction.
        goto    I2C_skip

        ; Clear Z, in case we want to tell the caller we saw REPEATED-START.
        ; We'll take this intruction cycle within the loop, in order to
        ; speed up returning to the caller to process the incoming data.
        clrz

        btfss   SDA_PORT, SDA
        ; SDA just transitioned LOW while SCL is HIGH. This is a (REPEATED)
        ; START. Z has been cleared just above. Return to caller.
        return

        ; SCL and SDA remain HIGH. Keep testing for a while.
        sublw   1
        bnz     detect_or_timeout

        ; Timeout. Declare the bus IDLE by setting the Z flag.
        setz
        return


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