Beaglebone Coding 101: I2C

Updated 12/20/12 – Corrected instructions for installing python smbus on Angstrom.
Updated 1/6/13 – Another correction to the instructions for installing python smbus on Angstrom 

In previous articles in the Beaglebone 101 series, I covered digital GPIOanalog and serial pins, PWM and SPI.

One important coding interface remains: I2C.  I’ll cover it today, including I2C input and output, and level conversion, culminating in the Weatherbone, a basic LCD-based thermometer.

I2C Overview

I2C’s main advantage is simplicity.  It requires just 4 connections, 2 fewer than SPI: voltage, ground, clock and data.

The clock pin works pretty much the same way as with SPI: it is driven by the Beaglebone to control the timing of the data transfer, and works fine at 3.3V even when interfacing to a 5V device.  As with SPI, one option for controlling the state of the I2 clock pin is “bit banging”: using a plain old GPIO pin as the clock.  However, since the Beaglebone supports much higher and more reliable clock speeds through specialized hardware, we’ll make use of that.

Unlike SPI, I2C uses just 1 data pin, which transfers data in both directions.  This is great for simplicity and cost, not so great for speed.  As a result, I2C devices is most appropriate for devices that don’t send a lot of data, like sensors.

The fact that data travels in both directions on the same pin is an important consideration when interfacing with a 5V device: you must always use a level shifter on the data pin.  This is necessary even if using an output device like an LCD — there is no such thing as a “write only” connection when dealing with I2C.  I’ll give an example of level shifting in the I2C LCD circuit.

Like SPI, I2C supports multiple devices on the same I2C bus.  While SPI uses a latch pin to select the device, I2C handles the addressing with software.  This, again, makes wiring the circuit easier.

I2C and the Beaglebone

Most Beaglebone distros allow access to 2 I2C buses: /dev/i2c-1 and /dev/i2c-3.  For reasons that I don’t fully understand, /dev/i2c-1 doesn’t play nice with user applications, so we’ll stick with /dev/i2c-3.

The 2 pins used to interface with I2C bus 3 are pins 19 (clock) and 20 (data) on port 9.  Both pins default to I2C mode in Ubuntu and Angstrom, so no pinmuxing is required.

You may have seen references to the importance of pull-up resistors when using I2C.  You can ignore this when using the Beaglebone, since pull-up resistors are enabled by default on pins 19 and 20 in both Ubuntu and Angstrom.

When accessing SPI devices from Python, I used a C language extension (spimodule.c) as a go-between.  With I2C this isn’t necessary, since a Python-friendly interface exists: SMBus.   Technically, SMBus and I2C aren’t the same thing, but they both support the high-level read and write actions used by most applications.

Installing SMBus on Ubuntu is easy:

sudo apt-get install python-smbus

Updated 12/20/12 – I finally had a chance to try installing smbus on Angstrom.  It wasn’t as straightforward as the original text of this article suggested: here are the gory details..

Installing SMBus on Angstrom is somewhat more difficult.   There is no Angstrom package for python-smbus, so you’ll need to download it from source:

1/6/13: Added some corrections to the following instructions, as provided by the Hipstercircuit blog.  See Hipster’s instructions on getting an I2C PWM controller to work on Beaglebone Angstrom,  a good way of extending the Bone’s relatively limited PWM capabilities.

opkg install kernel-headers python-distutils
tar -xjf i2c-tools-3.1.0.tar.bz2
cd i2c-tools-3.1.0/py-smbus

Edit to add a new “include_dirs” line, as  highlighted in bold below:


 include_dirs=["../include", "/usr/include"],
 ext_modules=[Extension("smbus", ["smbusmodule.c"])])

Without that change, python setuptools won’t be able to find the “limits.h” file.

Run the standard python build command:

python install

You’ll get a new error “this linker was not configured to use sysroots”.  I don’t know how to properly fix this error, but a workaround is to run the same command without the sysroots parameter. The error message will include the full command, so just copy it to the clipboard and remove the part that begins with “–sysroot”.  On my PC, the resulting command was:

arm-angstrom-linux-gnueabi-gcc -march=armv7-a -fno-tree-vectorize
 -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mtune=cortex-a8
 -D__SOFTFP__  -shared -Wl,-O1 -Wl,--hash-style=gnu 
-I../include build/temp.linux-armv7l-2.7/smbusmodule.o 
-L/usr/lib -lpython2.7 -o build/lib.linux-armv7l-2.7/

Lastly, run the python build command again to finish the process:

python install

The MCP23008 Port Expander

The MCP23008 is the I2C equivalent of the 74HC595 chip that I covered in my SPI article.  As mentioned then, port expanders aren’t terribly useful with the GPIO-rich Beaglebone, but when combined with buttons and LEDs the MCP23008 provides a quick and easy way of testing I2C circuits and code.

Compared to the Beaglebone’s GPIO pins, the MCP23008 does offer a few advantages:

1. It can interface directly with 5V devices.  Note that this requires powering the MCP23008 with 5V, so you still need to use a level shifter to connect the MCP23008’s data pin to the Beaglebone’s data pin.  But converting one pin is, of course, a lot easier than converting each GPIO pin separately.

2. It can source and sink a higher amount of current than the Beaglebone: 20 ma, compared to just 6 ma source and 8 ma sink for the Beaglebone.

3. It has registers to support interrupts.  This means that code doesn’t have to rely on polling to detect input — sort of.  I’ll show an example of that below.

To demonstrate the input and output interface to the MCP23008, I’ll use the circuit shown below:

On the Beaglebone, pin 19 on Port 9 is I2C Clock, and pin 20 is data.  Since I’m powering the MCP23008 at 3.3V, these can be directly connected to the chip’s pins 1 and 2.

The 3 address pins of the MCP23008 are all connected to ground.  These pins determine the address that is specified in our code to select the MCP23008, and are important when using multiple MCP23008s: since there is no latch pin, the address determines which chip gets the data.  With all 3 address pins connected to ground, we’ll use the MCP23008’s base address of 0x20.

Pins 10 (GP0) and 16 (GP6) are used as input pins, connected to a pushbutton that is connected to ground.  The MCP23008 has an optional pullup resistor, which we’ll enable on this pins.  So, the pin is normally high, but when the button is pressed it will change to low.

Pins 11 (GP1) and 17 (GP7) are connected to LEDs through resistors (anything in the range of 330 to 680 is fine).

The idea is that the buttons will toggle the state of the LEDs.  Yes, that does sound suspiciously like the same thing that we were doing back in the first Beaglebone Coding 101 article, but 2 LEDs makes it twice as exciting!

Before running the code, it’s a good idea to run the i2cdetect command to make sure that your MCP23008 is correctly connected.  This is part of the i2c-tools package, which is preinstalled in Beaglebone Ubuntu.  (If it’s not in your copy, run “sudo apt-get install i2c-tools.)

The MCP23008 should be detected at address x20, as shown below:

ubuntu@omap:~ sudo i2cdetect -r -y 3
 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

The Python code for this article can be downloaded from my Google Code page by running the following commands on your Beaglebone:

tar -xzvf WeatherBone.tgz
cd WeatherBone

The code for this circuit makes use of 2 Python classes:

  • Adafruit_I2C  – This is part of Adafruit’s Python library for the Raspberry Pi.  The code was almost 100% compatible with the Beaglebone: I only had to modify the bus number passed to SMBus (0 for the Pi, 3 for the Beaglebone)
  • – This is a wrapper I wrote for the MCP23008. has a short bit of test code at the bottom, which is executed if you run it directly from python.

sudo python

You should find that the buttons faithfully toggle the LEDs’ state, with a slight pause.

The code begins with

mcp = mcp23008(3, 0x20, True)

The 3 parms being passed are the I2C bus number (3), the MCP23008 address (0x20), and a Debug flag that results in a message being written to the console each time I2C data is sent or received.

The code then initializes the 4 pins a la Arduino:

# button 1 connected to ground on pin 0 of MCP23008
mcp.pinMode(0, mcp.INPUT, True, mcp.INTERRUPT_PREVVAL, mcp.EDGE_LOW)
# LED 1 on pin 1
mcp.pinMode(1, mcp.OUTPUT)
# button 2 connected to ground on pin 6 of MCP23008
mcp.pinMode(6, mcp.INPUT, True, mcp.INTERRUPT_PREVVAL, mcp.EDGE_LOW)
# LED 2 on pin 7
mcp.pinMode(7, mcp.OUTPUT)

The INTERRUPT_PREVVAL and EDGE_LOW parameters require some explanation.

The MCP23008 supports interrupts, internally using some registers, and externally through pin 8 (which I’m not using).  The registers can be used to detect a case where the user pressed and released the button when the Python code wasn’t checking the pin.

The INTERRUPT_PREVVAL setting will trigger an interrupt when the pin state changes, and the EDGE_LOW parm will limit that to when it changes from high to low (remember, the pin goes from high to low when the button is pressed).

A good resource for learning more about the MCP23008’s registers is this article on

The main part of the Python code loops indefinitely, checking the state of the buttons once per second — a leisurely rate that I’m using to test the interrupts:

# toggle LED when button is pressed
while 1 == 1:
   # NOTE!! - must get interrupts before reading pins, since reading pins clears the interrupts
   interrupt1 = mcp.wasInterrupt(0)
   interrupt2 = mcp.wasInterrupt(6)
   if interrupt1 or mcp.digitalRead(0) == mcp.LOW:
      led1State = not led1State
      mcp.digitalWrite(1, led1State)

   if interrupt2 or mcp.digitalRead(6) == mcp.LOW:
      led2State = not led2State
      mcp.digitalWrite(7, led2State)


As indicated by the comment, reading a pin’s state clears any interrupt for it. So, it’s necessary to call the wasInterrupt() method for both pins first.  This method returns True if the button was pressed since the last time the code checked (i.e. since the last call to digitalRead).

Writing to an LCD using the Adafruit I2C Backpack

As in my SPI article, I’ll leverage the GPIO extender code to write to a standard character LCD, using the Adafruit I2C/SPI LCD Backpack.

The backpack runs on 5V, and that voltage kills Beaglebones.  This wasn’t a concern when using SPI.  SPI uses a separate pin for input and output, so we were able to drive the output pin at 3.3V and leave the input pin disconnected.

With I2C, we need to use a level converter between the backpack and the Beaglebone’s I2C pins.  I used a $2 converter from Sparkfun.  I used the same Sparkfun board as a voltage divider in my article about analog input: the TX pins are level converters, while the RX pins are voltage dividers.  In this case, we want to use the TX pins.

I also made a small modification to the backpack to enable setting the backlight brightness via PWM.

The backpack normally feeds 5V into pin 15 of the LCD (the second from the end), resulting in maximum brightness.  As shown in the photo below, I’ve left pin 15 unsoldered in the backpack, connecting it instead to a NPN transistor.  One of the Beaglebone’s PWM pins is  connected to the base of the transistor.   A potentiometer attached to an analog pin is used to control the duty cycle setting of the PWM pin.

The PWM pin outputs 3.3V, which results in a noticeably dimmer LCD backlight, even though the transistor’s collector pin is connected to 5V.  So, I made use of the level converter to shift the PWM signal from 3.3V to 5V.

If all that seems way too complicated, you could actually avoid the need for level conversion by using the transistor with the LCD’s pin 16 instead of 15, connecting it to ground instead of 5V.  The only downside to that approach is you lose the API support for turning the backlight off and on, but the potentiometer can be used for this instead.

The circuit is shown below.  (The BMP085 part is explained in the next section.)

The Beaglebone pins are all on Port 9.  Along with ground, 3.3V and 5V, the pins are:

  • Pin 14 – PWM – white wire
  • Pin 19 – I2C Clock – brown wire (with no level conversion required)
  • Pin 20 – I2C Data – yellow wire (with level conversion)
  • Pin 32 – Analog 1.8V – orange wire
  • Pin 41 – Analog In – green wire

The Adafruit I2C backpack is (kind of) shown in the upper right of the diagram.  The wires leading to it are connected to:

  • Data – yellow wire1
  • Clock – brown wire
  • 5V – red wire
  • Ground – blue wire
  • Pin 15 of the LCD – Orange wire

I wrote a Python class, i2clcd to provide Arduino-like methods for writing to the LCD through the I2C port.  It’s included in the download file.

As a quick test, you can just run the Python class directly, which should turn on the backlight and write some text to the LCD:

sudo python

The i2clcd class supports many of the same methods as the Arduino LiquidCrystal library.  So, to write text to all 4 lines of a 4×20 LCD:

# the LCD is connected to I2C bus 3

l = i2clcd(3)

l.begin(20, 4)


for row in range(4):
   l.setCursor(0, row)
   l.Print("This is row " + str(row + 1))

Connecting Multiple I2C Devices

It’s quite easy to connect multiple devices to the same bus: just connect their clock pins and their data pins.  This is a lot simpler than controlling multiple SPI devices, which require a separate latch pin for each device.

I added a BMP085 to the circuit to demonstrate this.  It is powered by 3.3V, but coexists fine with the 5V LCD backpack provided that its data pin is connected to the 3.3V side of the level converter.

The code to read the BMP085 is taken from Adafruit’s Raspberry Pi Python library – it works on the Beaglebone without any changes at all.

To give all of the hardware something to do, I wrote a Python program,, to read the temperature and barometric pressure from the BMP085, and display that along with the sunrise and sunset time to the LCD.

Wrapping Up

The interfaces that I’ve covered in the Beaglebone Coding 101 series can be used to connect your Beaglebone to almost any type of device in the microcontroller world.

I have to admit that I’m not sure how big a gap the “almost” part leaves, since I tend to go down the path of least resistance.  If a particular sensor seems to be a little beyond the Beaglebone’s grasp (or beyond my grasp as a coder, anyway), I’ll switch to an equivalent sensor that uses a standard interface, or which others have already written code for.

The good news is that the paths of least resistance in embedded Linux coding are increasingly well marked, thanks to the Raspberry Pi.   It’s great to see so many new articles and sample code coming out of the Pi community.  The Pi’s code is generally easy to port to the Beaglebone, and the increased interest in embedded Linux is boosting both platforms.

This entry was posted in Electronics, Programming and tagged , , . Bookmark the permalink.

12 Responses to Beaglebone Coding 101: I2C

  1. Pingback: Beaglebone Coding 101: I2C « adafruit industries blog

  2. James says:

    I’m trying to get an I2C LCD working on my BeagleBone, but running into issues.

    You mention you need a level shifter on the data pin. I don’t understand why this is required. Can you explain why Arduino works without this, but BeagleBone requires it? This LCD i’m using worked on Arduino without anything like this.

    I have tried without the logic level converter, but the I2C bus on the BeagleBone is very very slow to respond when the LCD is connected, and it doesn’t actually find the LCD, so I guess this is why. I also receive a timeout when the i2cdetect program gets to address 32. My LCD is on address 50.

    I don’t believe you addressed the level converter well enough, what is there assumes a decent level of electronics knowledge – though this may be my own mis-understanding of what i’m reading; though it probably should be assumed that people are coming from Arduino or a similar platform, and probably expect things like I2C to be the same (IMO, they should be!)

    I’m a software guy, I don’t want to learn the intricacies of hardware just to get a simple I2C LCD to work :) I guess it’s frustrating to find out a plug and play device isn’t plug and play.

    • dwatts says:


      Yes, I agree with you. The transition from Arduino to Beaglebone is more difficult than it should be, mostly due to the relative lack of tutorials for the Beaglebone that are aimed at hobbyists. I’m a hobbyist myself, and relatively new at electronics, so I get frustrated with the Beaglebone quite regularly. I only blog about the projects that work, and as you can see I don’t write all that many blog posts! I often switch back to Arduino or Netduino projects, with which I have a much better success rate, just to keep my confidence up. As you probably know, the Raspberry Pi has proven to be a better choice for learning embedded Linux, and I’ve switched to using it for some projects.

      The level shifter is needed when working with the 5V LCD because it is powered by 5V, and 5V kills Beaglebones. I2C connections are bidirectional, meaning that the Beaglebone will send and receive signals on the same line, even with an LCD that will never actually send the Beaglebone any data. So, without the level shifter, the Beaglebone will be exposed to a 5V signal and will immediately drop dead. The level shifter ensures that the Beaglebone will never receive a 5V signal — it will be converted to 3.3V instead. The level shifter also converts the 3.3V signals sent by the Beaglebone to 5V signals when received by the backpack. This is actually not necessary in this case — the backpack will happily work with a 3.3V signal, as it did in my earlier article on SPI — but some I2C devices may need the data signal to be at 5V.

      I assume that you’re using some other model of I2C-enabled LCD. Bear in mind that my success rate isn’t great, but I’ll take a crack at guessing what is wrong. Since i2cdetect is unable to see it, the most likely problem is that the LCD isn’t happy with the clock line running at 3.3V. The Adafruit backpack I use works fine with the clock line at that voltage (because the underlying MCP23008 chip works fine with clock at that voltage). The clock signal is generated by the Beaglebone (with I2C only one device can generate the clock, and it’s almost always the “master”), so there is no risk of the backpack sending a 5V signal to the Beaglebone on the clock line.

      If you switch the clock to 5V on the LCD side, then you will need to use a level converter for the clock line (as well as the data line), to convert the clock signal between 5V on the LCD side and 3.3V on the Beaglebone side.

      If you aren’t using the same level converter as I did, then one other possible cause of the i2cdetect problem is that your level converter isn’t bidirectional. Since the I2C data wire passes signals in both directions, a bidirectional level converter is a must. If your level converter’s specs don’t mention that it is bidirectional, it probably isn’t. Most inexpensive level converters aren’t. The Sparkfun level converter I used is actually poorly documented, but based on what others have found, the 2 TX lines are bidirectional and the 2 RX lines are not.

      I hope this helps. If not, if you let me know what model of LCD and level converter you are using, I might be able to spot the problem.


  3. James says:

    Thanks Dan. I’ve tried with a 3.3v I2C 7 segment LCD in addition to a 5v 40×4 LCD (which will required the level converter) but neither are detected. 7seg display was wired directly to the 3.3v pins and the I2C pins with no level converter.
    I’m suspecting a kernel issue or a beaglebone hardware issue. When I first tried the LCD, and I didn’t understand why the logic converter was required, I tried it without. Fearing I’ve damaged the board now, actually. At least it didn’t completely kill the beaglebone…

    3.3v 7seg display was this one:
    LCD display backpack is this one:
    LCD backpack is connected to a 40×4 display, works fine on Arduino.
    Logic Level converter is: Easily available locally, uses the BSS138 chips which the Adafruit i2c-compatible conveter uses.

    BeagleBone is running stock Angstrom image, v2012.05, Kernel version Linux beaglebone 3.2.18 #1 Thu Jun 14 23:26:20 CEST 2012 armv7l GNU/Linux
    BeagleBone is a Revision 6.

    Unsure if a newer Angstrom kernel or OS image is available, I’ll investigate over the next few days (when I’m not stuffing my face with turkey).

    I have a spare RasPi here too so if I get motiviated I may try the i2c on that as well.

    Have a good Holiday season.

    • dwatts says:

      Hi James,

      I don’t have either of those I2C LCD devices, but it would appear that they are compatible with the Beaglebone.

      Since they work fine with an Arduino, then the problem might be with the version of the kernel you are using. 3.2.18 is quite old, and I’m not sure at what version the I2C support on the Beaglebone was stabilized. The first time I experimented with I2c on the Beaglebone, early in 2012, I couldn’t get anything to work. By the time I took another crack at it, the kernel version was something like 3.2.28. The latest kernel is 3.2.34.

      It’s possible that, at 3.2.18, the I2C pins defaulted to GPIO mode (mux mode 7), or they may not have had the pull-up resistors enabled. You can check for this with the following commands (as root):

      root@betabone:/sys/kernel/debug/omap_mux# cat uart1_rtsn
      name: uart1_rtsn.i2c2_scl (0x44e1097c/0x97c = 0x0073), b NA, t NA
      signals: uart1_rtsn | NA | d_can0_rx | i2c2_scl | spi1_cs1 | NA | NA | gpio0_13

      root@betabone:/sys/kernel/debug/omap_mux# cat uart1_ctsn
      name: uart1_ctsn.i2c2_sda (0x44e10978/0x978 = 0x0073), b NA, t NA
      signals: uart1_ctsn | NA | d_can0_tx | i2c2_sda | spi1_cs0 | NA | NA | gpio0_12

      The 2nd line of the output should include “OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE3″ – without either of these, I2C won’t work. (You can manually pull-up the lines by connecting them to 3.3V through a 4.7K resistor, but with recent kernels and the above mux mode settings the external pull-up isn’t necessary).

      You might want to get an MCP23008 or MCP32016 chip in order to do a quick test that I2C capability is functional on your Beaglebone. They are inexpensive and simple to connect.

      Using a Raspberry Pi to test your circuit and software is also a good idea: it’s what I did with the I2C circuits I wrote about in the blog. With the Pi’s stable kernel and no mux modes to worry about, I find myself going astray less often. You will, however, still need to use the level converter with the Pi is the I2C device is powered by 5V — the level converter you have should work fine with I2C.

      One other option to simplify things with the Pi and Beaglebone is to use a 3.3V LCD. You could then power your backpack at 3.3V, leaving 5V out of the circuit and eliminating the need for a level converter. I’m currently using this LCD with the Adafruit I2C backpack on a Pi, no level conversion required.



  4. Pingback: Brewing Control Computer, Progress Update | vortex's blog of brew!

  5. James says:

    Thanks again Dan. I’ve updated the BeagleBone to the latest Angstrom via opkg, and verified the pins are all configured correctly.

    No devices are detected. Looks like i’ve nuked something on the board. I’ll setup the RasPi and see if I have better luck, at least then I’ll know it’s the BeagleBone which is causing the issue. I have tried on both i2c busses, fwiw.

  6. Elias Bakken says:

    I think the pythona package has been misspelled, it should be ‘python-distutils’, should it not?
    Also, when I tried the i2c-tools link, the lm-sensors website was down. I did find the package by googling for it though, and I have uploaded it on my server for convenience:

    I’m not sure if that is a different package than the one you have, but it seems that the
    #include directive is missing from it smbusmodule.c, so the build failed..
    Including the directive fixed the problem. Thanks for the write-up! I always use Gigamegablog as a reference for Beaglebone low level stuff : )

    • dwatts says:


      Thanks for the corrections. I’ve updated my article.

      The copy of i2c-tools-3.1.0 you used is identical to the one of the lmsensors site. I was able to compile without the added ../include directive because i2c-dev.h somehow ended up in /usr/include/linux on my Bone. Not sure what package put it there, so I’ve updated my instructions to match yours.

      I’ve also added a link to your excellent write-up on using the PCA9685PW I2C PWM controller on Beaglebone Angstrom.


  7. Pingback: PCA9685PW – I2C controlled PWM chip for fan speed control | Hipstercircuits

  8. maybe this in not the write place to ask this

    I am graduate student and my research use the beaglebone heavily.

    one thing i am trying to do is to make the beaglebone go to sleep and wake it up through internal timer or any external signal.

    I am trying to do so cause my application is energy-critical one, so any tips about energy efficiency practices I can do with the beaglebone will be highly appreciated !

    so please if you can help me out on this please do so, even some little hints will suffice

    thanks for your amazing blog man ! …. really fantastic effort !
    ( PS: our instructor here at NCSU relied heavily on your blog to teach our class )

    • dwatts says:

      Thanks, but sorry: I can’t help you with that. I’ve never tried minimizing Beaglebone power usage, and I haven’t come across any information about how to do that.


Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>