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()