Friday, June 10, 2011

Notes on I2C and SSI bus implementation

A quick note:

When you implement I2C and/or SSI you must implement timeouts and anit-lockup code.
Both busses are driven with statements like

while(some flag)  wait forever.

And you can get locked up if there is a single clock glitch.


void I2CResetLoopTimer(int i2cDeviceIdx)
{
  xI2CConfigHandle h = I2CDevices[i2cDeviceIdx];
  h->loopTimer = xTaskGetTickCount();
}


int I2CHasError(int i2cDeviceIdx)
{
  xI2CConfigHandle h = I2CDevices[i2cDeviceIdx];
  portTickType dt = xTaskGetTickCount() - h->loopTimer;
 
  return dt > 200 ||
    I2C_GetFlagStatus(h->i2cDevice, I2C_FLAG_TIMEOUT) ||
      I2C_GetFlagStatus(h->i2cDevice, I2C_FLAG_PECERR) ||
        I2C_GetFlagStatus(h->i2cDevice, I2C_FLAG_AF) ||
          I2C_GetFlagStatus(h->i2cDevice, I2C_FLAG_ARLO) ||
            I2C_GetFlagStatus(h->i2cDevice, I2C_FLAG_BERR);
}


int I2CHasErrorAndClear(int i2cDeviceIdx)
{
  if(I2CHasError(i2cDeviceIdx))
  {
    I2CClearError(i2cDeviceIdx);
    return true;
  }
  return false;
}

And an example loop.

int WhileBusy(int i2cDeviceIdx)
{
  xI2CConfigHandle h = I2CDevices[i2cDeviceIdx];
  
  I2CResetLoopTimer(i2cDeviceIdx);
  while (I2C_GetFlagStatus(h->i2cDevice, I2C_FLAG_BUSY))
  {
    if(I2CHasErrorAndClear(i2cDeviceIdx))
    {
      return false;
    }
    
    portYIELD();
  }
  return true;
}

TF

I2C Reset on ST Microsystems Processors

So,
You have a ST processor (e.g. STM32F103) and a I2C device.  If the device, bus, or processor gets a bit error in the I2C bus, it will lock up the bus.

After trying many solutions, this one works...



void I2C_ResetAndInit()
{
/* This shuts off power to the I2C circuit on the processor. */
I2C_DeInit(I2C1);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

/* SCL  pin enable */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);

/* SDA pin enable  */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);

I2C_StructInit(&I2C1_InitDef);
// 400 khz.
I2C1_InitDef.I2C_ClockSpeed = 100000;
I2C_Init(I2C1, &I2C1_InitDef);

I2C_Cmd(I2C1, ENABLE);
}

And for the I2c Device out there on the bus...
Oh, we are using RTOS so that is the delayt and tick stuff.

portTickType Baro_LastErrorMessageTime = 0;
void BaroResetChip()
{
if(xTaskGetTickCount() - Baro_LastErrorMessageTime > 10000)
{
Baro_LastErrorMessageTime = xTaskGetTickCount();
mainMessagePrint(ROUTE_HALL, "Baro failed, reset.");
}

I2CClearError(I2C_DEVICE_BARO);
// Turn the baro power off and then back on.
// You DID put this in your design, right?
// Also, many I2C chips have a reset line that will do the same thing.
SetPwrBaro(0);
vTaskDelay(3000);
SetPwrBaro(1);

I2C_ResetAndInit();
}

In summary, the important pat is you must either reset or power cycle the I2C device, and you must use DeInit to power cycle the I2C internal device on the CPU.

TF