I2C Programming in Go
gmcbay.com
gmcbay.com
Access to various bits of I2C hardware probably limit the usefulness of this specific post to a narrow audience, but I should be writing a lot more about various bits of code I've done to get pure Go talking to linux at a low level -- framebuffer writing via /dev/fb, input handling of mice, touchpads, touchscreens, keyboards, etc via reading /dev/input/*, etc.
(All part of a vague New Years resolution to blog more and get more public code out there, since I've been tinkering on this stuff a lot privately for the past 8 months or so).
[1] https://www.adafruit.com/products/381
Speaking of the LCD, do you have any suggestions for finding the address of an I2C device? Is there a way to query or scan it to see at which address it responds? I just got the Sainsmart LCD2004 as a gift. Google searches about the device result in forum posts, where people have got it to work with addresses from 0x20 to 0x27 to 0x40 -- one guy had to work it out with a multimeter. Do you know of a more elegant solution than brute-forcing it?
http://www.lm-sensors.org/wiki/man/i2cdetect
Both the web page and the program itself will inform you that running it could cause Armageddon, but assuming all you have plugged into your i2c bus is an LCD it really shouldn't cause any harm.
On Raspbian, you'll have to apt-get install i2c-tools to install this (and you'll have to modprobe in the i2c drivers as described in my blog post). On the Adafruit distro it comes pre-installed.
Running i2cdetect on my Pi which currently has one of those LED matrix devices I talked about in the blog results in this:
pi@raspberrypi ~ $ sudo i2cdetect 1
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --
(Telling me it detected a device at 0x70, which is correct for this device). I don't have the LCD you have so I'm not sure if it is properly detected by i2cdetect, but that's what I'd try first.This obviously hadn't come up in relation to Arduino searches.
Could you elaborate on this? I'm just starting to play with Go and I think my first thought would be "channels". What doesn't fit here? Is just the requirement that all downstream callers would need to use channels too?
Say you have 3 goroutines, any of which might be requesting data from any/all of 3 connected I2C devices all on the same I2C bus. They could signal a read over a channel and then have a return channel they look for the result on, but if there is a single channel per I2C bus, how do they know the result they see is the one they asked for? (As opposed to one being asked for by another goroutine also reading from that channel). You can do it with some sort of token/id system but it gets complicated because if you read the data off the read channel and it isn't yours you have to stuff it back on there for others to read and make sure your goroutine isn't deadlocking everyone else out.
This is, of course, possible to work around while still using channels in many different ways. One way is every time a client calls Read a new channel is created to return the results, but given that the data returned is of variable size and the underlying I2C layer requires you to pass in a pointer to the data buffer being used channels don't buy you much here. Plus you end up with a lot more garbage to collect in the form of new channels being created each time Read is called, which is no big deal on a "big" computer, but when doing Go programming for smaller devices I do try to minimize the amount of garbage that needs to be collected.
When I first got into Go I wanted to do everything with channels and goroutines because where they fit they are awesome, but I've found the "essence" of Go is simplicity, not specific features, and sometimes a plain old mutex is just simpler than a more complex channel system.
And you can, of course, pretty trivially add channels on top of the I2C API like I do in the accel_dump example, where your code knows exactly what the access patterns of any single device will be (whereas the base I2C code doesn't really know or care).
What you say is exactly the class of problems I've previously encountered when, for example, you're trying to send an SMS via AT commands and the modem does something asynchronously (eg. RING indicator, or a periodic signal quality report), and you lose track of who's supposed to get the trailing "OK". Very tedious. In Java I implemented the equivalent of a bidi channel with some timeout tricks for buffering up entire response lines, but it wasn't much fun.
I had plans to drop some I2C eeprom chips on a pcengines ALIX x86 SBC (to gain a small amount of non-volatile storage without need to mount CF partitions read-write), but just haven't found the motivation to work through the software side. Problem solved!
export GOOS=linux
export GOARCH=arm
export GOARM=5
Then build the Go compiler tools using the src/make.bash (.bat on Windows) script that comes with the Go source code. That should build the Go compiler for that platform and stick it within your GOROOT bin directory.Then prior to building anything you're going to put on the chumby make sure those variables are defined so Go knows which compiler to use.
Dave Cheney's tools make this easy to set up for all the different targets but if you only have one non-local target, it is probably easier to just do it manually by setting the env vars and building the Go compiler again.