In Learning to Use I2C, I talked about the difficulty I’d been having getting the MAG3110 breakout board from Sparkfun to work, and my fears that I had burned it out by running it overnight at 5v (instead of the rated 3v). I suspected that my problem was really a software problem, but debugging the software when I was afraid that the hardware was fried seemed like an exercise in futility.
I bought another of the breakout boards from Sparkfun (they’re only $15), and soldered on a header yesterday. The code failed in almost exactly the same way with the new (presumed good) part as with the old (presumed fried) part, so I was convinced that the problem was indeed in the software.
I spent half of yesterday and most of this morning carefully rewriting the library of I2C interface code. I was starting with example code from Sparkfun for the MMA8452Q accelerometer, which I had generalized to handle other I2C devices.
The library worked fine with the accelerometer, so I thought it was ok, but it did not work with the magnetometer. I added a lot of error checking (making sure that the microprocessor was in the expected state after each I2C operation), and found that things were not working as expected. The extra error checking made it much easier to diagnose the problems. I had to re-read the I2C documentation in the ATMega328 datasheet several times, to make sure that I had all the details right (I didn’t, of course). The documentation in that data sheet is better than several of the tutorials I’ve seen on line, and is both thorough and comprehensible in describing the interface.
I did not implement all features of the I2C interface—in fact, I have a rather minimal implementation that uses polling rather than interrupts and assumes that the Arduino will always be the bus master. Those assumptions are fine for most Arduino projects, which just use the I2C bus for talking to a handful of peripherals, but sometime in the future I may need to make a more complete set of code that can handle multiple masters, the Arduino as a slave device, and interrupts rather than polling and waiting for operations to finish.
Because I was more interested in simplicity and robustness than speed, I rewrote the code so that transactions were finished (and appropriate status checked) before functions returned. With these changes I found that the STOP condition was not happening, despite being requested. All other operations on the bus are checked with the TWINT bit of the TWCR register and the upper bits of the TWSR register, but determining that STOP has completed requires checking the TSWTO bit of the TWCR register. The code I had started from just checked the TWINT bit for the other operations, and had a fixed timeout that was too short—it did no checking at all on the STOP state, just adding a fixed delay.
Once I got the STOP timing cleaned up (and earlier, making sure to send NAK when reading the last byte), everything worked fine. The accelerometer code had probably worked ok because there were enough delays after stops that the stops completed, even though I had not checked to make sure. With the fixed code, even the magnetometer that I thought I had fried seems to work ok.
The interface for the students in the Robotics Club is fairly simple (much simpler than the standard “Wire” library):
// Read one register uint8_t i2cReadRegister(uint8_t i2c_7bit_address, uint8_t address); // Read num_to_read registers starting at address void i2cReadRegisters(uint8_t i2c_7bit_address, uint8_t address, uint8_t num_to_read, uint8_t * dest); // Write one register void i2cWriteRegister(uint8_t i2c_7bit_address, uint8_t address, uint8_t data); // Write num_to_write registers, starting at address void i2cWriteRegisters(uint8_t i2c_7bit_address, uint8_t address, uint8_t num_to_write, const uint8_t *data);
I suppose I should check that there are no problems when I connect both devices to the same I2C bus, before handing this over to the students to use for their robot.
I should also put the i2c.h and i2c.cpp in some public place for others to use (perhaps on github? maybe on my web pages at work?). It is really a shame that WordPress.com does not permit code-like files as pages.
[Update 22 May 2012: I've had the code available at http://users.soe.ucsc.edu/~karplus/Arduino/libraries/ for some time now, but forgot to update this post to mention it.]