Posts

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

Setup PIC Timer with Interrupt Example (18F Family, MikroC)

software development on PIC microcontroler

Setting up a PIC timer to the correct frequency can be a tricky business for the uninitiated Software Engineer (i.e. Me). So I was pretty happy when I came across this great on-line tool whereby you just type in your oscillator frequency and desired interrupt rate and it generates the setup code for you!

For example I have an 8Mhz clock and wanted a 1KHz interrupt on Timer0, I punched that in and the tool said I should use this set-up code:

//
T0CONbits.T08BIT = 0;
T0CONbits.T0CS = 0;
T0CONbits.PSA = 0;
T0CONbits.T0PS2 = 1;
T0CONbits.T0PS1 = 0;
T0CONbits.T0PS0 = 0;
TMR0H = 0xB;
TMR0L = 0xDC;
T0CONbits.TMR0ON = 1;
//

Happy days!

Now there is a little more to defining the software interrupt handler and enabling the interrupt so I have included the code for a slightly more verbose example that configures and enables Timer0 via mikroC on the PIC18F25K22:

//
void init_timers();
void main() {
  init_timers();
  for (;;) {
    // do something...
  }
}
void init_timers() {
  // Initialise Timer0 for a 1Khz interrupt
  // 8Mhz clock & want 1Khz interrupt
  // See http://www.enmcu.com/software/timer0calculatorandcodegeneration
  // for values.
  //
  INTCON.GIE=1;         //globle interrupt enable
  INTCON.PEIE=1;        //peripharel interrupt enable
  INTCON.TMR0IF = 0x0;  //Clear timer0 overflow interrupt flag
  INTCON.TMR0IE = 1;    //enable the timer0 by setting TRM0IE flag
  T0CON.T08BIT = 0;     // 16 Bit timer
  T0CON.T0CS = 0;       // Internal clock
  T0CON.PSA = 1;        // Set scaler to 1:4
  TMR0H = 0xF8;         // Initial count of 0xF830
  TMR0L = 0x30;
  T0CON.TMR0ON = 1;     // Turn Timer0 on.
}
// interrupt handler for the timer0 overflow
void interrupt(void) {
  // http://www.enmcu.com/software/timer0calculatorandcodegeneration
  
  // Reset the timer count
  TMR0H=0xF8;
  TMR0L=0x30;
  
  // Reset interrupt flag
  INTCON.TMR0IF = 0;
  // Do some work here
}
//