This is an old revision of the document!
AVR BMS
// 8-cell Lithium BMS module
Code for ATmega16A by Ian Hooper, (C) ZEVA 2012
Fuses: 1Mhz clock, JTAGEN disabled And brown-out at 4 volts to help with the eeprom?
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> #include <avr/wdt.h>
#define ADC_VREF_TYPE 0x40 AVCC, with capacitor between AREF and GND division of 10K high : 1.5K low = 0.130 scaling factor 1V full → 0.13V scaled → ADC 26.60 per volt or, 37.435mV per ADC step #define LV_THRESHOLD 66 ~2.5V #define SHUNT_THRESHOLD 96 ~3.6V #define HV_THRESHOLD 106 ~4.0V #define TEMP_THRESHOLD 350 ~1.75V on the ADC, hopefully about 50 degrees
#define HV_SIG (1«PD4) #define LV_SIG (1«PD3) #define SHUNT_SIG (1«PD2)
#define TEMP1_IN (1«PB1); #define TEMP2_IN (1«PB0);
HV sample enable pins 1 2 3 4 5 6 7 8 PD6 PC0 PC2 PC4 PC6 PA7 PA5 PA3 Shunt enable pins 1 2 3 4 5 6 7 8 PD5 PD7 PC1 PC3 PC5 PC7 PA6 PA4
Status LED outputs 1 2 3 4 5 6 7 8 PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0 #ifndef true #define true 1 #define false 0 #endif Function declarations void SetupPorts(); int GetCell(int n); 0-8 void SetShunt(int n, char state); void SetLED(int n, char state); Global variables int level[9]; char lows[9]; char highs[9]; char shunts[9]; char temps[2];
unsigned char counter;
unsigned short ReadADC(unsigned char adc_input) {
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff); _delay_us(10); // Delay needed for the stabilization of the ADC input voltage ADCSRA|=0x40; // Start the AD conversion while ((ADCSRA & 0x10)==0); // Wait for the AD conversion to complete ADCSRA|=0x10; return ADCW;
}
int main(void) {
SetupPorts(); wdt_enable(WDTO_250MS);
level[0] = -16; // Fixed reference point // Opto has a ~0.6V forward drop? // 37mV per step -> 16 extra needed for level zero
while (1) { wdt_reset(); counter++; for (int n=1; n<=8; n++) level[n] = GetCell(n); GetCell(0); // Turn sampling off
for (int n=1; n<=8; n++) { int diff = level[n] - level[n-1]; // Tiny bit of hysteresis around the threshold value if (diff < LV_THRESHOLD) lows[n] = true; else if (diff > LV_THRESHOLD) lows[n] = false;
if (diff > HV_THRESHOLD) highs[n] = true; else if (diff < HV_THRESHOLD) highs[n] = false;
if (diff > SHUNT_THRESHOLD) shunts[n] = true; else if (diff < SHUNT_THRESHOLD) shunts[n] = false; SetShunt(n, shunts[n]); if (lows[n] || highs[n]) SetLED(n, false); // LED off else if (shunts[n]) SetLED(n, counter&1); // blink else SetLED(n, true); // Constant on = no errors }
char low = false; char high = false; char shunt = false; for (int n=1; n<=8; n++) { if (lows[n]) low = true; if (highs[n]) high = true; if (shunts[n]) shunt = true; }
// Temperatures, ADC0 = Temp2, ADC1 = Temp1 // Altronics thermistor is about 10K at room temp, i.e ADC 512 will be room temp // Voltage goes DOWN as it gets hotter // 0.68V is about 80degC - TOO HOT // 1.6V is about 45degC - bit hotter is fine // I'm thinking about 1V would be ~60degC which is a good upper limit for shunts and batteries for (int n=0; n<2; n++) { if (ReadADC(n) < TEMP_THRESHOLD-10) temps[n] = true; else if (ReadADC(n) > TEMP_THRESHOLD+10) temps[n] = false; }
if (temps[0] || temps[1]) { low = true; high = true; } // Shut down both charge and drive
// Reverse logic, so it holds a daisy chain closed whenever all modules are happy if (low) PORTD &= ~LV_SIG; else PORTD |= LV_SIG;; if (high) PORTD &= ~HV_SIG; else PORTD |= HV_SIG; if (shunt) PORTD &= ~SHUNT_SIG; else PORTD |= SHUNT_SIG; _delay_ms(100); // slow iterations down a little, slightly dirty coding }
}
void SetupPorts() {
// I/O direction registers; 0 means input, 1 means output DDRA = 0b11111000; DDRB = 0b11111111; DDRC = 0b11111111; DDRD = 0b11111100;
ADCSRA = 0b10000011; // ADC clock prescaler is /8 with 011 as final bits, ADC clock should be between 50khz and 200khz and we're running 1Mhz uC clock
}
int GetCell(int n) {
// Turn all HV sampler pins off PORTA &= ~0b10101000; PORTC &= ~0b01010101; PORTD &= ~0b01000000;
switch (n) // Turn selected sampler on { case 1: PORTD |= (1<<PD6); break; case 2: PORTC |= (1<<PC0); break; case 3: PORTC |= (1<<PC2); break; case 4: PORTC |= (1<<PC4); break; case 5: PORTC |= (1<<PC6); break; case 6: PORTA |= (1<<PA7); break; case 7: PORTA |= (1<<PA5); break; case 8: PORTA |= (1<<PA3); break; default: return 0; }
_delay_ms(1); // Allow filter capacitor to stabilise return ReadADC(2); // TODO: We may need to acount for optotransistor voltage drop here too
}
void SetShunt(int n, char state) state should be on or off / true or false { switch (n) { case 1: if (state) PORTD |= (1«PD5); else PORTD &= ~(1«PD5); break; case 2: if (state) PORTD |= (1«PD7); else PORTD &= ~(1«PD7); break; case 3: if (state) PORTC |= (1«PC1); else PORTC &= ~(1«PC1); break; case 4: if (state) PORTC |= (1«PC3); else PORTC &= ~(1«PC3); break; case 5: if (state) PORTC |= (1«PC5); else PORTC &= ~(1«PC5); break; case 6: if (state) PORTC |= (1«PC7); else PORTC &= ~(1«PC7); break; case 7: if (state) PORTA |= (1«PA6); else PORTA &= ~(1«PA6); break; case 8: if (state) PORTA |= (1«PA4); else PORTA &= ~(1«PA4); break; } } void SetLED(int n, char state) state should be on or off / true or false {
switch (n) // TODO: faster algorithm { case 1: if (state) PORTB |= (1<<PB7); else PORTB &= ~(1<<PB7); break; case 2: if (state) PORTB |= (1<<PB6); else PORTB &= ~(1<<PB6); break; case 3: if (state) PORTB |= (1<<PB5); else PORTB &= ~(1<<PB5); break; case 4: if (state) PORTB |= (1<<PB4); else PORTB &= ~(1<<PB4); break; case 5: if (state) PORTB |= (1<<PB3); else PORTB &= ~(1<<PB3); break; case 6: if (state) PORTB |= (1<<PB2); else PORTB &= ~(1<<PB2); break; case 7: if (state) PORTB |= (1<<PB1); else PORTB &= ~(1<<PB1); break; case 8: if (state) PORTB |= (1<<PB0); else PORTB &= ~(1<<PB0); break; }
}