Pages: [1]   Go Down
Author Topic: Arduino "Wire" i2c not compatible with SMBus Block Read ... is it?  (Read 2921 times)
0 Members and 1 Guest are viewing this topic.
Fresno, CA, USA
Offline Offline
Full Member
***
Karma: 1
Posts: 153
Arduino rocks (literally, with a WaveShield!)
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

OK, I literally spent the last 4...6 hours tinkering with this low-level AVR code trying to understand why:

Code:
 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:
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" smiley-wink
« Last Edit: May 15, 2011, 04:03:42 pm by FalconFour » Logged

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 180
Posts: 8108
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

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

Fresno, CA, USA
Offline Offline
Full Member
***
Karma: 1
Posts: 153
Arduino rocks (literally, with a WaveShield!)
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley-razz

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:
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 smiley-grin 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 smiley

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

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 180
Posts: 8108
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley

Glad I could help and thanks for the code.
Logged

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

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

unprobable country named Belgium
Offline Offline
Full Member
***
Karma: 2
Posts: 248
Ga bu zo
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*

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

unprobable country named Belgium
Offline Offline
Full Member
***
Karma: 2
Posts: 248
Ga bu zo
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
ret = i2c_readNak();        // read one byte

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

unprobable country named Belgium
Offline Offline
Full Member
***
Karma: 2
Posts: 248
Ga bu zo
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

With address 0x00 the code hangs after this:

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

With a random address it hangs after this:

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

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

Code:
i2c_stop();

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

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
#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:
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:
#define I2C_READ    1
Logged

unprobable country named Belgium
Offline Offline
Full Member
***
Karma: 2
Posts: 248
Ga bu zo
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
    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:
    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:
/*

  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);
 }
« Last Edit: June 11, 2011, 04:51:00 am by amundsen » Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You have:

Code:
#define I2C_READ 0
#define I2C_WRITE 1

However i2cmaster.h has:

Code:
/** 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.
Logged

unprobable country named Belgium
Offline Offline
Full Member
***
Karma: 2
Posts: 248
Ga bu zo
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*

  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);
}
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Not at all. Can you show the code with the for loop in it?
Logged

unprobable country named Belgium
Offline Offline
Full Member
***
Karma: 2
Posts: 248
Ga bu zo
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Pages: [1]   Go Up
Jump to: