Arduino LTC6804 BMS - Part 1: Main Board

Introduction: Arduino LTC6804 BMS - Part 1: Main Board

Part 2 is here

Part 3 is here

Overview

The LTC6804-2 is a battery monitor IC which can monitor up to 12 series connected batteries. It has five general purpose IO pins which can be used to measure sensor values ( e.g., battery temperatures) or control external relays. It also has 12 balancing control outputs for passive battery pack balancing. The LTC6804-2 can be controlled, and data registers can be read, through an 4-wire SPI interface.

The LTC6804-2 can fairly easily interface with an Adruino microcontroller board such as an Arduino Uno. I chose a stacked system with the LTC6804 PCB deisgned as an Arduino shield. The complete system comprises an Arduino Uno, a custom designed LTC6804 BMS board, a balance board and optionally an Xbee wireless mesh network shield to communicate with a monitoring station. The balance board and Xbee system will be explained in a separate Instructable.

Step 1: Theory of Operation

The BMS is configured for a battery pack are composed of A123 LiFePO4 cylindrical batteries in 12S8P configuration. The A123 cells have the following characteristics:

Nominal capacity 2.3Ah Min discharge voltage: 2.0 V Max charge voltage: 3.8V Max continuous charge current: 10A Max continuous discharge current: 60A Max recommended temperature 70C The function of the BMS is to maintain the pack within the above specifications. When one or more of the 8P sections of the battery pack are found to be out of range, the BMS deactivates the appropriate relay to isolate the pack. The charge relay disconnects the pack from the charger when an over-voltage or over-current state is detected. The discharge relay disconnects the pack from the load when an under-charge state or load over-current is detected. In addition, both relays are deactivated when an over-temperature state is detected. Care should be taken when a relay is deactivated since the BMS may reactivate the relay when the fault state is eliminated, supplying power to the load. Always assume the battery pack is live and disconnect manually before performing work on the electrical system.

Step 2: BMS Circuit

The BMS circuitboard was designed using Eagle CAD. The PCB is designed
to plug in to an Arduino Uno, which is programmed to control the LTC6804 through the 4-wire SPI interface at pins 41-44. The arduino reads the data collected by the LTC6804 such as cell voltages, current and cell temperatures and also controls peripheral circuitry such as relays. The schematic is provided in Figure 1. R-C filters are provided for each cell monitoring input. Analog inputs A2 - A5 are connected to the corresponding Arduino analog input pins, with +5 and ground also routed to each connector. A0 connects to a LEM hall effect sensor which outputs approximately Vin/2 at zero current where Vin is nominally 12 volts. A separate voltage divider connects to A1 which monitors the true Vin, which may vary with the state of charge of the external 12V supply. The relay drivers are controlled by the Arduino's digital pins 8 and 9. You can view the schematic in higher resolution here: http://caditz.us/images/electronics/BMS/BMS8d.png or you can download and extract the zip file, which contains a higher resolution schematic and a parts list (BOM).

Step 3: PCB Layout and Routing

The PCB is a 2-layer design using surface mount components. Most discreet components use 0805 packages since smaller packages are difficult to solder by hand. The LTC6804 itself is provided as a 48 pin SSOP with 0.5mm lead spacing which can be challenging to solder. The bottom layer is specified as a ground plane to minimize noise. Eagle CAD did a fairly good job of trace routing after the components were placed in reasonable locations with enough space between. You may contact me to obtain a PCB (I may do another PCB run if enough people are interested.) Also, Gerber files are provided here if you want to make your own PCBs. There are many free and paid tools to view the Gerber files. I use the free version of ViewMate by Pentalogix (https://www.pentalogix.com/t/software-products/viewmate). These Gerber files can be sent to a PCB manufacturer.

Step 4: PCB Assembly

Prototype BMS PCBs were soldered by hand using a Weller WESD51 temperature controlled soldering station with an ETB ET series 0.093 "screwdriver" tip. Although smaller tips may seem better for intricate work, they do not retain heat and make the job more difficult. Use a flux pen to clean the PCB pads before soldering. 0.3 mm solder works well for hand soldering SMD parts. It works well to place a bit of solder on one pad, place the device and tack down that pad. The remaining pads can then be soldered without the part moving. A different technique is needed for the LTC6804 SSOP package which has quite narrow pin spacing. One method uses large amount of solder which is allowed to bridge the pins. The excess is then removed with high quality solder wick. For the second method, pre-apply a small amount of solder on each pad. The IC is then placed and tacked down pin by pin. It is good practice to place the most difficult component first so that if it fails, the remaining components are not wasted. Work can be done under a magnifying glass. Taking a photo with your phone and zooming in is a good way to inspect for cold joints or bridging. A hot air rework station is also useful to remove surface mount components when all else fails.

Step 5: Connections

The BMS board is designed to be connected to an Arduino UNO as a standard shield. It draws power directly from the Arduino, which should be plugged in to a 12V supply. The BMS board has input connectors for two temperature sensors and a LEM Hall effect current sensor. The board uses TE/APM 5-103634-2 three position right angle header connectors for these sensor inputs. The sensor cables should mate to these headers with the pin definitions (gnd, +5v and signal) indicated on the circuitboard. There are optional slots on the circuitboard for two additional temperature sensor inputs and two general purpose input/outputs (GPIO). Temperature sensors are MPC9700 3-pin Thermistor ICs or similar: Datasheet. The arduino code is calibrated for a 50 amp LEM HTB 50-P/SP5 current sensor. Other current sensors can be used, but the code may need modification if the sensor has a different nominal current rating. There are two relay control outputs which use TE/APM 103635-1 two position headers. One relay header is for the charge relay and the other is for the discharge relay. These can be repurposed for other uses in the arduino code. The board supplies power to the relay coils with a maximum rated current of 9 amps, but this may be limited by your arduino power supply. Note: powering the Arduino from USB cable will probably not provide enough current to keep the relay coils energized. Battery sense wires plug into the top row of the 13 pin, 0.1 pin spacing header. Respect the indicated polarity when connecting the battery sense wires. Reversing the polarity may cause permanent damage to the BMS board. Gerber files are provided below if you want to make your own PCBs.

Step 6: Arduino Code

The Arduino code for the BMS is provided below. You may have to tweak it a bit to deal with updates to external
libraries since this project was published. There are also several optional features such as code for cell balancing or XBee communications that you may want to comment or uncomment based on your implementation.

The libraries for interacting with the LTC6804 are specified in the header. These can be found on the Linear Technology website:

https://www.analog.com/en/search.html?q=LTSketchbo...

Download and extract LTSketchbook.zip to find the listed libraries.

The code poles the LTC6804 every 5 seconds and reads the battery cell voltages, pack temperature sensors and total current. If any values are out of the pre-defined safe range, the arduino opens a relay to disconnect the battery pack. The code also writes cell voltages, module temperatures, current and relay states to both to the Serial monitor and to an XBee wireless mesh network (Discussed in a separate Instructable).

#include 
#include 
#include 
//#include 
#include "Linduino.h"
#include "LT_SPI.h"
#include "UserInterface.h"
#include "LTC68042.h"
#include "Average.h"


#define TOTAL_IC  1            // Number of ICs in the isoSPI network LTC6804-2 ICs must be addressed in ascending order starting at 0.

/***** Pack and sensor characteristics *****/
const float MAX_CURRENT = 5000000000.;          // Maximum battery current(amps) before relay opens
const float MAX_TEMP  = 50. ;                   // Maximum pack temperature (deg C) before relay opens
const float LEM_RANGE =  50.;                   // Rated range of LEM hall sensor.
const float MIN_CELL_V = 2.20;                  // Minimum allowable cell voltage. Depends on battery chemistry.
const float MAX_CELL_V = 3.60;                  // Maximum allowable cell voltage. Depends on battery chemistry.
const float CELL_BALANCE_THRESHOLD_V = 3.3;     // Cell balancing occurrs when voltage is above this value

/***** Xbee serial *****/
#define xbRxD 4
#define xbTxD 5
SoftwareSerial XbeeSerial(xbRxD, xbTxD);

/******** Arduino pin definitions ********/
int chargeRelayPin = 8;               // Relay output for overcharge conditions
int dischargeRelayPin = 9;            // Relay output for undercharge conditions
int currentPin = A0;                  // LEM Input should be Vcc/2 + I * 1.667 / LEM_RANGE
int currentBiasPin = A1;              // For comparing with LEM output (currentPin) since Vcc/2 may change as aux battery discharges.
int tempPins[] = {A2};                // Array of arduino pins used for temperature sensor inpout


/********  Variables for tracking cell voltages and states ***************/
int overCharge_state = LOW;           // Over charge state. HIGH = relay on, LOW = relay off
int underCharge_state = LOW;          // Under charge state. HIGH = relay on, LOW = relay off
int overTemp_state = LOW;             // Over temperature state. HIGH = relay on, LOW = relay off
int overCurrent_state = LOW;          // Over current state. HIGH = relay on, LOW = relay off
int chargeRelay_state;
int dischargeRelay_state;
int cellMax_i;                        // Temporary variable for holding index of cell with max voltage
int cellMin_i;                        // Temporary variable for holding index of cell with min voltage
float  cellMin_V;                     // Temporary variable for holding  min measured cell voltage
float  cellMax_V;                     // Temporary variable for holding  max measured cell voltage
float minV1 ;
float maxV1 ;


/********   Current and temperature variables ***********************/
const uint16_t imax = 100;                 // Size of arrays for averaging read measurements
Average lemHistory(imax);
Average lemBiasHistory(imax);
float lem = 0;
float lemBias = 0;
float lemZeroCal = 0;
float current = 0;
float temp[sizeof(tempPins)];

int error = 0;
unsigned long  tstart;


/******************************************************
  Global Battery Variables received from 6804 commands
  These variables store the results from the LTC6804
  register reads and the array lengths must be based
  on the number of ICs on the stack
 ******************************************************/


uint16_t cell_codes[TOTAL_IC][12];
/*!<
  The cell codes will be stored in the cell_codes[][12] array in the following format:

  |  cell_codes[0][0]| cell_codes[0][1] |  cell_codes[0][2]|    .....     |  cell_codes[0][11]|  cell_codes[1][0] | cell_codes[1][1]|  .....   |
  |------------------|------------------|------------------|--------------|-------------------|-------------------|-----------------|----------|
  |IC1 Cell 1        |IC1 Cell 2        |IC1 Cell 3        |    .....     |  IC1 Cell 12      |IC2 Cell 1         |IC2 Cell 2       | .....    |
****/

uint16_t aux_codes[TOTAL_IC][6];
/*!<
  The GPIO codes will be stored in the aux_codes[][6] array in the following format:

  |  aux_codes[0][0]| aux_codes[0][1] |  aux_codes[0][2]|  aux_codes[0][3]|  aux_codes[0][4]|  aux_codes[0][5]| aux_codes[1][0] |aux_codes[1][1]|  .....    |
  |-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|---------------|-----------|
  |IC1 GPIO1        |IC1 GPIO2        |IC1 GPIO3        |IC1 GPIO4        |IC1 GPIO5        |IC1 Vref2        |IC2 GPIO1        |IC2 GPIO2      |  .....    |
*/

uint8_t tx_cfg[TOTAL_IC][6];
/*!<
  The tx_cfg[][6] stores the LTC6804 configuration data that is going to be written
  to the LTC6804 ICs on the daisy chain. The LTC6804 configuration data that will be
  written should be stored in blocks of 6 bytes. The array should have the following format:

  |  tx_cfg[0][0]| tx_cfg[0][1] |  tx_cfg[0][2]|  tx_cfg[0][3]|  tx_cfg[0][4]|  tx_cfg[0][5]| tx_cfg[1][0] |  tx_cfg[1][1]|  tx_cfg[1][2]|  .....    |
  |--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|-----------|
  |IC1 CFGR0     |IC1 CFGR1     |IC1 CFGR2     |IC1 CFGR3     |IC1 CFGR4     |IC1 CFGR5     |IC2 CFGR0     |IC2 CFGR1     | IC2 CFGR2    |  .....    |

*/

uint8_t rx_cfg[TOTAL_IC][8];
/*!<
  the rx_cfg[][8] array stores the data that is read back from a LTC6804-1 daisy chain.
  The configuration data for each IC  is stored in blocks of 8 bytes. Below is an table illustrating the array organization:

  |rx_config[0][0]|rx_config[0][1]|rx_config[0][2]|rx_config[0][3]|rx_config[0][4]|rx_config[0][5]|rx_config[0][6]  |rx_config[0][7] |rx_config[1][0]|rx_config[1][1]|  .....    |
  |---------------|---------------|---------------|---------------|---------------|---------------|-----------------|----------------|---------------|---------------|-----------|
  |IC1 CFGR0      |IC1 CFGR1      |IC1 CFGR2      |IC1 CFGR3      |IC1 CFGR4      |IC1 CFGR5      |IC1 PEC High     |IC1 PEC Low     |IC2 CFGR0      |IC2 CFGR1      |  .....    |
*/



/*!**********************************************************************
  \brief  Inititializes hardware and variables
 ***********************************************************************/
void setup()
{
  pinMode(chargeRelayPin, OUTPUT);
  pinMode(dischargeRelayPin, OUTPUT);
  pinMode(currentPin, INPUT);
  pinMode(currentBiasPin, INPUT);
  for (int i = 0; i < sizeof(tempPins) / sizeof(int); i++)
  {
    pinMode(tempPins[i], INPUT);
  }
  digitalWrite(dischargeRelayPin, LOW); // turn off relays during setup
  digitalWrite(chargeRelayPin, LOW);    // turn off relays during setup

  overCharge_state = HIGH;
  underCharge_state = HIGH;

  Serial.begin(9600);
  XbeeSerial.begin(9600);

  LTC6804_initialize();  //Initialize LTC6804 hardware
  init_cfg();            //initialize the 6804 configuration array to be written
  delay(1000);
  lemZeroCal = zeroCurrentCalibrate();   // Calibrates LEM sensor at zero current
  overCurrent_state = HIGH;
  tstart = millis();
}

/*!*********************************************************************
  \brief main loop
***********************************************************************/
void loop()
{


  //  while (overCurrent_state == LOW) {
  //    Serial.println("RESET TO CONTINUE");
  //    delay(10000);
  //  }

  // read current:
  //overCurrent_state = HIGH;
  current = readCurrent();


  // read temperatures:
  overTemp_state = HIGH;
  for (int i = 0; i < sizeof(tempPins) / sizeof(int); i++)
  {
    temp[i] = (analogRead(tempPins[i]) * 5. / 1024 - 0.5) / 0.01;
    if (temp[i] > MAX_TEMP) {
      overTemp_state = LOW;
      Serial.println("OVER TEMPERATURE STATE DETECTED.");
    }
  }

  // read cells:
  wakeup_idle();
  LTC6804_adcv(); // do cell AD conversion and fill cell registers
  delay(10);
  wakeup_idle();
  error = LTC6804_rdcv(0, TOTAL_IC, cell_codes); // read cell voltages from registers
  if (error == -1)
  {
    Serial.println("A PEC error was detected in the received data");
  }

  // print to serial outputs:
  print_cells();

  // test for over charge/undercharge states:
  minV1 = MIN_CELL_V;
  maxV1 = MAX_CELL_V;

  if (overCharge_state == LOW) { // add hysteresis
    maxV1 = maxV1 - .2;
  }

  if (underCharge_state == LOW) { // add hysteresis
    minV1 = minV1 + .2;
  }

  // get maximum and minimum cells:
  cellMax_i = -1;
  cellMin_i = -1;
  cellMin_V = 100.;
  cellMax_V = 0.;
  for (int i = 0; i < 12; i++)
  {
    float V = cell_codes[0][i] * 0.0001;
    if (V < cellMin_V) {
      cellMin_V = V;
      cellMin_i = i;
    }
    if (V > cellMax_V) {
      cellMax_V = V;
      cellMax_i = i;
    }
  }

  underCharge_state = HIGH;
  overCharge_state = HIGH;
  overCurrent_state = HIGH;

  if (cellMin_V <= minV1)
  {
    underCharge_state = LOW;
    // Serial.println("V <= MIN_CELL_V");
  }
  if (cellMax_V >= maxV1)
  {
    overCharge_state = LOW;
    //Serial.println("V >= MAX_CELL_V");
  }
  if (abs(current) > MAX_CURRENT) {
    overCurrent_state = LOW;
  }
  // set relay states:

  chargeRelay_state = overCurrent_state &&  underCharge_state && overCharge_state &&  overTemp_state ;
  dischargeRelay_state =  overCurrent_state &&  overCharge_state && underCharge_state &&  overTemp_state;
  digitalWrite(chargeRelayPin, chargeRelay_state  );
  digitalWrite(dischargeRelayPin, dischargeRelay_state);

  //while (chargeRelay_state == LOW || dischargeRelay_state== LOW ) {
  //  Serial.println("RESET TO CONTINUE");
  //  delay(10000);
  //}

  //  if (abs(current) > MAX_CURRENT) {
  //    overCurrent_state = LOW;
  //    digitalWrite(chargeRelayPin,  overCurrent_state  );
  //    digitalWrite(dischargeRelayPin,  overCurrent_state );
  //    Serial.println("OVER CURRENT STATE DETECTED. PRESS RESET TO CONTINUE");
  //    delay(10000);
  //  } else {
  //    overCurrent_state = HIGH;
  //    //    chargeRelay_state = overCharge_state &&  overTemp_state ;
  //    //    dischargeRelay_state =  underCharge_state &&  overTemp_state;
  //    chargeRelay_state = underCharge_state && overCharge_state &&  overTemp_state ;
  //    dischargeRelay_state =  overCharge_state && underCharge_state &&  overTemp_state;
  //    digitalWrite(chargeRelayPin, chargeRelay_state  );
  //    digitalWrite(dischargeRelayPin, dischargeRelay_state);
  //  }

  //
  //  if (underCharge_state == LOW ) {
  //    Serial.println("UNDER VOLTAGE STATE DETECTED.");
  //    digitalWrite(dischargeRelayPin, dischargeRelay_state);
  //  }
  //
  //  if (overCharge_state == LOW ) {
  //    Serial.println("OVER VOLTAGE STATE DETECTED.");
  //    digitalWrite(chargeRelayPin, chargeRelay_state  );
  //  }


  // take advantage of open relay to recalibrate LEM zero current setting:
  if (underCharge_state == LOW or overCharge_state == LOW ) {
    lemZeroCal = zeroCurrentCalibrate();
  }


  //  cell balancing:
  //  Turn on switch Sx for highest cell x if voltage is above threshold
  //  Note: DCP is set to 0 in initialize() This turns off discharge when cell voltages are read.
  // set values in tx_cfg


  //cellMax_i = 5;
  if (cellMax_V >= CELL_BALANCE_THRESHOLD_V)
  {
    balance_cfg(0, cellMax_i);
    //Serial.print("Balance ");
    //Serial.println(cellMax_i);
  } else {
    balance_cfg(0, -1);
  }

  // write tx_cfg to LTC6804. This sets the LTC6804 DCCx registers which control the S pins for balancing:
  LTC6804_wrcfg( TOTAL_IC, tx_cfg);

  delay(5000);

}



/*!***********************************
  \brief Initializes the configuration array
 **************************************/
void init_cfg()
{
  for (int i = 0; i < TOTAL_IC; i++)
  {
    tx_cfg[i][0] = 0xFE;
    //tx_cfg[i][1] = 0x04 ;
    tx_cfg[i][1] = 0x4E1 ; // 2.0V
    //tx_cfg[i][2] = 0xE1 ;
    tx_cfg[i][2] = 0x8CA; // 3.6V
    tx_cfg[i][3] = 0x00 ;
    tx_cfg[i][4] = 0x00 ; // discharge switches  0->off  1-> on.  S0 = 0x01, S1 = 0x02, S2 = 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
    // tx_cfg[i][5] = 0x00 ;
    tx_cfg[i][5] = 0x20 ; // sets the software timer to 1 minute
  }
}



/*!***********************************
  \brief sets  the configuration array for cell balancing
  uses CFGR4 and lowest 4 bits of CGFR5
 **************************************/
void balance_cfg(int ic, int cell)
{
  tx_cfg[ic][4] = 0x00; // clears S1-8
  tx_cfg[ic][5] = tx_cfg[ic][5]  & 0xF0; // clears S9-12 and sets software timer to 1 min
  //Serial.println(tx_cfg[ic][5] & 0xF0,BIN);
  if (cell >= 0 and cell <= 7) {
    tx_cfg[ic][4] = tx_cfg[ic][4] | 1 << cell;
  }
  if ( cell > 7) {
    tx_cfg[ic][5] = tx_cfg[ic][5] | ( 1 << (cell - 8));
  }
}


/*!************************************************************
  \brief Prints Cell Voltage Codes to the serial port
 *************************************************************/
void print_cells()
{
  unsigned long elasped = millis()  - tstart;
  float moduleV;
  serialPrint(elasped);   //ELAPSED TIME:
  
  //INDIVIDUAL CELL VOLTAGES:
  for (int current_ic = 0 ; current_ic < TOTAL_IC; current_ic++)
  {
    moduleV = 0.;
    for (int i = 0; i < 12; i++)
    {
      moduleV = moduleV + cell_codes[current_ic][i] * 0.0001;
      serialPrint(cell_codes[current_ic][i] * 0.0001);     
    }
  }  
  serialPrint(moduleV);      // TOTAL MODULE VOLTAGE:
  serialPrint(current);     //MODULE CURRENT:
  
  //TEMPERATURES:
  for (int i = 0; i < sizeof(tempPins) / sizeof(int) ; i++)
  {
    serialPrint( temp[i]);
  }

  //RELAY STATES:
  serialPrint( chargeRelay_state);
  serialPrint( dischargeRelay_state);
  
  serialPrint("\r\n");

}


/*!****************************************************************************
  \brief print function overloads:
 *****************************************************************************/
void serialPrint(String val)
{
  Serial.print(val);
  Serial.print("\t");
  XbeeSerial.print(val);
  XbeeSerial.print("\t");
}

void serialPrint(unsigned long val)
{
  Serial.print(val);
  Serial.print("\t");
  XbeeSerial.print(val);
  XbeeSerial.print("\t");
}


void serialPrint(double val)
{
  Serial.print(val, 4);
  Serial.print("\t");
  XbeeSerial.print(val, 4);
  XbeeSerial.print("\t");
}

void serialPrint(int val)
{
  Serial.print(val);
  Serial.print("\t");
  XbeeSerial.print(val);
  XbeeSerial.print("\t");
}



/*!****************************************************************************
  \brief Prints GPIO Voltage Codes and Vref2 Voltage Code onto the serial port
 *****************************************************************************/
void print_aux()
{

  for (int current_ic = 0 ; current_ic < TOTAL_IC; current_ic++)
  {
    Serial.print(" IC ");
    Serial.print(current_ic + 1, DEC);
    for (int i = 0; i < 5; i++)
    {
      Serial.print(" GPIO-");
      Serial.print(i + 1, DEC);
      Serial.print(":");
      Serial.print(aux_codes[current_ic][i] * 0.0001, 4);
      Serial.print(",");
    }
    Serial.print(" Vref2");
    Serial.print(":");
    Serial.print(aux_codes[current_ic][5] * 0.0001, 4);
    Serial.println();
  }
  Serial.println();
}
/*!******************************************************************************
  \brief Prints the Configuration data that is going to be written to the LTC6804
  to the serial port.
 ********************************************************************************/
void print_config()
{
  int cfg_pec;

  Serial.println("Written Configuration: ");
  for (int current_ic = 0; current_ic < TOTAL_IC; current_ic++)
  {
    Serial.print(" IC ");
    Serial.print(current_ic + 1, DEC);
    Serial.print(": ");
    Serial.print("0x");
    serial_print_hex(tx_cfg[current_ic][0]);
    Serial.print(", 0x");
    serial_print_hex(tx_cfg[current_ic][1]);
    Serial.print(", 0x");
    serial_print_hex(tx_cfg[current_ic][2]);
    Serial.print(", 0x");
    serial_print_hex(tx_cfg[current_ic][3]);
    Serial.print(", 0x");
    serial_print_hex(tx_cfg[current_ic][4]);
    Serial.print(", 0x");
    serial_print_hex(tx_cfg[current_ic][5]);
    Serial.print(", Calculated PEC: 0x");
    cfg_pec = pec15_calc(6, &tx_cfg[current_ic][0]);
    serial_print_hex((uint8_t)(cfg_pec >> 8));
    Serial.print(", 0x");
    serial_print_hex((uint8_t)(cfg_pec));
    Serial.println();
  }
  Serial.println();
}

/*!*****************************************************************
  \brief Prints the Configuration data that was read back from the
  LTC6804 to the serial port.
 *******************************************************************/
void print_rxconfig()
{
  Serial.println("Received Configuration ");
  for (int current_ic = 0; current_ic < TOTAL_IC; current_ic++)
  {
    Serial.print(" IC ");
    Serial.print(current_ic + 1, DEC);
    Serial.print(": 0x");
    serial_print_hex(rx_cfg[current_ic][0]);
    Serial.print(", 0x");
    serial_print_hex(rx_cfg[current_ic][1]);
    Serial.print(", 0x");
    serial_print_hex(rx_cfg[current_ic][2]);
    Serial.print(", 0x");
    serial_print_hex(rx_cfg[current_ic][3]);
    Serial.print(", 0x");
    serial_print_hex(rx_cfg[current_ic][4]);
    Serial.print(", 0x");
    serial_print_hex(rx_cfg[current_ic][5]);
    Serial.print(", Received PEC: 0x");
    serial_print_hex(rx_cfg[current_ic][6]);
    Serial.print(", 0x");
    serial_print_hex(rx_cfg[current_ic][7]);
    Serial.println();
  }
  Serial.println();
}

void serial_print_hex(uint8_t data)
{
  if (data < 16)
  {
    Serial.print("0");
    Serial.print((byte)data, HEX);
  }
  else
    Serial.print((byte)data, HEX);
}


/*!***********************************
  \brief Reads current input from LEM sensor
 **************************************/
float  readCurrent() {
  for (int i = 0 ; i < imax; i++) {
    lem = lemHistory.rolling(analogRead(currentPin));
    lemBias = lemBiasHistory.rolling(analogRead(currentBiasPin));
  }
  current =  ((lem - lemBias / 2. - lemZeroCal) * 3. * 5. / 1024 *  LEM_RANGE / 1.667) * 8.9 / 8.0;// assumes lem and bias are on 10k/5k voltage divider. Calibration fudge factor added.
  if (abs(current) < .2) current = 0;
  return current;
}
/*!***********************************
  \brief Reads  LEM sensor value when current is zero. Used to calibrates to zero output for zero current
 **************************************/
float  zeroCurrentCalibrate() {
  // get initial readings for current and set the zero calibration
  int iSample = 500;
  for (int i = 0 ; i < iSample; i++) {
    lemHistory.push(analogRead(currentPin));
    lemBiasHistory.push(analogRead(currentBiasPin));
  }
  lem = lemHistory.mean();
  lemBias = lemBiasHistory.mean();
  return  lem - lemBias / 2.;
}





Step 7: Operation

The above images show data collected from an operating BMS. The first is the output of the BMS to a serial terminal (the result of the Serial.print() operations in the Arduino code). You can obtain output by connecting the BMS Arduino to a USB port on your computer using an appropriate USB cable.. Open the Serial Monitor on your Arduino IDE or a serial terminal program such as PuTTY or Realterm and connect to the appropriate COM port. The first column is elapsed time in ms, then 12 columns of battery cell voltages, the total module voltage, current, temperature and relay states. This data was taken when the pack was floating and the current was zero.

The graph shows cell voltages and module current collected during charge, discharge and float testing of the BMS. Different voltage curves (Channels 2-13) represent different cells in the battery module. In this case, the cell connected to Channel 2 has lower capacity than the remaining cells. Since the cells are connected in series, Channel 2 is limiting the capacity of the entire battery pack. This shows the importance of cell balancing, which I discuss in a separate Instructable.

Step 8: Going Further

The BMS system presented here can be used as a stand-alone system for protecting a battery pack from overcharge, undercharge, overcurrent and overtemperature conditions. I will discuss additional important functionality in subsequent Instructables including module balancing and monitoring of one or more BMS systems from a remote base station.

Step 9: Files

1) BMS.lbr Eagle library file

Attachments

Be the First to Share

    Recommendations

    • Battery Powered Contest

      Battery Powered Contest
    • Plywood Challenge

      Plywood Challenge
    • Plastic Contest

      Plastic Contest

    28 Discussions

    0
    ZeeshanS20
    ZeeshanS20

    2 months ago

    Hi,
    All the information is enough clear but I can't find the details of Relays which are used for disconnecting the charger/supply and load.
    Secondly the website you have recommended for the foot print library for LTC6804-2 can you please provide this.
    Thanks.

    0
    dcaditz
    dcaditz

    Reply 7 weeks ago

    Hi Zeeshan

    I used TE Connectivity Tyco Electronics EV200AAANA relay with integrated economizer (https://www.digikey.com/en/products/detail/te-conn.... You could use pretty much any relay you like depending on your current needs. The relay should have either an internal or external flyback diode.

    I have added my Eagle library file below.

    0
    yusufhncr
    yusufhncr

    2 months ago

    Hello,
    Can you share the LTC6804_initialize () function? I'm getting errors there. Thank you. Have you also worked on active balancing with the LTC3300?

    0
    dcaditz
    dcaditz

    Reply 7 weeks ago

    Hi yusufhncr. I have not worked at all on active balancing. That would be a great additon.

    The initialize function is in the file LTC68042.cpp. It looks like the following:

    void LTC6804_initialize()
    {
    quikeval_SPI_connect();
    spi_enable(SPI_CLOCK_DIV16);
    set_adc(MD_NORMAL,DCP_DISABLED,CELL_CH_ALL,AUX_CH_ALL);
    }

    0
    zamikshahid
    zamikshahid

    Question 3 months ago on Step 8

    Hi,
    I like your project and I am looking forward to implement it. I have already ordered LTC 6804-2 however I am having some issues regarding PCB design. Can you please send me Eagle CAD files of circuit board? and also is there anyway to get this battery management system with everything soldered on it? I just want to use it as a demo board Thank you

    0
    dcaditz
    dcaditz

    Answer 3 months ago

    Hi zamikshahid. Thanks for your message. I cannot provide a complete soldered system. Can you tell me what issues you have with the PCB design? Also, what is the intended end use of your project? Thanks. -David

    0
    zamikshahid
    zamikshahid

    Reply 3 months ago

    I want to design 44V 20 AH (12S10P) BMS for electric bike. Right now I have ordered LTC6804-2 and working on code. I will implement your design and will be needing your help in case if i stuck somewhere. Can you please provide more details, datasheet etc of the hall effect current sensor you are using?
    Are gerber files enough to make PCB? Thank you.

    0
    Fer2020
    Fer2020

    Question 4 months ago

    Hi,

    Amazing Instructable! I've been looking at the datasheet for the LTC6804 (page 67) use with less than 12 cells. It says that the unused C pins would simply result in 0V readings. Does this means that if making a BMS with 6 cells no changes would be needed for the code? (assuming everything else is the same)

    Also could you help me with this two questions: For only voltage and temperature reading can the BMS do without the LEM Current Sensor? In Part 2 the Arduino Shield Vin pin has a 12V, does this mean an external power source for the Arduino UNO?

    Again thanks for this Instructable! Very detailed and complete!

    0
    dcaditz
    dcaditz

    Answer 4 months ago

    Hi Fer2020 Thanks for your question. As you mentioned, page 67 of the datasheet discusses using the LTC6804 with fewer than 12 cells in series. The only issue with the Arduino code would be that a 0V reading in one or more channels would trigger an under-voltage fault. You would have to modify the code to ignore unused pins with 0V readings.

    You can certainly ditch the LEM, but again you would have to short the current sense pins together to not get random current readings. Or you could just comment out the current sensing section of the Arduino code.

    The BMS is powered by a separate 12v supply. This was due to the constraints for my particular application (basically the rules for the competition that the BMS was designed for). You could easily modify it to get power from the battery pack itself. It would be safest to use an isolated DC-DC converter between the battery pack and the BMS.

    0
    Blueinkscience
    Blueinkscience

    5 months ago

    Hi,

    I am just looking through your schematic and I noticed on pin 38 of the ltc6804 goes to the drive label, but I cannot see where that label is associated with anywhere else on the schematic. Could you elaborate on where that pin goes to? The data sheet says it should connect to a npn with the v+ acting as a collector and the vreg as emitter. You also have d2-d7 labelled on the arduino shield but they don't seem to lead anywhere either? This is a really cool instructables and thank you for efforts already, very easy to understand so far!

    0
    dcaditz
    dcaditz

    Reply 5 months ago

    Hi Blueinkscience. Thanks for your comment and thanks for looking over the schematic so closely! Sometimes I label pins on the schematic and never use them. I think the Drive pin is not connected to anything and I never used it for any purpose. Same for the Arduino D2-D7 pins. They are just available on the shield in case you want to use them for something. Hope this helps.

    1
    NeilRG
    NeilRG

    Question 6 months ago

    This is very thorough and well written. What changes are needed to make it compatible with 18650 batteries? Thank you.

    0
    dcaditz
    dcaditz

    Answer 6 months ago

    Hi NeilRG. Thanks for your question. I don't believe you would have to make any changes to the BMS to use 18650 batteries. Just make sure that you read the datasheet for your batteries and set MIN_CELL_V and MAX_CELL_V in the arduino code to the appropriate values. Also you might have to adjust the LEM_RANGE if you are using a different method to measure current that I used in the Instructable. Also make sure that your charger is set up correctly and does not exceed the max charge current from the battery datasheet.

    0
    NeilRG
    NeilRG

    Reply 6 months ago

    Thank you for your prompt response. Good luck going forward.

    0
    Totalimpact
    Totalimpact

    Question 6 months ago

    What about low voltage disconnect for charging circuit? LiFePo doesn't like to charge below freezing.

    0
    dcaditz
    dcaditz

    Answer 6 months ago

    Thanks for our comment. The value of MIN_CELL_V sets the cutoff voltage. The discharge relay should open when any cell reaches this voltage.

    0
    jeff45
    jeff45

    Question 7 months ago on Step 8

    I have 8 cell serially connected batteries. Would I be able to log self discharge rates on three of these batteries with two of these boards?

    0
    dcaditz
    dcaditz

    Answer 7 months ago

    Hi Jeff. If I understand the question I think you would be able to monitor all 8 with one of the BMS boards.

    0
    jlithen
    jlithen

    Question 8 months ago on Step 3

    Very nice project! I appreciate it being quite simple. Also I am no programmer but the arduino stuff I can almost understand :)
    I cannot find any drill files in the gerbers, could I have them?
    Of course Eagle files would be fine too if they can be shared.
    Another thing I am wondering about is how would this work with e.g. a 24cell in series pack = 2BMS boards on one Arduino?
    Thanks!

    Added on 29th, I now see the txt file is a drill file. Scale seems to be x10 though. I fixed it manually.

    0
    dcaditz
    dcaditz

    Answer 8 months ago

    Hi jliten. Thanks for your comment. You can adapt the project to use a 24 (or more) cell pack, but you will have to use 2 (or more) LTC6804 ICs. You will have to study the LTC6804 datasheet to see how it can be done. Note that the LTC6804-1 and LTC6804-2 ICs have different methods for dealing with more than 12 cells.