/*---------------------------------------------------------------*/
/* Battery discharge analyzer (C)ChaN, 2010 */
/*---------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <string.h>
#include "integer.h"
#include "xitoa.h"
#include "uart.h"
#define I_MAX 3000
#define STAT GPIOR0
#define S_IV10 0x01
#define S_IV1 0x02
/*------------------------------------------*/
/* Work area */
/*------------------------------------------*/
char Line[30]; /* Line inpu buffer */
volatile BYTE Timer; /* 256Hz increment timer */
typedef struct {
DWORD vadj; /* Voltage adjust */
DWORD iadj[3]; /* Current adjust (3 ranges) */
BYTE sum, sgn; /* Signature and check sum */
} CALS;
CALS Cals;
/*------------------------------------------*/
/* Read battery voltage */
/*------------------------------------------*/
UINT get_volt ( /* Voltage in unit of millivolts */
int adj /* 0:Uncalibrated, 1:Calibrated */
)
{
WORD n;
ADMUX = 0; /* Select ch0 */
ADCSRA = _BV(ADEN)|_BV(ADSC)|_BV(ADIF)|0b110; /* Start convertsion */
while (bit_is_clear(ADCSRA, ADIF)) ; /* Wait for EOC */
n = ADC; /* Get result */
if (adj) n = Cals.vadj * n / 256; /* Apply calibration if needed */
return n;
}
/*------------------------------------------*/
/* Get a console input */
/*------------------------------------------*/
static
void get_line (char *buff, int len)
{
BYTE c;
int idx = 0;
for (;;) {
c = uart_get();
if (c == '\r') break;
if ((c == '\b') && idx) {
idx--; uart_put(c);
}
if (((BYTE)c >= ' ') && (idx < len - 1)) {
buff[idx++] = c; uart_put(c);
}
}
buff[idx] = 0;
uart_put(c);
uart_put('\n');
}
/*------------------------------------------*/
/* Set load current */
/*------------------------------------------*/
UINT set_current ( /* Set current [mA] */
UINT cur /* Current to be set [mA] */
)
{
BYTE r;
UINT cv;
/* Select range */
r = 0;
cv = Cals.iadj[r] * cur >> 16;
if (cv >= 256) {
r = 1;
cv = Cals.iadj[r] * cur >> 16;
if (cv >= 256) {
r = 2;
cv = Cals.iadj[r] * cur >> 16;
if (cv >= 256 || cur > I_MAX) {
cv = 0;
cur = 0;
}
}
}
/* Set output */
OCR0A = (BYTE)cv;
if (cur) {
PORTB = (PORTB & 0xF8) | (1 << r);
PORTB |= _BV(4); /* LED ON */
} else {
PORTB &= 0xF8;
PORTB &= ~_BV(4); /* LED OFF */
}
return cur;
}
/*------------------------------------------*/
/* Load calibration value */
/*------------------------------------------*/
static
void load_cal (void)
{
BYTE *p, s;
int i;
eeprom_read_block(&Cals, 0, sizeof(Cals));
for (i = s = 0, p = (BYTE*)&Cals; (UINT)i < sizeof(Cals); s += p[i++]) ;
if (Cals.sgn != 0x55 || s) {
Cals.vadj = 1250UL;
Cals.iadj[0] = 65536UL * 2;
Cals.iadj[1] = 65536UL * 2 / 5;
Cals.iadj[2] = 65536UL * 2 / 25;
xputs(PSTR("Not calibrated.\n"));
}
}
/*------------------------------------------*/
/* Create calibration value */
/*------------------------------------------*/
static
void do_cal (void)
{
DWORD n;
char *p;
BYTE s;
int i;
/* Adjust voltage */
OCR0A = 0;
xputs(PSTR("Voltage [mV]:"));
p = Line; get_line(p, sizeof(Line));
xatoi(&p, (long*)&n);
if (n) Cals.vadj = n * 256 / get_volt(0);
/* Adjust three current ranges */
OCR0A = 128;
for (i = 0; i < 3; i++) {
PORTB = (PORTB & 0xF8) | (1 << i);
xprintf(PSTR("Current %u [mA]:"), i + 1);
p = Line; get_line(p, sizeof(Line));
xatoi(&p, (long*)&n);
if (n) Cals.iadj[i] = 128UL * 65536 / n;
}
OCR0A = 0;
PORTB = PORTB & 0xF8;
/* Save calibration values */
Cals.sum = 0; Cals.sgn = 0x55;
for (i = s = 0, p = (char*)&Cals; (UINT)i < sizeof(Cals); s += p[i++]) ;
Cals.sum = 0 - s;
eeprom_write_block(&Cals, 0, sizeof(Cals));
}
/*------------------------------------------*/
/* Initialize peripherals */
/*------------------------------------------*/
static
void ioinit (void)
{
PORTB = 0b00101000; /* Port B */
DDRB = 0b00010111;
PORTC = 0b00111110; /* Port C */
DDRC = 0b00000000;
PORTD = 0b10111110; /* Port D */
DDRD = 0b01000010;
OCR1A = F_CPU/8/256-1; /* Timer1: 256Hz interval (OC1A) */
TCCR1B = 0b00001010;
TIMSK1 = _BV(OCIE1A); /* Enable TC1.oca interrupt */
OCR0A = 0; /* Timer0: PWM out (OC0A) */
TCCR0A = 0b10000011;
TCCR0B = 0b00000001;
sei(); /* Enable interrupt */
uart_init(); /* Initialize serial port */
for (Timer = 0; Timer < 25; ) ; /* Delay 100ms */
}
/*------------------------------------------*/
/* Measure discharging characteristics */
/*------------------------------------------*/
static
void m_discharge (
char lm, /* Constatnt load mode. 'c':Current, 'p':Power, 'r':Registance */
UINT lv, /* Load [mA/mW/mOhm] */
char log, /* Logging interval mode. 't':time, 'c':coulomb */
UINT ivt, /* Logging interval [sec/mAh] */
UINT lvd /* Low battery threshold [mV] */
)
{
UINT vol, pow, cur;
DWORD ene, ah, tm, iv;
if (log != 't' && log != 'c') return;
switch (lm) {
case 'c':
if (lv > I_MAX) return; /* 3A max */
xprintf(PSTR("Load current[mA] = %u\n"), lv);
break;
case 'p':
if (lv > 10000) return; /* 10W max */
xprintf(PSTR("Load wattage[mW] = %u\n"), lv);
break;
case 'r':
if (lv < 500 || lv > 50000) return; /* 0R5 to 50R */
xprintf(PSTR("Load resistance[mOhm] = %u\n"), lv);
break;
default:
return;
}
xprintf(PSTR("Ending voltage[mV] = %u\n"), lvd);
if (log == 't')
xprintf(PSTR("Interval[sec] = %u\nTime,mV,mA,mAh,mW,J\n"), ivt);
else
xprintf(PSTR("Interval[mAh] = %u\nmAh,mV,mW,J\n"), ivt);
tm = iv = ene = ah = 0;
Timer = 0;
do {
do { /* Power/Resistance control loop */
if (uart_test() && uart_get() == '\x1B') {
set_current(0); return;
}
while (!(STAT & S_IV10)) ; /* Wait 1/8 sec interval */
STAT &= ~S_IV10;
vol = get_volt(1);
switch (lm) {
case 'c':
cur = lv;
break;
case 'p':
cur = (UINT)((DWORD)lv * 1000 / vol);
break;
case 'r':
cur = (UINT)((DWORD)vol * 1000 / lv);
break;
}
if (!set_current(cur)) return;
} while (!(STAT & S_IV1));
STAT &= ~S_IV1;
/* Executed every second */
ah += cur; /* Coulomb counter */
pow = (DWORD)cur * vol / 1000; /* Power */
ene += pow; /* Energy counter */
if (log == 't') { /* Output interval mode */
if (tm >= iv) { /* Output a line every interval time */
iv += ivt; /* Next interval */
xprintf(
PSTR("%u:%02u:%02u,%u,%u,%u,%u,%u\n"),
(UINT)(tm / 3600), (UINT)(tm / 60 % 60), (UINT)(tm % 60), vol, cur, (UINT)(ah / 3600), pow, (UINT)(ene / 1000)
);
}
} else {
if (ah >= iv * 3600) { /* Output a line every interval coulomb */
iv += ivt; /* Next interval */
xprintf(PSTR("%u,%u,%u,%u\n"), (UINT)(ah / 3600), vol, pow, (UINT)(ene / 1000));
}
}
tm++; /* Elapsed time */
} while (vol >= lvd);
set_current(0);
}
/*------------------------------------------*/
/* Measure series resistance */
/*------------------------------------------*/
static
void m_resistance (
long c1, /* Current 1 */
long c2 /* Current 2 */
)
{
int v1, v2;
if (c1 == c2) return;
/* Measure voltage under current 1 */
set_current(c1);
for (Timer = 0; Timer < 25; ) ;
v1 = (int)get_volt(1);
/* Measure voltage under current 2 */
set_current(c2);
for (Timer = 0; Timer < 25; ) ;
v2 = (int)get_volt(1);
set_current(0);
/* Put the result */
xprintf(PSTR("Rs[mOhm] = %d\n"), (int)(1000L *