Using I2C for SMBus device communications

I think I don’t quite follow, but that might be because I’m thinking to complicated.

So I’ll just blab out what I think might be useful for what I got :blush:

To get the 0x40 and the 0x85 into one variable as 0x8540

uint16_t GaugeStatus = 0;

GaugeStatus  = Wire.read();      // 0x40 into bottom byte
GaugeStatus |= Wire.read() << 8; // 0x85 into top byte

and to get the n-th bit

if (GaugeStatus & (1 << n)) ...
// or
bit = (GaugeStatus >> n) & 0x0001;

Thanks for the quick reply :smile:

OK, let's start with the getting the return into it's own register.

uint16_t GaugeStatus = 0; // This should be perfectly fine for holding the return. 

To get the 0x40 and the 0x85 into one variable as 0x8540

The block read is more complicated than that because it first reads the command and determines how much data is being returned by specific commands and then uses that to know how many times to read data on the following read cycle.

I think the read block is fine since it will always return the correct amount of data from the read command which can be 2 to 4 bytes long. I just need to figure out how to that the returned data and pass it back to a global variable.

Here is the ReadBlock Function code that works fine:

// 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;

}

This is the function that calls that ReadBlock function and passes the correct command address to the function. Both lines of code are located in teh .cpp file:

// Class Methods Below

int BQ20Z45::GaugingStatus(char *result)
{

 return readStringB(BQ20Z45_GaugingStatus, result);
}

Here is the .H file code if it makes a difference.

#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

Is there any way to set this up so the Block Read function passes the gathered data into a global variable by modifying the code above in the .H and .CPP files?

It looks like I need to change the result to a different data type for this to work correctly but I'm going to need some guidance to help get that setup correctly of course :blush:

I don’t think you need to change anything in that function.
It already takes a char * which can be any char array including global ones, so I still can’t quite follow where the problem is.

The function is called readStringB() but don’t expect this to return a C-string as there may be zero-characters embedded in that “quasi string”. You need to take the returned len to determin how many individual bytes you got from your block read and your desired “payload” will be there at the expected position in the array.

Via the Logic Analyser I can see the readStringB actually does it's job and is getting the data I'm seeking but where is that data going? Is it ending up in the global variable > strBuffer[33] variable in my main .INO code below?

If the is coming back and being placed in the strBuffer[33] ? If so then I just need to figure out how to save the current strBuffer to a new global variable called GaugingStatus :smiley:

I'm not sure how to serial print strBuffer[33] to check if it's receiving the or not.

#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);

}

Yea it's really a Block Read function but I called it readString for some reason during my testing of different code structures.

What else would this buffer be for otherwise? :confused:

That's one way :wink:

void loop(void)
{
  int len = bms.GaugingStatus(strBuffer); // as mentioned use the returned length info
  for (int i=0; i < len; i++) {
    // print byte by byte (in this case as two-digit hex with blanks in between and as block of eight)
    Serial.printf("%s%02x", i % 8 ? " " : "\r\n", strBuffer[i]);
  }
  delay(4000);
}

Yes :smile: That shows that the data is being returned and held in strBuffer as expected!

Here is what it serial prints:

Now instead of printing out the strBuffer, how can I just save the contents of the strBuffer into a global variable?

I tried creating a global variable called: uint16_t GaugingStatus = 0;

And then the following code trying to push the strBuffer data into the GaugingStatus variable but I get compile errors.

bms.GaugingStatus(strBuffer);
GaugingStatus = strBuffer;

Should I be trying to figure out how to convert a char to uint16_t variable type? I'm sure there is more than 1 way to skin this cat but I always prefer the most efficient method if possible.

That only copies the pointer address not the contents.
And if you looked at the way how the individual bytes were printed out (which is not much different top copying the contents from their store location) you might guess how you'd do that.
And taking my earlier advice of how to feed single bytes into a uint16_t you might figure the rest too :wink:

BTW, efficiency comes after understanding the how-to first.

Understanding pointers and data types is a fundamental skill for programming - especially in C/C++

I could show you a very efficient way for that, but only after the basics are clear :sunglasses:

1 Like

I see, I have to print out each piece of data in the strBuffer one at a time into the new GaugingStatus variable.

The len code just tells us how many positions there currently are in the strBuffer and we use that so we know how many times to run the for loop that will place each bit of data into the new GaugingStatus variable.

After searching Google for how others copied data from a Char array into an int or uint, I saw quite a few others use the atoi function like this: GaugingStatus = atoi(strBuffer);

But the atoi didn’t work for me.

I figure I can just modify the for loop to push data into the GaugingStatus variable one bit at time but I’m curious how we deal with bit shifting << the data as the for loop runs through all the array positions? We do not want to bit shift the first position do we? We just want to bit shift all array positions we read after we read the 1st position right? Or am I overthinking this?

Like here, you bit shift << only on the 2nd read. I assume we want to do the same as we read all the positions in the strBuffer array right?

atoi() is a function that translates a “written” representation of a number into the corresponding value, so no thst’s not what you want.
You want two “independent” bytes ([byte1][byte2]) to be taken as one combined variable ([uint16_t] which could be [byte1:byte2] or [byte2:byte1], depending on the endiannes of your systems).

But that discussion brings us back to the beginning of this thread. What are data types and how do they correlate?

The way I do the bit shifting is exactly what you want - no more no less.
And here is why (short (re)primer in binary data theory)

code                                   BIN                 HEX
GaugeState    = strBuffer[0];          00000000 01000000   0x0040
//GaugeState |= strBuffer[1] << 8;
//breaks down to
uint16_t x = strBuffer[1];             00000000 10000101   0x0085
x = x << 8;                            10000101 00000000   0x8500
GaugeState    = x | GaugeState;        10000101 01000000   0x8540

If you can follow this so far, we could move on the the pointer stuff and the shortest code for all that in a next step.

I’m pretty sure I’m following along just fine as far as understanding the difference between BIN vs. HEX data types and how they are the same thing displayed in different formats.

I get how bit shifiting works.

I get how the strBuffer is an Array with 33 positions based on how I have my code setup. We can access the individual positions in the strBuffer by using strBuffer[0], or strBuffer[10], or strBuffer[22], ect…

I am a bit confused on how the Bitwise OR operator is working in this line:

GaugeState    = x | GaugeState;      10000101 01000000   0x8540

It looks like the “I” is combining the variables x & GaugState; but based on the explination I get for Bitwise OR it does something different than combining the 2 variables together as described below:

I’m missing something or I just have not seen a working example that makes everything click in my mind yet. I feel like I’m really close :smile:

Thanks for helping with this and trying to push me to understand what’s going on vs. throwing out working code although I do learn tons from analyzing working code and then modifying it from there to meet my specific needs.

@RWB, you almost have it. Combining two 8-bit values into a single 16bit value requires knowing which of those 8-bit values is the most significant (MSB) representing the highest value bits of the 16-bit final value and which is the least significant 8-bit value (LSB). Based on this, you can “construct” the 16-bit value with:

16b = 8b_MSB << 8 - This shifts the 8 MSB bits into the upper bits of the 16-bit value by doing 8 shifts (<< 8) to the left (ie towards the upper bits).
NOTE that as each bit is shifted, a “zero” bit is shifted in at the same time. So, before the shift, +16b = 0000000000000000 and after, 16b MMMMMMMM00000000

Then, we need to add the 8-bit LSB to complete the 16-bit value:

16b = 16b | LSB - The | is a bit-wise OR function as you know. This line effectively “inserts” the LSB bits into the 16-bit value so that the final 16-bit value is 16b = MMMMMMMMLLLLLLLL

The final code is:

16b = 8b_MSB << 8;
16b = 16b | 8b_LSB;

:wink:

2 Likes

The point with the OR operation here is that wherever you’ve got a set bit (1) in one of the two operands you will get a set bit in the result but it also means where one operand has a 0 bit the result will exactly be what the other operand has got there.
And that’s what we want when we “inject” one byte into a longer variable (e.g. uint32_t) we want that byte as is in the destination while not changing any of the other bits that are already there.
So we could write the operands a bit more elaborate and replace some of the 0 bits with K (=keep) and X (=don’t care) to make it a bit clearer (also added an extra zero initialization)

GaugeState     = 0;                   00000000 00000000   0x0000
uint16_t lsb   = strBuffer[0];        KKKKKKKK 01000000   0xKK40
uint16_t msb   = strBuffer[1];        XXXXXXXX 10000101   0xXX85
msb = msb << 8;                       10000101 KKKKKKKK   0x85KK


GaugeState                            00000000 00000000   0x0000
lsb                                   KKKKKKKK 01000000   0xKK40
--------------------------            -----------------   ------
GaugeState after 1. OR              = 00000000 01000000   0x0040

GaugeState                            00000000 01000000   0x0040
msb                                   10000101 KKKKKKKK   0x85KK            
--------------------------            -----------------   ------
GaugeState after 2. OR              = 10000101 01000000   0x8540

Does this help lift the fog a bit?

1 Like

From your Like I’ll take that this did help a bit and I hope the bit operations (shift and or) are as clear as the fact that your block read just places bytes in memory at a location you provide to the function via a pointer (char*).

And here is the clue: Pointers can be addresses of any memory location, even ones that belong to other variables.
And - as long as you know the data will fit (by size and meaning) - you can pass the address of any variable instead of your char[] too. After all, these are just “dumb” memory locations where you can place any bit combination you want.

So as long as you know your block read will only give you exactly two bytes back and the endianness of your incoming data fits the endianness of your target system (which seems to be the case here) you can just provide the pointer to your final destination variable to your block read function and you’ll save the extra steps.

(I’ll just keep one “secret” for now, to give you the chance to try the above first :wink: )

@ScruffR I’m trying to figure this out myself based on the info you have provided.

It sounds simple but now I’m troubling shooting the Evaluation board I’m working with, I may have damaged it on accident while playing with a new Rigol Oscilloscope. :blush: I was reading I2C data on it only to find out the Saleae Logic 4 does a way better job at monitoring I2C communication.

I have backup evaluation boards & Photons so once I get communication with the Photon working again I’ll report back on my progress with this :wink:

And yes I know your making me take the long path to the destination before showing me the shortcut to get to the same place :smile:

@ScruffR OK! I didn’t damage anything after all :smile:

One of the metal leads on the jumper wires disconnected from the wire inside the black plastic cover.

All fixed up now :person_with_blond_hair:

No wonder the I2c clock signal on the scope was looking low and funky, it was trying to tell me something :smirk:

I’m pretty sure I had this coded correctly but the broken wire was causing the return to be 0 so I figured I had it wrong :-1:

Yup, not to be awkward, but so it sticks better :wink:

@ScruffR @peekay123

OK, after looking at both your latest explanations I would have bet the house that placing strBuffer[0] & strBuffer[1] into their own variables would allow me to print them out individually and correctly. But this does not work as shown below.

bms.GetDeviceName(strBuffer);

uint16_t lsb  = strBuffer[0];
uint16_t msb  = strBuffer[1];
Serial.println(lsb, HEX);
Serial.println(msb, HEX);



int len = bms.GaugingStatus(strBuffer); // as mentioned use the returned length info
for (int i=0; i < len; i++) {
 //print byte by byte (in this case as two-digit hex with blanks in between and as block of eight)
Serial.printf("%s%02x", i % 8 ? " " : "\r\n", strBuffer[i]);
}

Serial.println("");
Serial.println("------------------------------");
Serial.println("");

Here is what serial prints when I run the code above:

The 4a 00 is correct as refrenced in the PC software screenshot below:

and the 62 71 wich comes from strBuffer[0] & strBuffer[1] is not correct :man:

What is the difference in how Scruff prints strBuffer[0] vs how I’m printing strBuffer[0] ?

Are they not holding the exact same data after I call: bms.GetDeviceName(strBuffer);

bms.GetDeviceName(strBuffer); // <-- ????

uint16_t lsb  = strBuffer[0];
uint16_t msb  = strBuffer[1];
Serial.println(lsb, HEX);
Serial.println(msb, HEX);

Why did you request bms.GetDeviceName() when you want to read bms.GaugingStatus()?

After retrieving the correct info with your subsequent int len = bms.GaugingStatus(strBuffer); you get what you want, don’t you?

1 Like

:stuck_out_tongue: Good catch

It’s working now :smile:

So now that this is working I also know how to bitshift these into one single variable also.

Now to your more efficient short cut :airplane:

Sounds like your saying I can kind of copy and paste the data held in strBuffer into a new variable by providing the memory address via a “pointer” and then saying something like uint16_t = strBuffer[0]-[2], or something similar.

I get that the data is just a bit in memory and each bit has a specific memory address.

So ideally I could save the status of certain bits in strBuffer into different variables since most of the 16 bits in the strBuffer indicate the ON or OFF status of different fuel gauge features.

I’m eager to see this more efficient way of coding :wink:

Not quite. Why first put it into strBuffer[] at all, when I don't really want it there anyway? Just let the function read the data directly into its final destination.