I've made enough progress with my lithium BMS that I think its time to post up what I have so far. I am posting the info for others to use as well as get some feedback.
Let me preface this all with the fact that I am by and far no expert in this. I know some programming and a little bit about electronics. That and a lot of googling and help from others has lead me to developing this.
The BMS is currently setup to work with the plugin kit for my Prius. So, right now its very simple. For example, where you see dc-dc converter it would be the controller in an EV. However, it was developed while keeping in mind that I'd like to use a similar version on an EV.
With that I'd like to go over generally how it works without having to dive into code:
Charging protection is pretty simple. All the cells voltages are individually monitored via a series of
celllog devices. The celllog can measure up to 8 cells at once. If more cell monitoring is needed you need another celllog and the tiny circuit that accompanies it. My circuit diagram shows 2 celllogs which is what I am using. The celllog has a user settable high voltage alarm that is used. Once the alarm goes off, the arduino cuts power to the charger.
Discharge protection is taken care of via a current sensor. You have to enter in the amp hours your batteries are rated for and the max DOD you want them to see. From there the current sensor will keep track of amp hours used. For my plugin kit it automatically shuts the kit off after I hit my max DOD. For a full EV you could simply not use that part of the circuit. I have added an 8 LED bar graph that tracks SOC of the pack as a 'fuel gauge'.
Balancing is currently not done with the BMS. It is designed to have the batteries balanced at the low end of their charge and then put into use. I have yet to find a good way to keep track of the SOC of each cell and balancing makes that much more complicated.
Thermal protection is not currently done either. The plugin kit doesn't even pull 1C on the batteries so its not an issue. However, I would be interested in adding this.
Here is the schematic:
And here is the code (
NOT UP TO DATE):
Code:
/*
Lithium BMS
This is a simple BMS system for lithium ion batteries.
*/
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>
// Pins
const int OnSwitchPin = 2; // pin to read on switch status
const int ChargerRelayPin = 3; // pin to control the charger relay
const int Pin110V = 4; // pin to read if charger is plugged in
const int OnSwitchRelayPin = 5; // pin to control the on switch (dc-dc pwr)
const int AlarmPin = 6; // pin to read celllog alarm signal
const int latchPin = 8; // pin to connect to latch pin on shift register
const int dataPin = 11; // pin to connect to data pin on shift register
const int clockPin = 12; // pin to connect to clock pin on shift register
const int CurrentPin = 1; // analog pin to read the current sensor
// Variables
volatile float CurrentRead = 0; // holds CurrentPin analog read
volatile float AHused = 0; // holds amp hours used
float BatteryAH = 0; // holds usable battery amp hours
int AlarmPinStatus = 0; // holds AlarmPin status
int OnSwitchPinStatus = 0; // holds the OnSwitchPin status
int Pin110VStatus = 0; // holds the Pin110V status
unsigned long DelayTimer = 0; // variable to hold disconnect time
float SOC = 0; // holds state of charge
// User defined variables
const int RatedBatteryAmpHours = 78; // rated amp hours of the batteries
const float DOD = .7; // maximum depth of discharge allowed
void setup() {
pinMode(AlarmPin, INPUT);
pinMode(ChargerRelayPin, OUTPUT);
pinMode(Pin110V, INPUT);
pinMode(OnSwitchRelayPin, OUTPUT);
pinMode(OnSwitchPin, INPUT);
pinMode(CurrentPin, INPUT);
digitalWrite(AlarmPin, HIGH); // set AlarmPin high
digitalWrite(ChargerRelayPin, LOW); // turn charger relay off
digitalWrite(OnSwitchRelayPin, LOW); // turn on switch off
BatteryAH = RatedBatteryAmpHours * DOD; // calculate usable Ah
// initialize Timer1
cli(); // disable global interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
// set compare match register to desired timer count
OCR1A = 16001;
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10
TCCR1B |= (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
// enable global interrupts
sei();
}
void loop() {
// SOC gauge code
// calculate SOC
SOC = (BatteryAH - AHused) / BatteryAH;
// write SOC to led bargraph
if (SOC > .875) {
// 8 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 255);
digitalWrite(latchPin, HIGH);
}
else if (SOC > .75) {
// 7 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 127);
digitalWrite(latchPin, HIGH);
}
else if (SOC > .625) {
// 6 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 63);
digitalWrite(latchPin, HIGH);
}
else if (SOC > .5) {
// 5 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 31);
digitalWrite(latchPin, HIGH);
}
else if (SOC > .375) {
// 4 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 15);
digitalWrite(latchPin, HIGH);
}
else if (SOC > .25) {
// 3 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 7);
digitalWrite(latchPin, HIGH);
}
else if (SOC > .125) {
// 2 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 3);
digitalWrite(latchPin, HIGH);
}
else if (SOC > 0) {
// 1 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 1);
digitalWrite(latchPin, HIGH);
}
else {
// 0 leds lit up
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 0);
digitalWrite(latchPin, HIGH);
}
// Charger connect code
// read pin110v status
Pin110VStatus = digitalRead(Pin110V);
// if 110V power is plugged in
if (Pin110VStatus == HIGH) {
digitalWrite(ChargerRelayPin, HIGH); // turn charger on
}
// Charger disconnect code
// read alarm pin status
AlarmPinStatus = digitalRead(AlarmPin);
// if alarm turns on set a timer to detect false alarms
if (AlarmPinStatus == LOW && millis() - DelayTimer < 10) {
DelayTimer = millis(); // set timer
}
// if alarm stays on
if (AlarmPinStatus == LOW && millis() - DelayTimer > 2000) {
digitalWrite(ChargerRelayPin, LOW); // turn charger off
AHused = 0; // reset amp hours
DelayTimer = 0; // reset delay timer
}
// DC-DC connect code
// read on switch status
OnSwitchPinStatus = digitalRead(OnSwitchPin);
// if dc-dc switch is on && battery has capacity left
if (OnSwitchPinStatus = HIGH && AHused < BatteryAH) {
digitalWrite(OnSwitchRelayPin, HIGH); // turn dc-dc converter on
}
// DC-DC disconnect code
// if amp hours used exceeds usable amp hour capacity
if (AHused > BatteryAH)
{
digitalWrite(OnSwitchRelayPin, LOW); // turn dc-dc converter off
}
// DC-DC power down code
// if dc-dc converter switch is off enter sleep mode
if (OnSwitchPinStatus = LOW) {
// enter sleep mode
sleepNow();
}
}
// sleep mode code
void sleepNow() {
attachInterrupt(0,loop, RISING); // set interrupt to on switch
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
sleep_enable();
digitalWrite(latchPin, LOW); // turn off SOC gauge
shiftOut(dataPin, clockPin, MSBFIRST, 0);
digitalWrite(latchPin, HIGH);
sleep_mode(); // enter sleep mode
sleep_disable(); // resume code after exiting sleep mode
}
// interrupt to count amp hours used
ISR(TIMER1_COMPA_vect)
{
CurrentRead = analogRead(CurrentPin);
// Convert currentread to amps
CurrentRead = (CurrentRead - 512) * .78125;
// Convert to AH & add to total
AHused = AHused + (CurrentRead * .001);
}
Here is a list of the components:
I've been using the charging protection for a few months with my plugin kit and it has worked great. I have just finished working on the discharge protection and haven't yet assembled the circuit.
Anyway, thanks for reading if you got this far. Its a lot to look at, but I'd really appreciate some feedback.