; Primary application for one of the miscellaneous boards.
;
; ### license
;
; ### docco
;
; WRITE-TO and READ-FROM operations are supported.
;
; OPCODE values are defined as:
;
;   00000000 : reserved
;   00000001 : turn beefcake ON
;   00000010 : turn beefcake OFF
;   00000011 : reset Arduino watchdog timer
;   00000100 : latch watchdog data for reading
;   00000101 : reserved
;   0000011x : reserved
;   00001xxx : reserved
;   0001xxxx : reserved
;   00100xxx : turn OFF pins on PORTA[0,1,2]  (1=OFF; 0=unchanged)
;   00101xxx : turn ON pins on PORTA[0,1,2]   (1=ON; 0=unchanged)
;   00110xxx : turn OFF pins on PORTC[0,1,2]  (1=OFF; 0=unchanged)
;   00111xxx : turn ON pins on PORTC[0,1,2]   (1=ON; 0=unchanged)
;   01xxxxxx : reserved
;   1xxxxxxx : reserved
;
; PINS:
;   A0 - relay A0
;   A1 - relay A1
;   A2 - relay A2
;   A3 - watchdog/alive  (input; read-only)
;   A4 - Arduino reset pin
;   A5 - "Beefcake" power relay
;   C0 - relay C0
;   C1 - relay C1
;   C2 - relay C2
;   C3 - <unused>
;   C4 - SCL
;   C5 - SDA
;

        list p = 16f688
        include p16f688.inc

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

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

        __config DEFAULT_CONFIG


; Pin definitions
 constant ALIVE = 3             ; PORTA, input
 constant ARD_RESET = 4         ; PORTA, output
 constant BEEFCAKE = 5          ; PORTA, output


; Define this module's variables
.gpr0   udata

; The OPCODE sent in a WRITE-TO request on the I2C bus.
OPCODE  res     1

; Arduino watchdog timer counter. Each count represents 262 ms.
WATCHDOG res    1
 constant WDOG_COUNT = 6        ; 1.6 seconds

; Last value we saw on the ALIVE pin (bitmask)
ALIVE_LAST res  1
 constant ALIVE_MASK = (1 << ALIVE)


.reset: org  0x0000
        goto main


.main:  code                    ; relocatable
main:

        ; Disable comparators. We're going to use all RA* as INPUTS/OUTPUTS.
        disable_comparators
        ; Now in Bank 0

        ; Latch initial values for any OUTPUTs. ARD_RESET needs to be HIGH
        ; ### are the relays active-HIGH or active-LOW???
        movlw   ALIVE_MASK      ; all other bits are LOW
        movwf   PORTA
        clrf    PORTC

        bank1s
        run_at_8MHz

        ; Ensure AN* are configured as DIGITAL pins.
        clrf    ANSEL

        ; ### leave all tristates as INPUTS (HIGH-Z) for now
        ; ### ARD_RESET needs to be an OUTPUT now.

        bank0s

        ; Set Timer1 prescaler to 1:8, so rollover occurs at 64k * 8
        ; instruction cycles == 262 ms.
        movlw   0x30
        iorwf   T1CON, F

        ; Clear the Timer1 values, and ensure the rollover flag is cleared.
        clrf    TMR1L
        clrf    TMR1H
        bcf     PIR1, TMR1IF

        ; Just start with 0x00 in ALIVE_LAST, even if it flips on the first
        ; read: no big deal. It would just reset the counter right away.
        clrf    ALIVE_LAST

        ; ### need to set up the I2C slave module

        ; Initialization is complete. Let's start Timer1 rolling.
        bsf     T1CON, TMR1ON

reset_counter:
        ; We count multiple rollovers (of 262 ms each). Ready this counter.
        movlw   WDOG_COUNT
        movwf   WATCHDOG

        ; FALLTHRU

busy_loop:
        ; The I2C bus is IDLE. Any SDA transition is a START condition,
        ; so we don't really need to test SCL. Just need to start reading
        ; fast enough.

        ; Check if SDA has transitioned LOW (while SCL remains HIGH).
        ; t(HD;STA) is 4.0 µs minimum, then SCL will transition LOW and hold
        ; for t(LOW) of 4.7 µs until SCL then transitions HIGH to deliver
        ; the first bit. That gives us 17 instructions to see SCL LOW, then
        ; watch for the transition edge.

        ; NOTE: SDA might be managed by the Arduino, so the above
        ;       timings are strict. We will test HOUSE_SDA "often" to ensure
        ;       we never miss a START.


        ; Is SDA still HIGH?
        btfss   PORTC, SDA
        goto    i2c_activity

        ; Has the ALIVE pin changed state?
        movlw   ALIVE_MASK
        andwf   PORTA, W
        xorwf   ALIVE_LAST, W
        bnz     alive_changed

        ; Check the timer
        btfss   PIR1, TMR1IF
        ; Timer1 has not overflowed (yet), so nothing to do.
        goto    busy_loop

        ; Timer1 overflowed.

        ; FALLTHRU

arduino_watchdog:
        ; Reset the overflow flag.
        bcf     PIR1, TMR1IF

        decfsz  WATCHDOG, F
        ; Hasn't reached zero. Keep looking for activity.
        goto    busy_loop

        ; We haven't heard from the Arduino in WDOG_COUNT sets of 262 ms.
        ; Likely, its Ethernet chip has hung up, and we need to reset the
        ; Arduino (no way to reset just the chip).

        ; Pull ARD_RESET LOW. This sinks current through the external
        ; pull-up (maximum sink is 200 mA total).
        bcf     PORTA, ARD_RESET

        ; ATMega328's t(RST) is 2.5 µs, so hold the pin LOW for 6 instruction
        ; cycles (0.5 µs each) to produce a 3 µs external-reset signal.
        wait_6

        ; Return to HIGH state.
        bsf     PORTA, ARD_RESET

        ; Reset the WATCHDOG counter, and hope the reset worked.
        goto    reset_counter


i2c_activity:
        call    I2C_S_saw_START
        bz      busy_loop
        bc      read_from

        call    I2C_S_read_stretch
        movwf   OPCODE

        sublw   0x01
        bz      beefcake_ON

        ; ### switch below to sublw
        movlw   0x02
        subwf   OPCODE, W
        bz      beefcake_OFF

        movlw   0x03
        subwf   OPCODE, W
        bz      reset_watchdog

        movlw   0x04
        subwf   OPCODE, W
        bz      latch_timer_data

        ; ### end sublw switch

        ; ### anything besides the relay opcodes?

        ; Test for the relay opcode patterns

        ; OPCODE[7:5] should be 001
        btfsc   OPCODE, 7
        goto    busy_loop
        btfsc   OPCODE, 6
        goto    busy_loop
        btfss   OPCODE, 5
        goto    busy_loop

        ; Extract OPCODE[2:0] for the mask bits
        movfw   OPCODE
        andlw   0x07

        ; Check ports and on/off
        btfsc   OPCODE, 4
        goto    check_portC
        btfsc   OPCODE, 3
        goto    portA_on

        ; Mask some PORTA bits OFF
        xorlw   0xff
        andwf   PORTA, F
        goto    busy_loop

portA_on:
        iorwf   PORTA, F
        goto    busy_loop

check_portC:
        btfsc   OPCODE, 3
        goto    portC_on

        ; Mask some PORTC bits OFF
        xorlw   0xff
        andwf   PORTC, F
        goto    busy_loop

portC_on:
        iorwf   PORTC, F
        goto    busy_loop

read_from:
        ; ### do something
        goto    busy_loop


beefcake_ON:
        bsf     PORTA, BEEFCAKE
        goto    busy_loop


beefcake_OFF:
        bcf     PORTA, BEEFCAKE
        goto    busy_loop


alive_changed:
        movlw   ALIVE_MASK
        xorwf   ALIVE_LAST, F   ; Toggle the last-seen value

        ; FALLTHRU

; The counter can be reset via I2C or seeing the ALIVE pin toggle.
reset_watchdog:
        ; Reset Timer1 and the larger counter.
        clrf    TMR1L
        clrf    TMR1H

        ; Just in case it popped during I2C activity.
        bcf     PIR1, TMR1IF

        goto    reset_counter


latch_timer_data:
        ; ### do something
        goto    busy_loop


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