Go Down

Topic: Arduino "Wire" i2c not compatible with SMBus Block Read ... is it? (Read 4284 times) previous topic - next topic

FalconFour

May 15, 2011, 11:02 pm Last Edit: May 15, 2011, 11:03 pm by FalconFour Reason: 1
OK, I literally spent the last 4...6 hours tinkering with this low-level AVR code trying to understand why:

Code: [Select]
 Wire.beginTransmission(deviceaddress);
 Wire.send(command);
 Wire.endTransmission();
 Wire.requestFrom((uint8_t)deviceaddress,blockBufferLen);
 for (x = 0; x < blockBufferLen; x++ )
   if (Wire.available()) blockBuffer[x] = Wire.receive();


... with the data in the device, "(4)NIMH" - 4 being the number of bytes following (SMBus spec) ... results in "(4)N(FF)(FF)"... or to be precise, "FF"'s all the way up 'til the buffer length, without cutting off.

Yuck. I've tried slowing the I2C bus speed, debugging it line by line, debugging the buffer transfers, debugging the register values, adding delays, you name it, I've tried it.

I noticed that the timing diagrams for i2c block read and SMBus block read don't exactly line up right... but yet, I can't find ANYWHERE online where anyone's ever even *tried* to do an SMBus block read with an Arduino.

Why, you might ask, would I need this functionality? Well... I'm using Wire (and Arduino's i2c controller) mostly for the occasional PC laptop battery hack. There's lots of amazing information in these packs that can be hacked using the Arduino i2c interface. And the block read is only used for a couple useless quirks; otherwise i2c overlays on SMBus pretty nicely; sending "commands" instead of "addresses" and doing reads the same way as an EEPROM. Take a look at this hacked-up sketch I wrote that interfaces with this old NiMH battery I'm trying to repair... (">>" indicates things I enter into the Serial Monitor window)

>>r0x9; (<-- {r}eceive word from command 0x9, semicolon-terminated strtoul())
Reading word using command: 9
Completed, received: 2E25 (hex), 11813 (dec) (<-- 11,813 mV; 11.813 volts)
>>r0xa;
Reading word using command: A
Completed, received: 3D9 (hex), 985 (dec) (<-- 985 mA; 0.985 A charge rate currently)

But here's where things get munged up...
Code: [Select]
Reading block using command: 22
Completed, received 17 bytes:
Nÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ

... Nyyyyy, is right. It's supposed to read "NIMH" and end at 4 bytes (5, counting the byte count itself).

Is the i2c protocol hard-coded into the ATmega chip itself? There are just slight differences in handling sequential "ack"'s in order to extract a full sequence of bytes from an SMBus device, but I think the controller is sending a "refresh code" and knocking out the sequence. Problem is, there's no way to even retrieve those missing bytes without following protocol, since "bytes of a block" can't be directly addressed... one command of 11 characters is 0x20, the next command of 8 characters is 0x21. They just have to be read in bursts like this...

If I could get SMBus working properly, I could probably polish up this sketch and turn it into a much more useful "battery tool" ;)

johnwasser

In this old forum entry:  http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1214872633 someone was having trouble with an SMBus device using the "Wire" library so they switched to an i2cmaster library and that worked.  Perhaps that library will help with your problem.

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

FalconFour

Perfect! Works like a dream! Full control over ACK and NACK is exactly what I needed, and with the exception of a few "oops, didn't realize it didn't do that" - like turning on pull-ups and using i2c_start_wait() - it was a flawless conversion. Code blew up pretty big in size, though, to the tune of about 6.4kb, but meh, I'm not really going for efficiency :P

This is such a sweet sight to see, coming from 2 wires poking into the connector of a laptop battery (and a 12v/2a switching power supply providing the manual "but assisted" charging):
Code: [Select]
Ready!
------
Mfg: OEM | Dev: - | Chem: NIMH
Date: 2/22/2000 | Cycles: 1
11.92v, 1.28 A, 87.89 F, 57%, 87 min remaining.
11.91v, 1.28 A, 87.89 F, 57%, 87 min remaining.
11.90v, 1.28 A, 87.89 F, 57%, 87 min remaining.
11.92v, 1.28 A, 87.89 F, 57%, 85 min remaining.
11.92v, 1.28 A, 87.89 F, 57%, 85 min remaining.
11.91v, 1.28 A, 87.89 F, 57%, 85 min remaining.
>>b0x20;
Reading block using command: 20
Completed, received 3 bytes:
OEM
11.92v, 1.28 A, 87.89 F, 57%, 82 min remaining.
11.92v, 1.28 A, 87.89 F, 57%, 82 min remaining.
11.91v, 1.28 A, 88.07 F, 57%, 82 min remaining.

Yep, it grabs the "block read" flawlessly! Through this little thing, I now have full control over all the functions of the battery as documented in the datasheet :D I just send a read command with, e.g. "r0x0c;", for "read command 0x0C", and it confirms the interpreted input, sends the command, and returns the result. Woo!

Here's the full code, now that it's working pretty well... this is based on a bq2040 "Gas Gauge IC with SMBus Interface". By tweaking the command parameters (sorry, I didn't #define them), it's pretty easy to adapt it to other batteries... all batteries that I know of use this SMBus interface :)

Code: [Select]
#include <i2cmaster.h>

#define readBufferLen 17
char readBuffer[readBufferLen];
uint8_t i2cBuffer[readBufferLen];
#define deviceaddress B00010110
uint8_t serialCommand, loopCount;
unsigned int serialData;

void setup() {
  i2c_init();
  PORTC = (1 << PORTC4) | (1 << PORTC5); //enable pullups
  Serial.begin(9600);
  Serial.println("Ready!");
  Serial.println("------");
  Serial.print("Mfg: ");
  i2c_smbus_read_block(0x20,i2cBuffer,readBufferLen);
  Serial.print((char*)i2cBuffer);
  Serial.print(" | Dev: ");
  i2c_smbus_read_block(0x21,i2cBuffer,readBufferLen);
  Serial.print((char*)i2cBuffer);
  Serial.print(" | Chem: ");
  i2c_smbus_read_block(0x22,i2cBuffer,readBufferLen);
  Serial.println((char*)i2cBuffer);
  serialData = i2c_smbus_read_word(0x1b);
  Serial.print("Date: ");
  Serial.print((uint8_t)(serialData >> 5) & B00001111,DEC);
  Serial.print('/');
  Serial.print((uint8_t)serialData & B00011111,DEC);
  Serial.print('/');
  Serial.print((serialData >> 9) + 1980,DEC);
  Serial.print(" | Cycles: ");
  Serial.println(i2c_smbus_read_word(0x17));
}

void loop() {
  if (loopCount > 10) {
    // gather things
    int currVoltage, currAmps, estPercent, currTemp, timeLeft;
    currVoltage = i2c_smbus_read_word(0x9);
    currAmps = i2c_smbus_read_word(0xA);
    estPercent = i2c_smbus_read_word(0xD);
    currTemp = i2c_smbus_read_word(0x8);
    timeLeft = i2c_smbus_read_word(0x13);
 
    Serial.print((float)currVoltage / 1000, 2);
    Serial.print("v, ");
    Serial.print((float)currAmps / 1000, 2);
    Serial.print(" A, ");
    Serial.print(((float)currTemp/10 - 273.15) * 1.8 + 32, 2);
    Serial.print(" F, ");
    Serial.print(estPercent,DEC);
    Serial.print("%, ");
    Serial.print(timeLeft,DEC);
    Serial.println(" min remaining.");
    loopCount = 0;
  }
  loopCount++;

  if (Serial.available()) {
    delay(500);
    uint8_t x = 0;
    if (Serial.peek() == 'w') {
      // write
      Serial.read(); // get the state out of the buffer
      while (Serial.peek() != ';') {
        readBuffer[x] = Serial.read();
        x++;
        if (x > readBufferLen) return;
      }
      readBuffer[x] = 0; // null the buffer
      Serial.read(); // get the semicolon out of there
      serialCommand = strtoul(readBuffer, NULL, 0);
      Serial.print("Writing word using command: ");
      Serial.print(serialCommand,HEX);
      x = 0;
      while (Serial.peek() != ';') {
        readBuffer[x] = Serial.read();
        x++;
        if (x > readBufferLen) return;
      }
      readBuffer[x] = 0; // null the buffer
      Serial.read(); // clear the semicolon
      serialData = strtoul(readBuffer, NULL, 0);
      Serial.print(", data: ");
      Serial.println(serialData,HEX);
      i2c_smbus_write_word(serialCommand,serialData);
      Serial.println("Completed.");
     
    } else if (Serial.peek() == 'r') {
      // read word
      Serial.read(); // get the state out of the buffer
      while (Serial.peek() != ';') {
        readBuffer[x] = Serial.read();
        x++;
        if (x > readBufferLen) return;
      }
      readBuffer[x] = 0; // null the buffer
      Serial.read(); // clear the semicolon
      serialCommand = strtoul(readBuffer, NULL, 0);
      Serial.print("Reading word using command: ");
      Serial.println(serialCommand,HEX);
      serialData = i2c_smbus_read_word(serialCommand);
      Serial.print("Completed, received: ");
      Serial.print(serialData,HEX);
      Serial.print(" (hex), ");
      Serial.print(serialData,DEC);
      Serial.println(" (dec)");

    } else if (Serial.peek() == 'b') {
      // read block
      Serial.read(); // get the state out of the buffer
      while (Serial.peek() != ';') {
        readBuffer[x] = Serial.read();
        x++;
        if (x > readBufferLen) return;
      }
      Serial.read(); // clear the semicolon
      readBuffer[x] = 0;
      serialCommand = strtoul(readBuffer, NULL, 0);
      Serial.print("Reading block using command: ");
      Serial.println(serialCommand,HEX);
      x = i2c_smbus_read_block(serialCommand,i2cBuffer,readBufferLen);
      Serial.print("Completed, received ");
      Serial.print(x,DEC);
      Serial.println(" bytes:");
      x = 0;
      for (x=0; x<readBufferLen; x++) {
        Serial.print(i2cBuffer[x]);
      }
      Serial.println();
    } else {
      Serial.print("Junk: ");
      Serial.println(Serial.read(),HEX);
    }
  }
  delay(500);
}

void i2c_smbus_write_word ( uint8_t command, unsigned int data ) {
  i2c_start_wait(deviceaddress + I2C_WRITE);
  i2c_write(command);
  i2c_write((uint8_t)data);
  i2c_write((uint8_t)(data>>8));
  i2c_stop();
  return;
}

unsigned int i2c_smbus_read_word ( uint8_t command ) {
  unsigned int buffer = 0;
  i2c_start_wait(deviceaddress + I2C_WRITE);
  i2c_write(command);
  i2c_rep_start(deviceaddress + I2C_READ);
  buffer = i2c_readAck();
  buffer += i2c_readNak() << 8;
  return buffer;
}

uint8_t i2c_smbus_read_block ( uint8_t command, uint8_t* blockBuffer, uint8_t blockBufferLen ) {
  uint8_t x = 0;
  uint8_t y = 0;
  i2c_start_wait(deviceaddress + I2C_WRITE);
  i2c_write(command);
  i2c_rep_start(deviceaddress + I2C_READ);
  y = i2c_readAck();
  for (x=0; x<y-1; x++) {
    blockBuffer[x] = i2c_readAck();
  }
  blockBuffer[x] = i2c_readNak();
  blockBuffer[x+1] = 0;
  return y;
}

Also, it was mostly designed for my use, but since I had to ask for help in the forum (and got it, thanks!), I figured I'd return my work to the community :)

johnwasser


Also, it was mostly designed for my use, but since I had to ask for help in the forum (and got it, thanks!), I figured I'd return my work to the community :)


Glad I could help and thanks for the code.
Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

priya

Hey FalconFour,

I'm trying to get battery monitor bq2060 working with arduino fio. I noticed there is huge similarity between my project and what you have done here (Relief!) Initially i started out with wire library but could not get my ic working. I have tried i2c sensors before so my coding wasnt a problem. While searching for alternative library, i found this page and i must thank you for making your code public.

I have few doubts and would appreciate your help. I downloaded i2c master library from here( http://homepage.hispeed.ch/peterfleury/avr-software.html) is this the one you're referring to? When i copy paste this folder in 'library' folder of arduino and import the library in arduino IDE , it isnt working.. Do you have any idea why this must be happening?


Priya.

amundsen

The ic2master library won't work for me neither.

I have installed the library in Arduino.app/Contents/Resources/Java/Libraries/i2cmaster (I am on a Mac).
I have renamed twimaster.c as twimaster.cpp and test_i2cmaster.c as test_i2cmaster.cpp.
I have modified the F_CPU with value 16000000UL and the SCL_CLOCK with value 50000L in twimaster.cpp.
I have created a keywords file (see it at the bottom of this message) and put it within the library's folder.

However there is something wrong because the code doesn't run. Even my basic Serial.print() instructions are not executed.

What am I doing wrong ???


Here's the sketch:

Code: [Select]
/*

  i2cmaster library test with an AD7746
 
*/

#include <i2cmaster.h>

#define AD7746  0x48    // device address of AD7746 without 8th bit
unsigned char ret;


void setup() {   
                               
     Serial.begin(9600);
     Serial.println("Hello!"); // just to see if any code is executed
     i2c_init();   // initialize I2C library
}

void loop() {

     // read
     i2c_start_wait(AD7746+I2C_WRITE);     // set device address and write mode

     i2c_write(0x00);                        // read address = 0
     i2c_rep_start(AD7746+I2C_READ);       // set device address and read mode

     ret = i2c_readNak();        // read one byte
     i2c_stop();
     Serial.print("RET = ");
     Serial.println(ret);
}


Code: [Select]
#######################################
# Syntax Coloring Map For i2cmaster
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################

#######################################
# Methods and Functions (KEYWORD2)
#######################################

i2c_init KEYWORD2
i2c_start_wait KEYWORD2
i2c_write KEYWORD2
i2c_stop KEYWORD2
i2c_rep_start KEYWORD2
i2c_cursor KEYWORD2
i2c_readNak KEYWORD2
i2c_readAck KEYWORD2
i2c_read KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################

I2C_WRITE LITERAL1
I2C_READ LITERAL1

amundsen

if I remove the test_i2cmaster.cpp (previously test_i2cmaster.c) the code doesn't hang immediately but still does very soon.

Trough the insertion of Serial.print("dummy text") instructions, I could find where it hangs. It's just after this line:
Code: [Select]
ret = i2c_readNak();        // read one byte

So, is it my code, the installation of the library or the library itself ?

amundsen

It seems the point where the code hangs depends on the I2D device address used.

With address 0x00 the code hangs after this:

Code: [Select]
ret = i2c_readNak();        // read one byte

With a random address it hangs after this:

Code: [Select]
i2c_write(0x00);                        // read address = 0

And with the supposed correct address (0x90) almost all the code is executed excepted the last line:

Code: [Select]
i2c_stop();

Also, no value is returned by the I2C slave anyway (the ret variable remains without value).

Nick Gammon

Code: [Select]
#define AD7746  0x48    // device address of AD7746 without 8th bit
...
i2c_start_wait(AD7746+I2C_WRITE);     // set device address and write mode


That doesn't look right. According to the datasheet for the AD7746:

Quote
The start byte address for the AD7745/AD7746 is 0x90 for a write and 0x91 for a read.


But you are doing 0x48 for a write and 0x49 for a read.

i2c_start_wait does not appear to shift the address left 1 bit, and if it did, then when you use:

Code: [Select]
i2c_rep_start(AD7746+I2C_READ)

... then the 1 which is I2C_READ would be doubled to make it 2, which would be wrong.

Code: [Select]
#define I2C_READ    1
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

amundsen

#9
Jun 11, 2011, 11:48 am Last Edit: Jun 11, 2011, 11:51 am by amundsen Reason: 1
Nick Gammon wrote :

Quote
That doesn't look right. According to the datasheet for the AD7746:


Quote
The start byte address for the AD7745/AD7746 is 0x90 for a write and 0x91 for a read.


You're right but I have tried 0x48 as well as 0x90 because 0x48 =  0x90 >> 1 and I wasn't sure 0x90 included the read/write bit or not. Finally 0x90 seems indeed the right address because 0x48 hangs the program and 0x90 doesn't.

However the problem was indeed that I2C_WRITE and I2C_READ were not defined. I thought they were constants defined by the library hence I didn't define them myself.

Now the program doesnt' hang anymore but nothing seems to be read.

If I code like this
Code: [Select]
    ret = i2c_readNak();        // read one byte
    Serial.println(ret);

nothing appears on the terminal window

but if I convert the value supposed to be returned on the I2C bus into another format like this
Code: [Select]
    ret = i2c_readNak();        // read one byte
    Serial.println(ret, DEC);

the printed value is always 144 whatever the registrer address chosen.

Anyway, thanks for the help Nick !

Here is the complete sketch:
Code: [Select]
/*

 i2cmaster library test with an AD7746
 
*/

#include <i2cmaster.h>

#define AD7746  0x90    // device address of AD7746
#define I2C_READ 0
#define I2C_WRITE 1
unsigned char ret = 0;


void setup() {    
                             
    Serial.begin(9600);
    Serial.println();
    i2c_init();   // initialize I2C library
}

void loop() {

    // read
    i2c_start_wait(AD7746+I2C_WRITE);     // set device address and write mode
    i2c_write(0x00);                                  // read address = 0
    i2c_rep_start(AD7746+I2C_READ);       // set device address and read mode

    ret = i2c_readNak();        // read one byte
    Serial.print("RET = ");
    Serial.println(ret, DEC);
    i2c_stop();
    delay(500);
}


Nick Gammon

You have:

Code: [Select]

#define I2C_READ 0
#define I2C_WRITE 1


However i2cmaster.h has:

Code: [Select]
/** defines the data direction (reading from I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_READ    1

/** defines the data direction (writing to I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_WRITE   0


You have reversed the sense of read/write. You need to get this right, or you will have a lot of problems.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

amundsen

Oops !

OK, now it seems to work properly. Thanks Nick !

I am not sure about my reset command but it doesn't stop the program at least.

Still one strange thing: when I do not choose to read from address 0x00, the program hangs after one reading.

Also, it seems I cannot read bytes within a "for" loop. Does this kind of loop slow down the reading too much ?

Code: [Select]
/*

  i2cmaster library test with an AD7746
 
*/
//#include <avr/io.h>
#include <i2cmaster.h>

#define AD7746  0x90    // device address of AD7746 without 8th bit
#define I2C_READ 1
#define I2C_WRITE 0
#define RESET_ADDRESS 0xBF
unsigned char ret[10] ;


void setup() {   
                               
     Serial.begin(9600);
     Serial.println();
     i2c_init();   // initialize I2C library
}

void loop() {
     
     // Reset AD7746
     Serial.println("Initializing");
     i2c_start_wait(AD7746+I2C_WRITE);     // set device address and write mode
     i2c_write(RESET_ADDRESS);             
     i2c_stop();  // set stop conditon = release bus
     delay (1); // let AD7746 a short time to reset (max 200 µs)

     // read
     i2c_start_wait(AD7746+I2C_WRITE);   // set device address and write mode
     i2c_write(0x00);                        // read address = 0
     i2c_rep_start(AD7746+I2C_READ);       // set device address and read mode
     ret[0] = i2c_readAck();        // read one byte
     ret[1] = i2c_readAck();        // read one byte
     ret[2] = i2c_readAck();        // read one byte
     ret[3] = i2c_readAck();        // read one byte
     ret[4] = i2c_readAck();        // read one byte
     ret[5] = i2c_readAck();        // read one byte
     ret[6] = i2c_readAck();        // read one byte
     ret[7] = i2c_readAck();        // read one byte
     ret[8] = i2c_readAck();        // read one byte
     ret[9] = i2c_readAck();        // read one byte
     ret[10] = i2c_readNak();        // read one byte
     for(int i = 0; i < 10 ;  i++) {     
       Serial.print("RET["+String(i)+"] : ");
       Serial.println(ret[i], DEC);
     }
     i2c_stop();
     delay(500);
}

Nick Gammon

Not at all. Can you show the code with the for loop in it?
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

amundsen

Well, now the reading of several bytes within a loop is functioning correctly. Now that I understand how to use the the i2cmaster library, I'll try to communicate with the Si1143 IC for light sensing.

Go Up