; ; This code is Open Source, relased under GPL Licence, commercial use prohibitted. ; Version 1.0, 2017-08-17 12:50 ; ; I2C Transmit and Recieve, using base-line instruction set (PIC-33), Steve's Macro instruction set ; This version uses 2 pins only , so uses TRIS to isolate the drive from I2C bus and thus not very fast ; ; [I use a faster approach, no TRIS but needs 4 pins (2 out, 2 in) ... ; ... plus 2x Shockley diodes to isolate the 'Hi' drive (from the output only pins) from the I2C bus ] ; ; Quick overview of I2C operation: ; 1) To start a transfer, Master ensures CLK is HI, then pulls Data Lo, then CLK Lo ; 2) To send data, Master sets up Data, then sends CLK Hi pulse (Master has to check CLK goes Hi as slave can hold it Lo to force wait) ; 3) To fetch data, Master sets CLK Hi (checks CLK Hi), samples data, sets CLK LO ; 4) After 8 bits, Master checks the Slave Ack bit with a Clk Hi (+wait for hI). Ack = data Lo = OK, Nack = Hi = fail. ; 5) After Ack, both Master and Slave can hold CLK Lo until it's ready for next byte ; 6) To release the bus, Master removes all drive (allows both CLK and Data to go Hi) ; 7) To indicate busy, Slave waits until Master has recieved Ack (Clk Lo) then holds CLK Lo until it's ready ; ; See I2C manual:- http://www.nxp.com/docs/en/user-guide/UM10204.pdf ; ; ; NB. This code assumes this PIC is the (only) Master (i.e. I2C in single Master configuration) ; ; ; I2CPORT = the i/o PORT (address) containing the 3 bits used for I2C ; bSCL = Clk (i/o pin) bit number ; bSDA = Data (i/o pin) bit number ; rI2Ctris contains a copy of the tri-state settings for I2CPORT ; (this I2C subrouine will only modify the bSCL and bSDA bits) ; rData is the data byte to be sent (or recieved) ; rTemp is used as the bit counter ; ; CALL I2Cstart to grab the I2C bus and transmit a data byte from rData (this selects the Slave for TxD or RxD) ; CALL I2CTxD to transmit a date byte (start must be called first) ; CALL I2CRxD to recieve a byte (start must be called first) ; CALL I2Cstop to release the bus (must be called after TxD or RxD) ; ; On Return, Acc can be used for 'Skip if no Error' (ADD Acc,PCL) as follows :- ; If Error (no Ack recieved), Acc == 0 and ADD Acc,PCL will execute the next instruction (typically a JMP to error handler) ; If no Error, Ack recieved, Acc = 1 and ADD Acc,PCL will skip the next instruction ; NOTE. To work, the CALL has to be from LOW half of 512 address bank (ADD PCL clears bit9), and can't be on FE or FF (page boundary) ; ; WARNING - all subroutines will 'lock up' if the sCLK line is being held Lo (i.e. whilst Slave forces bit/bus wait) ; ; I2Cstart ;CALL to start the I2C and send a (slave select) byte. ; NOTE. we always start with a 'select slave', so at least one byte TxD always comes before RxD ; ; Don't assume this routine has been used befor, so start by making sure the clk and data drive bits are disabled (tri-state) bSET rI2Ctris,bSCL ;Set bits in tri-state 'copy' reg. (Hi = set tristate = disable output, set as input) bSET rI2Ctris,bSDA COPY rI2Ctris,Acc ;get the tri-state copy settings .. TRIS I2CPORT ; and make sure I2C lines are tri-state (will be pulled hi by the I2C 200R bus pull-ups)) ; Now make sure the clk and data bits will be driven Lo when TRIS ebables them ; WARNING bSET/bCLR one bit at a time will READ ALL THE PINS, then set ONLY that one, then write them to the o/p latch ; any pin that is an input will get it's current state written to the o/p latch .. ; Since bith SCL and SDA are tri-state, a bCLR on one will always set the other Hi ! ; so setting 2nd pin Lo can't be done using bCLR direct bCLR I2CPORT,bSCL,Acc ;get the pin state to Acc (with clk set lo) bCLR Acc,bSDA ;force Data 0 COPY Acc,I2CPORT ;copy settings to the port o/p latch (both clk and data 0) ; ; OK, now grab the bus .. first make sure CLK is Hi SKIPset I2CPORT,bSCL JMP $-1 ; Yep - set data Lo COPY rI2Ctris,Acc bCLR Acc,bSDA ;rtris with data Lo to Acc TRIS I2CPORT ; .. and to the I2C bus ; OK, now to select the Bus, with data Lo, set CLK lO .. then wait a bit bCLR Acc,bSCL ;set for o/p clk Lo TRIS I2CPORT ;clk Lo (and data Lo) to the I2C bus ; OK thats the start, now send data .. ; I2CTxD ; Entry point for transmit (another) byte (I2Cstart must have been called first to select the slave ..) LOAD 0x08 ;Set the bit count (DECFSZ will dec and skip if zero to end the loop) COPY Acc, rTemp ; (re)load the Acc with tris settings COPY rI2Ctris,Acc bCLR Acc,bSCL ;default = both clk and data Hi, need clk Lo ; ; Bit transmit will loop back here with clk Lo ready to set next data bit I2CdataTx ; OK, now we can set a (next) data bit .. set data, Clk Hi loop for next ROTR rData ;first (next) bit = b0 to Cy bCLR Acc,bSDA ;assume it's zero, set tris to o/p 0 SKIP nCY ;skip if bit is 0 bSET Acc,bSDA ;need hi, tris to input (i.e. set SDA bit Hi) TRIS I2CPORT ;data to the I2C bus ; release the CLK bSET Acc,bSCL ;prepare to release the clk (hi) TRIS I2CPORT ;release the Clk (data unchanged) SKIPset I2CPORT,bSCL ;check CLK Hi .. JMP $-1 ; .. Slave can hold clk Lo to force a wait ; Slace has data, pull clk Lo before changing to next bit bCLR Acc,bSCL ;enable output (Clk Lo) TRIS I2CPORT ;clk lo to the I2C bus ; any more to do ? DECFSZ rTemp ;dec count, skip if zero JMP I2CdataTx ; ; all done, release the data line, get the Ack bSET Acc,bSDA ;tri-state data bit Hi TRIS I2CPORT ;release data line ; now clk in the slave Ack bit (Hi = failed, Lo = ok) bSET Acc,bSCL ;clk hi ; this is where RxD arrives to get the slave Ack I2CslaveAck TRIS I2CPORT ; .. to the I2C bus bCLR Acc,bSCL ;prepare exit with clk Lo ; get slave Ack SKIPset I2CPORT,bSCL ; remember = slave can hold CLK Lo to force a wait JMP $-1 ; OK ack status is valid, sample data pin SKIPset I2CPORT,bSDA ;input data Lo is OK, hi is error JMP $+3 TRIS I2CPORT ; Clk lo to I2C bus RETURN 0x01 ;slave sent 0, all OK (return 1 for ADD Acc,PCL skip error jump if OK) TRIS I2CPORT ; Clk Lo to I2C bus RETURN 0 ;return with Acc= 0 = error (return 0 for ADD Acc,PCL, execute next inst = jump to error) ; ; ; ; I2CRxD ;reviece a byte (I2Cstart must have been called first to select the slave ..) ; on entry, I2C bus clk is Lo (rI2Ctris has both bits Hi) COPY rI2Ctris,Acc ;get the tri-state copy settings LOAD rDate 0x08 ;set the bit count ; the sequence is much the same as data transmit, expect we wait for clk Hi then sample bSDA I2CRxDwait TRIS I2CPORT ;release the clk SKIPclr I2CPORT,bSCL ;wait for CLk hi JMP $-1 bCLR Cy ;assume data bit is 0 SKIPclr I2CPORT,bSDA ;skip if is 0 bSET Cy ROTL rData ;get the bit bCLR Acc,bSCL ;say we got it (clk lo) TRIS I2CPORT bSET Acc,bSCL ;prepare to release the clk DECFSZ rTemp ;dec count, skip if zero JMP I2CRxDwait ; all done, exit via get slave Ack JMP I2CslaveAck ; ; ; I2Cstop ;release the I2C bus ; on entry, I2C bus clk is Lo (rI2Ctris has both bits Hi) ; to release, data Lo, then Clk Hi, then Data Hi COPY rI2Ctris,Acc ;get tris copy for this port bCLR Acc,bSDA ;set for tris data lo bCLR Acc,bSCL ;now clk Lo TRIS I2CPORT ;set both clk data lo bSET Acc,bSCL ; clk to Hi TRIS I2CPORT ;release the clk SKIPset I2CPORT,bSCL ;wait for slave to relese clk JMP $-1 COPY rI2Ctris,Acc ;both data and clk hi TRIS I2CPORT ;release the bus RETURN 0x01 ;return with all OK ; ; ends