I spent a few days going through commercially available BMS. I can't seem to find one with the options I want.
External relay/contactor support
Ability to control a heater
Ability to report data over bluetooth, LCD display, or similar
200mA balancing.
The Orion JR is nice, but it doesn't support heater control, and requires an expensive (and almost pathetic) external screen, or CANbus interface for monitoring.
The EMUs BMS requires a specific external shunt, and is designed around direct control of dumb charge sources, and has some behavior that is undesirable.
The various commerical grade BMS units either don't support 4 cell packs, or require extensive software or hardware to monitor, and I don't want to pull out my PC every time I need to check cell voltages. Nor do I want to program a front end either.
The simple chinese bluetooth bms (they all share the same core hardware) may work, but I need them to support an external shunt and contactor. Not sure if they could be modified for that.
I took a look at the arduino BMS posted previously. The code is well written, and I should be able to modify for my simple needs. There is not display though. So I would need to work out something with a bluetooth module, or rig up a basic character style LCD display to show vital stats. Linear tech (BMS chip maker) provides a decent library which makes interfacing fairly straightforward for newbs like me. Manually setting registers is not very intuitive.
I am going to keep looking to see whats available though, searching for this stuff on the web, and trolling through forums is not very expedient.
C++:
BMS_arduino_code
#include <Arduino.h>
#include <stdint.h>
#include <SoftwareSerial.h>
#include <SPI.h>
#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 = 50.; // Maximum battery current(amps) before relay opens
const float MAX_TEMP = 30. ; // 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.00; // Minimum allowable cell voltage. Depends on battery chemistry.
const float MAX_CELL_V = 3.60; // Maximum allowable cell voltage. Depends on battery chemistry.
/***** 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};
/******** variables for tracking cell voltages and states ***************/
float VBalanceThreshold = 3.5; // cell balancing occurrs when voltage is aboce this value
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 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
unsigned long tstart;
float minV1 ;
float maxV1 ;
int error = 0;
/******** Current and temperature variables ***********************/
const uint16_t imax = 100; // size of arrays for averaging read measurements
Average<float> lemHistory(imax);
Average<float> lemBiasHistory(imax);
float lem = 0;
float lemBias = 0;
float lemZeroCal = 0;
float current = 0;
float temp[sizeof(tempPins)];
/******************************************************
*** 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); i++)
{
pinMode(tempPins[i], INPUT);
}
digitalWrite(dischargeRelayPin, LOW);
digitalWrite(chargeRelayPin, LOW);
overCharge_state = HIGH; //HIGH = relay on, LOW = relay off
underCharge_state = HIGH; //HIGH = relay on, LOW = relay off
Serial.begin(19200);
XbeeSerial.begin(19200);
LTC6804_initialize(); //Initialize LTC6804 hardware
init_cfg(); //initialize the 6804 configuration array to be written
delay(1000);
lemZeroCal = zeroCurrentCalibrate();
tstart = millis();
}
/*!*********************************************************************
\brief main loop
***********************************************************************/
void loop()
{
// read current:
overCurrent_state = HIGH;
current = readCurrent();
if(current > MAX_CURRENT) overCurrent_state= LOW;
// read temperatures:
overTemp_state = HIGH;
for (int i = 0; i < sizeof(tempPins); i++)
{
temp[i] = (analogRead(tempPins[i]) * 5. / 1024 - 0.5) / 0.01;
if(temp[i]> MAX_TEMP) overCurrent_state= LOW;
}
// 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;
if (cellMin_V <= minV1)
{
underCharge_state = LOW;
// Serial.println("V <= MIN_CELL_V");
}
if (cellMax_V >= maxV1)