LTC2941 Battery Gas Gauge I2C problem

Hello

I need help from someone I2C guru.
How I get the communication to work with the LTC2941 battery gas gauge.

Someone help I do not really understand the data sheet.
http://cds.linear.com/docs/en/datasheet/2941fa.pdf

My arduino code.

#include <Wire.h>

void setup()
{
  Wire.begin();                // join i2c bus (address optional for master)
  Serial.begin(9600);          // start serial communication at 9600bps
}

int reading = 0;

void loop()
{
  
  Wire.beginTransmission(100); // address #100 (0x64)                           
  Wire.write(0);               // write (0x00)  
  Wire.write(0x00);            // write (0x00) register   
  
  Wire.beginTransmission(100); // transmit to device #100
  Wire.write(byte(0x01));      // write (0x01)
  
  Wire.requestFrom(100, 2);    // request 2 bytes from device #100

  if(2 <= Wire.available())    // if two bytes were received
  {
    reading = Wire.read();     // receive high byte (overwrites previous reading)
    reading = reading << 7;    // shift high byte to be high 7 bits
    reading |= Wire.read();    // receive low byte as lower 7 bits
    Serial.println(reading);   // print the reading
  }
  
  Wire.endTransmission();      // stop transmitting

  delay(500);                 
}

Could you run the i2c_scanner to verify the address : http://playground.arduino.cc/Main/I2cScanner

Have a look at this example : http://arduino.cc/en/Reference/WireWrite
And this : http://arduino.cc/en/Reference/WireRead

Writing something is : beginTransmission - write - write - write - endTransmission
Reading something is : requestFrom - read -read - read

You have a endTransmission() after the requestFrom(), that is not correct.

Could you fix that ?
Is there Arduino code for this chip somewhere ? or perhaps for an older version of this chip ?

Thank you Peter

i2c Scanner gives me the right address 0x64.

but I did not get any real data out of the LTC2941 gas gauses.
Something it only counts …

The problem is the code / command…

#include <Wire.h>

void setup()
{
  Wire.begin();                // join i2c bus (address optional for master)
  Serial.begin(9600);          // start serial communication at 9600bps
}



void loop()
{
  Wire.beginTransmission(0x64); // address #100 (0x64)                           
  Wire.write(0);
  Wire.write(0x02);  
  Wire.write(0);
  Wire.endTransmission();      // stop transmitting
  
  Wire.requestFrom(0x64, 6);    // request 6 bytes from slave device #2

  while(Wire.available())    // slave may send less than requested
  {
    int c = Wire.read();    // receive a byte as character
    Serial.print(c);         // print the character
    Serial.print("-");
  }
  delay(500);  
    Serial.println();  
}

The manufacturers page : LTC2941 Datasheet and Product Info | Analog Devices

You write 0x02 to register A, and 0x00 to register B. After that you read 6 bytes starting with register C (I think). That is not what you want to do.

Perhaps you could a write 4 functions: read a byte, read two bytes, write a byte, write two bytes.

Writing a two bytes (a 16-bit word) can be done with highByte() and lowByte(), as here : power-monitors/ltc2943.cpp at master · zapta/power-monitors · GitHub

Reading two bytes and combine them to a 16-bit word can be done with word() : http://arduino.cc/en/Reference/WordCast

Can you write those function or do you want my help ?

The datasheet is not to impressive in it’s clarity but try the code below…
I’m not sure if the address (0x64) needs bit shifting 1 to the left before using (making it 0xC8).

#include <Wire.h>
unsigned int reading = 0;

void setup()
{
  Wire.begin();                // join i2c bus (address optional for master)
  Serial.begin(9600);          // start serial communication at 9600bps
}

void loop()
{
  
  Wire.beginTransmission(100); // address #100 (0x64)                           
  Wire.write(0x02);            // read from register 0x02
  Wire.endTransmission();      // stop transmitting
  
  Wire.requestFrom(100, 2);    // request 2 bytes from device #100
  
  if(Wire.available() > 1)    // if two (or more) bytes were received
  {
    reading = Wire.read();     // receive high byte (overwrites previous reading)
    reading = reading << 7;    // shift high byte to be high 7 bits
    reading |= Wire.read();    // receive low byte as lower 7 bits
    Serial.println(reading);   // print the reading
  }
  
  
  delay(500);                 
}

Hello Riva thanks to the code, unfortunately, it does not seem to work correctly.
Prints only 16444 ...
The address is correct 0x64.

Peter, if you can help arduino code Function. it would be really nice :smiley:
github c code example will certainly work. Somehow, I just do not like to test this for atmel studio.

Thank you very much for your help.

t4k4ll:
Hello Riva thanks to the code, unfortunately, it does not seem to work correctly.
Prints only 16444 ...
The address is correct 0x64.

Did you try the other address of 0xC8? The data sheet shows the 7 bit address to be 0x64 but when passing this address to the device the address is bit shifted to the left by one bit and bit 0 is used as a read/write flag. I don't think the Wire library does this shift else how can you specify the read/write bit.

Another thing to try is comment out the Wire.endTransmission();
And change this line Wire.requestFrom(0xC9, 2); // request 2 bytes from device #100

If the i2c_scanner finds 0x64, that is the address to use. The i2c_scanner finds it when using the Arduino functions and therefor the address can be used with the Arduino functions.

The Arduino Wire library uses the 7-bit shifted address. But you can forget that, just use what the i2c_scanner found.

// not tested

#include <Wire.h>

const int ltc_address = 0x64;


void setup()
{
  Wire.begin();                // join i2c bus (address optional for master)
  Serial.begin(9600);          // start serial communication at 9600bps
}

void loop()
{

  // read a register, write a register, and so on.
  Serial.print("Status is : 0x")
  Serial.println( readReg8( 0), HEX);       // register 0 is status
  ...
 
  delay(500);                 
}

void writeReg8( int reg, byte data)
{
  Wire.beginTransmission( ltc_address);
  Wire.write( reg);
  Wire.write( data);
  Wire.endTransmission();
}

void writeReg16( int reg, unsigned int data)
{
  Wire.beginTransmission( ltc_address);
  Wire.write( reg);                       // first byte is register address inside ltc 
  Wire.write( highByte( data));
  Wire.write( lowByte( data));
  Wire.endTransmission();
}

byte readReg8( int reg)
{
  byte data = 0;       // preset to zero, return zero if something wrong

  Wire.beginTransmission( ltc_address);
  Wire.write( reg);               // set register address inside ltc
  Wire.endTransmission();

  int n = Wire.requestFrom( ltc_address, 1);
  if( n == 1)
  {
    data = Wire.read();
  }
  else
  {
    Serial.println("ERROR, slave did not want to send a byte");
  }

  return( data);
}

unsigned int readReg16( int reg)
{
  byte dataH, dataL;
  unsigned int data = 0;    // preset to zero, return zero if something wrong

  Wire.beginTransmission( ltc_address);
  Wire.write( reg);
  Wire.endTransmission();

  int n = Wire.requestFrom( ltc_address, 2);
  if( n == 2)
  {
    dataH = Wire.read();
    dataL = Wire.read();
    data = word( dataH, dataL);
  }
  else
  {
    Serial.println("ERROR, slave did not want to send 2 bytes");
  }

  return( data);
}

Thank you for your code by Peter. It looks nice.
I just do not understand this ... :slightly_frowning_face:

What I need write first that I can read the voltage of the battery.

writeReg8(0,0); // write 0x00 to register A
Serial.println( readReg8(0), HEX); // read register A

The program will always print only 0x80

Thank you very much for your tips and your time.

Yes, those functions are only to interface with the chip. I do not use registers to set something. I have not read the datasheet that well.

You could try to read a number of 8 bit and 16 bit registers, to see if those functions work. If that is okay, you can try to write some registers and see what happens.

Do you know how to control the chip ? how to set the registers ?

Peter_n:
If the i2c_scanner finds 0x64, that is the address to use. The i2c_scanner finds it when using the Arduino functions and therefor the address can be used with the Arduino functions.

The Arduino Wire library uses the 7-bit shifted address. But you can forget that, just use what the i2c_scanner found.

Silly me, I should have first looked at the library code to see if it did the bit shifting but I can see now it bit shifts and sets/clears the R/W flag bit.

t4k4ll:
What I need write first that I can read the voltage of the battery.

writeReg8(0,0); // write 0x00 to register A
Serial.println( readReg8(0), HEX); // read register A

The program will always print only 0x80

Thank you very much for your tips and your time.

I’m using an LTC2943 and couldn’t get it to work with the standard Wire library. Tried I2C master library, worked out of the box (i’m guessing because of support for the repeated Start Condition). For the LTC2941 you will probably need to tweak the register adresses. Here’s my code printing to an lcd, maybe it will help someone out.

PS. Just looked up the ltc2941 datasheet, it seems it doesn’t include an ADC, so no voltage, current, temp readings using that IC, ltc2942 and ltc2943 however do.

#include <LiquidCrystal.h>
#include <I2C.h>

LiquidCrystal lcd(12, 11, 5, 4, 8, 7);

// Define register addresses LTC2493...
#define LTC2943Address 0x64
#define regAddressStatus 0x00
#define regAddressControl 0x01
#define regAddressAccuCharge 0x02
#define regAddressVoltage 0x08
#define regAddressCurrent 0x0E
#define regAddressTemp 0x14
#define senseResistor 0.050

void setup() {
  
  // Start Serial, I2c and LCD
  Serial.begin(9600);
  I2c.begin();
  lcd.begin(20, 4);
  
  // Set control register 01h, ADC to scan Mode (default ADC mode is sleep, no current/voltage/temeperature measurements)
  I2c.write(LTC2943Address, regAddressControl, B10111100);
  
}

void loop() {
  
  // Read single byte from regAddressControl (0x01)
  byte LTC2943StatusRaw = readReg8(regAddressControl);
  
  // Read two bytes form regAddressAccuCharge and convert to Accumulated Charge
  // When starting up LTC2943 sets the ACR to midscale, will need to calibrate...
  int LTC2943AccuChargeRaw = readReg16(regAddressAccuCharge);
  float LTC2943AccuCharge = 0.340 * LTC2943AccuChargeRaw;
  
  // Read two bytes from regAddressVoltage and convert to Voltage
  int LTC2943VoltageRaw = readReg16(regAddressVoltage);
  float LTC2943Voltage = 23.600 * LTC2943VoltageRaw / 65535.000;
  
  // Read two bytes from regAddressTemp and convert to Celsius
  unsigned int LTC2943TempRaw = readReg16(regAddressTemp);
  float LTC2943Temp = (510.00 * LTC2943TempRaw / 65535.00) - 273.15;
  
  // Read two bytes from regAddressCurrrent and convert to A
  word LTC2943CurrentRaw = readReg16(regAddressCurrent);
  float LTC2943Current = 0.060 / senseResistor * ( ( LTC2943CurrentRaw - 32767.000 ) / 32767.000 );
  
  // lcd.print received values
  lcd.setCursor(0,0);
  lcd.print("Qbat: ");
  lcd.print(LTC2943AccuCharge, 1);
  lcd.print("mAh  ");
  
  lcd.setCursor(0,1);
  lcd.print("Vbat: ");
  lcd.print(LTC2943Voltage, 3);
  lcd.print("V  ");
  
  lcd.setCursor(0,2);
  lcd.print("Temp: ");
  lcd.print(LTC2943Temp, 1);
  lcd.print("C  ");
  
  lcd.setCursor(0,3);
  lcd.print("Ibat: ");
  lcd.print(LTC2943Current, 3);
  lcd.print("A  ");
  
  // Wait some...
  delay(500);
  
}

byte readReg8(int regAddress)
{
  byte data = 0;

  // Point to status register regAddress and receive one byte of data
  I2c.read(LTC2943Address,regAddress,1);
  data = I2c.receive();
  
  return(data);
}

word readReg16(int regAddress)
{
  byte dataH, dataL;
  word data = 0;

  // Point to status register regAddress and receive two bytes of data
  I2c.read(LTC2943Address,regAddress,2);
  dataH = I2c.receive();
  dataL = I2c.receive();  
  // Combine MSB and LSB to word
  data = word(dataH, dataL);

  return(data);
}