I2C Communication from Linux Userspace – Part I

0.0 Introduction

Since gathering data from sensors is a crucial component of any control system, a fitting place to get started is getting sensors connected to an embedded device like the Pandaboard and reading data from them.

The first type of sensors we will take a look at are ones which support I2C as a transport. What we will attempt to do in these series of blog posts is electrically connect the devices and read data from them using Linux as our operating system.
As such, without any further ado, let’s jump into it

1.0 What is I2C?

In a nutshell I2C is a two wire communication protocol first published by Philips, used for connecting together low speed peripherals, any of which having the ability to become a bus master.

2.0 Hotplugging

It is important to note that I2C does not have any dynamic detection of new devices on the bus as compared with PCIe or USB. As such if a peripheral were to be attached to the bus during runtime of the OS, it would not trigger any action by the OS as it would not be aware of its existence.

Compare this with PCIe (PCMCIA)/USB whereby the mere action of connecting the device (hotplugging), triggers the driver to be loaded and a whole slew of other events to take place.

Normally, this would not pose much of a challenge in the embedded world, as most I2C devices would be mounted on the PCB alongside the microcontroller eliminating the possibility of any hotplugging. In such a scenario, a device tree structure (dts) file would contain the entire description of all I2C devices present on the bus, and form of this file would be passed to the Linux kernel at boot time by the bootloader. The Linux kernel would then load each driver for the devices present in the passed dtbs file if the drivers are available.

So given that electrically I2C has no support for hotplugging how do we inform Linux to become aware of a device’s existence, once we have physically connected the device on the I2C bus at runtime? To answer this question, we need to understand a little more about the I2C bus.

3.0 Bus Probing

Since I2C is a multi master bus, that means that every device can drive the bus. However, in order for the Linux to communicate to a specific device, it first needs to know the address of the device it is talking to. Since we do not yet know the address, we can tell Linux to probe (issue a read command) on the entire possible address range for I2C peripherals, all 7 bits (or 10 bits on some platforms) and list the device addresses we get a reply from. Keep in mind, that when probing the entire address range, all devices on that bus will answer irrespective on whether or not Linux already knows about them. As such, we will have to cherry pick our device by matching the correct address to our chip.

Now, this all sounds complicated, and it certainly would be were it not for some smart people already providing some tools to make our lives easier.

4.0 A Practical Approach

Let’s talk first about a sample setup which we will use for the purpose of this example.

In the below picture a PandaboardES (right) is connected to an IMU (Inertial Measurement Unit) (left) on Pandaboard’s fourth I2C bus. This IMU has connected on its I2C bus an accelerometer , a gyroscope, a digital compass and a magnetometer. Since we have connected the IMU’s onboard I2C bus to the Pandaboard’s I2C bus, we should expect, that when probing the entire I2C address range, in addition to what devices are already on the pandaboard’s bus, we find the aforementioned four devices.

20130401_222641

In order to know which device is which, once we probe the full address range, we need to check the datasheet of each component we expect to find, and figure out which I2C addresses this device supports.

It is also worthwhile, if not obvious, to mention that you need to check what TTL voltage each board supports if you are planning to connect different peripherals together. The IMU has a 3.3V I2C rail, while the pandaboard has a 1.8V. Due to this, we can’t just connect the two together, as we risk over-volting the pandaboard leading to a very expensive failed experiment. As such if there is a mismatch in voltage levels, we will need a TTL voltage level translator (such as this), like I have in the picture mounted on the breadboard. Also, this goes without saying, never connect anything on the live circuit, first remove power make the connections then bring the system up.

So now that we have everything connected and our Pandaboard still managed to boot into Linux, it’s time to issue some commands. First let’s get the tools:

The tools we are looking for are  the i2c tools from the lm-sensors package.

 sudo apt-get install lm-sensors 

Now, let’s probe the bus:

 sudo i2cdetect -r 4 

Number 4 represents the bus number we want to probe. A given SoC may have multiple I2C buses, specifically, the Pandaboard’s OMAP4460 SoC has 4.

The “-r” option represents the type of probe the tool will attempt to do, in this case the issued command will be a read from the entire address range. A full list of options for i2cdetect can be found here. Warning, this type of probing may confuse the devices on the bus. Devices that may be write only, do not like to have reads issued to them and may enter an indeterminate state. If you encounter such a problem, a simple restart should return things back to normal, but make sure you save your work before running anything which may directly affect hardware.

The output from the above command after running it on my pandaboard with the IMU connected is:

detect_output

Here we notice that we found 4 devices at addresses 0x1E, 0x53, 0x68 and 0x77.

This ensures that the devices are correctly connected at least from an electrical standpoint, to the PBs I2C bus.  Now we can find out more information by probing into the device’s register range.

 sudo i2cdump 4 0x53 

Device with address, 0x53, in this case is an ADXL345 3 Axis Accelerometer. By probing the device’s
internal register range, we can dump out the entire contents of its register space. If we match this output
with the datasheet of the ADXL345 we can start decoding the state of this chip. Full information on i2cdump can be found here.

Now, as a last exercise, we will attempt to write a bit into the device’s register space.

 sudo i2cset -y 4 0x53 0x2D 8 b 

The above command will attempt to write  to device 0x53,  located on bus 4 , address 0x2D, a value of 8 as a byte.
The reason as to why I chose 0x2D as a write location in the device’s address space is because I know that 0x2D represents the POWER_CTL register for the ADXL345 chip.

Writing a value of 8 at that location will put the ADXL345 chip into measure mode.
All this information is of course, obtained from the ADXL345 datasheet.

This command is especially dangerous, special care must be paid to writing registers to the I2C device as oftentimes individual bits of that register have a very wide reaching impact for the chip; carefully masking the appropriate values before writing is crucial. Also under no circumstance should you write to read only registers as such writes tend to have an indeterminate behavior on the hardware.

5.0 Conclusion

I hope I have been able to shed some light on a topic which is very interesting in my work with the Pandaboard. In a future post, I plan on outlining how to access

the device register space programatically from userspace. Following that, we will start tackling some kernel space drivers to harness the full power of the Linux Kernel when

dealing with I2C peripherals.

Please report to me any comments and or in-accuracies found in this post, I will be glad to fix them.

Happy Coding,

Andrei

Leave a Reply