Pages: [1] 2 3 ... 5   Go Down
Author Topic: New and growing well-documented, feature-complete I2C device library  (Read 17800 times)
0 Members and 1 Guest are viewing this topic.
Roanoke, VA
Offline Offline
Jr. Member
**
Karma: 6
Posts: 66
Creating order out of chaos!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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)
https://github.com/jrowberg/i2cdevlib (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? :-)

    Jeff (the Keyglove guy)
« Last Edit: March 27, 2012, 12:55:48 pm by Jeff Rowberg » Logged

Left Coast, CA (USA)
Online Online
Brattain Member
*****
Karma: 332
Posts: 16567
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Roanoke, VA
Offline Offline
Jr. Member
**
Karma: 6
Posts: 66
Creating order out of chaos!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Left Coast, CA (USA)
Online Online
Brattain Member
*****
Karma: 332
Posts: 16567
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

Code:
   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.
« Last Edit: August 02, 2011, 02:46:41 am by mem » Logged

nr Bundaberg, Australia
Offline Offline
Tesla Member
***
Karma: 121
Posts: 8461
Scattered showers my arse -- Noah, 2348BC.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

______
Rob
Logged

Rob Gray aka the GRAYnomad www.robgray.com

Topsham, Vermont USA
Offline Offline
Edison Member
*
Karma: 24
Posts: 1766
... in The Woods In Vermont
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

I'll be following...

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

Regards, Terry King terry@yourduino.com  - Check great prices, devices and Arduino-related boards at http://YourDuino.com
HOW-TO: http://ArduinoInfo.Info

Roanoke, VA
Offline Offline
Jr. Member
**
Karma: 6
Posts: 66
Creating order out of chaos!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

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!

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.

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
« Last Edit: August 03, 2011, 12:38:40 am by Jeff Rowberg » Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

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.
Logged

Left Coast, CA (USA)
Online Online
Brattain Member
*****
Karma: 332
Posts: 16567
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

Code:
#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:

Code:

#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:

Quote
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
Logged

Roanoke, VA
Offline Offline
Jr. Member
**
Karma: 6
Posts: 66
Creating order out of chaos!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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!

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
« Last Edit: August 03, 2011, 11:28:45 am by Jeff Rowberg » Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 54
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I was actually just asking if there was a common repository for libraries here http://arduino.cc/forum/index.php/topic,65941.msg483201.html#msg483201 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.
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

... 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!

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.
Logged

0
Offline Offline
Sr. Member
****
Karma: 0
Posts: 360
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

nr Bundaberg, Australia
Offline Offline
Tesla Member
***
Karma: 121
Posts: 8461
Scattered showers my arse -- Noah, 2348BC.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
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
Logged

Rob Gray aka the GRAYnomad www.robgray.com

Pages: [1] 2 3 ... 5   Go Up
Jump to: