I2C1_Wr / I2C1_Rd functions hang with MikroC for PIC – Need Timeout
In some cases both the MikroC i2c functions I2C1_Wr() and I2C1_Rd() can hang or lockup indefinitely until the PIC is reset. This can happen if the I2C bus isn’t properly terminated, for example if a connection breaks or similar (broken wire or solder joint etc). Having your PIC lockup during operation is generally not very desirable if your are trying to develop a stable software system – if for some reason an i2c device hasn’t been properly connected your system will just lockup for good!
Different posts discuss this problem and its various causes:
https://forum.mikroe.com/viewtopic.php?f=13&t=21270&sid=f45f0199695794c83516f89919183cbb&start=0
and
https://forum.mikroe.com/viewtopic.php?f=88&t=60224
MikroC provided the code to their functions so that software developers (us) could work around the problem, this library implements versions of I2C1_Wr() and I2C1_Rd() that timeout rather than hang, a timeout values is passed as a parameter:
https://libstock.mikroe.com/projects/view/1052/i2c-non-blocking
The library doesn’t implement a C version, so inspired by it (thanks Danny!) I have ported the functions into C, and added some comments – use at your own risk, it works well for me on PIC18F family chips but hasn’t been exhaustively tested!
I have declared the timeouts as constants rather than allowing the timeout to be passed in as a parameter – this is so that the functions can be used as drop-in replacements without having to modify client code.
#define I2C_WRITE_TIMEOUT_US 200
unsigned short tI2C1_Wr(unsigned short d) {
const unsigned int delay = 2; // us
unsigned int max_retry = I2C_WRITE_TIMEOUT_US / delay;
unsigned int retry;
if (max_retry == 0)
max_retry = 1;
// Interrupt Flag bit - Waiting to transmit/receive
PIR1.SSP1IF = 0;
// Set data for transmission
SSP1BUF = (unsigned char)d;
// Wait for transmission to complete
// 1 = Transmit is in progress
// 0 = Transmit is not in progress
//
retry = max_retry;
while (SSP1STAT.r_not_w == 1 && --retry > 0)
delay_us(delay);
// Timed-out stop transfer and return error
if (SSP1STAT.r_not_w == 1) {
// Enable Stop Condition
SSP1CON2.PEN = 1;
return 1;
}
// Wait for completion
// 1 = The transmission/reception is complete (must be cleared by software)
// 0 = Waiting to transmit/receive
//
retry = max_retry;
while (PIR1.SSP1IF == 0 && --retry > 0)
delay_us(delay);
// Timed-out stop transfer and return error
if (PIR1.SSP1IF == 0) {
// Enable Stop Condition
SSP1CON2.PEN = 1;
return 1;
}
// Check that we got an ACK
// 1 = Acknowledge was not received
// 0 = Acknowledge was received
//
if (SSP1CON2.ACKSTAT != 0) {
// No ACK, abort
// Enable Stop Condition
SSP1CON2.PEN = 1;
return 1;
}
// All good...
return 0;
}
And to Read:
#define I2C_READ_TIMEOUT_US 200
unsigned short tI2C1_Rd(unsigned short ack) {
unsigned short d = 0;
const unsigned int delay = 2; // us
unsigned int max_retry = I2C_READ_TIMEOUT_US / delay;
unsigned int retry;
if (max_retry == 0)
max_retry = 1;
// Interrupt Flag bit - Waiting to transmit/receive
// 1 = The transmission/reception is complete (must be cleared by software)
// 0 = Waiting to transmit/receive
//
PIR1.SSP1IF = 0;
// Set receive mode
// 1 = Enables Receive mode for I2C
// 0 = Receive idle
//
SSP1CON2.RCEN = 1;
// Wait for read completion
// 1 = The transmission/reception is complete (must be cleared by software)
// 0 = Waiting to transmit/receive
//
retry = max_retry;
while (PIR1.SSP1IF == 0 && --retry > 0)
delay_us(delay);
// Still not complete, get out...
if (PIR1.SSP1IF == 0)
return 0;
// grab the data
d = (unsigned short)SSPBUF;
// ACK required?
if (ack == 0) {
// No
// 1 = Not Acknowledge
// 0 = Acknowledge
//
SSP1CON2.ACKDT = 1;
} else {
// Yes
SSP1CON2.ACKDT = 0;
}
// Interrupt Flag bit - Waiting to transmit/receive
// 1 = The transmission/reception is complete (must be cleared by software)
// 0 = Waiting to transmit/receive
//
PIR1.SSP1IF = 0;
// Start Ack sequence
// 1 = Initiate Acknowledge sequence on SDAx and SCLx pins, and transmit ACKDT data bit. Automatically cleared by hardware.
// 0 = Acknowledge sequence idle
//
SSP1CON2.ACKEN = 1;
// Wait for completion of ack sequence
retry = max_retry;
while (PIR1.SSP1IF == 0 && --retry > 0)
delay_us(delay);
return d;
}
This works for the first i2c device, it is left as an exercise for the reader to convert it for a second i2c device, i.e. I2C2_Wr() and I2C2_Rd()
