; PIC 16F688 code for monitoring/reading MPR121-based touch sensors
;
; ### license
;
; NOTE: this is *not* a generic interface to the MPR121. It has specific
;       assumptions about the hardware (eg. ELE11 is a LOW OUTPUT, that
;       only 11 sensors are in use, 3.3V operation, etc)
;
;       Certainly, this can be used as a start for any PIC code being used
;       to communicate with an MPR121.
;

        list p = 16f688
        include p16f688.inc

        include ../include/handy.inc

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


; Define symbols for the various MPR registers
 constant MHD_R = 0x2b
 constant NHD_R = 0x2c
 constant NCL_R = 0x2d
 constant FDL_R = 0x2e
 constant MHD_F = 0x2f
 constant NHD_F = 0x30
 constant NCL_F = 0x31
 constant FDL_F = 0x32
 constant NHD_T = 0x33
 constant NCL_T = 0x34
 constant FDL_T = 0x35

 constant ELE0_T = 0x41
; ... ELE0_R, ELE1_T, ELE1_R, ..., ELE11_R = 0x58

 constant AFE1 = 0x5c
 constant AFE2 = 0x5d
 constant ECR = 0x5e

 constant GPIO_CTL0 = 0x73
 constant GPIO_CTL1 = 0x74
 constant GPIO_DAT = 0x75
 constant GPIO_DIR = 0x76
 constant GPIO_EN = 0x77

 constant AUTO_CFG0 = 0x7b
 constant AUTO_CFG1 = 0x7c
 constant AUTO_USL = 0x7d
 constant AUTO_LSL = 0x7e
 constant AUTO_TL = 0x7f

 constant SRST = 0x80


        ; Define this module's variables
.gpr0   udata

; Which MPR are we talking to right now?
WHICH_MPR res   1

; Register address and value, for read/write
REG_ADDR res    1
REG_VALUE res   1

; Used while initializing one of the LOCAL MPR devices
ELE_COUNTER res 1


mpr121:  code                   ; relocatable


; Initialize the MPR121 device at the address specified in W.
 global mpr121_init
mpr121_init:
        ; Which MPR device are we initializing?
        movwf   WHICH_MPR

        ; Various sample initialization code, found at:
        ;   https://learn.sparkfun.com/tutorials/mpr121-hookup-guide
        ;   https://github.com/adafruit/Adafruit_MPR121/blob/master/Adafruit_MPR121.cpp#L22
        ;   https://github.com/BareConductive/mpr121/blob/public/MPR121/MPR121.h
        ;   https://community.freescale.com/thread/300901
        ;   https://github.com/AMPWorks/ArduinoLibs/blob/master/MPR121/MPR121.cpp
        ;   http://nagasm.org/ASL/max05/fig2/MPR121.txt

        ; Perform a Soft Reset. Most registers return to 0x00.
        movlw   SRST
        movwf   REG_ADDR
        movlw   0x63
        call    write_reg

        ; Configure the baselines, per the AN3944 Quick Start guide.
        movlw   MHD_R
        movwf   REG_ADDR

        movlw   0x01
        call    write_reg       ; MHD_R = 0x01

        movlw   0x01
        call    write_reg       ; NHD_R = 0x01

        ; NCL_R = 0x00
        ; FDL_R = 0x00

        movlw   MHD_F
        movwf   REG_ADDR

        movlw   0x01
        call    write_reg       ; MHD_F = 0x01

        movlw   0x01
        call    write_reg       ; NHD_F = 0x01

        movlw   0xff
        call    write_reg       ; NCL_F = 0xff

        movlw   0x02
        call    write_reg       ; FDL_F = 0x02

        ; NHD_T = 0x00
        ; NCL_T = 0x00
        ; FDL_T = 0x00

        ; Set the Touch/Release Thresholds for ELE0..ELE10 per the
        ; AN3944 Quick Start Guide.
        movlw   11              ; 11 of the 12 electrodes
        movwf   ELE_COUNTER

        movlw   ELE0_T
        movwf   REG_ADDR

init_loop:
        movlw   0x0f
        call    write_reg       ; ELE*_T = 0x0f
        movlw   0x0a
        call    write_reg       ; ELE*_R = 0x0a

        decfsz  ELE_COUNTER, F
        goto    init_loop

        ; AFE CONFIGURATION REGISTER (AFE1, 0x5c, default 0x10)
        ;   FFI = 00      : take 6 samples (default)
        ;   CDC = 000000  : disable charging (will be set by auto-config)
        movlw   AFE1
        movwf   REG_ADDR
        movlw   0x00
        call    write_reg

        ; FILTER CONFIGURATION REGISTER (AFE2, 0x5d, default 0x24)
        ;   CDT = 000  : disable charging (will be set by auto-config)
        ;   SFI = 00   : #samples = 4 (default)
        ;   ESI = 100  : interval period = 16 ms (default)
        movlw   0x04
        call    write_reg

        ; ELE11 is not used as a sensor. Instead, the header containing all
        ; sensor pins uses ELE11 to carry GND to the sensor board. Thus, we
        ; configure ELE11 as a GPIO OUTPUT pin, and set it to LOW.

        ; GPIO_CTL0 = 0x00
        ; GPIO_CTL1 = 0x00
        ; GPIO_DAT  = 0x00

        ; ELE11 is an OUTPUT.
        movlw   GPIO_DIR
        movwf   REG_ADDR
        movlw   0x80
        call    write_reg       ; GPIO_DIR = 0x80

        ; ELE11 is enabled as a GPIO pin.
        movlw   0x80
        call    write_reg       ; GPIO_EN = 0x80

        ; NOTE: GPIO_CTL0/1, EN, and DIR define ELE11 as a CMOS OUTPUT pin.
        ; DAT is at the reset condition of LOW.

        ; Perform autoconfig using values for 3.3V operation. See AN3889
        ; for the equations that derive these values.

        ; AUTO-CONFIGURE CONTROL REGISTER 0 (AUTO_CFG0, 0x7b)
        ;   FFI   = 00  : copied from FFI bits in AFE1 (0x5c)
        ;   RETRY = 00  : no retry
        ;   BVA   = 10  : copied from CL bits in ECR (0x5e)
        ;   ARE   = 1   : auto-reconfiguration enabled
        ;   ACE   = 1   : auto-configuration enabled
        movlw   AUTO_CFG0
        movwf   REG_ADDR
        movlw   0x0b
        call    write_reg       ; AUTO_CFG0 = 0x0b

        incf    REG_ADDR, F     ; skip AUTO_CFG1 (0x7c)

        movlw   0xc9
        call    write_reg       ; AUTO_USL = 0xc9

        movlw   0x83
        call    write_reg       ; AUTO_LSL = 0x83

        movlw   0xb5
        call    write_reg       ; AUTO_TL = 0xb5

        ; Initialized. Move into RUN mode with ELE0..10 operating.
        ;
        ; Note: this will also start the auto-config process.
        ;
        ; ELECTRODE CONFIGURATION REGISTER (ECR, 0x5e)
        ;   CL         = 00    : baseline tracking enabled
        ;   ELEPROX_EN = 00    : proximity detection disabled
        ;   ELE_EN     = 1011  : RUN mode with ELE0..10
        movlw   ECR
        movwf   REG_ADDR
        movlw   0x0b
        call    write_reg       ; ECR = 0x0b

        return


; INTERNAL FUNCTION:
;   write W to the register specifed in REG_ADDR, to the device specified
;   by WHICH_MPR.
;
; ENTRY:  value in W
;         register in REG_ADDR
;         device at WHICH_MPR (including R/W flag)
; EXIT:   flags and W are indeterminate
;         REG_ADDR is incremented
; ASSUME: Bank 0.
;
write_reg:
        movwf   REG_VALUE

        ; ### are we guaranteed the bus is idle? can we simply issue a START?

_write_retry:
        call    I2C_M_start

        movfw   WHICH_MPR
        call    I2C_M_send_one
        bnz     write_failed

        movfw   REG_ADDR
        call    I2C_M_send_one
        bnz     write_failed

        movfw   REG_VALUE
        call    I2C_M_send_one
        bnz     write_failed

        call    I2C_M_stop

_write_exit:
        incf    REG_ADDR, F
        return

write_failed:
        call    I2C_skip

        ; ### retry?
        ; goto _write_retry

        goto    _write_exit     ; ### wrong, but whatevs. should signal error.


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