Using I2C for SMBus device communications

Hey everybody!

I’m needing some help understanding how to communicate with a battery management system via i2c.

I have a working library that is successfully communicating with the battery management chip over i2c and I’m pulling most of the info I need but there are more registers I need help with accessing.

I’ll try to be as clear as I can, so lets start off with the first task I need help with.

  • My current sketch is using the following 3 functions to read registers over the Wire library on a Arduino Micro and its working perfectly.
uint8_t BQ20Z45::read(uint8_t address)
{
	uint8_t registerValue;
        Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,1,true);
	registerValue = Wire.read();
	Wire.endTransmission();
        return registerValue;
}
uint16_t BQ20Z45::read16u(uint8_t address)
{
	uint16_t registerValue;
        Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
        registerValue |= (Wire.read()<<8);
	Wire.endTransmission();
        return registerValue;
}
int16_t BQ20Z45::read16(uint8_t address)
{
	int16_t registerValue;
        Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
        registerValue += (Wire.read()*256);
	Wire.endTransmission();
        return registerValue;
}

Those work fine for pulling data from registers that only hold and return 1 or 2 Bytes of signed & unsigned int data.

Now I want to read from this register that returns a 2 Byte Hex value held in a unsigned Int data format.


Now I’m thinking that I need to convert that HEX value to string of 16 individual bits. The 0 or 1 status of those bits will tell me whats up based on this graph:


The next register returns the same Hex data type but instead of 2 Bytes it returns 4 + 1 Bytes. See below:






I’m not sure what I should change in my current unsigned int wire request code that is working fine for other registers that are not returning data in HEX format.

uint16_t BQ20Z45::read16u(uint8_t address)
{
	uint16_t registerValue;
        Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
        registerValue |= (Wire.read()<<8);
	Wire.endTransmission();
        return registerValue;
}

I’m thinking I need to take the returned “registerValue” and convert the HEX to a string of 1’s and 0’s?

I’m sure its simple but beyond me at the moment. I’ve learned a lot about i2c communication over the last few days and I find it fascinating how it works and works so quickly.

Any help or advice is greatly appreciated :tophat:

@RWB, as I’m not sure how adept you are with C, you have to excuse, if I’m too basic for some or too much into detail for other things.

First of, HEX is just a different way of writing numbers compared to DEC or BIN (e.g. DEC 12 = HEX 0C = BIN 00001100 - there is even OCTal 14 ).
And C provides an easy way to write number literals of different bases (HEX 0x0C, BIN 0b00001100 and OCT 014 - beware of this when writing DEC literals with leading zeros (!)).
If you are using Windows (others will have too, I guess) you have the Calc.EXE which provides you with a Programmers-View and does show you all different number representations for free ;-).
So when you get a HEX return, you can treat it just like any other number, meaning for your 2 byte HEX you can just use the read16u().
When you want to test the single bits of any number you do something like this

  uint16_t myValue;

  myValue = bq.read16u(addr);
  if (myValue & (1 << bitNumber))  // shift a single bit and mask all others out 
    doSomethingBecauseBitIsSet();

For your longer returns you might want to write another more flexible fn that reads a given number of bytes into a byte array - if you happen to have several 4byte returns a version that returns a uint32_t might me useful, too. The you’d do some <<16 and <<24 just the same way as you can see the <<8 in the given read16u().

1 Like

What battery management IC are you using? Also, I second @ScruffR’s comment: HEX data is just data represented in HEX format. The bytes themselves shouldn’t have any concept of being “HEX” so you can treat them how you like, don’t worry about the confusing datasheet

@ScruffR , thank you for taking the time to explain. I appreciate it.

As far as my knowledge base, I just figured out what an Arduino was about a year ago but I’m learning quickly and its amazing stuff.

This video helped me understand that the micro controller is only seeing the individual bits. DEC and Hex are just a short hand way of reading larger strings of bits.

This also helped me out:

OK so if I am understanding you correctly, for any register returning 2 Bytes of Data I can use this function code:

uint16_t BQ20Z45::read16u(uint8_t address)
{
	uint16_t registerValue;
      Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
     registerValue |= (Wire.read()<<8);
	Wire.endTransmission();
        return registerValue;
}

I can use this code because its retrieves 16 bits aka “2 Bytes” of unsigned data to my uint16_t defined registerValue.

So I tried just that function to pull from this “Battery Status” register which should fill the registerValue with 16 bits of info.


To be clear here is the 3 lines of code from the .ino & .h & .cpp that is all working together.

.ino Code from main loop:

    // Include the Wire library for I2C access.
    #include <Wire.h>
    
    // BQ20Z45 library.
    #include <BQ20Z45.h>
    
    // Store an instance of the BQ20Z45 Sensor
    BQ20Z45 bms;

void loop()
{
    Serial.print("Battery Status Flags: ");
    Serial.println(bms.BatteryStatus());
}

.h File Code for the above function call:

#define BQ20Z45_Address           0x0B
#define BQ20Z45_BatteryStatus     0x16

  class BQ20Z45
    {
    	public:
    
    	uint16_t BatteryStatus(void);
        
    	protected:
    	  
    	private:
        
        uint16_t read16u(uint8_t);
        
    };

.cpp File code for this function call:

    uint16_t BQ20Z45::read16u(uint8_t address)
    {
    	uint16_t registerValue;
          Wire.beginTransmission(BQ20Z45_Address);
    	Wire.write(address);
    	Wire.endTransmission();
    	Wire.requestFrom(BQ20Z45_Address,2,true);
    	registerValue = Wire.read();
         registerValue |= (Wire.read()<<8);
    	Wire.endTransmission();
            return registerValue;
    }
 

  uint16_t BQ20Z45::BatteryStatus()
{
 
	return (uint16_t)read16u(BQ20Z45_BatteryStatus);
}

So when the serial print code runs in the main loop:

Serial.print("Battery Status Flags: ");
    Serial.println(bms.BatteryStatus());

It returns 2775 and some times it returns 3031 .

I’m assuming that this might be the 16 bit reading back in HEX which is what the fuel gauge data sheet says it returns 2 bytes in Hex format. Does the return look correct?

When I pull out the calculator I can convert that 2775 to BIN format to get all 16 bits but I’m not sure if its right or not. The data data sheet says it returns data in Little Endian format so should I be reading left to right or right to left?

Here is a screen shot of the returned data from the serial monitor:

Here is the data converted from HEX to BIN via the windows calculator:

So I can see that if the 2775 is indeed a correct HEX value then converting it to BIN provides the 16 bit readings. If that is correct then I’m still not sure if I should be reading from left or right. Below is how Texas Instruments says the data were reading is formatted.


I like to compare learning the Arduino & Spark programming language from scratch with no prior experience like dropping a English speaking person off in the middle of China with zero knowledge of the Chinese language. It takes alot of patience and persistence to keep digging for the solutions to all the issues that come up when trying to design some widget from the ground up.

Please any help or guidance is honestly greatly appreciated!

1 Like

OK, that's another treat the computer gods have for you :wink:
Little endian or big endian. This is something I always have to wrap my head round aswell, but I usually end up trying it out to be sure.
Does the least significant bit arrive first or last, is the lesser significant byte left or right of the more significant - I can't really say for your reading, since I have no clue what your battery status is.
But given the impression, that your other readings made sense, I'd guess you can forget about the endianness since your libs do it correctly already and you can read the bits of the number you got just the way you'd read a DEC number - most significant at the left, least at the right.
But this is just for you to read. The test-code I've given you above does not care about left or right. Bit 12 is always the twelth one even if you write it in Chinese from top to bottom :wink:

BTW: You wouldn't need the typecast here, since the fn does already return the correct type

Thanks for replying so soon :smile:

Ok I checked to see if the 2775 number thats being returned actually provides a 16 Bit reading that makes sense when compared to real bit status that is being shown on the PC Chip Evaluation software.

No matter which way I read the 16 bits they do not match up to the actual bit readings.

So somethings not right still.

Based on your advice I removed the (uint16_t) from the code below and it still works and returns the same 2775.

Is there anything else you can think of that could be wrong? I'm stumped for now.

@BDub @peekay123 @bko I know one of you guys probably know exactly what I'm doing wrong :smile: I've fried my brain trying to get this working for the last 2 days but I'm no further along than when I started LOL Gotta Love it :goberserk:

@RWB, for debugging purposes, can you try the following:

uint16_t BQ20Z45::read16u(uint8_t address)
{
uint16_t registerValue;
Wire.beginTransmission(BQ20Z45_Address);
Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(BQ20Z45_Address,2,true);
uint8_t lsb = Wire.read();
uint8_t msb = Wire.read();
registerValue = (uint16_t)(msb << 8) + (uint16_t)(lsb);
Wire.endTransmission();
Serial.print("lsb = “);
Serial.print(lsb, HEX);
Serial.print(”, msb = “);
Serial.print(msb, HEX);
Serial.print(”, regval = ");
Serial.println(registerValue, HEX);
return registerValue;
}

Run that and let me know what values you get for lsb, msb and regval. :smile:

Despite not being one of your first choice respondants, I’ll still try to answer :wink: Or rather ask more stupid questions.
What does your PC Chip Evaluation software tell?

I’m not sure, but you said you get a reading printlned of 2775, but this is DEC
This would be 0x0AD7 (HEX) or 0b0000101011010111 (BIN)
If you want Serial.println() to give you HEX, you’d have to tell (Serial.println(myNumber, HEX);), otherwise it will assume you want what most people want to see when they demand to get a number printed - decimal.

Would this make more sense, to you?

This would tell me that your battery is fully discharged (bit 5) and not being charged (bit 6) which could be the reason for the TDA (bit 11) & RCA (bit 9) alarms.
I’m not sure what “Initialization active” (bit 7) should mean and also the “UnknownError” (bits 0:3) is a bit obscure - maybe the datasheets tell you something.

For your other reading (3031 = 0x0BD7) it looks the same, but also shows an intermittent RTA (bit 8), which seems consistent with the reading above.

1 Like

@ScruffR you are the one of the first to step up and try to help out and I'm grateful for that believe me! I just wanted to bring a few more minds to the table to see if they could help out and keep me from spending a few more days in frustration :smile:

So now its a new day and I've had a few hours sleep lets keep trouble shooting :smiley:

It tells everything, it pulls all data from the fuel gauge and displays it in nice UI. See below:

My current library will return all the data shown below correctly from the fuel gauge mins the first "Manufacturer Access" reading that is in HEX. I can now see that the HEX return for "Manufacturer Access" is reading a simple 4 digit number just like were seeing comeback from the "Battery Status" register which is also HEX.

Yea that makes perfect sense and I tried adding HEX & BIN to the Serial.Println to the main loop code but the data output always remained 2775 for some reason, probably because of how the print line is written, its alittle different than your code. Here is my Println code.

Main Loop code: 

Serial.print("Battery Status Flags: ");
    Serial.println(bms.BatteryStatus());

It calls this function to return the BatteryStatus info:

.h File Code for the above function call:

#define BQ20Z45_Address           0x0B
#define BQ20Z45_BatteryStatus     0x16

  class BQ20Z45
    {
    	public:

    	uint16_t BatteryStatus(void);

    	protected:

    	private:

        uint16_t read16u(uint8_t);

    };

.cpp File code for this function call:

  uint16_t BQ20Z45::read16u(uint8_t address)
    {
    	uint16_t registerValue;
          Wire.beginTransmission(BQ20Z45_Address);
    	Wire.write(address);
    	Wire.endTransmission();
    	Wire.requestFrom(BQ20Z45_Address,2,true);
    	registerValue = Wire.read();
         registerValue |= (Wire.read()<<8);
    	Wire.endTransmission();
            return registerValue;
    }


  uint16_t BQ20Z45::BatteryStatus()
{

	return read16u(BQ20Z45_BatteryStatus);
}

Now I just don't know where I should be placing the HEX code to get the Serial.Println to print out the correct 16 digit Binary data that is returned from the fuel gauge?

I did try to use the calculator to convert the HEX 2775 to Binary but I'm getting different readings than you are providing. Maybe I'm doing that wrong?

The BIN return for the 2775 HEX via the calculator is:

vs your reading which is: 0b0000101011010111 (BIN)

Where does the 0b come from?

Regardless lets check your BIN reading against the PC Software Bit Status:

The top left OCA is Bit 15 and the bottom Left EC0 is Bit 0.

Assuming that I cut off the first 0b from your 0b0000101011010111 (BIN) , I end up with 0000101011010111 and that does not jive with the bit status readings the PC software is showing. Should I be reading the 0b?

:smile:

@peekay123 Thanks for trying to help out.

I replaced this code:

 uint16_t BQ20Z45::read16u(uint8_t address)
{
	uint16_t registerValue;
      Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
     registerValue |= (Wire.read()<<8);
	Wire.endTransmission();
        return registerValue;
}

With your modified code:

uint16_t BQ20Z45::read16u(uint8_t address)
{
uint16_t registerValue;
Wire.beginTransmission(BQ20Z45_Address);
Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(BQ20Z45_Address,2,true);
uint8_t lsb = Wire.read();
uint8_t msb = Wire.read();
registerValue = (uint16_t)(msb << 8) + (uint16_t)(lsb);
Wire.endTransmission();
Serial.print("lsb = ");
Serial.print(lsb, HEX);
Serial.print(", msb = ");
Serial.print(msb, HEX);
Serial.print(", regval = ");
Serial.println(registerValue, HEX);
return registerValue;
}

And it returns this:

Battery Status Flags: lsb = D7, msb = A, regval = AD7
2775

It looks like the same 2775 I’m getting with the first line of code I replaced with your code.

Hum… :hankey: Kinda looks like the 2775 might be the correct feedback and I’m not deciphering it correctly to bits?

Sorry if I was not clear enough with this before

The 0b is the way to mark a binary number for the compiler. How could it otherwise distinguish between 101 (one hundred and one) or 101 (five as binary)?
Everything after 0b is taken as the binary number.

When I take your screenshot (0x0BD0) and the other reading you got intermitently (3031 = 0x0BD7) it seems to fit not to badly - and since in 2775 only on bit differs this fits fine, too.
Only the bottom three bit seem to be wrong (OK vs. UnknownError). But this might not be a sign that your reading is wrong, but something about the way how you do it - the PC software obviously does it correctly (EC0..3 = 0000).
If you look at the other possible "Error Codes" I'd suspect that these codes do refer to communication conditions rather than the battery status - but this could only be clarified by the datasheet.
Maybe there are also some hints what the cause of an "UnknownError" could be - e.g. timing, command size, ???

As a help to decipher the green/red/gray indicators: 0 .. green or gray / 1 .. red (RTA bit red for 3031 and green for 2775).


BTW: With Calc try to select the Dec radio button THEN enter 2775 THEN click Hex and NOW read the bits (2775 Dec = AD7 HEX = 0000101011010111 BIN)


Edit: exchanged typo 3013 for correct 3031

Thank you, I learned something new today.

0b indicates the following numbers are Binary.
0x indicates the following numbers are HEX.

The 2 different readings I get back from the micro controller are:

and

The only bit that changes in the PC software is the RTA (Remaining Time Alarm) which makes sense because the battery is considered empty. Here are the 2 bit readings shown from the PC software:

In this reading the RTA bit is low/off and I'm thinking that this is represents the DEC 2775 reading the fuel gauge is returning.

Now here is where I start getting confused. When we convert the 2775 DEC number in BIN. (2775 Dec = AD7 HEX = 0000101011010111 BIN)

By looking at the PC Bit Register above it reads like this starting with bit 15: 0000 1010 1101 0000

Now those last 4 bits EC3, EC2, EC1, EC0 are defined in the datasheet as:

I do not care about the status of these 4 bits and do not plan on reading them but I am wondering why all 4 of these bits are showing 0 on the PC software but 0111 in the BIN conversion "0000 1010 1101 0111"

Above is the other reading I see in the PC Software which shows the RTA bit at high/on . I'm guessing this is the other DEC 3031 reading.

So lets do the DEC to Bin Conversion: (3031 Dec = BD7 HEX = 0000 1011 1101 0111 BIN)

The PC Bit map above reads out like this "0000 1011 1101 0000" which matches the 3031 Dec conversion except for the last 4 bits EC3-EC0 which I do not care about.

So yes it does look like the readings are indeed matching the PC software except for the last 4 bits for some reason and at this point I don't really care as long as the first 12 bits are working just fine :smile:

Gotta love progress!!!!

So back to your original advice about how to trigger a function when a Bit changes to 1.

uint16_t myValue;

  myValue = bq.read16u(addr);
  if (myValue & (1 << bitNumber))  // shift a single bit and mask all others out 
    doSomethingBecauseBitIsSet();

Do I just change the 1 to what ever bit I'm wanting to read and act on?

The next task is reading from a register that returns 4 Bytes in Hex. The data sheet says it returns 4 + 1 Bytes, I'm not sure what the +1 is all about. See below:


I'm assuming I need to change the function below to a uint32_t data type:

uint16_t BQ20Z45::read16u(uint8_t address)
{
	uint16_t registerValue;
      Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
     registerValue |= (Wire.read()<<8);
	Wire.endTransmission();
        return registerValue;
}

Would this be a proper 32byte conversion of the above function?

uint32_t BQ20Z45::read32u(uint8_t address)
{
	uint32_t registerValue;
      Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,2,true);
	registerValue = Wire.read();
     registerValue |= (Wire.read()<<8);
	Wire.endTransmission();
        return registerValue;
}

I'm wondering if the 2 in the code below needs to be changed to 4 since were reading 4 bytes?

Wire.requestFrom(BQ20Z45_Address,2,true);

I'm also wondering if this code is still right if were working with a 32 Byte data read.

registerValue = Wire.read();
    registerValue |= (Wire.read()<<8);

Thanks so much for your help, as you can see I'd be totally lost without your assistance :smile:

The 3013 was only a typo - the HEX I had given with it was indeed the one for 3031. I have edited this already.

No you don't change the 1, you provide the desired bit number in the designated variable bitNumber.
If bitNumber == 3 the 1 (0b00000001) gets shifted three places to the left (0b00001000) so you'll test bit 3.

Similarly for the 32bit question you are shifting the bits to the left to bring them in position.

    registerValue = Wire.read();
    registerValue |= (Wire.read() <<  8);
    registerValue |= (Wire.read() << 16);
    registerValue |= (Wire.read() << 24);

As stated in short in an earlier post

In binary this would look like this, if your read byte is always 0b0111 for example:

1.  0b00000000 00000000 00000000 00000111  // shift <<  0
2.  0b00000000 00000000 00000111 00000000  // shift <<  8
3.  0b00000000 00000111 00000000 00000000  // shift << 16
4.  0b00000111 00000000 00000000 00000000  // shift << 24
or  ------------------------------------------------  // logical OR with | symbol
    0b00000111 00000111 00000111 00000111 

BTW: I don't think it's good practice just to ignore the error bits. You may run into troubles later on when the indicated error starts to impair your following readings.
And I already had answered your question

Please! Read the posts!

@ScruffR I've been reading your replies quite a few times but the problem is this has been really hard to wrap my head around. Some stuff you mentioned earlier is only just now making sense to me. The more info you give the more it makes sense and the clearer it becomes.

I have a 1 & 5 year old children running around screaming like its a circus over here while I'm trying to concentrate long enough to fully comprehend what your trying to tell me to do :smile: It took me 3 hours just to gather my thoughts and reply to your suggestions. I think we almost have this wrapped up though so thats really good news :beer:

OK, so far we have a function successfully returning 2 byte registers that are formatted in HEX. I also know how to convert the returned data from Dec to Hex, Hex to Bin, Bin to Dec, ect.... I'm happy I learned how this works, it's has made zero sense to me up until today. Thank you!

Got it. I replace the "bitNumber" with the actual bit number I want to check the On/Off status of. Thank you for providing this code also, it will be useful.

OK, I think I finally get whats going on here with the shifting of the bits location as they are returned. If I'm following correctly my function code for pulling a 32 Bit , 4 Byte Register should look something like this:

uint32_t BQ20Z45::read32u(uint8_t address)
{
	uint32_t registerValue;
      Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,4,true);
	registerValue = Wire.read();
        registerValue |= (Wire.read()<<8);
        registerValue |= (Wire.read() << 16);
        registerValue |= (Wire.read() << 24);
	Wire.endTransmission();
        return registerValue;
}

I agree with you about the last 4 bits and not ignoring them. I'm going to ask TI for more specifics on these bits and if the PC software does anything special with them or not.

Assuming I have the Uint32_t read code shown above correct the only thing that I have left to figure out is how to do is read registers that return 32 Bytes of data in String Format.


I still have no idea what the +1 is after the 32 in the Size of Bytes column. Any ideas?

Maybe this explains it?

I read that the Wire Library for Arduino has a 32 byte read buffer limit?

Note: if num_bytes exceeds the size of the transmit/receive buffer (currently 32), it will be truncated to 32.

I have no idea what would be the most efficient way to wire read a 32 byte value. Once I figure this out I will know how to pull and process all the data that this fuel gauge can provide. I owe ya something your time for sure.

1 Like

I know how this is :stuck_out_tongue_winking_eye:, but I can comfort you, it won't change for the next few years :wink:
Mine are 4, 10 and 15 and not any better!!!

Given these circumstances, I'll be more patient - promise :smile:

Since we got the 32bit sorted (although I don't understand what the +1 here - especially since the given max. is 0xFFFFffff which does not require more than 4 bytes) we can move on to the string.

Here I'd assume the +1 might stand for the string terminator '/0' which may not be necessary to be transfered, but you may append yourself to finalize your string, once done.

I'll fix up a quick code sample - stay tuned.
Till then: A string is a sequence of characters (=bytes) which you can store in an array char myString[33] by doing myString[byteNumber++] = Wire.read();
And since the first byte to read tells you how many will follow, you can do the same thing so many times.

Maybe you can beat me to this and come up with your code, before I come back with mine :wink:

1 Like

What a pitty - I can’t test my code before posting it, so it might not work :wink:

// pass a pointer to a char[] that can take up to 33 chars
// will return the length of the string received
int readString(uint8_t address, char* result)
{
	int pos = 0;
	int len;
    
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	
	// only one len byte to start with
	Wire.requestFrom(BQ20Z45_Address, 1, false);
	while(!Wire.available())  // you may want to wait for bytes to arrive
	   SPARK_WLAN_Loop();
	len = Wire.read();
	if (len > 0)
	{
		// then request the rest
		Wire.requestFrom(BQ20Z45_Address, len, true);
		while(!Wire.available())  // you may want to wait for bytes to arrive
		   SPARK_WLAN_Loop();
		for (pos = 0; pos < len; pos++)
			result[pos] = Wire.read();
	}
	else
		Wire.endTransmission();

	result[pos] = '\0';  // append the zero terminator

    
	return len;
}
    

Edit: Only if len <= 0 transmission should be ended explicitly

HA! Yea and my wife keeps talking about wanting to have a 3rd baby :baby: :heavy_exclamation_mark: I've been trying to reply to you all day but only just now got a chance to sit down without constant interruptions to actually write this reply LOL

Thank you for the code you provided. I think I have some code that may explain whats going on with that +1 that shows up in the register datasheet. Below is code I found for pulling info from similar TI fuel gauges, it's not based off the normal Arduino Wire Library but you can see how they are dealing with the +1 I think. Let me know if it makes sense to you.

  uint8_t i2c_smbus_read_block ( uint8_t command, uint8_t* blockBuffer, uint8_t blockBufferLen ) 
  {
    uint8_t x, num_bytes;
    i2c_start((deviceAddress<<1) + I2C_WRITE);
    i2c_write(command);
    i2c_rep_start((deviceAddress<<1) + I2C_READ);
    num_bytes = i2c_read(false); // num of bytes; 1 byte will be index 0
    num_bytes = constrain(num_bytes,0,blockBufferLen-2); // room for null at the end
    for (x=0; x<num_bytes-1; x++) { // -1 because x=num_bytes-1 if x<y; last byte needs to be "nack"'d, x<y-1
      blockBuffer[x] = i2c_read(false);
    }
    blockBuffer[x++] = i2c_read(true); // this will nack the last byte and store it in x's num_bytes-1 address.
    blockBuffer[x] = 0; // and null it at last_byte+1
    i2c_stop();
    return num_bytes;
  }

Also here is how their code pulls regular 2 Byte registers. Its using the Repeated Start feature that is supposed to be a feature of SMBus but this library didn't work for me for some reason but did work for another guy using different fuel gauge chips than me.

I'm wondering if this code vs my 2 Byte - uint16 read function code has anything to do with those 4 bits (bits 3-0) that didn't match up with the PC software?

Here is their code for returning a 2 Byte unsigned int register. The only difference I see is the repeated start.

My code is working just fine without using the repeated start when it comes to pulling regular 2 Byte registers. The exception possibly being the Battery Status 0x16 register returning bits 0-3 differently than the PC software. I'm just wondering if not doing a repeted start could cause the last 4 bits to of a 2 Byte reading to be off like were seeing or not. Take a look at their code.

int fetchWord(byte func)
{
  i2c_start(deviceAddress<<1 | I2C_WRITE);
  i2c_write(func);
  i2c_rep_start(deviceAddress<<1 | I2C_READ);
  byte b1 = i2c_read(false);
  byte b2 = i2c_read(true);
  i2c_stop();
  return (int)b1|((( int)b2)<<8);

Also just to double verify, your saying that for pulling 32Bit returns the code below is formatted just fine? I just need to adjust the number 4 to how every many Bytes I plan on having returned to my variable?

uint32_t BQ20Z45::read32u(uint8_t address)
{
	uint32_t registerValue;
      Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission();
	Wire.requestFrom(BQ20Z45_Address,4,true);
	registerValue = Wire.read();
        registerValue |= (Wire.read()<<8);
        registerValue |= (Wire.read() << 16);
        registerValue |= (Wire.read() << 24);
	Wire.endTransmission();
        return registerValue;
}

Now my last question is about if you do coding for a living or have you played with it enough that its just another language you speak?

I do have a Saleae Logic 4 https://www.saleae.com/ I2C data recorder and analyzer should you think it would be useful during any of this.

Since I don’t know their i2c_ function implementation I’m not sure what the difference between i2c_start and i2c_rep_start is.
But the Core standard provides a dedicated requestFrom function instead, I’d guess that works just fine as is.
The only thing interesting for me is that their i2c_read is called for all but the last byte as i2c_read(false). Only the last one is i2c_read(true).
But as I understand the Core’s requestFrom-stop parameter that should do just the same and for that reason you do need to pass the number of bytes you want to read, so that it knows, when to stop the transmission.
And in that is the answer to your question about the 4 in your read32u - it just does what you thought it would.

But given the 4+1 thing, you might try do change this into a 5 and just not read, or for test purposes you could do Serial.println(Wire.read(), HEX); to see what the last byte looks like.
Maybe this gets rid of the ‘UnkonwnError’,although I wouldn’t see how the fourth byte could be set as error before knowing that the fifth won’t be read afterwards, unless your very first reading was a 2768 or 3024 and only subsequent ones turned out to be +7 since the previous read was not performed to the end.

I hope this does make some sense :blush:

One other thing I do like about the code you have given above is that they do actually limit the bytes they are reading to the size of the given buffer - in my code, I didn’t do that, but it would be good pracitice to prevent buffer overflow which may cause some trouble.


About my programming skills:
I used to do programming for living a “long time” ago :wink: But only PC applications - the micro programming I’m only doing for fun and as such most of the things I know about it now is learn-as-you-go, but general knowledge of the inner workings of a CPU and ASM/C/C++ jargon does help tho’.

For anybody new to the ideas of programming it may appear as a completely different way of thinking - but I found it is easier if you do understand what’s actually goning on inside the hardware before you learn the language.
Just as you could learn a natural language best when you are with the people speaking it rather than out a textbook.
And for this I have already linked to this video series of Harvard CS50 computer science course several times on this forum - it’s a super easy starting point and fun (in a nerdy way ;-))

I have to make this one quick but I’ll be back with more replies.

For now here is the other I2C library http://playground.arduino.cc/Main/SoftwareI2CLibrary
To save you time here is what it says about the repeated start line:

    i2c_rep_start(addr | R/W-bit) 

Sends a repeated start condition, i.e., it starts a new transfer without sending first a stop condition.

I am running this code on the Arduino Micro for now. It will run on the Teensy 3.1 and new Photon once I have all the code up and running correctly.

I’ll be back with more tomorrow. But I’m interested in if you think that repeated start and not using it could be causing the last 4 bits to not show correctly now that you know what its doing.

After following your link through to some other links, I finally found the (?) source for your lib here
And therein I found the implementation of i2c_rep_start()

unsigned char i2c_rep_start(unsigned char address)
{
    return i2c_start( address );

}/* i2c_rep_start */

So it doesn’t do anything different to the initial start, as it only calls it as is.


Edit: This was wrong - don’t do it :blush: Thanks @pra
To mimic the same behaviour you could try this

	...
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	// ****** Wire.endTransmission(); ******
	Wire.requestFrom(BQ20Z45_Address,4,true);
	...

Better try:

	...
	Wire.beginTransmission(BQ20Z45_Address);
	Wire.write(address);
	Wire.endTransmission(false);  // this prevents the STOP condition on the lines
	Wire.requestFrom(BQ20Z45_Address,4,true);
	...
	// if requestFrom is used without STOP=true
	// Wire.endTransmission(true);  // don't forget to finally send STOP
	return registerValue;   

But since I’m not an expert on I2C maybe one of our fellow Sparkies might know more, if this and the +1 tip from above doesn’t work either.

1 Like