Battery controller

From silverwiki

I had an idea a while ago for a daisy-chainable battery cell protection system.

Tmp 24669-IMG 20161229 110205825497813.jpg

The idea is to have each cell in the chain report 2 signals to the next cell, which are repeated if received high and measured good. This means that any failure would be treated as the cell being out of limits and the pack charge and discharge would be stopped. Bit one is charge ready (voltage under charge limit, and previous cells also ready) and bit two is discharge ready (voltage over discharge limit, and previous cells also ready). The cell signals are optoisolated between each cell, so the voltage difference between cells will not matter. Each cell has it's own 1.8v microcontroller, which should be able to operate safely through the full range of charge of a 3.7v cell. 3.3v controllers or 5v controllers could work for groups of cells, perhaps. In any case, the only requirement is that they be able to measure their own input voltage against a reference voltage. The attiny85 seems to work. Other controllers can be used and there is no reason they would need to be uniform.


Lets say we have cells A through F. If all are good for charge and discharge the chain will look like this:

A: 1 1

B: 1 1

C: 1 1

D: 1 1

E: 1 1

F: 1 1

Controller: Sees 1 and 1, meaning good to charge or discharge. Both relays enabled.

Discharge cutoff

Now lets say that cell B is a little weaker than the rest, and reaches the low voltage limit before the others. The chain will look like this:

A: 1 1

B: 1 0

C: 1 0

D: 1 0

E: 1 0

F: 1 0

Controller: Sees 1 and 0, meaning good to charge but not to discharge. The charger relay is enabled, and the discharge relay is disabled.

Charge cutoff

Now lets say that cell D is charging faster than the rest, and reaches the high voltage limit before the others. The chain will look like this:

A: 1 1

B: 1 1

C: 1 1

D: 0 1

E: 0 1

F: 0 1

Controller: Sees 0 and 1, meaning good to discharge but not to charge. The charger relay is disabled, and the discharge relay is enabled.

Fault cutoff

Now lets say that cell C never receives the signals from cell B due to severed wire bundle. B is reporting 1 1, and C is in a state to report 1 1, but since C doesn't get the signals from B it reports 0 0 down the chain (1 is only reported is both 1 is received AND 1 is the value to be sent). If the wires are shorted it will not matter for cell health, as the microcontroller will pop or the small wires will act as a fuse. To recover from this, the wires need to be checked and repaired, and the controller nearest the fault may need replacement. The chain will look like this:

A: 1 1

B: 1 1

~~Broken wires!~~

C: 0 0

D: 0 0

E: 0 0

F: 0 0

Controller: Sees 0 and 0, meaning not good to discharge or charge. The charger relay is disabled, and the discharge relay is disabled. This is a safe failure mode.

attiny code

int chargein = 0;
int runin = 1;
int chargeout = 2;
int runout = 3;
long voltage;
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
  long result = (high<<8) | low;
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts

void setup()


void loop()
voltage = readVcc();
if (voltage > 4000){
  if (digitalRead(chargein) == HIGH){
if (voltage < 3000){
  if (digitalRead(runin) == HIGH){


So if we whip up an attiny84 board with two opto isolators, it might look like this:

Tmp 3618-IMG 20170619 121326543736311.jpg

and we can see how it would work in a series of battery cells like this:


And I sketched up what it might look like as a PCB from a board house. Note the outputs moved down a pin to avoid AREF.

Tmp 8249-IMG 20170625 1220001524854774.jpg

Some additional changes are required. We need a spot to stick an indicator LED and resistor near the outputs, and the outputs are probably going to move to the left side (same as inputs) to reduce the board size. Also the micro was unreliable with a logic high for an input 1, so the optos will need to be tied to ground instead of vcc and the code path logic reversed. Then it is pulled up and the optos pull it down, with no floating state. Also it could be a bit wider so that drag soldering on protoboard is possible with the same layout.

The current design is that by using the previous cell's - on the optoisolator with the current cell's + as a ground, we pull high to turn off the LED and pull low to turn it on. Must confirm that the pin goes float and will pass no current if a microcontroller loses power.

First whack at making on protoboard

IMG 20170701 095950.jpg

IMG 20170701 095942.jpg