\ name : MSP430FR5xxx_I2C_Slave.f
RST_STATE
\ NOECHO

\ Copyright (C) <2015>  <J.M. THOORENS>
\
\ This program is free software: you can redistribute it and/or modify
\ it under the terms of the GNU General Public License as published by
\ the Free Software Foundation, either version 3 of the License, or
\ (at your option) any later version.
\
\ This program is distributed in the hope that it will be useful,
\ but WITHOUT ANY WARRANTY\ without even the implied warranty of
\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
\ GNU General Public License for more details.
\
\ You should have received a copy of the GNU General Public License
\ along with this program.  If not, see <http://www.gnu.org/licenses/>.
\
\ driver I2C SLAVE with START interrupt only, for any I2C_Slave addresses
\ Target: MSP430FR5xxx @ 8,16,24 MHz
\ version 1.0 2015-03-17

\ ---------------------------------------------------------------------------------------------------------------------;
\ SCL clock generation, timing, and test of data(s) number are made by I2C_Master.
\ slave can strech SCL low after Start Condition and after any bit.
\
\ address Ack/Nack is generated by the slave on SDA line (released by the master)
\ Two groups of eight addresses (000$xxy and 1111xxxy) are not allowed (reserved)
\ after address or data is sent, the transmitter (Master or Slave) must release SDA line to allow (N)Ack by the receiver
\ data Ack/Nack are generated by the receiver (master or slave) on SDA line
\ a master receiver must signal the end of data to the slave transmitter by sending a Nack bit
\ Stop or restart conditions must be generated by master after a Nack bit.
\ after Ack bit is sent, Slave must release SDA line to allow master to do stop or restart conditions
\
\     __      _____ _____ _..._ _____ _____ _NACK _____ _____ _..._ _____ _____ _NACK     _
\ SDA   \____/_MSB_X_____X_..._X_LSB_X_R/W_x_ACK_x_MSB_X_____X_..._X_____X_LSB_X_ACK_X___/
\     _____     _     _           _     _     _     _     _           _     _     _     ___
\ SCL      \___/1\___/2\___...___/7\___/8\___/9\___/1\___/2\___...___/7\___/8\___/9\___/
\       ^   ^                             ^     ^                             ^     ^    ^
\       |   |Slave Stretch Low            |SSL  |SSL                          |SSL  |SSL |
\       |                                                                                |
\       |Start Condition                                                                 |stoP Condition
\
\     __      _____ _____ _..._ _____ _____ _NACK _____ _____ _..._ _____ _____ _NACK ___
\ SDA   \____/_MSB_X_____X_..._X_LSB_X_R/W_x_ACK_x_MSB_X_____X_..._X_____X_LSB_X_ACK_X   \____...
\     _____     _     _           _     _     _     _     _           _     _     _     ____
\ SCL      \___/1\___/2\___...___/7\___/8\___/9\___/1\___/2\___...___/7\___/8\___/9\___/    \_...
\       ^   ^                             ^     ^                             ^     ^    ^
\       |   |Slave Stretch Low            |SSL  |SSL                          |SSL  |SSL |
\       |                                                                                |
\       |Start Condition                                                                 |reStart Condition
\
\ tHIGH : SCL high time
\ tLOW : SCL low time
\ tBUF : SDA high time between Stop and Start conditions
\ tHD:STA : Start_Condition SCL high time after SDA is low
\ tSU:STO : Stop_Condition SCL high time before SDA rise
\ tSU:STA : Start_Condition SCL high time before SDA fall
\ tHD:DAT : SDA data change time after SCL is low
\ the SDA line must be strobe just after SCL is high
\ the SDA data must be change just after SCL is low
\ standard mode (up to 100 kHz) :   tHIGH   =   tHD:STA =   tSU:STO =   4µs
\                                   tLOW    =   tSU:STA =   tBUF    =   4,7µs
\                                   tHD:DAT <=  3,45 µs
\
\ fast mode     (up to 400 kHz) :   tHIGH   =   tHD:STA =   tSU:STO =   0,6µs
\                                   tLOW    =   tSU:STA =   tBUF    =   1,3µs
\                                   tHD:DAT <=  0,9 µs
\ -------------------------------------------------------------------------------------------------------------------;



VARIABLE I2CS_OWN       \ slave I2C address without RW flag (low byte) + DATA0 input (HIGH byte)
\ 2 ALLOT               \ next the low byte of I2CS_OWN word, it is the input buffer
VARIABLE I2CS_BUF       \ buffer output, lentgh (low byte),DATA0 output (HIGH byte)
\ 2 ALLOT               \ this byte lentgh is shared by input and output buffers

\ ******************************\
ASM I2CS_TX                     \ TX part of I2C_Slave
\ ******************************\
\                               \       T = TX buffer_org
\                               \ use   W = TX buffer_ptr
\                               \ out : low(I2CS_BUF) = RX or TX lentgh
\ ------------------------------\
MOV T,W                         \ W = TX_buf_ptr -1
BEGIN                           \
    ADD #1,W                    \ first reserve one byte for length then inc
    MOV.B @W,&UCB0TXBUF         \ +[W] --> UCB0TXBUF
\   ----------------------------\
\   slave send byte             \
\   ----------------------------\
    BEGIN                       \
        BIT #$0C,&UCB0IFG       \ UCB0IFG(STP,STT) = 1 ?
        0<> IF                  \
\           --------------------\
\           stop or restart received
\           --------------------\
            SUB   T,W           \
            SUB.B #1,W          \ sub #1, because char +[W] is not sent
            MOV.B W,0(T)        \ store length in first byte of buffer output
            BIC #UCTR,&UCB0CTLW0 \ reset UCTR R/W bit for next START
            MOV @RSP+,PC        \ ===> ret
\           --------------------\
        THEN                    \
        BIT #2,&UCB0IFG         \ UCB0IFG(TX0) = 1 ?
    0<> UNTIL                   \
AGAIN                           \
ENDASM                          \
    \

\ -------------+------+------+------+------++---+---+---+---+---------+
\ SR(low byte) | SCG1 | SCG0 |OSCOFF|CPUOFF||GIE| N | Z | C | current |
\ -------------+------+------+------+------++---+---+---+---+---------+
\ LPM0 = $18  |  0   |  0   |  0   |  1   || 1 | x | x | x |  180uA  | default mode
\ LPM1 = $58  |  0   |  1   |  0   |  1   || 1 | x | x | x |         | same mode as LPM0
\ LPM2 = $98  |  1   |  0   |  0   |  1   || 1 | x | x | x |   60uA  |
\ LPM3 = $D8  |  1   |  1   |  0   |  1   || 1 | x | x | x |   10uA  | 32768Hz XTAL is running
\ LPM4 = $F8  |  1   |  1   |  1   |  1   || 1 | x | x | x |    6uA  |
\ -------------+------+------+------+------++---+---+---+---+---------+

\ **************************************\
ASM I2C_S                               \ <== eUSCIB0 interrupt vector : i2c_addres&R/w sent by master is received
\ **************************************\
BIC #$F8,0(RSP)                         \ SCG1,SCG0,OSCOFF,CPUOFF and GIE are OFF in retiSR to force LPM0_LOOP with pending interrupt
MOV #$98,&LPM_MODE                      \ to reenter in LPM2 mode. LPM3-4 don't work with MSP430FR57xx.
\ --------------------------------------\
MOV #I2CS_BUF,T                         \ T = buffer output address -1
MOV #I2CS_OWN,S                         \ S = buffer input address -1
MOV #0,&UCB0IFG                         \ write UCB0IFG to clear all int flags (STTIFG,TXIFG,..)
CMP.B &UCB0I2COA0,&UCB0ADDRX            \ UCB0ADDRX = own address ?
0<> IF                                   \ 
BIC #UCTXACK,&UCB0CTLW0                 \ send Nack address
RETI                                    \
ELSE
\ --------------------------------------\
\ It's my own address                   \
\ --------------------------------------\
    BIS #UCTXACK,&UCB0CTLW0             \ send software Ack address
    BIT #UCTR,&UCB0CTLW0                \ test UCB0CTLW0(UCTR) R/W bit
    0= IF                               \ I2C_Master Write
        \ ------------------------------\
        \ slave receive datas           \ yes
        \ ------------------------------\
        MOV S,W                         \ W = input buffer address - 1
        BEGIN                           \
            \ --------------------------\
            \ slave receive one byte    \
            \ --------------------------\
            BEGIN                       \
                BIT #$8C,&UCB0IFG       \ UCB0IFG(STP,STT,CLTO) = 1 ? (STOP, START, SCL low timeout)
                0<> IF                  \
                    \ ------------------\
                    \ TX stop or restart\ from master
                    \ ------------------\
                    SUB   S,W           \ W = Adr_end - Adr_start = length
                    MOV.B W,0(T)        \ store length in first byte of buffer output
                    \ --------------------\
                    \ here your RX process\
                    \ --------------------\


                    \ --------------------\
                    \ end of RX process   \
                    \ --------------------\
                    RETI                \
                THEN                    \ if not (stop, restart, CLTO)
                BIT #1,&UCB0IFG         \ UCB0IFG(RX0) = 1 ?
            0<> UNTIL                   \ 
            ADD #1,W                    \ reserve one byte for length first, then preincrement
            MOV.B &UCB0RXBUF,0(W)       \ [UCB0RXBUF] = data --> +[W]
        AGAIN                           \ loop for new received data if any
    THEN                                \ end of I2C_Master read
\   ------------------------------------\
\   slave transmit datas variant 1      \ to loop back after bad_own_address code
\   ------------------------------------\
    CALL #I2CS_TX                       \ 
    RETI                                \
\   ------------------------------------\
\   slave transmit datas variant 2      \
\   ------------------------------------\
\         MOV T,W                         \ W = output buffer address -1
\         BEGIN                           \
\             ADD     #1,W                \ first reserve one byte for length
\             MOV.B   @W,&UCB0TXBUF       \ +[W] --> UCB0TXBUF
\ \           ----------------------------\
\ \           slave send byte             \
\ \           ----------------------------\
\             BEGIN                       \
\                 BIT.B #$0C,&UCB0IFG     \ UCB0IFG(STP,STT) = 1 ?
\                 0<> IF                  \
\ \               ------------------------\
\ \               RX stop or restart      \ from master
\ \               ------------------------\
\                     SUB   T,W           \ W = Adr_end - Adr_start = length
\                     SUB.B #1,W          \ sub #1, because char +[W] is not sent
\                     MOV.B W,0(T)        \ store length in first byte of buffer output
\                     BIC.B #UCTR,&UCB0CTLW0  \ reset UCTR R/W bit for next START
\                     RETI                \
\                 THEN                    \
\                 BIT.B #$02,&UCB0IFG     \ UCB0IFG(TX0) = 1 ?
\             0<> UNTIL                   \
\         AGAIN                           \
\   ------------------------------------\
\   End of slave transmit datas variants\
\   ------------------------------------\
THEN                                    \ if bad I2C address
\ --------------------------------------\
\ BAD I2C ADDRESS CaseOf                \
\ --------------------------------------\
\ insert here post BAD I2C address code \
\ ...that can loop back to I2CS_TX...   \
\ --------------------------------------\
ENDASM
    \

\ --------------------------------------\
CODE START                              \ init I2C_slave 
\ --------------------------------------\
\ UCB0CTLW0 = %0000 0111 1100 0001     $640
\                    -                  UCMST = 0 : I2C_Slave
\                     --                UCMODE = %11 = I2C
\                       _               USYNC=1 (always 1)
\                         --            UCSSEL=SMCLK (don't care in slave mode)
\                           -           UCTXACK=0 not auto ACK slave address
\                            -          UCTR=0 : RX (for RX address)
\                                 -     UCSWRST=1
\ UCB0CTLW1 = %0000 0000 1101 0000     $642
\                       -               UCETXINT=0 : UCTXIFG0 set address match UCxI2COAx and TX mode
\                         --            UCCLTO=%11 : SCL low time out = 34 ms
\                            -          UCSWACK=1 : UCTXACK must be written to continue
\ UCB0RXBUF                             $64C
\ UCB0TXBUF                             $64E
\ UCB0I2COA0                            $654 must be written ? enabled ?
\ UCB0ADDRX                             $65C
\ UCB0ADDMSK                            $65E
\ UCB0IE    = %0000 0000 0000 0100     $66A
\                               -       UCSTTIE : StartCond Interrupt only
\ UCB0IFG                               $66C
\ UCB0IV                                $66E : write it to clear all IFG 

\ ------------------------------\
\ init I2C_slave                \
MOV #1,&UCB0CTLW0               \ set eUSCI_B in reset state, clear UCB0IE & UCB0IFG all flags
BIS #$07A0,&UCB0CTLW0           \
BIS #$10,&UCB0CTLW1             \ set software ack address (UCSWACK=1)
MOV #%1010,&UCB0I2COA0          \ set my own address
BIS #$0400,&UCB0I2COA0          \ UCOAEN=1 enable UCB0I2COA0 with address slave
\ MOV #0,&UCB0ADDMSK              \ enable address mask for all addresses i.e. software address 
BIC #1,&UCB0CTLW0               \ activate eUSCI_B (UCSWACK release)
MOV #4,&UCB0IE                  \ enable StartCond interrupt; WARNING ! UCB0IE must be set only after UCSWACK release !
\ ------------------------------\
\ init interrupt vectors
MOV #I2C_S,&$FFEE               \ eUSCIB0 interrupt vector
\ ------------------------------\
\ init PORTA (P2:P1) (complement)
\ notice : UCB0 I2C driver seems to control only DIR register !!!
BIC.B #S_BUS,&I2CS_REN          \ SDA + SCL pullup/down disable
BIC.B #S_BUS,&I2CS_OUT          \ OUT0 : preset output low
BIS.B #S_BUS,&I2CS_SEL1         \ enable I2C I/O
\ ------------------------------\
COLON
." I2C_Slave is running. Type STOP to quit"
    LIT RECURSE IS WARM         \ insert this starting routine between COLD and WARM...
    ['] WARM >BODY EXECUTE      \ ...and continue with WARM (very, very usefull after COLD or RESET !:-)
 ;

: STOP                          \ stops multitasking, must to be used before downloading app
    ['] WARM >BODY  IS WARM     \ remove START app from FORTH init process
    ECHO COLD                   \ reset CPU, interrupt vectors, and start FORTH
;

ECHO
PWR_HERE
\ START
