www.tothpal.eu - Logo - Tóthpál István

Menu

  1. Tóthpál István - Home

  2. Weblinks

  3. Curriculum Vitae

  4. Photography

  5. Trips

  6. Fishing

  7. HW Programming

    • Arduino

    • AVR

    • I2C

    • PonyProg

    • SMBus

    • Serial

    • SPI

SMBus
HW Programming > 
Tóthpál István > 

 

SMBus - System Management Bus

The System Management Bus (SMBus) is simple two-wire bus works very similar like I2C communication. Usually used in computers, e.g. laptop computers to communicate with smart batteries use SMBus.

I won't give information "How to replace battery cells on my laptop battery", is it worth while? This page can help using smart battery on your own built system. I will use a BQ2040 IC to demonstrate the communication.

BQ2040 in Smart battery

The bq2040 Gas Gauge IC With SMBus Interface can monitor the battery state/capacity, so your system can warn you before losing power. It has charge control functions too. The bq2040 learns battery capacity meanwhile you fully charge and discharge it, so I think it really knows the battery condition and current capacity. It uses an external EEPROM to load and store settings.

Read more from BQ2040.

Schematic diagram of BQ2040 and its eeprom - Tóthpál István - www.tothpal.eu

As SMBus is derived from I2C, i2c-dev can handle, and it works similar, moreover in some conditions you can mix the different devices in the same line. In this case you have to solve e.g. different signal voltage levels.

I have connected BQ2040 to my CH341a USB-I2C adapter (SMBCLK to SCL, SMBDATA to SDA) and it recognized that I2C/SMBus device connected at 0x0B. The EEPROM behind the Gas Gauge IC wasn't shown, as it wasn't really connected, it is in a separate I2C Line.

As I developed my simple I2C thermostat, my plan to connect it to a smart battery, so the MCU can get informations from remaining capacity and can send alarm to change/charge battery. This would be more interesting on other systems, which ones use much more power.

Programming

Programming SMBus is similar to I2C. As bq2040 is supports SMBus 1.0 the main difference you would see that mostly the answers are words and not bytes. The commands based on SBData.

If you would like to communicate with SMBus 1.1 or later devices, you may need to deal with PEC (packet error checking) data. SMBus Quick Start Guide

Here is a not full example (only some function coded) of accessing BQ2040 through i2c-dev:

//
//      Test program for accessing BQ2040 Smart battery IC through i2c-dev + i2c-ch341a-usb in C++ 
//
//      Copyright (c)2017-2020 Tóthpál István, istvan@tothpal.eu
//

#include <linux/i2c-dev.h>
#include <stdio.h>

// -------------------------------- My classes -----------------------------
#include "include/i2c/bq2040.h"

int main (void) {
  
  __s32 r;
  __u8  result[50];
  char  string[256]; 
  
  // init i2c line4 at 0x0B address in my hardware
  bq2040 *mybq2040 = new bq2040( 4, BQ2040_BASE_ADDRESS );  

  if ( (r = mybq2040->get_BatteryStatus()) >= 0 ) {
    mybq2040->decode_BatteryStatus(r, string ); 
    printf("Status: %x - %s\n", r, string);
  }
  if ( (r = mybq2040->get_ManufacturerName( result )) >= 0 ) {
    printf("Manufacturer: %s\n", result );
  }
  if ( (r = mybq2040->get_DeviceName( result )) >= 0 ) {
    printf("Device: %s\n", result );
  }
  if ( (r = mybq2040->get_DesignCapacity()) >= 0 ) {
    printf("Design capacity: %dmAh  ", r );
    if ( (r = mybq2040->get_FullChargeCapacity()) >= 0 ) {
      printf("Full charge capacity: %dmAh  ", r );
    }
    if ( (r = mybq2040->get_RemainingCapacity()) >= 0 ) {
      printf("Remaining: %dmAh", r );
    }
    printf("\n");
  }
  if ( (r = mybq2040->get_DesignVoltage()) >= 0 ) {
    printf("Voltage: %dmV  ", r );
    if ( (r = mybq2040->get_Voltage()) >= 0 ) {
      printf("Currently: %dmV", r );
    }
    printf("\n");
  }
  if ( (r = mybq2040->get_Current()) >= 0 ) {
    printf("Current: %dmA\n", r );
  }
  if ( (r = mybq2040->get_Temperature()) >= 0 ) {
    printf("Temperature: %d.%d°K   %d.%d°C\n", r/10, r%10, (r-2732)/10, (r-2732)%10 ); // 0°K = -273.15°C
  }
  
  delete mybq2040;   
  return 0;
}

I've connected an old wrong battery and the test result:

Result of the test program - Tóthpál István - www.tothpal.eu

include/i2c/bq2040.h:

//
//      Class header file BQ2040 Smart battery IC through i2c-dev + i2c-ch341a-usb in C++  
//
//      Copyright (c)2017-2020 Tóthpál István, istvan@tothpal.eu
//

#define BQ2040_BASE_ADDRESS 0x0B

#define BQ2040_TEMPERATURE_REGISTER 0x08
#define BQ2040_VOLTAGE_REGISTER 0x09
#define BQ2040_CURRENT_REGISTER 0x0a

#define BQ2040_REMAININGCAPACITY_REGISTER 0x0f
#define BQ2040_FULLCHARGECAPACITY_REGISTER 0x10

#define BQ2040_BATTERYSTATUS_REGISTER 0x16
#define BQ2040_CYCLECOUNT_REGISTER 0x17
#define BQ2040_DESIGNCAPACITY_REGISTER 0x18
#define BQ2040_DESIGNVOLTAGE_REGISTER 0x19

#define BQ2040_MANUFACTURERNAME_REGISTER 0x20
#define BQ2040_MANUFACTURERNAME_MAXLEN 11
#define BQ2040_DEVICENAME_REGISTER 0x21
#define BQ2040_DEVICENAME_MAXLEN 7

class bq2040 {
 
  int i2c_line;
  int i2c_address;
  int myfile;

public:

  //constructor
  bq2040( int line, int address );
  // destructor
  ~bq2040();

  int get_i2c_line();
  int get_i2c_address();
 
  __s32 get_Temperature();
  __s32 get_Voltage();
  __s32 get_Current();
  __s32 get_RemainingCapacity();
  __s32 get_DesignCapacity();
  __s32 get_FullChargeCapacity();
  __s32 get_DesignVoltage();

  __s32 get_BatteryStatus();
  int decode_BatteryStatus( __s32 statuscode, char *statusstring );
  __s32 get_ManufacturerName( __u8 *result );
  __s32 get_DeviceName( __u8 *result );
};

bq2040.c:

//
//      Class file for BQ2040 Smart battery IC through i2c-dev + i2c-ch341a-usb in C++ 
//
//      Copyright (c)2017-2020 Tóthpál István, istvan@tothpal.eu
//

#include <i2c/smbus.h>
#include <linux/i2c-dev.h>

#include "../include/i2c/bq2040.h"

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>

/*
   ********************************************************************
   *                            BQ2040 CLASS                          *
   ******************************************************************** 
*/

// --------- Constructor ---------
bq2040::bq2040( int line, int address ) {
  __s32 opResult = 0; // for error checking of operations
  char line_str[20];
  
  i2c_line = line;
  i2c_address = address;

  sprintf(line_str, "/dev/i2c-%d", line);
  
  myfile = open( line_str, O_RDWR );
  
  opResult = ioctl( myfile, I2C_SLAVE_FORCE, address );
  if (opResult < 0) {
    printf("%mn", opResult );
    exit(EXIT_FAILURE); 
  } 
  else {
    read_registers();
  }
}

// ---------- Destructor ------------
bq2040::~bq2040() {

  close( myfile );
}

int bq2040::get_i2c_line() { 
  return i2c_line;
}

int bq2040::get_i2c_address() { 
  return i2c_address; 
}

__s32 bq2040::get_BatteryStatus() {
  return i2c_smbus_read_word_data( myfile, BQ2040_BATTERYSTATUS_REGISTER );
}

__s32 bq2040::get_Temperature() {
  return i2c_smbus_read_word_data( myfile, BQ2040_TEMPERATURE_REGISTER );
}

__s32 bq2040::get_Voltage() {
  return i2c_smbus_read_word_data( myfile, BQ2040_VOLTAGE_REGISTER );
}

__s32 bq2040::get_Current() {
  return i2c_smbus_read_word_data( myfile, BQ2040_CURRENT_REGISTER );
}

__s32 bq2040::get_RemainingCapacity() {
  return i2c_smbus_read_word_data( myfile, BQ2040_REMAINIGCAPACITY_REGISTER );
}

__s32 bq2040::get_FullChargeCapacity() {
  return i2c_smbus_read_word_data( myfile, BQ2040_FULLCHARGECAPACITY_REGISTER );
}

__s32 bq2040::get_DesignCapacity() {
  return i2c_smbus_read_word_data( myfile, BQ2040_DESIGNCAPACITY_REGISTER );
}

__s32 bq2040::get_DesignVoltage() {
  return i2c_smbus_read_word_data( myfile, BQ2040_DESIGNVOLTAGE_REGISTER );
}

// Get Manufacturer Name
__s32 bq2040::get_ManufacturerName( __u8 *result ){
  return i2c_smbus_blockread( myfile, i2c_address, BQ2040_MANUFACTURERNAME_REGISTER, result,
                              BQ2040_MANUFACTURERNAME_MAXLEN);
}

// Get Device Name
__s32 bq2040::get_DeviceName( __u8 *result ){
  return i2c_smbus_blockread( myfile, i2c_address, BQ2040_DEVICENAME_REGISTER, result, 
                              BQ2040_DEVICENAME_MAXLEN);
}

Accessing from ATTiny

After I have finished the code of my simple I2C thermostat, I could create it easily.

This example only asks the smart battery about the manufacturer and checks if it is "GTK" as a validation that the communication works well.

I show only main.c, you can realize that I used the same i2c read function I have written previously. If the communication success the green led will lit only, if on error the red led stays on. If only red led lit, means that you get data successfully, but not what it is expected.

//
//      Test program for accessing bq2040 through SMBus/i2c from ATTiny45 in C 
//
//      Copyright (c)2017-2020 Tóthpál István, istvan@tothpal.eu
//
// ======================================================================================
//                       ATtiny                    Smart battery
//                      25/45/85
//                     +--------+                          
//            RESET  --+ o  Vcc +------------         
//  Red   LED1 - PB3 --+        +-- SCK (PB2)      ---------> SMBC 
//  Green LED2 - PB4 --+        +-- NC (PB1)          
//         ------------+ GND    +-- SDA (PB0)      ---------> SMBD
//                     +--------+
// ======================================================================================

#ifndef __globals__   
  #include "globals.h"
#endif

#include <avr/io.h>

#ifndef __i2csmbus__   
  #include "include/i2csmbus.h"
#endif

int main(void) {
  
  DDRB &= 0b11100000;
  DDRB |= (1 << LED_PORT1); // set PortB to output
  DDRB |= (1 << LED_PORT2);

  PORTB &= 0b11100000;
  PORTB |= 1 << LED_PORT1;  // set Power Led ON
  _delay_ms(100);

  PORTB |= 1 << LED_PORT2;  // set Status led ON
  
  // do communication
  uint8_t buffer[48];
  int bq2040a = 0x0b; // address at SMBus
  int bq2040c = 0x20; // command (get Manufacturer)
  int error = 0;
  
  error |= i2c_getline( 0 );
  error |= i2c_blockread( bq2040a, bq2040c, buffer, 4 );
  // have to return "GTK", this is in my battery
  if ( (buffer[0]!=3) | (buffer[1]!=0x47) | (buffer[2]!=0x54) | (buffer[3]!=0x4B) ) { 
    PORTB &= (0b00011111 ^ ( 1 << LED_PORT2) ); // Incorrect DATA received
    error = -1;
 }
  error |= i2c_leaseline(); 

  _delay_ms(100);
        
  if (error == 0) { // Turn RED led off, everything is OK
    PORTB &= (0b00011111 ^ ( 1 << LED_PORT1) ); // set Power Led OFF - Process finished, Status led stay ON
    _delay_ms(100);
  }

  return 0;
}

MLX90614 Digital Non-Contact Infrared Thermometer

MLX90614 Digital Non-Contact Infrared Thermometer - Tóthpál István - www.tothpal.eu

This is an SMBus 1.1 device from Melexis. My version is MLX90614-BAA means that it works on ~3V and the accuracy of measurement is +/-0.5°C. If you need more accuracy you should choose different version. (e.g. DCI - medical version)

You can directly connect to CH341A I2C pins, - don't forget to set to 3.3V, - and can detect with i2cdetect as I've shown at I2C.

 

The example C++ code and the result:

//
//      Test program for accessing MLX90614 Thermometer through i2c-dev + i2c-ch341a-usb in C++ 
//
//      Copyright (c)2020 Tóthpál István, istvan@tothpal.eu
//

//    3.3V !!!!!!!!!

#include <linux/i2c-dev.h>
#include <stdio.h>

// -------------------------------- My classes -----------------------------
#include "include/i2c/mlx90614.h"

float temp_to_Kelvin( __s32 tempdata ){
  float kelvin = tempdata*0.02;
  return kelvin;
}

float temp_to_Celsius( __s32 tempdata ){
  float celsius = temp_to_Kelvin(tempdata) - 273.15; // 0°K = -273.15°C;
  return celsius;
}

int main (void) {
  
  __s32 r;
  
  // init i2c line4 at 0x5a address in my hardware
  mlx90614 *mythermometer = new mlx90614( 4, MLX90614_BASE_ADDRESS );

  if ( (r = mythermometer->get_Ambient_Temperature()) > 0 ) {
    printf("Ambient Temperature: %.2f°K   %.2f°Cn", temp_to_Kelvin(r), temp_to_Celsius(r) ); 
  }
  if ( (r = mythermometer->get_TObject1_Temperature()) > 0 ) {
    printf("TObject1 Temperature: %.2f°K   %.2f°Cn", temp_to_Kelvin(r), temp_to_Celsius(r) ); 
  }
  if ( (r = mythermometer->get_TObject2_Temperature()) > 0 ) {
    printf("TObject2 Temperature: %.2f°K   %.2f°Cn", temp_to_Kelvin(r), temp_to_Celsius(r) ); 
  }
  
  delete mythermometer;    
  return 0;
}
//
//      Class header file MLX90614 Thermometer through i2c-dev + i2c-ch341a-usb in C++  
//
//      Copyright (c)2020 Tóthpál István, istvan@tothpal.eu
//

#define MLX90614_BASE_ADDRESS 0x5A

// Commands:
//
// 000x xxxx -  RAM access /Read Only/
// 001x xxxx -  EEPROM access ( to modify first have to clear with writing 0x00 )
// 1111 0000 -  Read flags
// 1111 1111 -  Enter Sleep mode

#define MLX90614_REG_IR1_RAW_DATA   0x04
#define MLX90614_REG_IR2_RAW_DATA   0x05
#define MLX90614_REG_TA_DATA        0x06
#define MLX90614_REG_TObj1_DATA     0x07
#define MLX90614_REG_TObj2_DATA     0x08

#define MLX90614_REG_TOmax          0x20
#define MLX90614_REG_TOmin          0x21
#define MLX90614_REG_PWMCTRL        0x22
#define MLX90614_REG_TArange        0x23
#define MLX90614_REG_Emissivity     0x24
#define MLX90614_REG_Config1        0x25
#define MLX90614_REG_SMBus_Address  0x2E

#define MLX90614_REG_FLAGS          0xF0
#define MLX90614_REG_ENTER_SLEEP    0xFF

class mlx90614 {
 
  int i2c_line;
  int i2c_address;
  int myfile;

public:

  //constructor
  mlx90614( int line, int address );
  // destructor
  ~mlx90614();

  int get_i2c_line();
  int get_i2c_address();

  __s32 get_Ambient_Temperature();
  __s32 get_TObject1_Temperature();
  __s32 get_TObject2_Temperature(); // in DUAL zone sensores
    
  ...

};
//
//      Class file for MLX90614 Thermometer through i2c-dev + i2c-ch341a-usb in C++ 
//
//      Copyright (c)2020 Tóthpál István, istvan@tothpal.eu
//

#include <linux/i2c-dev.h>
#include <i2c/smbus.h>

#include "../include/i2c/mlx90614.h"

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>

/*
   ********************************************************************
   *                          MLX90614 CLASS                          *
   ******************************************************************** 
*/

// --------- Constructor ---------
mlx90614::mlx90614( int line, int address ) {
  __s32 opResult = 0; // for error checking of operations
  char line_str[20];
  
  i2c_line = line;
  i2c_address = address;

  sprintf(line_str, "/dev/i2c-%d", line);
  
  myfile = open( line_str, O_RDWR );
  
  opResult = ioctl( myfile, I2C_SLAVE_FORCE, address );
  if (opResult < 0) {
    printf("%mn", opResult );
    exit(EXIT_FAILURE); 
  } 
  else {
    ioctl(myfile, I2C_PEC, 1);  // Enable PEC generation
  }
}

// ---------- Destructor ------------
mlx90614::~mlx90614() {
  close( myfile );
}

int mlx90614::get_i2c_line() { 
  return i2c_line;
}

int mlx90614::get_i2c_address() { 
  return i2c_address; 
}
  
__s32 mlx90614::get_Ambient_Temperature() {
  return i2c_smbus_read_word_data( myfile, MLX90614_REG_TA_DATA );
}
__s32 mlx90614::get_TObject1_Temperature() {
  return i2c_smbus_read_word_data( myfile, MLX90614_REG_TObj1_DATA );
}
__s32 mlx90614::get_TObject2_Temperature() {
  return i2c_smbus_read_word_data( myfile, MLX90614_REG_TObj2_DATA );
}

MLX90614 example code result - Tóthpál István - www.tothpal.eu

Different surfaces has different emissivity, but the sensor cannot recognise them, You can define. Default setting is 100% and it is near good for the human skin ~95-98%, but not for all surfaces. For better results you should set the correct emissivity parameter.

Terms and Privacy
Copyright (c)2008-2020 Tóthpál István