New and growing well-documented, feature-complete I2C device library

UPDATED 3/27/2012 @ 1:50pm EDT

Hey everyone!

I've just begun working on a consistent, documented, centralized library of different I2C device classes, built on top of a basic but simple I2Cdev class which provides a uniform single-bit, multi-bit, single-byte, and multi-byte reads and writes. I also recently added 16-bit register support in the form of word-based read/write methods in addition to the original byte-based ones.

http://www.i2cdevlib.com (fledgling project website)
GitHub - jrowberg/i2cdevlib: I2C device library collection for AVR/Arduino or other C++-based MCUs (source repository)

My goal is to have it continue to grow through my own code contributions as well as those of anyone else who wants to contribute, though that part is up to you all.

Current device classes:

  • ADS1115 - Texas Instruments ADS1115 16-bit A/D converter
  • ADXL345 - Analog Devices ADXL345 3-axis accelerometer
  • AK8975 - AKM AK8975 3-axis magnetometer
  • BMA150 - Bosch Sensortec BMA150 3-axis magnetometer
  • DS1307 - Maxim DS1307 real-time clock
  • HMC5843 - Honeywell HMC5843 3-axis magnetometer
  • HMC5883L - Honeywell HMC5883L 3-axis magnetometer
  • ITG3200 - InvenSense ITG-3200 3-axis gyroscope
  • MPR121 - Freescale MPR121 12-bit capacitive touch sensor (thanks to Andrew Schamp)
  • MPU6050 - InvenSense MPU-6050 6-axis accelerometer/gyroscope combo (compatible with MPU-6000)
  • SSD1308 - Solomon Systech SSD1308 128x64 OLED/PLED driver
  • TCA6424A - Texas Instruments TCA6424A 24-bit I/O expander

Some public code already exists for some of these, but nothing is really consistent. I want libraries that are well-documented, predictable, and feature-complete according to the manufacturer datasheets. I am also concerned with the lack of a centralized place to search, which I think can easily be overcome by using a repository like GitHub (and, eventually, creating a website for it, which I haven't got around to yet). Additionally, all of my code here is released under the MIT license, because I want people to be very free to do whatever they want with it. Some of the existing libraries out there are under the GPL, which is great but not always ideal depending on your project.

The I2Cdevlib project also aims to include detailed register information for every device in its database, which allows for rich interactive register maps like this:

(This particular image shows data for the MPU-6050, visible here)

This dynamic register info will shortly be able to be used to automatically create device class code, not just for Arduinos, but ultimately for other platforms like PIC, ARM, MSP430, etc.

Each device class is written with these ideas in mind:

  • Helpful and thorough documentation in Doxygen comments right in the source code.
    That way you don't have to go looking for it, but it's also easy to convert into well-organized HTML or PDFs.

  • Abstracted I2C hardware communication into a separate, static class.
    This means (1) less memory usage for multiple devices, and (2) easy modification if you need to switch everything from the Arduino "Wire" library over to, say, a software bit-banging implementation or something like Fastwire.

  • Uniform constant definitions.
    This is done so you know that ADXL345_RA_[anything] is a register address, and you know that ADXL345_[function]_BIT is a bit position, no matter what it's called. This convention is kept across all devices.

  • Uniform convenience methods.
    All devices have an "void initialize()" method and a "bool testConnection()" method, which can be used in your setup() function to verify connectivity easily. The TCA6424A's "initialize()" has no code in it because it requires no initialization (this is documented in a Doxygen comment also), but it's there anyway for consistency. The rest of the methods of each class are generally "get*" and "set*" except for very specific action methods, like "reset()", "readPin()" or "writePin()". You'll still want to look through the header files or docs, but functions are all named obviously according to their behavior as defined in the datasheet. Other than these few convenience functions, other "helper" functions like data conversion, application-specific calibration, etc. are not included in the classes in order to keep everything clear and modular from a design standpoint.

So far, so good, at least as far as my needs go, but I'd love to hear from you all about whether (1) you think this is a good idea, (2) you have specific devices you want to see in the library, (3) you are using (or will use) this code anywhere, or (4) you want to contribute code. Although I can't promise particularly speediness, I can write libraries for devices upon request (though if you want to send me a breakout board to test it with, that would be much appreciated also). Code contributions are welcome and easy using GitHub's fork and pull request features, though I will likely make a few small tweaks to contributed code just to make sure it fits well with the library's conventions. I want to keep all the code under the MIT license as well.

So...thoughts? :slight_smile:

Jeff (the Keyglove guy)

Jeff that looks like a great idea/project you have started. I recently bought a 16 bit I2C A/D converter based on the TI ADS1115 chip. it's pretty well priced in my opinion for being a complete break-out module and seems to have very good specs. It would be great if you would consider developing a library based on it and I would consider loaning you device if that would help the cause.

Module: http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&item=230619695873&ssPageName=STRK:MEWAX:IT

Datasheet: http://focus.ti.com/lit/ds/symlink/ads1115.pdf

Lefty

retrolefty:
I recently bought a 16 bit I2C A/D converter based on the TI ADS1115 chip. it's pretty well priced in my opinion for being a complete break-out module and seems to have very good specs.

Hi Lefty! I just glanced through the I2C register descriptions in the ADS1115 datasheet, and it doesn't look too bad at all. I'll see if I can come up with a library that I think should work, then you can try it and tell me how it goes. If we can't figure it out that way, then maybe I'll find a way to get my hands on the hardware to debug more efficiently.

Jeff

Thanks Jeff, that would be great. I've had the device for over a week now but just not the time to try and cobble something together. I'm a hardware type and software comes slow and gradual to me. The built in A/D pins on a arduino are useful but sadly fall far short of instrumentation quality. I thoough this would be a nice improvement to have avalible.

Lefty

Hi jeff, that looks very useful.

one suggestion is to add a timeout property to the library so calls want hang if the I2C device does not respond. Perhaps something like this:

    uint8_t count = 0;

    Wire.beginTransmission(devAddr);
    Wire.send(regAddr);
    Wire.endTransmission();

    Wire.beginTransmission(devAddr);
    Wire.requestFrom(devAddr, length);    // request length bytes from device

    unsigned long readStart = millis();
    do
    { 
      if(Wire.available() >= length ) {
        for (; Wire.available(); count++) {
        data[count] = Wire.receive();
        #ifdef I2CDEV_SERIAL_DEBUG
            Serial.print(data[count], HEX);
            Serial.print(" ");
        #endif
        }
      }
    }
    while(millis() - readStart < timeout); //wait up to timeout period reading   
    
    Wire.endTransmission();

You would want to inform the caller of a timeout, for example return 0 if the code times out.

I suppose you library doesn't think of itself as a slave device, but can you implement that for inter-Arduino talks.


Rob

Jeff, Very nice professional work! Thanks for the effort.

I'll be following...

Regards, Terry King
...In The Woods In Vermont
terry@yourduino.com

retrolefty:
Thanks Jeff, that would be great. I've had the device for over a week now but just not the time to try and cobble something together. I'm a hardware type and software comes slow and gradual to me. The built in A/D pins on a arduino are useful but sadly fall far short of instrumentation quality. I thoough this would be a nice improvement to have avalible.

Lefty, I just pushed a new ADS1115 class up on GitHub for you to try. It compiles okay, and it's as close to the datasheet as I could make it. There should hopefully be enough comments that you can tell how to use it, but check the datasheet or ask me if you aren't sure. I might not have an answer, but I can give it a shot. Let me know how it works.

The ADS1115 is interesting because it uses 16-bit registers, which I haven't run into before. I had to update the main I2Cdev library to support word-size operations conveniently instead of just byte-size ones, though I'm sure that will come in handy for other devices as well, so in no way was it wasted effort or a one-time-use thing.

mem:
one suggestion is to add a timeout property to the library so calls want hang if the I2C device does not respond. ... You would want to inform the caller of a timeout, for example return 0 if the code times out.

Good idea! I've added this as well. The timeout is set to a default of 250ms (defined in I2Cdev.h). Read operations that time out return -1, while 0 indicates instant failure and 1 or more indicates success. A timeout length of zero disables timeout detection. Thanks for the suggestion!

Graynomad:
I suppose you library doesn't think of itself as a slave device, but can you implement that for inter-Arduino talks.

This could be useful, though I'd have to think about how best to build it into the library's basic structure. The I2Cdev main class and all device classes are specifically designed to interface with slave devices, and doing an Arduino-to-Arduino link is unpredictable and arbitrary in terms of protocols. The Arduino MasterReader example uses a very simple Wire implementation to do just that, including the necessary "onRequest" event hook on the "slave" Arduino. I don't know if it would be valuable to write one of these class libraries for generic communication, but if you had a specific functional protocol in mind for the slave device (like these manufactured I2C slave chips), then maybe it would be.

If you just use the basic "I2C::readBytes()" and other core functions of the main library, you might be able to accomplish simple communication without any specific device classes. I haven't tried, but it would be interesting.

terryking228:
Jeff, Very nice professional work! Thanks for the effort. I'll be following...

Thanks, Terry! I'm glad you and others believe it will be helpful. That's my goal!

Jeff

I think that 250ms is too short for some precision I2C devices that have long sample times.
I suggest at least doubling the default value to avoid false errors with those devices.

Jeff;

Thank you very much for your quick work on this ADS1115 device. I will try and check it out with the module this weekend. One possible question I have on a quick read through, are you sure about the I2C device addresses this device would use? You show:

#define ADS1115_ADDRESS_ADDR_GND    0x90 // address pin low (GND)
#define ADS1115_ADDRESS_ADDR_VDD    0x91 // address pin high (VCC)
#define ADS1115_ADDRESS_ADDR_SDA    0x92 // address pin tied to SDA pin
#define ADS1115_ADDRESS_ADDR_SCL    0x93 // address pin tied to SCL pin
#define ADS1115_DEFAULT_ADDRESS     ADS1115_ADDRESS_ADDR_GND

I would think the 7 bit I2C possible addresses that the Wire library would require would be:

#define ADS1115_ADDRESS_ADDR_GND    0x48 // address pin low (GND)
#define ADS1115_ADDRESS_ADDR_VDD    0x49 // address pin high (VCC)
#define ADS1115_ADDRESS_ADDR_SDA    0x4A // address pin tied to SDA pin
#define ADS1115_ADDRESS_ADDR_SCL    0x4B // address pin tied to SCL pin
#define ADS1115_DEFAULT_ADDRESS     ADS1115_ADDRESS_ADDR_GND

From the datasheet (QuickStart Guide, page 11) the 7 bit address is show as:

Hardware for this design includes: one ADS1113/4/5 configured with an I2C address of 1001000;

My understanding with using the Wire library calls, that one uses the 7 bit address for the calls and the Wire library adds the internal R/W bit to form the full 8 bit I2C address?

Thanks again;
Lefty

mem:
I think that 250ms is too short for some precision I2C devices that have long sample times.
I suggest at least doubling the default value to avoid false errors with those devices.

Good point. I was unaware of devices that take that long; my thought was that 1 sec (in your original example) was a LONG time to block for actions that typically execute orders of magnitude faster than that. As a compromise, I've left the default at 250ms and added an optional "timeout" argument to the tail end of each read* method, so that for those specific actions that might legitimately take a long time, it's easy to specify a longer timeout. You could already have just modified the I2C::readTimeout variable before calling the desired read* method, but that's not very convenient or pretty. This is much better, and I probably should have done it that way in the first place. Yay for helpful ideas from other people!

retrolefty:
Thank you very much for your quick work on this ADS1115 device. I will try and check it out with the module this weekend. One possible question I have on a quick read through, are you sure about the I2C device addresses this device would use? ... From the datasheet (QuickStart Guide, page 11) the 7 bit address is show as [1001000]. ... My understanding with using the Wire library calls, that one uses the 7 bit address for the calls and the Wire library adds the internal R/W bit to form the full 8 bit I2C address?

Lefty, you are absolutely right. I read through the address descriptions too fast, assumed the left-most bit was 0x80 instead of 0x40, and totally borked the calculations in my head. I've fixed it per your corrections and pushed the update to the repository. You'd think that knowing all base I2C addresses are 7 bits long would have thrown up a red flag while looking at a clearly 8-bit value...

Jeff

I was actually just asking if there was a common repository for libraries here Is there a standard place to upload libraries you create? - Programming Questions - Arduino Forum I think something like this would really help. I'll see if I can redo the code I posted to match your convention once I get the parts in for my clock.

[quote author=Jeff Rowberg link=topic=68210.msg503820#msg503820 date=1312388793]
... As a compromise, I've left the default at 250ms and added an optional "timeout" argument to the tail end of each read* method, so that for those specific actions that might legitimately take a long time, it's easy to specify a longer timeout. You could already have just modified the I2C::readTimeout variable before calling the desired read* method, but that's not very convenient or pretty. This is much better, and I probably should have done it that way in the first place. Yay for helpful ideas from other people![/quote]

I think having a one second default is a better idea. It won't block unless the device doesn't respond and in that case does it matter if the error is returned in one second rather than 250ms? (in the previous code, the error condition would cause it to block indefinitely)

The advantage of a longer default time-out is that inexperienced users won't get false errors when using devices that do take longer than 250ms. Experienced users can increase or decrease the time-out if they want, but it seems more friendly to have a default that minimizes the chance that errors could be reported even when the system if functioning correctly. The longer default time-out has no impact on performance on systems where the I2C devices are responding correctly.

In terms of doing a slave module, I feel that it would be best to do it like I2C EEPROM:

On construction / init: User passes object a pointer to a preallocated buffer and the length of the buffer
On master write: first byte or first word (depending on how you want to do it) parks read pointer at a specified index
Any further bytes sent are added sequentially starting from pointer to the buffer.
For ex: (using word addressing)
START 0x00 0x00 0x01 STOP
would write 0x01 to buf[0]
START 0x00 0x02 0x01 0x02 0x03 STOP
would write 0x01 to buf[0x02], 0x02 to buf[0x03], and so on.

On master read: return sequential data starting from where the read pointer was parked in the previous write, incrementing index each time
If overflow (master wants to read past length - 1) continue to return last byte

I think that this mostly removes the need for various protocols: it also has the advantage of the ability to pass a struct into the byte* instead of an array, which makes it very easy to use after initial set up.

Another suggestion is to include a detect method for devices with multiple possible addresses. This method would return true if the start byte is ACKed, and false otherwise - that way it can dynamically get the actual address to use.

for ex:
Device can be at 0x4A or 0x4B

0x4A(W) -> no ACK move to 0x4B
0x4B(W) -> ACK use 0x4B for future communications.

The Arduino MasterReader example

I'm not very familiar with i2c but like most of the Arduino examples that one is way too simplistic for general use I feel.

On the other hand there's no need to be complicated. R/W is already part of the protocol so I would think something very simple like

A read request has no data from the master. The slave just responds with what ever data it has. (Does the master need to know how many bytes?)

A write request simply sends X bytes.

I'm more used to asynch and this is the sort of thing I'd do there (with the addition of some data length info), not sure what's practical/easy for i2c but a simple transfer of N bytes is all that is required I think. Any more can be dealt with by the next level up in the application.


Rob

Jeff;

I found some time to try out the ADS1115 library you developed, but I seem to have a problem. I made a minimlist sketch for just the init function (which is a do nothing) and the test connection, to see if the device is seen:

#include <Wire.h>
#include "ADS1115.h"
#include "I2Cdev.h"
ADS1115 adc1115;

#define LED_PIN 13
bool blinkState = false;

void setup() {
    // join I2C bus
    Wire.begin();
    
    // initialize serial communication
    Serial.begin(38400);
    
    // initialize all devices
    Serial.println("Initializing I2C devices...");
    adc1115.initialize();
  
    Serial.println("Testing device connections...");
    Serial.println(adc1115.testConnection() ? "ADS1115 connection successful" : "ADS1115 connection failed");
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
    delay(100);
    blinkState = !blinkState;
    digitalWrite(LED_PIN, blinkState);
}

It seems to fail, here with debug enabled:

Initializing I2C devices...
Testing device connections...
I2C (0x48) reading 1 words from 0x0.... Done (0 read).
ADS1115 connection failed

I think the device is avalible because I can run a simple I2C address scanner sketch and get:

I2CScanner ready!
starting scanning of I2C bus from 1 to 7F...Hex
addr: 1 addr: 2 addr: 3 addr: 4
addr: 5 addr: 6 addr: 7 addr: 8
addr: 9 addr: A addr: B addr: C
addr: D addr: E addr: F addr: 10
addr: 11 addr: 12 addr: 13 addr: 14
addr: 15 addr: 16 addr: 17 addr: 18
addr: 19 addr: 1A addr: 1B addr: 1C
addr: 1D addr: 1E addr: 1F addr: 20
addr: 21 addr: 22 addr: 23 addr: 24
addr: 25 addr: 26 addr: 27 addr: 28
addr: 29 addr: 2A addr: 2B addr: 2C
addr: 2D addr: 2E addr: 2F addr: 30
addr: 31 addr: 32 addr: 33 addr: 34
addr: 35 addr: 36 addr: 37 addr: 38
addr: 39 addr: 3A addr: 3B addr: 3C
addr: 3D addr: 3E addr: 3F addr: 40
addr: 41 addr: 42 addr: 43 addr: 44
addr: 45 addr: 46 addr: 47 addr: 48 found!
addr: 49 addr: 4A addr: 4B addr: 4C
addr: 4D addr: 4E addr: 4F addr: 50
addr: 51 addr: 52 addr: 53 addr: 54
addr: 55 addr: 56 addr: 57 addr: 58
addr: 59 addr: 5A addr: 5B addr: 5C
addr: 5D addr: 5E addr: 5F addr: 60
addr: 61 addr: 62 addr: 63 addr: 64
addr: 65 addr: 66 addr: 67 addr: 68
addr: 69 addr: 6A addr: 6B addr: 6C
addr: 6D addr: 6E addr: 6F addr: 70
addr: 71 addr: 72 addr: 73 addr: 74
addr: 75 addr: 76 addr: 77 addr: 78
addr: 79 addr: 7A addr: 7B addr: 7C
addr: 7D addr: 7E addr: 7F
done

Any thoughts?

Thanks, Lefty

Activity in the Libraries is always a good thing!

I'd like to see TWI more interrupt driven - so that you could readFrom a device and do other stuff while the 100Khz bus is doing its thing, rather than having code like this (twi.c, twi_readFrom):

while(TWI_MRX == twi_state){
continue;
}

Which sits there and does nothing until the truly interrupt-driven code is finished.

I'm going to be making changes to these routines for my own purposes (narrow focus), but I'd be happy to share with your much more general focus.

retrolefty:
I found some time to try out the ADS1115 library you developed, but I seem to have a problem. I made a minimlist sketch for just the init function (which is a do nothing) and the test connection, to see if the device is seen...Any thoughts?

Hmm, I was afraid of this. A library written with no hardware to test that implements a new set of untested 16-bit register functions--not a good combination! It's possible that my testConnection() method is not written correctly, for one thing. The ADS1115 doesn't have a designated test function or "internal ID" register that some others have, so it's really just trying to read the conversion register once to see if it gets data back. I'd try three things to test further:

  1. Try a short delay between the initialize() call and the testConnection() call--maybe 100ms or 200ms to be safe I don't know if this is necessary based on the info in the datasheet, but it's one less possible cause if the delay's there.

  2. Try some of the other methods in the class, like reading the differential or one of the config settings to see if anything comes back.

  3. Use a raw I2Cdev class call to test words vs. bytes, such as:

uint16_t wBuf[1];
int count = I2Cdev::readWord(0x48, ADS1115_RA_CONFIG, wBuf);
Serial.println(count); // display # of words read, should be 1 if working

uint8_t bBuf[2];
count = I2Cdev::readBytes(0x48, ADS1115_RA_CONFIG, 2, bBuf);
Serial.println(count); // display # of bytes read, should be 2 if working

Without knowing more, I'd be inclined to blame my 16-bit register functions because I have no way of testing them with my hardware. Those three things above should shed some light on where the problem actually is.

gknight4:
Activity in the Libraries is always a good thing! I'd like to see TWI more interrupt driven - so that you could readFrom a device and do other stuff while the 100Khz bus is doing its thing, rather than having code like this (twi.c, twi_readFrom)...which sits there and does nothing until the truly interrupt-driven code is finished.

I would also definitely like to go this direction. Ideally, we'd move away from the Wire implementation into something simpler that could be bundled with the I2Cdev class. This would make it really easy to use with non-Arduino projects that are only using avr-gcc without the rest of the support libraries. I've got a couple of #defineable IMPLEMENTATION constants to allow for switching between different implementations, but so far only the Wire one has any support. An interrupt-driven approach would be very useful, though all the read* and write* functions would be done very differently so they don't block and return, but instead have handler/event methods assigned.

gknight4:
I'd be happy to share with your much more general focus.

Anything you want to send my way would be excellent. Thanks!

Jeff

  1. Use a raw I2Cdev class call to test words vs. bytes, such as:

So I added the two raw calls you suggested:

Initializing I2C devices...
Testing device connections...
I2C (0x48) reading 1 words from 0x0.... Done (0 read).
ADS1115 connection failed

I2C (0x48) reading 1 words from 0x1.... Done (0 read).
0

I2C (0x48) reading 2 bytes from 0x1...85 83 . Done (2 read).
2

So it appears that reading bytes works, but 1 word fails?

Lefty

retrolefty:
So it appears that reading bytes works, but 1 word fails?

Ah, I think I found the bug, based on your info: the readWords() method was relying on an unsigned 8-bit count variable that was being decremented once in the middle of the for() loop so the MSB and LSB could both be written to the same word index in the read buffer. The loop condition also tests to make sure the count < length, so we don't read more bytes/words than the user asked for. The problem was that count starts at 0, and since it's unsigned, count - 1 = 0xFF, which is always >= length. Oops.

I've adjusted the code so that the loop works differently so negative values are never required, and also made the count variable signed to support -1 return values, which occurs in a timeout condition. The changes are up in the repository now.

Jeff