Using the Gumstix I2C bus
Last Updated on Friday, 19 August 2011 11:06
I2C is a common peripheral bus for embedded systems and one of the easiest to use with the Gumstix.
The OMAP3's have 3 I2C controllers all of which can be configured as a slave or master device at the hardware level. The Linux driver can only operate as an I2C master.
The I2C bus is usually single master/multi-slave.
TODO: I2C does allow multiple masters. Check whether the Linux driver supports this.
The I2C bus uses two lines, SCL (clock) and SDA (data).
Gumstix brings out the third I2C bus, i2c-3, on pins 23 (SCL) and 24 (SDA) on most of the Overo expansion board headers. The Thumbo and Pinto boards use pins 31 (SCL) and 32 (SDA).
The pins are labeled GPIO184_SCL3 and GPIO185_SDA3. The reason for the names is that the underlying pads for these pins can be multiplexed in software. In this case, the SCL3 and SDA3 pins could also be used as GPIO 184 and 185 respectively.
The default Gumstix software correctly muxes the i2c-3 pins on the expansion board for I2C use.
If you are working with another OMAP3 board like the Beagles, you may want to check that the I2C pins aren't defaulted to another use.
Pin multiplexing for the OMAP3s is still mostly done by the bootloader, U-Boot. Some pin muxing is done in the Linux kernel, usually (maybe only?) in the board file. You may have to check both sources.
There are some notes on modifying the u-boot pin muxing here.
For troubleshooting, omap3-mux is a small driver that can check the pin muxing on a running system.
I2C Bus Speed
The default Gumstix kernels set the i2c-3 bus speed to 400kHz or Fast mode.
You can change the speed by either modifying the board file where the kernel I2C driver is initialized or by passing the kernel an argument in the bootloader (the easier way). A common speed you might want is 100kHz Standard mode.
If you choose the board file method, there are instructions here for modifying the Gumstix board-overo.c file.
The line you are looking to change is
omap_register_i2c_bus(3, 400, NULL, 0);
The easier way, using a kernel boot argument, is done by adding a statement like this i2c_bus=3,100 to the bootargs variable. This changes i2c bus 3 to 100kHz.
To modify the bootloader, start your system with a console connection and then stop the u-boot process by hitting a key during the countdown. Make the following change to either nandargs or mmcargs depending how you boot. The nandargs/mmcargs variables are just Gumstix conventions for initializing bootargs which is the value u-boot cares about.
Using mmc booting as the example, the old value should look something like this:
Overo # printenv
So a add new variable so you can easily change the speed again and then modify mmcargs and save your changes.
Overo # setenv i2cspeed 3,100
Another option is to change the built-in u-boot settings to add the i2c_bus parameter. You can do this by modifying include/configs/omap3_overo.h in the u-boot source and rebuilding it. Instructions for working on u-boot can be found here.
One last way to add the i2c_bus parameter is to use a boot script file. The default Overo u-boot looks for a file called boot.scr on the bootable FAT partition of the SD card, the same partition as u-boot.bin. If one is found, the declarations and commands found within it take precedence when booting. This is particularly useful for Overo Tide COMs which don't have any NAND to save u-boot changes.
The procedure is straightforward. You create a text file of standard u-boot commands and then run them through a utility called mkimage to generate a binary file that u-boot can use. The generated file does have to be called boot.scr unless you change omap3_overo.h. I have some examples in a github repository here - boot-scripts.
Voltage Level Conversion
The logic levels coming from the gumstix expansion board are 1.8V. Most I2C devices will require 5V or 3.3V on the I2C SDA and SCL lines. You will need to do some voltage level conversion in order for communication to be possible.
And here is an example circuit using these parts with an Overo - generic_i2c_circuit.png
Note that pull-up resistors for the SDA/SCL lines are only required on the device side. The Overo COMs already have pull-ups on the motherboard.
The Linux I2C driver exposes the i2c buses as character devices. So for instance, on a standard Overo system you'll see this
root@overo:~# dmesg | grep i2c
To use the I2C bus from a userland program you can use the standard Unix file operations, open/close/read/write/ioctl. The one difference is the requirement to specify the slave device address using an ioctl call before the first read or write.
In C, it goes like this
#include <linux/i2c-dev.h> /* for I2C_SLAVE */
You only have to make the ioctl call when you switch to another slave device. If you only have one slave device, you only need to make the call once with the open file handle. If you close and re-open the file handle you will have to specify the slave address again.
I2C addresses are normally limited to 7 bits. The lowest bit on the first byte of a transfer is reserved to indicate the direction, read or write. The Linux I2C driver takes care of this for you. The first byte of every transfer is the slave address you provided in the ioctl call shifted by one and with the lowest bit set to zero for a write or one for a read. If you are watching with a scope or signal analyzer, keep this in mind.
Some manufacturers specify their devices as having one address for writing and another address for reading. What they are doing is specifying the base address shifted with the read or /write bit already appended. For Linux use, you will want to use the /write address they specify right shifted by one bit.
Of course, you can use languages other then C for your Overo I2C programming. For example, here is a small Python script gum-to-arduino.py that I wrote fooling around with an Arduino.
A common need is to implement some I2C control of a device within the context of a larger device driver. I have a simple example here - http://github.com/scottellis/mi2c - that demonstrates how you might go about embedding some i2c functionality within a driver.
When I get some time I will add a little write up about how it works. The code is pretty simple and probably self-explanatory for driver developers.
The one twist that might be useful is the dynamic declaration of devices on module load, rather than the static declaration of I2C devices in a board file that you normally see.
Gumstix cameras that use the OMAP3 ISP like the Caspa or the ones from e-consystems use I2C-3 for camera control in the drivers.
Here's some more Gumstix I2C code I've posted while experimenting.