; Testing application for the I2C slave code.
;
; ### license
;
; This application maintains some registers, and allows the I2C master
; to manipulate them (read/write).
;
; The I2C master can be (say) an Arduino, or a Raspberry Pi. Anything
; that supports driving an I2C bus.
;
; NOTE: we currently define (4) registers for storing values.
;
;
; I2C WRITE-TO has many potential operations. The first written byte
; is the OPCODE, described using bit patterns.
;
; These OPCODEs are a single WRITE-TO operation of N bytes:
;
;  0000 00xx : unused
;  0000 01xx : write next byte to register XX
;  0000 100x : write next two bytes to register X and X+1
;  0000 1010 : write next two bytes to register 2 and 3
;  0000 1011 : illegal, would lead to register overrun
;  0000 110x : write next three bytes to register X, X+1, X+2
;  0000 1110 : unused
;  0000 1111 : write next four bytes to REGISTER[0..3]
;
; Once the OPCODE is specified, then the Master must use REPEATED-START
; to begin reading the selected data. Lack of a REPEATED-START means
; that a second Master may have applied a new OPCODE, throwing off the
; intent of the first Master.
;
; These are the OPCODEs requiring a WRITE-TO of the OPCODE, followed
; by a READ-FROM of the intended query.
; NOTE: a register overrun will read as 0xFF, just like attempting to
;   directly read from the testapp without first supplying an OPCODE
; NOTE: similarly reading *past* the requested amount will return 0xFF
;   (eg. using the read-one opcode, but then trying to read 3)
; NOTE: no opcode for read-four; just read four starting at 0
;
;  0001 00xx : read N bytes, starting at register XX
;  0001 01xx : read one byte -- register XX
;  0001 10xx : read two bytes, starting at XX (note potential overrun)
;  0001 110x : read three bytes, starting at X
;  0001 111x : unused
;
;  001x xxxx : unused
;  01xx xxxx : unused
;  10xx xxxx : unused
;  110x xxxx : unused
;  1110 xxxx : unused
;  1111 0xxx : unused
;  1111 10xx : unused
;  1111 110x : unused
;  1111 1110 : unused
;  1111 1111 : reserved, to mean "no OPCODE specified"
;
;
; I2C READ-FROM will return (2) bytes:
;  * PORTA switch state
;  * PORTC switch state
;
; RA0 is an LED pin, blinking for heartbeat
; RC4,5 are the I2C interface
;
;

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

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

        __config DEFAULT_CONFIG

        ; our ACTIVE output line is RC3
        constant LED = RA0

; Define this module's variables
.gpr0   udata

; The registered opcode that we are/will process.
OPCODE          res 1

; Our testing registers; we'll hold 4.
REGISTERS       res 4

; Count of times we've seen timer-overflow
COUNTER         res 1

; We will decrement COUNTER until it reaches 0, then alter
; the LED (blink). We'll then reset COUNTER to set up the
; next cycle.
;
; Go with 20 cycles of 32.8ms TIMER0 rollovers, aka 650ms.
        constant COUNTER_CYCLES = D'20'


.reset: org  0x0000
        goto main


.main:  code                    ; relocatable
main:
        ; Note: bank is ENOCARE. This will force bank 0.
        ; Disable comparators. RA* will be digital inputs.
        disable_comparators
        ; Note: now in bank 0.

        ; Start: all registers should be zeroed.
        clrw
        movwf   REGISTERS
        movwf   REGISTERS+1
        movwf   REGISTERS+2
        movwf   REGISTERS+3

        ; How many times to rollover TMR0
        movlw   COUNTER_CYCLES
        movwf   COUNTER

        ; Ensure the LED port will be LOW when it becomes an OUTPUT.
        bcf     PORTA, LED

        ; We are going to assign the prescaler to TIMER0. To safely
        ; do this, we need to clear the prescaler (so it doesn't trip
        ; during assignment). This is easiest:
        clrwdt

        bank1s
        run_at_8MHz

        ; We don't need ANALOG inputs; switch to all DIGITAL.
        no_analog

        ; At reset time, all ports are INPUT/HIGH-Z, with weak pull-ups
        ; enabled. Turn RA0 (the LED pin) into an OUTPUT.
        bcf     TRISA, TRISA0

        ; Setup and assign prescaler to TIMER0. Also note the CLRWDT above.
        ; The prescaler will be set to 1:256. Overflow is every 32.8ms.
        movlw   0x07
        iorwf   OPTION_REG, F   ; PS<2:0> = 111
        bcf     OPTION_REG, T0CS
        bcf     OPTION_REG, PSA

        ; Note: remain in Bank 1

        ; Prepare for I2C slave operation.
        call    I2C_S_listen
        ; Note: now using Bank 0

busy_loop:
        ; ASSUMED: Bank 0.

        ; Has TIMER0 overflowed? We may need to blink the LED.
        btfsc   INTCON, T0IF
        goto    timer_overflowed ; bit was set

        ; Has the I2C bus just become busy?
        ; Wait for SDA to transition to LOW.
        btfsc   SDA_PORT, SDA
        goto    busy_loop       ; SDA is still HIGH (no activity)

        ; I2C activity has started
        ; FALLTHRU

repeated_start:
        call    I2C_S_detect_START
        ; Note: we are in Bank 0.

        ; Multiple transfers may have occurred (via Repeated Starts).
        ; The Z flag specifies our current state:
        ;   Z is SET: the I2C bus is now IDLE
        ;   Z is CLEAR: self has been addressed.
        bz      busy_loop       ; wait for some work.

        ; Some transfers may have been skipped, but we have arrived at one
        ; addressing self. We have sent an ACK regarding our address.

        ; CARRY indicates the I2C READ-FROM(1)/WRITE-TO(0) status.
        bc      read_from

        ; This is a WRITE-TO self.
        ;
        ; ### TBD
        ;
        ; NOTE: we ACK'd the address transmission. Best we can do now is to
        ;       NACK the written bytes (skip does not pull SDA LOW for an
        ;       ACK to the master).

        ; FALLTHRU

finish_transaction:
        ; All work associated with the current (maybe unfinished/ignored)
        ; transaction has been completed.
        ;
        ; Wait for the transaction to finish. Get a STOP or REPEATED-START.
        ;
        ; NOTE: SCL "should" be LOW, but it is possibly HIGH (by now) for
        ;       the first data bit. Use skip_unknown for safety.
        call    I2C_skip_unknown

        ; If Z is CLEAR, then we saw a REPEATED-START. We will need
        ; to examine the next transaction for SELF, and the R/W bit.
        bnz     repeated_start

        ; Z is SET -- the bus is now idle (STOP was seen).
        goto    busy_loop


read_from:
        ; ### parse the address that was stored in a WRITE-TO
        ; 
        ; ### or, if no address was written (followed by a
        ; ### REPEATED-START for a READ-FROM), then return indicators
        ; Write the two sets of switch state, for the response.
        movlw   1               ; ### rando
        call    I2C_S_write

        ; If the MASTER sent a NACK, then it is done reading. Finish this
        ; transaction and watch for what is next.
        ; NOTE: the ACTIVE pin is unchanged since the PORTC data is unread.
        bz      finish_transaction

        movlw   2               ; ### rando
        call    I2C_S_write
        ; NOTE: Z specifies result of write back to the master.

        ; Master sent NACK/DONE. Finish up, read what is next.
        bz      finish_transaction

        ; FALLTHRU

        ; We have nothing more to send. Just send zeroes.
write_zeroes:
        clrw
        call    I2C_S_write

        ; Master wants more? Keep writing zeroes.
        bnz     write_zeroes

        ; All done. Got a NACK/DONE from the Master.
        goto    finish_transaction


; Our timer has overflowed; maybe blink the LED
timer_overflowed:
        ; ASSUMED: Bank 0.

        ; Wait for COUNTER_CYCLES of timer-overflow to blink the LED
        decf    COUNTER

        ; If the COUNTER hasn't hit zero, then reset the timer and
        ; return to the busy loop.
        bnz     reset_timer

        ; COUNTER has now hit zero. Time to blink.
        movlw   (1 << LED)      ; load this bit into W
        xorwf   PORTA, F        ; use it to flip the LED

        ; Note: still in Bank 0.

        ; Reset the COUNTER to await the next hit-zero.
        movlw   COUNTER_CYCLES
        movwf   COUNTER

        ; FALLTHRU

reset_timer:
        ; Note that the Timer0 Interrupt Flag (T0IF) gets set upon
        ; overflow. Interrupts are not enabled, so the flag gets set
        ; and nothing more happens. But we need to clear it, so that
        ; we can look for it getting raised at the next overflow.
        bcf     INTCON, T0IF

        ; All done. Go look for more work.
        goto    busy_loop


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