Trying to simulate a SMBus from an Smart Battery accu pack with the Arduino Uno

As written in the subject I’m trying to simulate the communication of an Smart Battery accu pack which uses the SMBus. I need this for testing the software of a technical device which runs with told accu pack as power source. As we just want to test the software, not the hardware, we’re trying to simulate as much as possible, including the communication over the SMBus.
The master device requests every second 38 different values from the accu pack, so we set up the Arduino as slave device which answers the requests with the appropriate data. Or at least it should. The Arduino gets the command, which data should be transfered and (after finally finding the problem) also answers the read-requests, but with the wrong data. It always sends 0x01 as lowByte and 0xFF as high/all other Bytes.
I already tried sending the data in various different ways, simply writing it with “Wire.write(variable)”, separating high and low Byte with the according Functions and sending these and there are also some Strings to be send, neither of them worked yet. I could understand it, if the Hex-Integers just have a wrong format or can’t be transmitted that way, but why does the transmitting of the Strings fail as well?
I hope you can help me with this problem, I don’t really know what to do now.

Following the full programm code. It’s quite long because we have such many variables and a bit messed up from the tests with different transmitting methods.
I removed most of the comments, because it exceeded the maximum message length, just if you’re wondering. I attached the full programm to this post, if you want to take a look at the comments.

 #include <Wire.h>

// The Following variables are write only by the Serial Interface 
// and read Only for the I2C Master all other Values for the Smart battery are constants

unsigned int Temperature            = 2980;
unsigned int Voltage                = 11000;
  signed int Current                = -300;
unsigned int RelativeStateOfCharge  = 100;
unsigned int AbsoluteStateOfCharge  = 100;
unsigned int RemainingCapacity      = 2700;
unsigned int BatteryStatus          = 0x00C0;
unsigned int RunTimeToEmpty         = 700;
unsigned int AverageTimeToEmpty     = 700;


// The following constants are Smart Battery Values which are constant but still be requested
const unsigned int ManufactureAccess      = 0x010A;
const unsigned int RemainingCapacityAlarm = 0x0000;
const unsigned int RemainingTimeToAlarm   = 0x0000;
const unsigned int BatteryMode            = 0x6001;
const signed int   AtRate                 = 0x0000;
const unsigned int AtRateTimeToFull       = 0xFFFF;
const unsigned int AtRateTimeToEmpty      = 0xFFFF;
const boolean      AtRateOK               = 0x0001;

const signed int   AverageCurrent         = -268;
const unsigned int MaxError               = 0x0001;
const unsigned int FullChargeCapacity     = 3265;


const unsigned int AverageTimeToFull      = 0xFFFF;
const unsigned int CycleCount             = 0x00C0;
const unsigned int DesignCapacity         = 3350;
const unsigned int DesignVoltage          = 10800;
const unsigned int SpecificationInfo      = 0x31;
const unsigned int ManufactureDate        = 0x42C5;
const unsigned int SerialNumber           = 0x0002;
const char         ManufacturerName[]     = "DREAGER";
const char         DeviceName[]           = "IP1-3S1p-000000022";
const char         DeviceChemistry[]      = "LI0N";
const char         ManufacturerData[]     = "0x0E0001000100010001000C000B0000";

//more constants
const unsigned int ChargingCurrent        = 0x0658;
const unsigned int ChargingVoltage        = 0x3138;

const char         OptionalFunction[]       = "0x140000000000000000000000000000000000000000";

const unsigned int VoltageCell1           = 0;
const unsigned int VoltageCell2           = 3500;
const unsigned int VoltageCell3           = 3500;
const unsigned int VoltageCell4           = 3500;

const char         ErrorMessage[]         = "something";

char adress = ' ';
String value = "";
boolean stringComplete = false;
boolean request = false;
int command = -1;

void setup() {
  Wire.begin(11);
  Wire.onReceive(receiveEvent);nt
  Wire.onRequest(requestEvent);
  Serial.begin(9600);
  value.reserve(20);
}

void loop() {  
  if (stringComplete) {
    switch(adress) {
       case 'a': Temperature = value.toInt(); break;
       case 'b': Voltage = value.toInt(); break;
       case 'c': Current = value.toInt(); break;
       case 'd': RelativeStateOfCharge = value.toInt(); break;
       case 'e': AbsoluteStateOfCharge = value.toInt(); break;
       case 'f': RemainingCapacity = value.toInt(); break;
       case 'g': BatteryStatus = value.toInt(); break;
       case 'h': RunTimeToEmpty = value.toInt(); break;
       case 'i': AverageTimeToEmpty = value.toInt(); break;
       default: break;
    }
    adress = ' ';
    value = "";
    stringComplete = false;
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    if (adress == ' ') {
      adress = inChar;
    } else {
      value += inChar;
    }
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

void receiveEvent(int numOfBytes)
{
    command = Wire.read();
}

void requestEvent()
{
    switch(command){
      case 0:
        Wire.write(lowByte(ManufactureAccess)); 
        Wire.write(highByte(ManufactureAccess));
        break;
      case 1: Wire.write(RemainingCapacityAlarm); break;
      case 2: Wire.write(RemainingTimeToAlarm); break;
      case 3: Wire.write(BatteryMode); break;
      case 4: Wire.write(AtRate); break;
      case 5: Wire.write(AtRateTimeToFull); break;
      case 6: Wire.write(AtRateTimeToEmpty); break;
      case 7: Wire.write(AtRateOK); break;
      case 8: Wire.write(Temperature); break;
      case 9: Wire.write(Voltage); break;
      case 0x0a: Wire.write(Current); break;
      case 0x0b: Wire.write(AverageCurrent); break;
      case 0x0c: Wire.write(MaxError); break;
      case 0x0d: Wire.write(RelativeStateOfCharge); break;
      case 0x0e: Wire.write(AbsoluteStateOfCharge); break;
      case 0x0f: Wire.write(RemainingCapacity); break;   
      case 0x10: Wire.write(FullChargeCapacity); break;
      case 0x11: Wire.write(RunTimeToEmpty); break;
      case 0x12: Wire.write(AverageTimeToEmpty); break;
      case 0x13: Wire.write(AverageTimeToFull); break;
      case 0x14: Wire.write(ChargingCurrent); break;
      case 0x15: Wire.write(ChargingVoltage); break;
      case 0x16: Wire.write(BatteryStatus); break;
      case 0x17: Wire.write(CycleCount); break;
      case 0x18: Wire.write(DesignCapacity); break;
      case 0x19: Wire.write(DesignVoltage); break;
      case 0x1a: Wire.write(SpecificationInfo); break;
      case 0x1b: Wire.write(ManufactureDate); break;
      case 0x1c: Wire.write(SerialNumber); break;
      case 0x1d: Wire.write("TBD"); break;
      case 0x1e: Wire.write("TBD"); break;
      case 0x1f: Wire.write("TBD"); break;     
      case 0x20: Wire.write(ManufacturerName); break;
      case 0x21: Wire.write(DeviceName); break;
      case 0x22: Wire.write(DeviceChemistry); break;
      case 0x23: Wire.write(ManufacturerData); break;   
      case 0x3c: Wire.write(VoltageCell1); break;
      case 0x3d: Wire.write(VoltageCell2); break;
      case 0x3e: Wire.write(VoltageCell3); break;
      case 0x3f: Wire.write(VoltageCell4); break;    
      //default: Wire.write(ErrorMessage); break;
    }
}

Arduino_changeValue.ino (7.94 KB)

The first piece of advice that you will get from most experienced members of this forum is to stop using Strings and use C style strings instead. What Arduino board are you using ?

What do you see if you print the String and adress when stringComplete is true ?

Actually, the code does not verify because of this line  Wire.onReceive(receiveEvent);ntbut I assume that is as artefact of how you copied the code to the post.

I'm already using C style strings for all string variables but "value", which isn't even used yet. The complete Serial/stringComplete-Part wasn't tested yet, that's just for being able to change some of the values. At the moment we're just using the default values and totally ignoring any serial communication. We're only using the I²C-Bus to simulate that SMBus and USB for monitoring.

We have two Arduion Uno boards here, as mentioned in the subject.

That 'nt' is an artefact of the deleted comments, as you assumed.

I think I found the main problem, but I can't figure out how to fix it.

We did a little change in the twi.cpp to change the way the Wire library reacts to the commands of the master. We deleted line 468 with the call to twi_stop(), because the master sends a write without the stop bit and about a half millisecond later the read. The Arduino Uno didn't react on the read because it did wait for the stop bit for the write command, which never really came. Instead it took the stop bit after the read command as stop bit for the write command and ignored the read command. But now the Arduino doesn't read the data of the write command correctly as it doesn't know where to stop.

Can anybody please explain me how I can change the behaviour of the Wire/twi library to react the way we need it?

@General_Victm this is a very old discussion, but it was very very useful to me, so i wish to thanks you for your work. based on this your initial sketch, i built my battery emulator, it is here: [SMBus] A smart battery emulator