Arduino and the Two-Wire Interface (TWI/I2C) Including A Short Didactic Parenthetical On Making TWI Work On An Arduino Mini

I have been using the Arduino and Atmel microcontroller’s generally using the SPI (serial-peripheral interface), but decided to look at the two-wire (a.k.a. I2C) interface as well. I’m doing this partially because it would be good to know how it works, but also because it’s electrically more compact. It only uses two-wires, rather than the four required for SPI, so schematic designs and board layouts become a bit more manageable. The trade-off is a little bit more complicated protocols semantics, but nothing out of control. (I’m also looking at using a two-axis accelerometer that’s much less expensive than the three-axis one I’ve been using – $4 versus $15. For some experiments, two-axes may be perfectly fine, and I’m happy to save the $11.)

The first step was making sure the Arduino would handle the TWI – there’s pretty much no reason it shouldn’t, because the Atmega8 certainly handles it. So, the next step was finding out how best to handle TWI transactions.

To do this, I consulted the Atmega8 specification sheet, which has a pretty thorough explanation of the protocol and the implementation details on the Atmega8. (There are also a couple of useful application notes available here and here.) It’s so thorough that I had to print it out. I got a pretty good understanding of how it works but before I started coding, I noticed that there were some TWI libraries both in avrlibc and in the “Wire” library in Wiring.org happens to be packaged as a “sanctioned” external library for Arduino, so that was pretty much that.

Nicholas Zambetti, who wrote the Wire library, pretty much told me it should work with no problems, and he was pretty much right. No problems. His library abstracts the TWI innards really nicely, so I don’t have to muck with any Atmega registers or anything of that sort.

I hooked up my Arduino to a handy accelerometer for testing. (I’m still using the expensive LIS3LV02DQ.) Analog In 4 goes to SDA, and Analog In 5 goes to SCL. I have pull-up resistors on those lines, but Nicolas explains that his library enables the internal pull-ups on the Atmega, so I can probably pull those from my breadboard. Either way, it’s working just fine, even with the external pull-up resistors. (I’m using 4.7k resistors.)

Arduino Mini TWI (I2C)

(I also found in one weird situation that I had to explicitly clear the clock prescaler to get the UART functioning properly. This was while getting TWI working on an AT90USB1287 – TWI worked fine, but the UART was spitting out garbage. On chips where the clock prescaler can be set through the fuses, it’s important to verify that the prescaler isn’t hard wired to divide the clock. This’ll cause anything that is dependent on timing to potentially be off kilter. In my case, the UART was expecting an 8MHz clock to determine timing and baud rate, but the clock was being divided in hardware – not even in the firmware.)

Wire/TWI (I2C) on the Arduino Mini

Getting TWI working on the Arduino Mini (Although the Arduino and Arduino Mini share a name, they don’t share the same processor. The Arduino Mini uses an ATmega168, while the Arduino..normal.. uses an ATmega8. You’ll need to recompile/re-“verify” the Wire library files in order to get TWI to work on the ATmega168/Arduino Mini. There’s a thing or two you’ll have to do by hand to get this to work, and you’ll need to do it each time you move code that’s using the Wire library to a different processor. In other words, when you decide to port the code to the ATmega8, or you put an ATmega16 in your Arduino, or whatever — you’ll need to recompile these libraries. Here’s the drill:

Navigate in a file browser or command prompt to the root of your Arduino file hierarchy. Then go into:

lib/targets/libraries/Wire/
delete Wire.o

lib/targets/libraries/Wire/utility
delete twi.o

Once you’ve done this, make one modification to the header file twi.h in lib/targets/libraries/Wire/utility. Look around line 27 for a commented out line like this:

//#define ATMEGA8

Un-comment it (take out the “//” in the front.) This’ll ensure that the internal pull-up resistors on the ATmega8 are enabled when you’re developing for the normal Arduino. You’ll only need to make this change to twi.h once and for all. Future builds of Arduino should have this fixed.

For those who are curious — pull-up resistors are required on the TWI lines — SCL and SDA. You may use your own external pull-ups, but enabling the internal ones saves you the hassle. But, I don’t think you’ll do much harm if you have the internal one’s enabled and use external ones. But, generally you’ll want to avoid having both internal and external pull-ups.)

Anyway. These modifications described above should get TWI working on the Arduino Mini.)

Here’s the code I wrote and ran:

#include

// TWI (I2C) sketch to communicate with the LIS3LV02DQ accelerometer
// Using the Wire library (created by Nicholas Zambetti)
// http://wiring.org.co/reference/libraries/Wire/index.html
// On the Arduino board, Analog In 4 is SDA, Analog In 5 is SCL
// These correspond to pin 27 (PC4/ADC4/SDA) and pin 28 (PC5/ADC5/SCL) on the Atmega8
// The Wire class handles the TWI transactions, abstracting the nitty-gritty to make
// prototyping easy.

void setup()
{
  pinMode(9, OUTPUT);
  digitalWrite(9, HIGH);
  Serial.begin(9600);

  CLKPR = (1<<clkpce);
  CLKPR = 0;

  Wire.begin(); // join i2c bus (address optional for master)
  Wire.beginTransmission(0x1D);
  Wire.send(0x20); // CTRL_REG1 (20h)
  Wire.send(0x87); // Device on, 40hz, normal mode, all axis's enabled
  Wire.endTransmission();

}

void loop()
{

  byte z_val_l, z_val_h, x_val_l, x_val_h, y_val_l, y_val_h;
  int z_val, x_val, y_val;
  //Serial.println("hello?");
  //byte in_byte;
  // transmit to device with address 0x1D
  // according to the LIS3L* datasheet, the i2c address of is fixed
  // at the factory at 0011101b (0x1D)
  Wire.beginTransmission(0x1D);
  // send the sub address for the register we want to read
  // this is for the OUTZ_H register
  // n.b. supposedly masking the register address with 0x80,
  // you can do multiple reads, with the register address auto-incremented
  Wire.send(0x28);
  // stop transmitting
  Wire.endTransmission();
 // Now do a transfer reading one byte from the LIS3L*
 // This data will be the contents of register 0x28
 Wire.requestFrom(0x1D, 1);
  while(Wire.available())
 {
   x_val_l = Wire.receive();
 }
 Wire.beginTransmission(0x1D); Wire.send(0x29); Wire.endTransmission();
 Wire.requestFrom(0x1D, 1);
 while(Wire.available())
 {
    x_val_h = Wire.receive();
 }
 x_val = x_val_h;
 x_val <<= 8;
 x_val += x_val_l;

 // Y Axis
 Wire.beginTransmission(0x1D); Wire.send(0x2A); Wire.endTransmission();
 Wire.requestFrom(0x1D, 1);
 while(Wire.available())
 {
    y_val_l = Wire.receive();
 }
 Wire.beginTransmission(0x1D); Wire.send(0x2B); Wire.endTransmission();
 Wire.requestFrom(0x1D, 1);
 while(Wire.available())
 {
    y_val_h = Wire.receive();
 }

 y_val = y_val_h;
 y_val <<= 8;
 y_val += y_val_l;

// Z Axis
 Wire.beginTransmission(0x1D); Wire.send(0x2C); Wire.endTransmission();
 Wire.requestFrom(0x1D, 1);
 while(Wire.available())
 {
    z_val_l = Wire.receive();
 }
 Wire.beginTransmission(0x1D); Wire.send(0x2D); Wire.endTransmission();
 Wire.requestFrom(0x1D, 1);
 while(Wire.available())
 {
    z_val_h = Wire.receive();
 }

 z_val = z_val_h;
 z_val <<= 8;
 z_val += z_val_l;
  // Set up to read the next register
  // Supposedly, if you set the most significant bit of
  // the register address to 1, you can do a multiple read,
  // with the register address auto-incrementing. This way, you
  // don't have to do another write — specifying the next register —
  // before reading the next register value. This would be very good, but
  // with a cursory attempt, I have yet to make this work.
/*
    Wire.beginTransmission(0x1D); // transmit to device #4
  Wire.send(0x2C);        // read outx_h
  // stop transmitting
  Wire.endTransmission();
  Wire.requestFrom(0x1D, 1);
 while(Wire.available())
 {
   z_val_l = Wire.receive();
 }

 z_val = z_val_h;
 z_val <<= 8;
 z_val += z_val_l;

  // z axis acceleration, all set.
  // Perfectly still and with the z-axis parallel to the ground, it should be about 1024-ish
  // (I find in practice it is around 1019.)
  // When the z-axis is orthogonal to the ground, it should be zero.
  // 1024 is +1g acceleration, or normal gravity
  // 2048 is +2g
  // 0 is 0g
  Serial.println(z_val, DEC);
*/
Serial.print(x_val, HEX); Serial.print(" ");Serial.print(y_val, HEX); Serial.print(" "); Serial.println(z_val, HEX);
delay(100);
}

Technorati Tags: ,