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 GPIO, analog 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’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
Edit setup.py to add a new “include_dirs” line, as highlighted in bold below:
Without that change, python setuptools won’t be able to find the “limits.h” file.
Run the standard python build command:
python setup.py 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
-L/usr/lib -lpython2.7 -o build/lib.linux-armv7l-2.7/smbus.so
Lastly, run the python build command again to finish the process:
python setup.py 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
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)
- gm_mcp23008.py – This is a wrapper I wrote for the MCP23008.
gm-mcp23008.py has a short bit of test code at the bottom, which is executed if you run it directly from python.
sudo python gm-mcp23008.py
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
# 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
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 embedded-lab.com.
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
if interrupt2 or mcp.digitalRead(6) == mcp.LOW:
led2State = not 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 i2clcd.py
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)
for row in range(4):
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, WeatherBone.py, to read the temperature and barometric pressure from the BMP085, and display that along with the sunrise and sunset time to the LCD.
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.