#include <avr/io.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <stddef.h>
#define V_MAX 4096 //AVCC(5.02V)
/*
* calculated based on measured ADC refrence voltage(5.02V)
* possible to do in software but needs two accurate voltage refrences
*/
#define V_CUTOFF 3420 //4.2V
#define V_TOLERANCE 10 //12mV
#define V_HYST_CHARGE 8 //10mV
#define V_HYST_BAL 3 //4mV
#define V_MIN 2450 //3.0V
#define V_ABSENT 10 //12mV, treat this as there being no cell, jut in case of ADC errors
#define CELLS_MAX 4
uint8_t CELLS_COUNT;
/*
* wdt_off will get called before main() in case the watchdog
* timer is accidentally set so short it runs out before main
* is called and the board goes on an infinite reset cycle.
*/
void wdt_off() __attribute__ ((naked)) __attribute__((section(".init3")));
void wdt_off()
{
//disable the watchdog timer
MCUCR = 0;
wdt_disable();
}
void get_cell_voltages(uint16_t cells[CELLS_MAX])
{
uint8_t i, j, channel;
uint16_t sample;
uint32_t avg;
for(channel = 0; channel < CELLS_COUNT; channel++)
{
//clear nybble 0 and set the new channel
ADMUX = (ADMUX & 0xF0) | channel;
avg = 0;
//average out 32 12-bit oversamples
for(i = 0; i < 32; i++)
{
sample = 0;
//oversample 10-bit ADC to 12-bits
for(j = 0; j < 16; j++)
{
//start AD conversion
ADCSRA |= 1 << ADSC;
//busy wait for ADC to complete
while(!(ADCSRA & (1 << ADIF)))
;
//clear ADIF bit, i don't get it but thats what the datasheet says to do
ADCSRA |= 1 << ADIF;
sample += ADC;
}
//bring sample down to 12-bits
sample >>= 2;
avg += sample;
}
//store the oversampled and averaged result
cells[channel] = avg / 32;
}
}
void get_cell_states(uint16_t cells[CELLS_MAX], uint8_t *high, uint8_t *low)
{
uint8_t i;
*high = 0;
for(i = 1; i < CELLS_COUNT; i++)
*high = (cells[*high] < cells[i]) ? i : *high;
*low = 0;
for(i = 1; i < CELLS_COUNT; i++)
*low = (cells[*low] > cells[i]) ? i : *low;
}
void begin_charge()
{
/*
* not exactly sure how im going to do this but
* for now ill pretent PORTD bit-0 will control the charging
* 1 for charge and 0 for off
*/
PORTD |= 1 << PD0;
}
void end_charge()
{
/*
* not exactly sure how im going to do this but
* for now ill pretent PORTD bit-0 will control the charging
* 1 for charge and 0 for off
*/
PORTD &= ~(1 << PD0);
}
void begin_balance(uint8_t cell)
{
PORTB = (PORTB & 0xF0) | (1 << cell);
}
void end_balance()
{
PORTB &= 0xF0;
}
//turn on a red LED or a siren or fireworks or something
void error(uint8_t code)
{
PORTD |= 1 << code;
for(;;)
;
}
void init()
{
uint8_t i, total;
uint16_t cells[CELLS_MAX];
//need to setup CELLS_COUNT like this first so get_cell_voltages works
CELLS_COUNT = CELLS_MAX;
get_cell_voltages(cells);
//make sure the present cells are all at a sane voltage
total = 0;
for(i = 0; i < CELLS_MAX; i++)
if(cells[i] > V_MIN && cells[i] < V_CUTOFF)
total++;
else if((cells[i] < V_MIN && cells[i] > V_ABSENT) || cells[i] > V_CUTOFF)
error(1);
//make sure the present cells are contiguous starting from 0
for(i = 0; i < total; i++)
if(! (cells[i] > V_MIN && cells[i] < V_CUTOFF))
error(2);
CELLS_COUNT = total;
}
int main()
{
uint16_t cells[CELLS_MAX], diff, cutoff, charging, balancing, tolerance;
uint8_t high, low;
//set system clock prescaler to 1(8MHz * 1 = 8MHz)
CLKPR = 1 << CLKPCE;
CLKPR = 0;
//set PORTB and PORTD to output
DDRB = 0xFF;
DDRD = 0xFF;
//AD pins 4-6 output, 0-3 to input
DDRC |= 0x30;
DDRC &= ~0xF;
//turn on internal pull-up resistors, resistance of my testing pot is too high for the pull-ups
//PORTC |= 0xF;
//PORTB 0:3 for draining high cell, and maybe some other stuff
PORTB = 0;
//PORTD, probably for the safety cutoff and other stuff
PORTD = 0;
//enable ADC, prescaler=64(125000 Hz)
ADCSRA |= (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1);
//AVCC as VREF
ADMUX |= 1 << REFS0;
init();
//begin_charge();
charging = 0;
balancing = 0;
tolerance = V_TOLERANCE;
cutoff = V_CUTOFF;
for(;;)
{
get_cell_voltages(cells);
get_cell_states(cells, &high, &low);
diff = cells[high] - cells[low];
/*
* check highest cell voltage, if it is over the cutoff terminate charging,
* reduce the cutoff voltage by V_HYST_CHARGE and wait till the voltage
* drops below the new cutoff voltage in case the cell is left charging
* for a long time, the hysteresis keeps it from toggling
* on and off quickly once it finishes charging.
*/
if((cells[high] > cutoff) && charging)
{
end_charge();
charging = 0;
cutoff = V_CUTOFF - V_HYST_CHARGE;
}
else if((cells[high] < cutoff) && (!charging))
{
begin_charge();
charging = 1;
cutoff = V_CUTOFF;
}
/*
* pretty much the same as above
* this should work ok
*/
if(diff > tolerance && (!balancing))
{
begin_balance(high);
balancing = 1;
tolerance = V_TOLERANCE - V_HYST_BAL;
}
else if(diff < tolerance && balancing)
{
end_balance();
balancing = 0;
tolerance = V_TOLERANCE;
}
/*
* could add some other stuff, not sure what yet
*/
}
return 0;
}