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).
Thanks for sharing this! I will definitely have a look at your code for inspiration later on, since I'd like to use Go to read 1-wire components (like the DS18B20 temp sensor) through a LinkUSB device, without relying on OWFS.
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?
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.
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.
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?
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).
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.
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!
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:
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.
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).
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).