Bit-Banging I2C with ResetBus() Functionality

The good thing with the internet is: it allows engineers to collaborate. And here is an example: Marc is a reader of this blog had a problem with the I2C hardware of a Freescale Kinetis ARM microcontroller. In his case, the I2C bus could be stuck, and there seems no way to reset it with the I2C hardware on the microcontroller. So a solution would be to reset it with software instead.

Bit Banging Software I2C Driver

Bit Banging Software I2C Driver

I had created a software ‘bit banging’ I2C driver which can be used if there is no hardware I2C driver (see “Bit Banging I2C“), but it was not available for Kinetis and with the “Generic I2C Driver” component. But to overcome I2C issues, Marc wanted that software I2C driver for Kinetis, plus a function to reset the bus by software. He used my work as a starting point, fixed some issues and greatly extended it, and he contributed it back so everyone else can benefit :-).

Bit Banging

“Bit Banging” means that instead using dedicated hardware, normal general purpose I/O pins are used to implement a protocol like I2C or SPI. I had to develop such a bit banging I2C driver because Processor Expert did not implement a hardware driver for microcontroller like the Freescale ColdFire V2 MCF52259 :-(. Having a big banging driver is not as fast as using the hardware I2C, but for slow devices this is not much of a problem.

The code below shows how to receive a byte using bit banging:

byte I2C2_RecvChar(byte *Chr)
{
  word Trial;
  bool Acknowledge;
  word timeout;

  Trial = TRIALS;
  do {
    SDA_SetDir((bool)INPUT);     /* SDA HIGH - START SETUP */
    SCL_SetDir((bool)INPUT);     /* CLOCK HIGH PULSE */
    Delay();                     /* CLOCK HIGH PULSE & BUS FREE TIME */
    /* check that we have a valid start condition: SDA needs to be high */
    timeout = 65535U;
    while((SDA_GetVal()==0U)&&(timeout!=0U)) { /* WAIT FOR CLOCK HIGH PULSE */
      timeout--;
      I2C2_OSYIELD();
    }
    Delay();
    if (timeout==0) {
      InternalStop();
      return ERR_BUSY;
    }
    SDA_SetDir((bool)OUTPUT);
    SDA_ClrVal();                /* START CONDITION */
    Delay();                       /* START HOLD TIME */
    SCL_SetDir((bool)OUTPUT);
    SCL_ClrVal();                /* CLOCK LOW PULSE */
    Delay();
    Write((byte)(SlaveAddr + READ));
    Acknowledge = GetAck();
    --Trial;
  } while ((Trial != 0U) && Acknowledge);
  if (Acknowledge) {
    SCL_SetDir((bool)OUTPUT);
    SCL_ClrVal();                /* CLOCK LOW PULSE */
    InternalStop();
    return ERR_BUSY;
  } else {
    SCL_SetDir((bool)OUTPUT);
    SCL_ClrVal();                /* CLOCK LOW PULSE */
    Delay();
  }
  *Chr = Read();
  SetAck((bool)NOACK);
  return ERR_OK;
}

Hardware Bugs and locked up I2C bus

As for myself, I have faced bugs in the hardware too (see “KL25Z and I2C: Missing Repeated Start Condition“). I was to solve my problem with the information from the errata and using special clock configurations. Still there might be other problems with hung up I2C bus. This does not have to be necessarily a bug in the I2C silicon implementation, but could happen with external I2C devices going wild. Then it could be difficult to get that back under control (except you can perform a power-on reset). Or to send 9 clocks followed by a STOP condition on the bus (see this forum post). But this kind of detailed control might not be availble in the I2C hardware, so bit banging is the solution here.

Reset I2C Bus

Marc added a function to the driver to reset the bus in the GenericSWI2C component:

I2C ResetBus

I2C ResetBus

This method tries to reset the bus with 9 clocks followed by a STOP. It returns TRUE if the bus is IDLE afterwards, or FALSE if the bus still hangs:

bool I2C2_ResetBus(void)
{
  char i;

  if(SDA_GetVal() && SCL_GetVal()) {
    return TRUE;
  }
  SCL_SetDir((bool)INPUT);
  SDA_SetDir((bool)INPUT);
  Delay();
  if(!SCL_GetVal()) {
    return FALSE; /* SCL held low externally, nothing we can do */
  }
  for(i = 0; i<9; i++) { /* up to 9 clocks until SDA goes high */
    SCL_SetDir((bool)OUTPUT);
    SCL_ClrVal();
    Delay();
    SCL_SetDir((bool)INPUT);
    Delay();
    if( SDA_GetVal()) {
      break; /* finally SDA high so we can generate a STOP */
    }
  } /* for */
  if(!SDA_GetVal()) {
    return FALSE; /* after 9 clocks still nothing */
  }
  InternalStop();
  return(SDA_GetVal() && SCL_GetVal()); /* both high then we succeeded */
}

Yield while Delay

The other contribution from Marc is to perform a yield if the driver is waiting:

Yield in GenericSWI2C

Yield in GenericSWI2C

The software driver needs to wait in several places. If using an RTOS like FreeRTOS, and if this attribute is set to ‘yes’, it will perform a task yield. For this the macro OSYIELD() is defined in the driver:

#if I2C2_HAS_RTOS && I2C2_YIELD
  #define I2C2_OSYIELD() taskYIELD()
#else
  #define I2C2_OSYIELD() /* do nothing */
#endif

The driver will automatically detect if an RTOS is present and generates the appropriate driver code.

GenericI2C

I’m using the GenericI2C driver as a general wrapper between LDD (Logical Device Drivers) and non-LDD drivers. So far the Processor Expert component only allowed either LDD or non-LDD components. Now with the addition of the GenericSWI2C component, I needed to change the component to allow either LDD or non-LDD components.

To use the bit banging GenericSWI2C component with the GenericI2C component:

  1. In GenericI2C, enable non-LDD I2C and have LDD I2C disabled:

    GenericI2C Settings

    GenericI2C Settings

  2. Specify GenericSWI2C as interface:

    Adding GenericSWI2C Interface

    Adding GenericSWI2C Interface

  3. Continue to configure GenericSWI2C (pin settings, timing).
  4. Done 🙂

Summary

Bit-Banging the I2C protocol is not the fastest way, but because very generic it has a lot of advantages: it gives me complete control over the communication bits, and I can use any general purpose Bit I/O pins and I’m not limited to the I2C hardware. Sure, that software bit banging I2C driver is not catching every special case, but worked for me very well with my hardware. So I hope it is useful for you too. And with the extensions contributed by Marc the driver is now even more complete and useful.

The updated components are available on GitHub, see “Processor Expert Component *.PEupd Files on GitHub” how to load them.

Thanks again to Marc for his contribution!

Happy Bit-Banging 🙂

Links

Advertisements

7 thoughts on “Bit-Banging I2C with ResetBus() Functionality

  1. TRUE or TRUE ? (This method tries to reset the bus with 9 clocks followed by a STOP. It returns TRUE if the bus is IDLE afterwards, or TRUE if the bus still hangs)

    Like

  2. Great writeup! Erich’s software has saved me so much time, it’s only proper to contribute something back!

    I should point out that the timing of the driver is based on Fast Mode. Most devices are these days but if you have a genuine slow-mode I2C device you might have problems. Not sure when I’ll have time to go back thru and change where the timing needs to be stretched (anyway, some of the times would be so long it would make sense to use FreeRTOS’s delay if available rather than software timer loops)

    So I think I found another with the IC2 module, and I think it should be dealt with there…. the issue is with I2Cbus_ReadAddress/I2Cbus_WriteAddress and multibyte addresses. They need to be byte swapped on the ARM architectures. If I pass e.g. address 0xE228 (length 2), what comes out on the bus is 0x28, 0xE2.

    Strictly, you could byte swap it calling into the driver, but it seems more convenient if you could do something like:

    uint16_t address;
    I2Cbus_ReadAddress(devAddress, &address, sizeof(address), pData, length);

    Thoughts?

    Like

  3. Hi Eric,

    Processor: KL16Z256VLH4
    I installed the Generic I2C component using a LDD option for I2c channel 0.
    I need a second instance of the Generic I2C with a non-LDD for I2C channel 1.
    When I installed the non-LDD version, I selected the Generic SWI2C option, and then a new wait component. The install never completes.
    Have you seen an issue with a second instance of the Generic I2C component?

    Regards,
    George

    Like

    • Hi George,
      there is a bug in Processor Expert: it can hang while creating a shared component :-(. I have reported that to Freescale.
      For some components it helps if you press ‘Cancel’ when it asks you to add/create the shared component.
      As a workaround, I have disabled the inherited components in the settings. I will send you that component by email.
      What you could try otherwise is to copy-paste the component (from another project or from the one you have): then it should not ask you for the inherited component during creation of the component.

      Like

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s