Quantcast
Channel: Hacker News
Viewing all articles
Browse latest Browse all 10943

I2C Programming in Go | George McBay

$
0
0

Comments:"I2C Programming in Go | George McBay"

URL:http://www.gmcbay.com/2013/01/i2c-programming-in-go/


I2C is a two-wire serial bus interface historically used to communicate between different components of an electronics device on the same circuit board.  Amidst the rise in hobbyist/open electronics and the “Internet of Things”, I2C has also become popular as a well-defined communication technology between off-the-shelf electronic project components of the type commonly sold at Adafruit, Sparkfun, and Newark.  Accelerometers, NFC chips, LED displays, and many other types of devices are available using this interface, allowing hackers to easily communicate bidirectionally with special-purpose components from their micro-controller or full-fledged computer projects.

One example of such a component that is fun to play with is the Adafruit 8×8 LED Matrix w/I2C Backpack.  This device is pretty much exactly what it sounds like, 64 LEDs arranged 8×8 that can be individually controlled over an I2C interface via an HT16K33 integrated circuit.  Starting with a few of these devices and a Raspberry Pi, I decided to delve into what it would take to drive the LEDs from my favorite language, Go.

I started by soldering the LED matrix devices to their backpacks as described here:

http://learn.adafruit.com/adafruit-led-backpack/0-8-8×8-matrix

and hooking one up to the Raspberry Pi as described here:

http://learn.adafruit.com/matrix-7-segment-led-backpack-with-the-raspberry-pi/overview

Adafruit provides great support for controlling these devices and others that they carry from within Python code (available via GitHub (https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code) and this code works well for spot-checking that the device has been successfully connected and is in working order.  All of my soldering went okay and the device worked (though I did have to modify the Python code slightly because at the time it assumed it would be driving a LED matrix on I2C bus 0, but it should have been using bus 1 because the I2C bus number designations flipped around the time the newer 512MB Raspberry Pi devices became available and my Pi is a 512MB model).

Python is a fine language for many things, but my current go-to language for new projects is Go.  As someone who programmed in C and C++ professionally for a number of years and then moved to other languages like C#, Java, and others, Go has reintroduced me to the simplicity in programming that I didn’t fully realize I was missing, while doing away with the bits I never cared much for like make/autoconf hell and always worrying about the specifics of memory allocations and deallocations.  The fact that it has fantastically clean support for multithreading out of the box is an additional benefit, though for this specific use case multithreading isn’t much of a factor.

On the initial software setup side, it is pretty easy to set up a Go compiler environment on the Raspberry Pi.  Dave Cheney has excellent instructions here:

http://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi

I recommend skipping the ./all.bash step and just doing ./make.bash (also mentioned on that page) as it probably isn’t necessary for you to run all of Go’s unit tests and doing so can take a considerable amount of time on the Pi.

Linux has excellent support for the I2C protocol within kernel drivers, which is how the Adafruit Python code and other I2C-driving applications talk to the external components from a full SoC-type device like the Raspberry Pi.  C language interfaces for this functionality can be found described in various Linux header files such as <linux/i2c.h>, <linux/i2c-dev.h> and an API exists as part of the lm-sensors I2CTools package.  Using CGO, we could use these API interfaces directly from C, and then link that to our Go code, but I generally prefer to write code entirely in Go when possible. Pure Go code keeps things simpler overall and makes it easier to cross-compile code as Go has fantastic support for native cross-compiling, but CGO is currently unsupported when cross-compiling..

So, what other options do we have?   Well, like most UNIX derivatives, Linux has a history of allowing device control through access to files (residing in the /dev directory) that appear to be ordinary file system files.   While this isn’t quite as universal as say Plan9, where basically everything is a file, it does make it pretty easy for a language like Go to communicate with devices easily, so long as the language is capable of reading and writing files (which Go certainly is).

Luckily for us, modern Linux systems with I2C support usually export this control out to device files.  The files reside in /dev as expected and there is one per I2C adapter bus, taking the naming convention /dev/i2c-(bus_number), so on a device like the Pi with two I2C busses available the files are /dev/i2c-0 and /dev/i2c-1  Read/write access to the  bus is accomplished via standard read/write calls to these files along with some special ioctls available on them.  Go can read and write files and has ioctl support via the syscall package, so it should be pretty easy to write a pure Go library to handle this communication!

Note: On Rasbian, which I think is the most popular distro for the Pi, you’ll need to load a couple of driver modules before the /dev/i2c- device files will appear.  You can do this using the modprobe command as shown below. On Adafruit’s Occidentalis distro, these drivers should automatically be loaded without needing to modprobe them in.


sudo modprobe i2c-dev
sudo modprobe i2c-bcm2708

Using the documentation for the Linux I2C interface at http://www.kernel.org/doc/Documentation/i2c/dev-interface and reading through the associated header files, I came up with the following Go code for reading from and writing to the I2C bus adapters:

i2c_bus.go

Those familiar with Go may wonder why this code doesn’t use channels, one of the signature features of Go.   I certainly considered using channels here as they would make handling the potential threading issues very easy, but ultimately channels were a bad fit for reads (though writes would have worked well).  In order to keep the API for reads and writes consistent, I went with more traditional mutexes to control access to the singleton-like I2CBus devices to make sure address sets were handled atomically with device reads and writes.

Using this code, clients can trivially request a pointer to an I2CBus via the adapter bus number and then read and write to devices via addresses and device registers.

On top of this I2CBus base code, I wrote an HT16K33 device class based on the Adafruit Python code allowing application code to write to a buffer consisting of 8 unsigned 16-bit integers which represent a data block that can be sent to and from the HT16K33 over I2C describing the current state of the LED pixels on the device.

I then implemented some control code (also based largely on the existing Python code) to act as  simple interface to an 8×8 LED matrix array (since the HT16K33 can be used to control various configurations of LED devices).

The end result of this is the HT16K33.EightByEight class which can be instantiated and then read from or written to using the Pixel and SetPixel functions:

// Create a new instance of an 8x8 LED matrix class
// corresponding to a device on I2C adapter 1 with a
// device address of 0x70 and turn on the LED at
// location 0, 0
grid, err := HT16K33.NewEightByEight(0x70, 1)
if err != nil {
 log.Panicf("uh oh! %v\n", err)
}
grid.SetPixel(0, 0, true)

(Continued on Page 2)


Viewing all articles
Browse latest Browse all 10943

Trending Articles