Using I2C for SMBus device communications

@ScruffR here is the statement calling readStringB()

 int BQ20Z45::SafetyAlert(char *result)
{
 
	return readStringB(BQ20Z45_SafetyAlert, result, 4);
} 

and the function calling that function from the main loop is this:

bms.SafetyAlert(strBuffer);
Serial.println(strBuffer);

@pra I'm not clear on what changes you want me to make before doing a new LA graph?

The return from the code above is this:

I tried changing quite a few things around but non of them return correctly.

I do have a email out to the guy who does have this working that I found on the TI forum. I'm hoping he can share his code since it should give us some insight to what he is actually doing.

@RWB,

I didnā€™t suggest you make changes. What I want to see is the Logic Analyzer graph output when you run this so I can verify that a I2C restart read is happening, as the data returned is systematic to a start rather than a restart being performed.

Peter

@Pra @Scruff Here is the LA graph when I run the code:

That happens when I run this code sequence:

1st Function Call from main loop:

 bms.SafetyAlert(strBuffer);

That calls this function:

 int BQ20Z45::SafetyAlert(char *result)
{
 
	return readStringB(BQ20Z45_SafetyAlert, result, 4);
}

And that function calls this function:

int BQ20Z45::readStringB(uint8_t address, char* result, int len)
{
	int pos = 0;
	int newlen;

        // Initiate a manufacturer_block_Read
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.write(0x02);
	Wire.write(address);
	Wire.write(0x00);

	Wire.endTransmission(false);
	 newlen = Wire.requestFrom(BQ20Z45_Address, len + 1,true);
	Wire.read();
	if (newlen > 0)
	{
                newlen--;
                Wire.read();
		for (pos = 0; pos < newlen; pos++)
			result[pos] = Wire.read();
	}
	result[pos] = '\0';  // append the zero terminator

	return newlen;
}

Ryan,

That is not a graph. Here is a link to one you posted earlier, so I know you know how to do this

@pra, @RWB I wonder if we should not change the title of this topic since it is really focused on an adaptation of I2C to talk to SMBus devices. Perhaps ā€œUsing I2C for SMBus device communicationsā€?

@peekay123 Yea I was thinking the same thing. I changed it.

1 Like

@pra Here ya go.

Also in the Manufacturer Block Access read function code above I had added a Wire.read(); to the code while I was attempting to try to get the proper return. This added Wire.read(); right after this line newlen = Wire.requestFrom(BQ20Z45_Address, len + 1,true); . The code was left when I pasted the following function code above.

int BQ20Z45::readStringB(uint8_t address, char* result, int len)
{
	int pos = 0;
	int newlen;

        // Initiate a manufacturer_block_Read
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.write(0x02);
	Wire.write(address);
	Wire.write(0x00);

	Wire.endTransmission(false);
	 newlen = Wire.requestFrom(BQ20Z45_Address, len + 1,true);
	Wire.read();
	if (newlen > 0)
	{
                newlen--;
                Wire.read();
		for (pos = 0; pos < newlen; pos++)
			result[pos] = Wire.read();
	}
	result[pos] = '\0';  // append the zero terminator

	return newlen;
}

I have tried the your code and the code above and both return the same result.

Just for clarification here is a LA scan while running the above code with the added Wire.read(); eliminated after the newlen = Wire.requestFrom(BQ20Z45_Address, len + 1,true);

The returns are identical it looks like to me.

Thanks.

Seems like a good transaction with an inappropriate response from the device. PEC is recommended for block process functions in the SMBus spec. Iā€™m wondering whether TI has made it mandatory for the block write. They only talk about it being option on the word writes, and donā€™t mention block writes.

Try adding this after the last Wire.write and immediately before the Wire.endTransmission(false);

      Wire.write(ox60);

See if the results are different. If not, I think you need to look to the TI forums for help.

@pra I tried it and the results are the exact same as when we didnā€™t add that in there.

I posted this on TIā€™s support forum about 20 mins ago. I also emailed a guy who has the code working so hopefully that will bring back some sort of feedback that is helpful.

@pra

I made some progress today after some more searching on the TI's forum.

I came across this post:

Notice that he says that his PC software sens MAC command 0x00 first and then the 0x0007.

So I changed the 0x44 I was using to 0x00 and it made a difference and it looks like this has solved the problem. Take a look and let me know what you think.

I changed the Mac Block Read Function to this:

int BQ20Z45::readStringB(uint8_t address, char* result, int len)
{
	int pos = 0;
	int newlen;

        // Initiate a manufacturer_block_Read
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x00);
	Wire.write(0x02);
	Wire.write(address);
	Wire.write(0x00);
	
	Wire.endTransmission(false);
	
	 newlen = Wire.requestFrom(BQ20Z45_Address, len + 1,true);
	
	if (newlen > 0)
	{
                newlen--;
                Wire.read();
		for (pos = 0; pos < newlen; pos++)
			result[pos] = Wire.read();
	}
	result[pos] = '\0';  // append the zero terminator

	return newlen;
}

This returns the following Data:

So it looks like were getting the proper response after the Setup Read now.

Its saying its returning 0x02 bytes of data from Register 0x50

Its still not the same as what this guy was getting back but we have made progress.

Any thoughts on this @Pra @ScruffR

Have to run now, but I'll be back with more testing data.

@Pra I also came across this bit of info from a question I had submitted last year when I had no clue what a Hex number was or how to convert it.

Here is what they told me about accessing Mac commands for a different fuel gauge I was evaluating before this new better chip was released.

Also the guy who posted the code above using the 0x44 command replied and said that was communication logged between his fuel gauge <> and the PC Software. He also verified that the 0x60 was PEC data.

/////////////////////////////////////////////////////////////////////////

And another peice of the puzze that will make sense of seeing the 0x16 and 0x17 are explained here i think.

Answer:

I think this explains the 0x17 in Toms code on the Read part, it indicates that PEC is being used. I have it turned off in my code so were only seeing 0x16.

That make sense?

@pra @ScruffR

Ok TI has provided the missing link we have been needing. I can now pull the needed data via the PC Software while the fuel gauge is in Sealed Mode. This allows me to record the communication structure with the Logic Analyzer.

Here is the reply from TI:

This was successful for me also and here is the data log:


As you can see their data sheet is way off.

@pra now that we know the proper read structure how would you propose we change the read function for the most efficient operation?

Looks this is just about done!!!

1 Like

@RWB

That is the word process call function or manufacturer_access() not a manufacturer_block_access(), so you cannot send it a block write. You are in fact telling it that you are wanting to access 0x5002 with a PEC of 0x0. So I donā€™t think that is going to work. You could try removing the write(ox2) but Iā€™m not sure that it will respond with block read data.

It seems unnecessary for the 2nd write to ox44, the manufacturer_block_access(). Did you do what I suggested previously adding a PEC of 0x60 and seeing whether the results change? You can see that is what your PC software did in the above example.

@pra

OK I got it working after hours of trial and error.

The READ > Write > Read pattern that the PC is using is correct as shown below.

When I tried this I kept getting a + NAC after 0x44 after the 2nd Setup Write to [0x16] + ACK.
No matter what I tried I kept getting the +NAC.

Then finally I figured out that I had to use the (true) at the end of this line instead of the (false); Like this Wire.endTransmission(true);

So "Wire.endTransmission(true);" = Repeated Start
& "Wire.endTransmission(false);" = +NAK

I read this in the twi.C file for the Wire Library about the Repeted Start Feature:

if (true == twi_inRepStart) {
// if we're in the repeated start state, then we've already sent the start,
// (@@@ we hope), and the TWI statemachine is just waiting for the address byte.
// We need to remove ourselves from the repeated start state before we enable interrupts,
// since the ISR is ASYNC, and we could get confused if we hit the ISR before cleaning
// up. Also, don't enable the START interrupt. There may be one pending from the
// repeated start that we sent outselves, and that would really confuse things.
twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR
TWDR = twi_slarw;
TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START
}
else
// send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

// wait for read operation to complete
while(TWI_MRX == twi_state){
continue;
}

So I stripped down the function to make troubleshooting easier and this is the code I'm using to get the proper response now:

int BQ20Z45::readStringB(uint8_t address, char* result)
{
	int pos = 0;
	int len;

        // Read the length of the string
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.write(0x02);
	Wire.write(address);
	Wire.write(0x00);
	Wire.endTransmission(true);
	
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.endTransmission(true);
	
	Wire.requestFrom(BQ20Z45_Address, 7, true);	
	
}

That returns this:

The 0x60 PEC is not required.

You guys have taught me enough that I think successfully altered the function code to do the following:

1 - Find out how many Bytes will be returned and save that to "len".
2 - Read the register again using "len" as the byte read legnth.
3 - Subtract 3 from "len" before the for loop. *Because the first 3 returned bytes we do not want.
4 - Wire.read(); 3 times *This keeps the first 3 reads from being transferred to the StringBuffer
5 - start the for loop which will transfer the 4 bytes of data were looking for.

Here is the code I modified to try to do the above and it looks like its working just fine but I can't figure out how to serial print the stringBuffer so see if it saving the last 4 bytes or not. That's the next part of the puzzle I need to work on.

Here is the function code:

// pass a pointer to a char[] that can take up to 33 chars
// will return the length of the string received
int BQ20Z45::readStringB(uint8_t address, char* result)
{
	int pos = 0;
	int len;

        // Read the length of the string
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.write(0x02);
	Wire.write(address);
	Wire.write(0x00);
	Wire.endTransmission(true);
	
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.endTransmission(true);
	
	Wire.requestFrom(BQ20Z45_Address, 1, true);
	len = Wire.read();    // length of the string
        len++;            // plus one to allow for the length byte on the reread
                          // if len > 32 then the it will be truncated to 32 by requestFrom

        // Now that we know the length, repeat the read to get all the string data. 
        // we need to write the address again and do a restart so its a valid SMBus transaction
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.write(0x02);
	Wire.write(address);
	Wire.write(0x00);
	Wire.endTransmission(true);
	
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(0x44);
	Wire.endTransmission(true);
	
	len = Wire.requestFrom(BQ20Z45_Address, len, true);    // readRequest returns # bytes actually read

        len--;   		// we won't move the first 3 bytes as its not part of the string
	    len--;
		len--;
	if (len > 0)
	{
                Wire.read();
				Wire.read();
				Wire.read();
		for (pos = 0; pos < len; pos++)
			result[pos] = Wire.read();
	}
	result[pos] = '\0';  // append the zero terminator
	
	return len;
	
}

Here is what that code looks like on the LA:

I'm beginning to see the light at the end of the tunnel now!

Thatā€™s not the way itā€™s supposed to work according to the BQ78350 TRM and the SMBus spec but if it gets you what you needā€¦

@pra Yea buddy! TIā€™s data sheets look to be notorious for being incorrect & incomplete which can be very frustrating. At least their fuel gauge chips are the best money can buy.

So I have the code working to pull the data from Manufacturer address after the chip has been sealed.

Iā€™m trying to figure out how to take the 2 Byte Signed Int data that is in 2ā€™s complement format and display the value between -32768 and 32768 .

Iā€™ve searched and watched videos and know that Arduino handles Int data in 2ā€™s complement format automatically.

Since TIā€™s datasheet is prone to being off Iā€™m verifying the data that the fuel gauge is providing and comparing that with the PC software readings that I know are correct.

So the Fuel Gauge is returning 2 byte signed int data. How do I convert these dual HEX bytes to a number format like -32 or 100?

I looked all over the place and couldnā€™t find anything that seemed simple. Iā€™m sure this has be a very simple thing to do right?

It seems like it would be as simple as converting the HEX to DEC.

What is the quick and easy way to do this?

And again - there is NO difference in the data, no matter if you look at it as DEC, HEX or BIN. It's all binary data and you only demand to have this binary data presented to you in a different less or more readable way. Just as you can write the number twelve in arabic notation as 12 or roman notation as XII - both mean the same kind of data ............ twelve dots or sticks or other things.

And the thing about unsigned/signed (2-complement) is pretty much the same and actually hidden from you since the receiving datatype already does that for you. And as I recall one of your early posts you even have a function read16 that should give you some clue.

Example for int8_t/uint8_t

 +13  0b00001101  (HEX 0x0D)
      0b11110010  (1-complement - toggle each bit HEX 0xF2)
    + 0b00000001  (plus one HEX 0x01)
      ----------
 -13  0b11110011  (2-complement - HEX 0xF3)

// but as uint8_t the same bit pattern would mean +243
// hence uint8_t can have values 0..255
// while int8_t does translate the same bit patterns to
// 0..+127 and then wraps round -128..-1

It's actually -32768 to +32767 (0xFFFF to 0x7FFF)

And as little riddle try to guess what output this will give you :wink:

  Serial.println((uint16_t)0xFFF3);
  Serial.println((int16_t)0xFFF3);
1 Like

Equals

Thanks @ScruffR , that simple Serial.println((int16_t)0xFFF3); is exactly what I was looking for :smiley:

1 Like

@ScruffR @peekay123 Iā€™m back at finishing a few missing functions with this library and now have it running on a Photon.

I need help figuring out how to isolate certain bitā€™s from a I2C Block Read function so I can update individual variables.

So let me try to explain what I have going on:

To read the ā€œFuel Gauge Statusā€ Register using the Photon I take the following steps:

1 - To read the Fuel Gauge Status Register I have to do a Block Read via Manufacturer Access command 0x44 and then call function I want to read data from, in this case we will read data from the GaugingStatus command 0x0056:


Since I have PC software that reads this info I already know the correct response we should get from command 0x0056 - GaugingStatus function is: 0x8540

2 - The read block code I have running on the Photon also reads this 0x0056 function via a Block Read function and returns the correct data.

Below is the code Iā€™m using on the Photon to do the Block read to get the Gauging Status:

#include "application.h"
#include "BQ20Z45.h"            //Include BQ78350 Header File

// Store an instance of the BQ20Z45 Sensor
BQ20Z45 bms;

char strBuffer[33];    // This is a Buffer to store 32 bit data strings read from the BQ78350 Fuel Gauge. Not Sure if this is the best place for this code.


void setup(void)
{
  // We start the serial library to output our messages.
  Serial.begin(115200);

  // Start i2c communication.
  Wire.begin();
}


void loop(void)
{

//Serial.println("Gauging Status");
bms.GaugingStatus(strBuffer);


delay(4000);

}

Here is the .H file:


#ifndef BQ20Z45_h
#define BQ20Z45_h


#define BQ20Z45_Address           0x0B
#define BQ20Z45_ManBlockAccess    0x44
#define BQ20Z45_ManAccess         0x00

#define BQ20Z45_GaugingStatus     0x56

class BQ20Z45
{
    public:


    int GaugingStatus(char *result);


    protected:

    private:


    int readStringB(uint8_t address, char* result);

};
#endif

Here is the cpp file:


#include "application.h"
#include "BQ20Z45.h"
//#include "Arduino.h"
#include "application.h"

/////////////////////////////////////////////////////////////////////////////
// Functions Below


// pass a pointer to a char[] that can take up to 33 chars
// will return the length of the string received
int BQ20Z45::readStringB(uint8_t address, char* result)
{
    int pos = 0;
    int len;

        // Read the length of the string
    Wire.beginTransmission(BQ20Z45_Address);      //Setup Write to Gauge
    Wire.write(0x44);                             //Manufacturer Block Read
    Wire.write(0x02);                             //How Many Bytes to expect Next
    Wire.write(address);                          //Manufacturer Access Command - Example 0x01 Device Type
    Wire.write(0x00);                             //First Byte of Command
    Wire.endTransmission(true);                   //Repeated Start = True

    Wire.beginTransmission(BQ20Z45_Address);      //Setup Write to Gauge
    Wire.write(0x44);                             //Manufacturer Block Read
    Wire.endTransmission(true);                   //Repeated Start

    Wire.requestFrom(BQ20Z45_Address, 1, true);   //Setup Reading of Returned Data
    len = Wire.read();    // length of the string thats about to return
        len++;            // plus one to allow for the length byte on the reread
                          // if len > 32 then the it will be truncated to 32 by requestFrom


    len = Wire.requestFrom(BQ20Z45_Address, len, true);    // readRequest returns # bytes actually read

        len--;           // we won't move the first 3 bytes as its not part of the string
        len--;
        len--;
    if (len > 0)
    {
                Wire.read();
                Wire.read();
                Wire.read();
        for (pos = 0; pos < len; pos++)
            result[pos] = Wire.read();
    }
    result[pos] = '\0';  // append the zero terminator

    return len;

}

/////////////////////////////////////////////////////////////////////////////
// Class Methods Below

int BQ20Z45::GaugingStatus(char *result)
{

 return readStringB(BQ20Z45_GaugingStatus, result);
}

Now when I run the code below and view what happens on the Saleae Logic analyzer I see that the ReadStringB function correctly reads the status of function 0x0056 as shown in the screen shot below:

So the Gaguing Status is returned in these 2 lines:

This matches the data returned in the PC evaluation software as shown below:

Great! I have verified the ReadBlockB function successfully returns data for all the other block read functions also.

What I now need to do is to be able to update an individual variable based on the status of the individual bits in the returned status readings like what was returned for Gaguing status.

For example this is what the individual bits in the Gauging Status return stand for:




  1. How should I go about getting the successfully returned data from the ReadBlock command into a variable that I can then take and update the status of the individual items contained in the 16 individual bits?

I guess just having the 0x8540 store in a variable would allow me to then create code that can read that varaible bit by bit and update the status of the 16 items those bits reffer to.

What do I need to change to get the return to store in a variable called GaugeStatus?

Then I would just need to figure out how to read each bit of that variable and update the state of other variables.

Let me know if that makes sense or not. I think Iā€™m on the right track but not sure :smile:

Thanks taking the time to read this :wink: