Electronic – Can’t get I2C to work on PIC32 (“skips” bytes/hangs)

i2cpic

I am working on a PIC32-MAXI-WEB development board from Olimex that I'd like to have communicating with an Invensense MPU-6050 6DOF accelerometer/gyro. By modifying an demo from Olimex I managed to get the pic communicating.

I started working on a project for which I created a new project(in IDE) as I did not need/want the FreeRTOS kernel that the demo is built on. However I can't make the same I2C code work there. I get some output, but not what I should get.

Helper functions from the Olimex DEMO:

BOOL StartTransfer( BOOL restart )
{
    I2C_STATUS  status;

    // Send the Start (or Restart) signal
    if(restart)
    {
        I2CRepeatStart(I2C2);
    }
    else
    {
        // Wait for the bus to be idle, then start the transfer
        while( !I2CBusIsIdle(I2C2) );

        if(I2CStart(I2C2) != I2C2)
        {
            DBPRINTF("Error: Bus collision during transfer Start\n");
            return FALSE;
        }
    }

    // Wait for the signal to complete
    do
    {

        status = I2CGetStatus(I2C2);

    } while ( !(status & I2C_START) );

    return TRUE;
}


BOOL TransmitOneByte( UINT8 data )
{
    // Wait for the transmitter to be ready
    while(!I2CTransmitterIsReady(I2C2));

    // Transmit the byte
    if(I2CSendByte(I2C2, data) == I2C_MASTER_BUS_COLLISION)
    {
        DBPRINTF("Error: I2C Master Bus Collision\n");
        return FALSE;
    }

    // Wait for the transmission to finish
    while(!I2CTransmissionHasCompleted(I2C2));

    return TRUE;
}

void StopTransfer( void )
{
    I2C_STATUS  status;

    // Send the Stop signal
    I2CStop(I2C2);


    // Wait for the signal to complete
    do
    {
        status = I2CGetStatus(I2C2);

    } while ( !(status & I2C_STOP) );
}

My code:

int I2C_CLOCK_SPEED = 400000;

int actualClock;

// Configure Various I2C Options
I2CConfigure(I2C2, 0);

// Set Desired Operation Frequency
actualClock = I2CSetFrequency(I2C2, GetPeripheralClock(), I2C_CLOCK_SPEED);
if ( abs(actualClock-I2C_CLOCK_SPEED) > I2C_CLOCK_SPEED/10 ) {
    return -1;
}

// Enable the module
I2CEnable(I2C2, TRUE);

BYTE ADDR = 0x68<<1;

BIT WRITE = 0;
BIT READ = 1;

BYTE tmp;

StartTransfer(FALSE); // Read power management register

TransmitOneByte(ADDR|WRITE);
TransmitOneByte(0x75);

StartTransfer(TRUE);

TransmitOneByte(ADDR |READ);
I2CReceiverEnable(I2C2, TRUE);
while(!I2CReceivedDataIsAvailable(I2C2));
I2CAcknowledgeByte(I2C2, FALSE);
tmp = I2CGetByte(I2C2);

StopTransfer();

With a logic analyzer I can see that it seems to drop the address and send just the second byte of the three. It also fails to send the stop bit/return the bus to idle:

raw i2c logic capture
i2c analysis

It should not be a hardware problem as the original demo works. I am using the xc32 compiler and MPLAB X IDE.

Best Answer

Some observations:

1) You're blindly calling StartTransfer(FALSE) without checking to see if the start condition was successful (it should return TRUE if it was - if false, you shouldn't send anything until the bus is clear and the start is valid.)

2) After you send the address out, you're not checking to see if it was acknowledged before sending the data byte out. You need to make sure the address is acknowledged before taking any further action - if it isn't, you need to send a stop.

3) Replace the canned library calls with direct SFR manipulation (i.e. write and read directly to the I2C peripheral SFRs) - the library code often has nuances that aren't immediately obvious and since source isn't available, you don't have an easy way to troubleshoot the issue.