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.
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 }
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 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
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 }
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 }
Software
The whole MPLABX C project
for the PIC18F4620
is available as an open source at
GitHub repository