Monday, April 29, 2013

Adventures in I2C

I've spent the last couple of days up to my armpits in I2C, so before it all evaporates I thought I'd better make some notes, and hopefully this helps someone else in the process.

In the last post I described having to grab some analog-ish data closer to the source, and I decided that I2C would be a good choice of bus because I can use smaller microcontrollers and two less pins. I'm not sure that's a good idea; it works, but I'm leaning towards going with AT328's all around, and sticking with SPI.

But for the time being, I'm going to give I2C a chance. I've used it before, from projects as small as an ATTiny 85 running an RGB LED, all the way up to LCD and LED displays, and it can work, so I know it's not a totally pointless exercise.

For background on I2C and the Atmel family, the best two sources - although somewhat conflicting - are these two pages:

http://www.gammon.com.au/forum/?id=10896&reply=9#reply9 (the link starts towards the end of the page, but that's where the summary is - the whole page is a good read).

and an alternate library:

http://dsscircuits.com/articles/arduino-i2c-master-library.html

The second page, although it describes a fairly different set of commands than the Arduino documentation for Wire.h, does shine a light on the fact that Wire isn't so great, and how to use I2C as it was designed. I don't think Wire follows standards 'exactly'. Not that I2C has abundant standards, but that's another post... at least this alternate library does make the effort.

The interesting thing is how those two pages diverge in how they use I2C; the first link does have some goodness in it, but as the examples build, you end up with a somewhat non-standard protocol built on top of the I2C transport. That's not all bad, but it can mess with your head a bit in terms of interfacing at a more generic I2C level, and how to approach it in your own code.

The second link is more straightforward, and really shows what a vanilla-standard implementation should look like. The interesting thing is that the method of building a protocol on top of the transport is already assumed to be inherent in I2C; after all, there a zillion little I2C widgets out there already, and interfacing with them is usually a snap.

So what gives? What's the conceptual difference?

Commands.

It all boils down to commands.

You don't need 'em - at least not as the first link describes them, and in fact if you try to make the examples in the first link talk to the examples in the second link - it won't work 'out of the box', because the implementers came at their solutions quite differently.

As an example, take a small analog-to-digital sensor of some sort, with an I2C interface to it. Pretty much all you have to do is wire it up, plug in the slave address from the datasheet into your Wire function calls, and start getting data, right?

You can even do things like change the I2C address in software (sometimes it's hardware jumpers, but for this example, assume it's software), or feed it a new calibration value that it uses in it's conversion math.

Those start to sound like commands... 'Change your I2C address to xxx, please!' or 'Set the calibration value to yyy right now!'.

Typically I2C devices are 'register based', so getting data or setting values is all about registers, which are most easily imagined as an array of bytes. Sometimes just one or two entries long; sometimes a dozen or more.

There is also the notion of reading and writing to an I2C device in the standard; partly by how you change the 7 bit address on the wire (with the LSB being the read/write bit), and partly by how it's implemented.

In the example device, you might read a calibration value from a register, so you know how the device is configured; or you could write to that register address with a new value, and change the calibration value.

And here is the magic. If it makes sense, the implementation should just take that value and also write it to EEPROM internally, so that the next time it's powered up, all the defaults are what they should be. You don't need an explicit command to write stuff to EEPROM - it just happens.

With that kind of conceptual model there is really no need for 'commands' at all - writing to registers ARE commands.

And if you really really want some extra flexibility, remember that a register entry, being a byte, can hold 255 different bit values... each one could be a different command. But if you are working with an 8k code limit in an 8 bit Atmel chip, I bet you run out of flash before you run out of command numbers to implement.

This register model is at the heart of the way that I'm building up the rover. Implementation-wise it's also a not-so-accidental fact that the register address label #defines can be stuffed into a header file and then shared between master and slave device. Nice and tidy.

But keeping the model 'pure', and NOT straying down the path of the first links example code has been a pain, since that's where I started, and expunging that has taken some time, but it's worth it. In the end I'll have a set of I2C slaves that will function with any master device, instead of a custom built master that layers on it's own protocols.

Sorry for not having any pretty pictures this time... perhaps next time I'll have some hardware moved off the breadboard to show for my coding efforts.






No comments:

Post a Comment