
I2C Programming in Go - geetarista
http://www.gmcbay.com/2013/01/i2c-programming-in-go/
======
georgemcbay
blog author here, feel free to direct any questions into this thread since I'm
a Hacker News addict^H^H^H^H^H^Hregular.

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).

~~~
stephengillie
Excellent post! I'll be adding a RasPi to my collection soon, and I've been
wrestling with an I2C 20x4 LCD. I'll definitely be rereading your blog when it
arrives.

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?

~~~
georgemcbay
i2cdetect?

<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.

~~~
stephengillie
Wow, thank you so much!

This obviously hadn't come up in relation to Arduino searches.

~~~
georgemcbay
Ah, yeah, not relevant to Arduino but if you have any Linux boxes with I2C
support available (which you will when your RaspPi arrives), i2cdetect is
great for this sort of thing.

------
jrabone
_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)_

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?

~~~
georgemcbay
I'm okay with exposing channels as part of an API. The problem was that in my
experience doing async reads (especially where the data size is variable, not
fixed) where you may have multiple possible callers reading from what is
basically the same resource doesn't mesh too well with channels.

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).

~~~
jrabone
Thanks, that's helpful. I've toying with porting some code I wrote (in Java)
to access an embedded GSM modem over the serial port to Go as a learning
exercise, hence thinking about the "right" way to handle the underlying
read/write semantics.

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.

------
joe_bleau
Thanks for starting the blog! As a fellow chumby enthusiast, I've been meaning
to shoot you an email asking for more detail on your cross-compiled-go-for-
chumby setup.

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!

~~~
georgemcbay
Assuming you have an environment where you can build Go already (which
basically means having a working local gcc -- it only needs to be able to
produce code for the system you will build the code on, it doesn't have to be
a cross-compiler for the target system), it is pretty much as simple as
defining those variables I mention in the blog post:

    
    
      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.

~~~
stock_toaster
If you use homebrew on osx, you can `brew install go --cross-compile-all` to
get all of them built. Or `brew install go --cross-compile-common` to get the
common ones (arm is in the common group).

