It’s been too long since my last “Beaglebone Coding 101″ article. Perhaps many of you have graduated to Beaglebone Coding 201 by now, but I’m going to continue with the 101 level for us less gifted students.
I’ve been experimenting with SPI and I2C on my Beaglebone lately. When I first tried using them at the beginning of the year, I found they required a level of Linux Kung Fu that was beyond me. However, both of these protocols are now supported from “userland” applications in both Angstrom and Ubuntu. A Google search turns up plenty of working examples for SPI and I2C on the Beaglebone, along with some more advanced projects. I think the water’s safe safe for the rest of us.
I’m going to begin with SPI. Ultimately I’ll be using a graphical LCD, specifically an Adafruit ST7565-based LCD, but in this article I’m going to use a simpler character LCD backpack, also from Adafruit, that uses the popular and well-documented 74HC595 SPI chip.
My coding language of choice is Python. I really wanted to do this in Bonescript instead, since I like Jason Kridner’s idea of using it as a way for Arduino coders to ease into the embedded Linux world. Unfortunately, I haven’t found the time to get the hang of coding in node.js. Bonescript has added SPI support through a shiftOut method. If you’re interested in learning how to use SPI with Bonescript, I’d recommend Ken Keller’s blog and Github code library.
Updating the Kernel
SPI has been supported in the Angstrom kernel for awhile now, and was added to Ubuntu’s kernel relatively recently. A quick way to check if you have SPI support is to look in the /dev directory:
ubuntu@omap:~$ ls /dev/spi* /dev/spidev2.0
Note that bus 2, device 0 is the only option available to “userland” code in the current Angstrom and Ubuntu kernels. Multiple SPI buses and devices are possible, but require that you build a custom kernel.
If you don’t see an SPI device in the /dev directory, you’ll need to upgrade your kernel. If you are running Angstrom, either download a new image (I use the top entry on the Beaglebone demo files page), or follow the instructions in the “Enabling PWM Support in the Kernel” section of my Buttons and PWM article.
[Update Sept 11: I just noticed that Angstrom kernel 3.2.28 was automatically enabled on my Beaglebone when installed via opkg. I didn't have to manually copy the uImage file onto the boot partition, just reboot. Whoa!
I'm using the 2012.08.14 image from the Beaglebone demo files page. I'm not sure yet whether this feature is only to be found in newer Beaglebone images. If you find that the latest kernel isn't automatically installed for you, then you'll have to manually update it as described in the Buttons and PWM article.]
If you’re running Ubuntu, see my recent article on updating the kernel.
(If you’re running Android or another OS, sorry, dunno :-(.)
Adding an SPI Extension Module to Python
If you Google Python SPI, you’ll find various references to a C extension for Python, py-spi. It was written back in 2009 by Volker Thoms, but as far as I can tell its still the best method for giving Python full access to SPI on Linux.
The module consists of C code with Python bindings, spimodule.c. It limits the amount of data being sent to 32 bytes, and the speed to 8 Mhz — both unnecessarily low limits on the Beaglebone — but for now we’ll use it as-is. We’ll need to lift those limitations later when we use it with an SPI graphical LCD.
(If you’d rather avoid building the extension on your Beaglebone, I’ve built binaries for Angstrom and Ubuntu. See the Python Extension Binaries section below).
Before we can install it, we need the python-dev package.
opkg update opkg install python-dev
sudo apt-get update sudo apt-get install python-dev
To install the py-spi module, you’ll need to download a few files into any directory on your Beaglebone:
wget http://elk.informatik.fh-augsburg.de/da/da-49/trees/pyap7k/lang/py-spi/src/setup.py wget http://elk.informatik.fh-augsburg.de/da/da-49/trees/pyap7k/lang/py-spi/src/spimodule.c
Note: if you have problems downloading these files (I downloaded them without problems a few weeks ago, but got 0-length files when I tried today), you can download my slightly customized versions instead:
wget http://gigamega-micro.googlecode.com/files/setup.py wget http://gigamega-micro.googlecode.com/files/spimodule.c
If you download the original setup.py, you’ll need to make a couple of changes to the setup.py file. (You can skip this if you download my customized version). The original looks like this
#!/usr/bin/env python from distutils.core import setup, Extension setup( name="spi", version="1.1", description="Python bindings for Linux SPI access through spi-dev", author="Volker Thoms", author_email="email@example.com", maintainer="Volker Thoms", maintainer_email="firstname.lastname@example.org", license="GPLv2", url="http://www.hs-augsburg.de/~vthoms", include_dirs=["/usr/src/linux/include"], scripts=['scripts/93LC46_LAtest.py','scripts/93LC46_test2.py', 'scripts/93LC46_test.py','scripts/ds1305_test.py', 'scripts/mcp3304.py','scripts/mcp3304_test.py', 'scripts/mcp4922.py','scripts/spitest3.py'], ext_modules=[Extension("spi", ["spimodule.c"])])
We’ll need to change the include_dirs line to match the correct location of the Linux include files, /usr/include. Otherwise, when you compile on Angstrom you’ll get an error “no include path in which to search for limits.h“. Incidentally, this appears to be one of those error messages for which Google returns 1000 explanations, none of them being this one. I hate it when Google makes me think for myself!
We’ll also remove the scripts line, since we aren’t using those files. The setup.py should look like this:
#!/usr/bin/env python from distutils.core import setup, Extension setup( name="spi", version="1.1", description="Python bindings for Linux SPI access through spi-dev", author="Volker Thoms", author_email="email@example.com", maintainer="Volker Thoms", maintainer_email="firstname.lastname@example.org", license="GPLv2", url="http://www.hs-augsburg.de/~vthoms", include_dirs=["/usr/include"], ext_modules=[Extension("spi", ["spimodule.c"])])
To compile and install the Python extension, enter the following as root:
python setup.py install
On Ubuntu, you should get the following. (The warnings won’t be there if you use my customized version – 10 points for Gryffindor!):
running install running build running build_ext building 'spi' extension creating build creating build/temp.linux-armv7l-2.7 gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include -I/usr/include/python2.7 -c spimodule.c -o build/temp.linux-armv7l-2.7/spimodule.o spimodule.c: In function ‘SPI_readbytes’: spimodule.c:156:7: warning: unused variable ‘addr’ [-Wunused-variable] spimodule.c: In function ‘SPI_xfer’: spimodule.c:204:10: warning: unused variable ‘ret’ [-Wunused-variable] creating build/lib.linux-armv7l-2.7 gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl, -Bsymbolic-functions -Wl,-z,relro build/temp.linux-armv7l-2.7/spimodule.o -o build/lib.linux-armv7l-2.7/spi.so running install_lib copying build/lib.linux-armv7l-2.7/spi.so -> /usr/local/lib/python2.7/dist-packages running install_egg_info Removing /usr/local/lib/python2.7/dist-packages/spi-1.1.egg-info Writing /usr/local/lib/python2.7/dist-packages/spi-1.1.egg-info
An Extra Step on Angstrom
On Angstrom, you’ll run into a problem:
python setup.py install running install running build running build_ext building 'spi' extension arm-angstrom-linux-gnueabi-gcc -march=armv7-a -fno-tree-vectorize -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mtune=cortex-a8 -D__SOFTFP__ --sysroot=/OE/angstrom-v2012-05/build/tmp-angstrom_v2012_05-eglibc/sysroots/beaglebone -fno-strict-aliasing -O2 -pipe -g -feliminate-unused-debug-types -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -I/usr/include -I/usr/include/python2.7 -c spimodule.c -o build/temp.linux-armv7l-2.7/spimodule.o arm-angstrom-linux-gnueabi-gcc -march=armv7-a -fno-tree-vectorize -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mtune=cortex-a8 -D__SOFTFP__ --sysroot=/OE/angstrom-v2012-05/build/tmp-angstrom_v2012_05-eglibc/sysroots/beaglebone -shared -Wl,-O1 -Wl,--hash-style=gnu -Wl, --as-needed build/temp.linux-armv7l-2.7/spimodule.o -L/usr/lib -lpython2.7 -o build/lib.linux-armv7l-2.7/spi.so /usr/lib/gcc/arm-angstrom-linux-gnueabi/4.5.4/../../../../arm-angstrom-linux-gnueabi/bin/ld: this linker was not configured to use sysroots collect2: ld returned 1 exit status error: command 'arm-angstrom-linux-gnueabi-gcc' failed with exit status 1
OK, whose idea was it to use sysroots?? Anyone?
Actually, I have no idea who or what is telling setup.py to include that “sysroot=..” parm (another seemingly unGoogleable solution), so I just created stripped the sysroot part out of the compile and link commands:
arm-angstrom-linux-gnueabi-gcc -march=armv7-a -fno-tree-vectorize -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mtune=cortex-a8 -D__SOFTFP__ -fno-strict-aliasing -O2 -pipe -g -feliminate-unused-debug-types -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -I/usr/include -I/usr/include/python2.7 -c spimodule.c -o build/temp.linux-armv7l-2.7/spimodule.o 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 -Wl,--as-needed build/temp.linux-armv7l-2.7/spimodule.o -L/usr/lib -lpython2.7 -o build/lib.linux-armv7l-2.7/spi.so
Having generated the object file, you can rerun python setup.py install to complete the process.
python setup.py install running install running build running build_ext running install_lib copying build/lib.linux-armv7l-2.7/spi.so -> /usr/lib/python2.7/site-packages running install_egg_info Writing /usr/lib/python2.7/site-packages/spi-1.1-py2.7.egg-info
Python Extension Binaries
If above process left you totally confused, or simply didn’t work, you can try downloading a binary of my customized version of the Python extension.
If you’re running Ubuntu on your Beaglebone:
wget http://gigamega-micro.googlecode.com/files/spi.so.gz gunzip spi.so.gz
If you’re running Angstrom:
wget http://gigamega-micro.googlecode.com/files/spi-angstrom.so.gz gunzip spi-angstrom.so.gz mv spi-angstrom.so spi.so
Writing to an SPI Device from the Python Shell
Before running some Python code to write to an SPI-attached LCD, we can test the setup using an 74HC595 chip. (If you don’t have a 74HC595 chip, you can skip ahead to the example Python commands: they would be basically the same for any SPI-controlled device).
The 74HC595 is commonly used as a GPIO-extender: by using the 3 SPI pins on the Beaglebone, we get 8 extra GPIO pins. This isn’t a particularly attractive tradeoff on the GPIO-rich Beaglebone, but the 74HC595 makes a good testbed for playing with SPI before moving on to more sophisticated (and finicky) devices.
The HC74595 pinout looks like this, as taken from the datasheet. You can find a thorough and easy-to-understand explanation of this chip on the Arduino web site. Note that the latch pin, aka the Chip Select pin, is aaka the “storage register clock input” pin here. Don’t ask me!
An easy way to test SPI output is by hooking by LEDs (through resistors) to each of the 8 GPIO pins on the 74HC595.
The following diagram shows this kind of setup. I used an LED bar instead of individual LEDs. If you’re lazy and/or don’t have an LED bar or 8 LEDs, you can hook up a couple of LEDs — I’d suggest Q0 and Q 1 — and still verify that everything is working OK.
The Beaglebone pins involved in SPI Bus 1 are as follows:
- Port 9 Pin 28 – Chip Select – connected to pin 12 of the HC74595 with yellow wire
- Port 9 Pin 29 – Data 0 (aka MISO – Master In Slave Out) – unused in this project, but could be connected to pin 9 of the HC74595
- Port 9 Pin 30 – Data 1 (aka MOSI – Master Out Slave In) – connected to pin 14 of HC74595 with green wire
- Port 9 Pin 31 – Clock – connected to pin 11 of the HC74595 with white wire
The “master”, in this case, is the Beaglebone. Since at this point we are only writing, not reading, we’ll leave Pin 29 unconnected. This gives us the liberty of connecting the 74HC595’s voltage pin, Pin 16 (to the right of the chip’s semicircular imprint), to 5V if we want to make those LEDs sweat. More usefully, it also allows us to safely connect the Beaglebone to SPI write-only devices that run at 5V, as we will with the LCD. Do not connect Pin 29 to an SPI device running at 5V!
Note that the spidev2.0 device is identified as SPI1 in the System Reference Manual. Note also that the pinmuxing for the SPI1 pins all default to SPI mode (mode 3).
Now that we have things hooked up, let’s fire up Python and send a command to the 74HC595. Note that this needs to be done as root, so run sudo python if you are on Ubuntu:
root@beaglebone:# python Python 2.7.2 (default, Apr 27 2012, 09:45:45) [GCC 4.5.4 20120305 (prerelease)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from spi import SPI >>> leds = SPI(2, 0) >>> leds.writebytes([0x00])
Even if you don’t know Python, you probably figured out that this should turn all of the LEDs off (by setting all of the 74HC595 pin’s low). To turn the LEDs on:
If that’s not working for you, check the wiring: aside from the GPIO pins, there are a bunch of pins on the 74HC595 that need to be connected to ground or to voltage, as shown in the figure above.
Let’s see which pins correspond to which bits:
This will set high the lowest GPIO pin on the 74HC595, Q0, which is connected to the leftmost LED in the circuit diagram and photograph. Similarly, if we had written 0xC0, it would set Q0 and Q1 to high. So, the data is sent LSB first — the opposite of what you might expect (but the same as the Arduino, by the way). We’ll have to keep this in mind when coding.
Although we won’t need it when dealing with the 74HC595, sending multiple bytes of data is pretty simple from Python:
>>> leds.writebytes([0x01, 0x02, 0x03, 0x04])
Technically, that command turns on each of pins Q0 to Q3 in turn, though of course it occurs too quickly to see any but the last one.
There are a bunch of other commands that are supported by the spi Python extension:
>> dir(SPI) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'bpw', 'close', 'cshigh', 'loop', 'lsbfirst', 'mode', 'msh', 'open', 'readbytes', 'threewire', 'writebytes', 'xfer', 'xfer2']
I haven’t experimented with most of these, but 2 that are worth pointing out are:
- writeBytes – writes a list of byte data, as shown in the example above. The original spimodule.c had a hard-coded limit of 32 bytes, which I increased to 1024. I don’t know what the practical limit is: 1024 is enough for my current uses.
- msh – Sets the clock frequency. For example, the following displays the clock rate being used in our connection to the 74HC595, then changes it to 1 Mhz.
>>> leds.msh 8000000 >>> leds.msh = 1000000
The default of 8 Mhz is actually the maximum clock rate allowed by the original spimodule.c code, though I removed that in the version I customized.
Having said all that, changing the clock rate is completely unnecessary for this project, either with the LEDs or the LCD. The 74HC595 is very flexible about the frequency (it supports up to 100 Mhz, and anything from 1 Mhz up worked fine when I tried it), and neither the LEDs nor a character LCD require much bandwidth. The speed setting will be more useful when dealing with graphical LCDs.
Writing to an I2C/SPI LCD Backpack from Python
Adafruit’s I2C/SPI LCD Backpack is a handy accessory for testing SPI and I2C. It uses 2 common GPIO extenders ( the 74HC595 and, for I2C, the MCP20008) to connect to a standard character LCD, allowing you to test SPI and I2C without much wiring. It’s also the cheapest LCD connector that I’ve seen: $10. I’ll be using it again in a future article about using I2C with the Beaglebone.
Assembly instructions for the backpack are at http://learn.adafruit.com/i2c-spi-lcd-backpack. Note that, by default, it uses I2C, so you’ll need to put a blob of solder on the SPI bridge for use with the code below.
The 5 screw terminals on the backpack should be connected to the Beaglebone as follows:
- LAT – Port 9 Pin 28 (i.e. Chip Select)
- DAT – Port 9 Pin 30
- CLK – Port 9 Pin 31
- 5V – Port 9 Pin 5 or 6
- GND – Port 9 Pin 1 or 2
I’ve written some Python class modules to make it easy to write to a backpack-connected LCD:
wget http://gigamega-micro.googlecode.com/files/spilcd.py wget http://gigamega-micro.googlecode.com/files/gmspi.py wget http://gigamega-micro.googlecode.com/files/hd44780lcd.py wget http://gigamega-micro.googlecode.com/files/bone.py
You will also need the spi.so Python extension, either installed as a Python library or as a file in the same directory as the other files. If you don’t have it, go back to the section Adding an SPI Extension Module to Python section above for instructions.
Once you have all the files, run the following as root to write some text to the LCD
root@beaglebone:# python Python 2.7.3 (default, Aug 1 2012, 08:09:58) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from spilcd import spilcd >>> lcd = spilcd(2, 0) >>> lcd.begin(16, 2) >>> lcd.setBacklight(True) >>> lcd.home() >>> lcd.Print("Hello world") >>> lcd.setCursor(0, 1) >>> lcd.Print("Hello line 2")
(You should, of course, use begin(20, 4) for a 4-row, 20-character LCD.)
As you can see, the syntax is based on the Arduino LiquidCrystal library. I’ve implemented many (but not all) of the same methods: enter help(spilcd) from Python to get the list.
I’ve also written a plant light controller application which makes use of the spilcd class, along with the Arduino-like bone class for accessing the GPIO . It uses the LCD to display the current date and time, the sunrise and sunset times, and the plant light settings. While it doesn’t break any new ground in terms of Beaglebone functionality, you might want to refer to it for sample code on how to use my Python classes for SPI and GPIO access.
You can download the plant light controller code from here.
This article covers one of the simpler uses of SPI, but this protocol opens the door to a lot of inexpensive LCDs. I’m currently working on a Beaglebone project that uses an Adafruit ST7565-based 128×64 LCD, and Adafruit has an assortment of other SPI-based LCDs for sale. SPI-based input devices are less common: I2C is the standard protocol used by sensors. I’ll look at I2C in upcoming “Beaglebone Coding 101″ article.