309 void I2cData (unsigned char DevPtr, unsigned char TxBytes, 
310                 unsigned char TX1, unsigned char TX2, unsigned char TX3,
311                 unsigned char TX4, unsigned char RxBytes)
312 {// Exchange data with I2C device
313     I2c[DevPtr].Flag.Tx = TxBytes;  // n bytes to TX
314     I2c[DevPtr].TxBuff[0] = TX1 ;   // first byte
315     I2c[DevPtr].TxBuff[1] = TX2 ;   // second byte
316     I2c[DevPtr].TxBuff[2] = TX3 ;   // third byte
317     I2c[DevPtr].TxBuff[3] = TX4 ;   // fourth byte
318     I2c[DevPtr].Flag.Rx = RxBytes;  // n bytes to RX
319     I2c[DevPtr].Done = 0;           // reset flag, wait for execution
320 }

Each I2C device has its own buffer (4 byte for RX & 4 byte for TX). The device specific routine fills up the buffer with the bytes to exchange and sets the flags according to the actions to perform:
I2c[I2cDevPtr].Flag.Rx
I2c[I2cDevPtr].Flag.Tx

All of the critical procedures has been rewritten in a time-safe mode using SSP interrupts, status flags and global variables in a Finite State Machine.
A useful trace to use this kind of technique is the Microchip AN736.

Application layer
239 void I2cHighService (void)
240 {
241  /*
242  initialize I2C sequence resetting flags, counters and buffers
243  kick start
244  all the following procedures will be triggered by interrupts
245  */
246   Ptr.Tx = 0;       // reset TX buffer pointer
247   Ptr.Rx = 0;       // reset RX buffer pointer
248   I2c[I2cDevPtr].RxBuff[0] = 0; // reset RX buffer
249   I2c[I2cDevPtr].RxBuff[1] = 0; // reset RX buffer
250   I2cEventFlag = 0;
251   I2cBusCollFlag = 0;
252   I2cBusyFlag = 1;
253   I2cStat = START;  // FSM pointer
254   
255   StartI2C();       // start with first status 
256   
257 }
I2cHigh Level layer

The High Level procedure is executed if the addressed device has something to exchange:

I2c[I2cDevPtr].Flag.Rx !=0 || I2c[I2cDevPtr].Flag.Tx!=0

and if the lower level routine is available:

I2cBusyFlag = 0.

Exchanges the bytes through the lower level routine and sets the I2cBusyFlag

The I2C communication is performed through several layers .

The Low Level procedure is the real FSM and is performed if the ISR has set I2cEventFlag

-START->ADRW-> if there is something to TX
otherwise Rx

-WRITE byte[0]->...WRITE byte[n]->

-RSTART->ADRR->

-READ byte[0]->ACK...READ byte[n]NACK->

-STOP

-FINE

Description of the FSM

1-the I2c event is communicated from SSP through the interrupt. I2cLowService executes the I2C sequence to exchange a single byte, resets I2C event or bus collision flag and, once exchanged all the buffer for that specific device, resets I2cBusyFlag to enable upper level routine

2-I2cBusyFlag is set from I2cHighService and reset from I2cLowService.

3-The device specific routine has filled up the buffer, set the I2cHighService flags, initialized the I2cLowService sequence. This one exchanges every single byte. At each byte exchanged the counter is decreased. When the TX counter is 0 it starts the RX. When both counters are 0 it can start with the following device if needed.

4-All the devices buffers have been used, at next cycle it restarts form the first device.

5-It's ready to control the next device at next cycle

118 void I2cLowService (void)
119 {
120   I2cEventFlag=0; // wait for next interrupt
121 
122   if (I2cBusCollFlag || SSPCON2bits.ACKSTAT)
123   { // Collision on bus or NACK ->  sequence aborted
124     I2cBusCollFlag = 0; // error status reset
125     I2cStat = FINE;        // current status = FINE
126     StopI2C();             // sends stop
127     // it doesn't reset RX & TX counter to retry on next round
128   }
129   
130   else
131   { 
132   // execute scheduled sequence for each byte in the selected record
133   
134     switch (I2cStat)
135     {
136       case (STRT):  // START sequence
137         if (I2c[I2cDevPtr].Flag.Tx > 0)// something to send?
138         {
139              I2cStat = WRITE;             // next status
140              SSPBUF = I2cAdr[I2cDevPtr];  // update flag: R/W = write
141         }
142         else  // if no bytes to send it has some byte to receive
143         {
144             I2cStat = READ;              // next status
145             SSPBUF = I2cAdr[I2cDevPtr]+1;// update flag: R/W = read
146         } 
147         break;
148       
149       
150       case (WRITE):
151         /* sends Nth byte
152            all bytes sent?
153            all bytes received?
154            otherwise stops sequences
155         */
156         SSPBUF = I2c[I2cDevPtr].TxBuff[Ptr.Tx];    // TX first byte
157         Ptr.Tx ++;                                 // points to next byte
158         if (Ptr.Tx >= I2c[I2cDevPtr].Flag.Tx)      // all bytes sent?
159         {
160           if (I2c[I2cDevPtr].Flag.Rx > 0)  // all bytes received?
161           {
162             I2cStat = RSTART;              // starts RX sequence
163           }
164           else                             // nothing to receive
165           {
166               I2cStat = STP;               
167           }
168         }
169         else                             // still something to TX
170         {
171             I2cStat = WRITE;             // TX next byte(s)
172         }
173         break;
174         
175         
176       case (READ):
177         SSPCON2bits.RCEN = 1;                   // starts RX sequence
178         Ptr.Rx ++;                              // next byte Rx
179         if (Ptr.Rx >= I2c[I2cDevPtr].Flag.Rx)   // all bytes received?
180         {
181           I2cStat = NACK;               // YES, send NACK (Rx over)
182         }
183         else
184         {
185           I2cStat = ACK;                // NO, send ACK (Rx proceed)
186         }     
187         break;
188         
189         
190       case (ACK):
191         I2c[I2cDevPtr].RxBuff[Ptr.Rx-1] = SSPBUF; // store Nth byte received
192         I2cStat = READ;                           // more bytes
193         AckI2C();
194         break;
195       
196         
197       case (NACK):
198         I2c[I2cDevPtr].RxBuff[Ptr.Rx-1] = SSPBUF; // store Nth byte received
199         I2cStat = STP;                           // last byte received
200         NotAckI2C();                
201         break;
202         
203         
204       case (RSTART):
205         // reinit bus without release
206         I2cStat = ADRR;              // next status = start RX
207         RestartI2C();
208         break;
209         
210         
211       case (ADRR):
212         I2cStat = READ;              // next status = RX
213         SSPBUF = I2cAdr[I2cDevPtr]+1;// update flag: R/W = read
214         break;
215     
216     
217       case (STP):     
218         I2cStat = FINE;             // next status
219         I2c[I2cDevPtr].Flag.Rx = 0; // no more bytes to RX or TX
220         I2c[I2cDevPtr].Flag.Tx = 0; // high level procedures can start again
221         StopI2C();                  // send stop
222         break;
223         
224         
225       case (FINE):
226         I2cBusyFlag = 0;            // I2C comm over
227         I2c[I2cDevPtr].Done = 1;    // procedure complete for this device
228         break;
229       
230         
231       default:
232         break;
233         
234     } // end switch
235   } // end else
236           
237 }
I2cLow Level layer
in the main loop the different flags are checked starting from below. The whole cycle is repeated for each device, if needed, scanning the buffers filled by the devices specific procedures.
255 // High priority interrupt vector
256 void interrupt high_isr (void)
257 {
258      ....... 
293 
294     if (PIR1bits.SSPIF)      // an I2C event is over
295     {
296         PIR1bits.SSPIF = 0;  // reset I2c interrupt flag
297         I2cEventFlag = 1;    // I2cLowService will be executed
298     }
299 
300     if (PIR2bits.BCLIF)      // I2c bus collision
301     {
302         PIR2bits.BCLIF = 0;  // interrupt flag reset
303         I2cEventFlag = 1;    // execute I2cLowService
304         I2cBusCollFlag = 1;  // a collision occurred
305     }
306 }  
Interrupt Service Routine
The whole Finite State Machine is driven by the SSP peripheral interrupt
update on 13-08-2014
Software

Software

I2C procedures

The whole MPLABX C project
for the PIC18F4620
is available as an open source at
GitHub repository