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.
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. 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:
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:
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 */ #endifThe 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:
- In GenericI2C, enable non-LDD I2C and have LDD I2C disabled:
- Specify GenericSWI2C as interface:
- Continue to configure GenericSWI2C (pin settings, timing).
- 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
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)
LikeLike
ups, indeed 😉 Fixed now.
LikeLike
Thanks Marc!
Happy to see that everyday more people is contributing to this amazing project that Erich started 🙂
I expect to make my part and soon give you some of my advances.
LikeLike
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?
LikeLike
Hi Marc,
yes, that thing with the Fast Mode is noted.
About little/big endian, you are referring to the GenericI2C driver, correct? Yes, I agree that that should be handled in the driver, and I’m doing this already e.g. search for ‘little endian’.
The thing is that some I2C devices are using little endian, some big endian. So I think this should be device (e.g. MMA8451) specific, and not I2C driver specific?
LikeLiked by 1 person
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
LikeLike
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.
LikeLike
Hi Eric,
I am having some troubles caused by a timeout in the genericI2C driver bean.
What happens is that if the remote sensor I am talking to gets it’s SCL line grounded, then the read or write transaction currently underway slows down by a huge amount (like 1000 times slower).
I believe while this might be correct behavior (the I2C slave is pulling the clock low to tell the host it is not ready, and the host is happy to wait for it), in my case my slave devices would never behave that way and I want that read or write to fail.
There is a bunch (10?) of places in the driver where a timeout value is loaded with 65535U. When I change this to a reasonable number (like 1000U) I am able to sense that the sensor or it’s wiring has failed, and quickly take appropriate action.
It would be great if there was a box in the PE bean where I could set this timeout number instead of having to resort to editting the generated code.
Thanks!
Brynn
LikeLike
Are you using the latest GenericSWI2C (note the name) driver (currently the latest release is from https://mcuoneclipse.com/2018/04/01/mcuoneclipse-components-1-apr-2018-release/). I ask because that one has an optional timeout setting you could configure. Unless I’m missing something?
LikeLike
Apparently I am not using the latest.
I just downloaded the new components, told KDS to import the new components,
Then I went into my code and deleted the old component in the PE components in my program list, and added a new genericSWI2C component. However, it still looks like the old component and in the generated code the comment gives the version as 1.021.
What is the correct procedure to get the new component into my project?
LikeLike
The version shown in the generated sources should be
Version : Component 01.021, Driver 01.00, CPU db: 3.00.000
So your version looks good?
But I see now what you mean: The component still has
timeout = 65535U;
in it. I thought I had it removed, so I have to check this and improve this.
Sorry about the wrong information, I thought this already had been removed.
LikeLike
Hi Brynn,
I have improved that component: https://github.com/ErichStyger/mcuoneclipse/commit/312fcc7b702fe6e1aed5efc2e0103769e26c3a8d
I have sent it to you by email as well.
I hope this helps,
Erich
LikeLike
Also I now get an error compiling the generated code:
../Generated_Code/GI2C2.c:242: undefined reference to `GI2C2_CONFIG_SEND_BLOCKContinue’
I Can’t find any other references to that, it almost looks like the Continue part is a typo.
(I have both the genericI2C component and the genericSWI2C components in the code)
Brynn
LikeLike
Hmm, yes, that would be a typo. But I cannot see this on my side? Strange 😦
LikeLike
GI2C2_CONFIG_SEND_BLOCK should be defined in the GI2C2Config.h file.
LikeLike
Hi Erich, I’m encountering a bus lockup issue while using the InternalI2C component (SDA being held low by MAX7311). I’m not sure about implementing an equivalent to ‘*_ResetBus()’ for this component. Looks pretty well wrapped up, can’t easily get access to pins. Any thoughts on this?
Not even sure why InternalI2C component was chosen tbh, doesn’t seem to be as commonly used on these forums. Do you know what’s going on there? Should I be using the GenericSWI2C component instead?
Many thanks in advance.
LikeLike
I don’t think you can do that ResetBus() with the hardware I2C. What you could do is to switch to GPIO mode and do the ResetBus(), then switch back to the InternalI2C one?
LikeLike
Yes I think you’re right. I tried adding a GPIO component on the SCL line (set to Input initially) but PE wouldn’t let me use it for both GPIO and I2C. I guess I’ll have to patch something else in. If I get it working I’ll report back. Cheers.
LikeLike
There is a way to share pins between commponents, see
https://mcuoneclipse.com/2012/11/12/tutorial-bits-and-pins-with-kinetis/
LikeLike
Hi Erich.
I started using GenericSWI2C to use the ResetBus command and noticed that I2C slowed down. I did not find an option to set the frequency. Is there any way to make it faster?
LikeLike
Yes, there are a few settings (you need to check with your device):
do not generate error events:
#define McuGenericI2C_CONFIG_USE_ON_ERROR_EVENT 0
No Mutex if you are the only one using the bus:
#define McuGenericI2C_CONFIG_USE_MUTEX 0
Disable yield():
#define McuGenericSWI2C_CONFIG_DO_YIELD 0
Set the delay to zero:
#define McuGenericSWI2C_CONFIG_DELAY_NS (0)
I hope this helps,
Erich
LikeLike
With these changes it got faster, but not enough for my application. If I go back to using the internalI2C component, is there any command similar to resetBus?
LikeLike
You could turn on -O3 which improves things too.
The InternalI2C does not have the reset bus functionality. But you could do the ResetBus with the GenericSWI2C (say after power-up) and then switch over to the InternalI2C later on.
LikeLike
Hello Erich,
I would like to use this I2C bit-banging driver on another hardware using another IDE not supporting PE. Can you tel me where I can find the c source code so I can port this out of PE to my own dev tools ?
Many thanks in advance
LikeLiked by 1 person
There is the McuLib, see https://mcuoneclipse.com/tag/mculib/ and https://github.com/ErichStyger/McuOnEclipseLibrary which has many of the Processor Expert components as plain C/C++ source files provided, including the GenericI2C component.
LikeLike
Thanks a lot
LikeLike