For many years I've been irritated by the relatively large amount of code needed to talk to I2C EEPROMS, especially on c5x parts where stack depth is limited. I swore one day I'd try to do some highly optimised code. I finally got around to it, and 30-odd sheets of paper later, below is the result - any comments & further optimisations are welcome! It's primarily targetted at c5x applications, and was designed with the following criteria & limitations in mind (based on several real applications) : Absolute minimum code size, stack usage and register usage, in that order. Must be able to read or write one *or more* bytes to a single small (<=256 byte) I2C eeprom, transferring data to/from an area of RAM pointed to by the FSR. I2C address of eeprom fixed at assemble time. Automatic retry if the eeprom is busy with a previous write. Correct I2C timing at 4MHZ. SDA and SCL will be on the same port, but no restrictions on which bits are used. No assumptions to be made on the state of SCL and/or SDA on entry - this is important where pins are shared with other funcions - SCL can often be re-used as the eeprom will ignore it as long as SDA is stable. No jump table schemes (as used in Microchip's code in the 12CE518 data sheet) to allow the code body to be moved out of page zero if required. The following limitations of the current code are considered acceptable for the applications targeted : The number of bytes to read/write, and the RAM address to read/write are likely to be fixed at assemble time, so it doesn't matter if offsets etc. are required to these values, as this costs no code space. The code will read or write up to 14 bytes per call, although the write cache size of small eeproms will usually limit writes to 8 bytes or fewer. The current code body uses 68 words (including return). The code used to set-up addresses etc. is not counted as this will be different in different applications. Three words can be saved if 'fast mode' eeproms are used (e.g. 24LC01B at 5V). 4 registers are used,(plus FSR), one of which is for the eeprom address, which is preserved and can be eliminated if only one address will be used. No internal CALLs are used. Explanatory notes - the following notes describe the more subtle aspects of the code - you don't need to understand them if you just want to use the code 'as is', but you will if you want to modify or optimise it further! The code runs in two 'phases' - a write phase and a read phase, the latter only being used for read operations. Each phase begins with a start condition, followed by the control byte. The write phase sends the control byte, the eeprom address, and for write operations, the data to be written. The read phase sends the control byte and reads the bytes from the eeprom. The variable 'cnt' holds two counts, one per nibble, stored as negative numbers, i.e. counting up to zero. Bits 7-4 hold the number of bytes in the write phase Bits 3-0 hold the number of bytes in the read phase The flags byte is used as follows : bit 0 'read' is set for reading bytes, cleared for writing bit 1 'addr' is set after the eeprom address has been sent, to ensure it only gets sent once. bit 2 'rden' is a 'read pending' flag, which causes a switch to read mode after the second control byte has been written bits 7..5 are used as a bit counter for the byte send/recive section. Using the same byte for flags and the bit count doesn't actually take any more code - the extra cycles to increment the count it by adding 20h are saved by not having to initialise the count - it's done when the flags are set up. When SCL is set high, if SDA is tri-state (input or '1' output), the SDA output register bit may get set high (which would prevent SDA going low) by read-modify-write bit operations on SCL. This problem is avoided by clearing both SDA and SCL bits together with ANDWF. This will not cause SDA glitches, as the only time this clearing will change the SDA output register state is when SDA is tri-stated. FSR is incremented on every byte - there's no point doing it conditionally as all that's needed to compensate is an assembly-time offset. Note that in a few cases you will need to add a clrwdt somewhere inside the retry loop, depending on choice of eeprom, WDT prescale setting, and the delay between a write and any subsequent read or write attempt. The worst-case write time of a 24LC01B is 10mS, and the PIC's worst-case undivided watchdog period is 9mS. Everyone's application is different, and so there is scope for further optimisation depending on particular requirements - here are a few suggestions : If only one eeprom address is needed (e.g. a single parameter block), the eeadr register can be replaced with a literal. If only single byte reads & writes are required, a couple of optimisations are possible - the INCF FSR goes, and the conditional write to INDF can be an unconditional write to the targer register, as it doesn't matter if it gets written with rubbish before the actual data is read. If the routine is called from several places, some of the set-up done in the macros could be placed at the head of the code, depending on what parameters are the same for all calls. If the system timing is such that the eeprom will never be busy writing when an attempt to access it again is made, the 2 words of retry code can be omitted. It may be possible to simplify some of the start/stop condition code if it is known that other I/O activity will not affect the state of the SCL/SDA porta and TRIS registers between calls. The retry on busy could easily be changed to return immediately, for example by replacing the 'goto retry_iic' with 'retlw 1'. The calling code could then check W to test for success. Some of the delays (as noted in comments) can be omitted for 'fast mode' compatible eeproms. For use on the 12CE51x parts, some simplification should be possible due to the open-drain output on SDA. If power consumption is an issue, you may want to add a delay to the retry loop to reduce the number of retries that will be attempted when the eeprom is busy writing. ;*********************************************************************** ; compact I2C eeprom access code V1.0 ; (C) Mike Harrison 1998 ; example code for PIC12C508 ;------------------------------------------------------- workspace cnt equ 08 ; byte count register flags equ 09 ; flags and bit count : read equ 0 ; 1 to read bytes, 0 to write addr equ 1 ; 0 to send eeprom address byte rden equ 2 ; 1 to enable read next cycle ; b5-7 used as bit counter temp equ 0a ; read/write data byte eeadr equ 0b ; eeprom address iiport equ gpio ; port address sclbit equ 4 ; SCL port pin sdabit equ 5 ; SDA port pin lotris equ b'00101' ; TRIS setting with SCL and SDA outputs, other bits as required by application hitris equ lotris+(1<, movlw address-3 ; FSR offset due to unconditional increment movwf fsr movlw 0ef - bytes ; 2 writes (control, address) + n+1 reads (control,data) call do_iic endm writeee macro bytes,address ; usage : writeee , movlw address-1 movwf fsr movlw 0e0 - (bytes <<4) ; n+2 writes (control,address,data), no reads call do_iic endm ;--------------------------------------------------------- ; calling examples : ;to read 3 bytes from eeprom addr 10..2 to registers 14..6 ; movlw 10 ; movwf eeadr ; readee 3,14 ;to write 5 bytes from registers 19..1d to eeprom address 0 ; clrf eeadr ; writeee 5,19 ;-----------------------------------------------------------do_iic ; read/write byte(s) to I2C EEPROM ; W & FSR to be setup as follows : ; read : EF - nbytes FSR = RAM address-1 ; write : E0 - (nbytes<<4) FSR = RAM address-3 ; eeadr holds eeprom address (preserved on exit) ; on exit, FSR points to the byte after the last one read/written ; nbytes can be up to 14, but eeprom write cache may limit this do_iic movwf cnt retry_iic clrf flags ; initialise flags and bit count phaseloop movlw hitris tris iiport ; ensure SDA high bsf iiport,sclbit ; SCL high bcf iiport,sdabit ; ensure SDA o/p reg low movlw lotris goto $+1 ; ensure Tsu:sta - can be omitted in fast mode tris iiport ; sda low - start condition movlw iiadr ; IIC control byte (write) btfsc flags,rden movlw iiadr+1 ; .. or read control byte if read pending movwf temp ; IIC control byte bcf iiport,sclbit ; scl low - code above ensures Thd:sta byteloop ; ; start of byte read/write section movlw lotris btfss flags,read ; set SDA high (tri-state) if reading btfsc temp,7 ; set SDA low only if writing and data bit = 0 movlw hitris ; (sda o/p register bit will be low) tris iiport goto $+1 ; wait set-up time bsf iiport,sclbit ; clock high (may set SDA o/p reg bit high) clc ; used later - done here for timing movlw 0ff^(1<