alix-usv/alix-usv.c

261 lines
6.7 KiB
C
Raw Permalink Normal View History

2009-03-11 21:25:34 +01:00
/***************************************************************************
* Copyright (C) 01/2009 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; version 2 of the License, *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include "alix-usv.h"
#include "eeprom.h"
#include "usi-i2c-slave.h"
extern volatile struct ee_param params;
static const uint8_t adc_mux[] = {
CH_CURRENT_P, CH_CURRENT_N,
CH_VOLTAGE_BAT, CH_VOLTAGE_SUP,
};
static volatile uint8_t adc_update;
static volatile int16_t adc_value[3];
enum {
ADC_CURRENT = 0,
ADC_UBAT = 1,
ADC_UIN = 2,
};
static volatile uint8_t sys_state;
enum {
STATE_IDLE = 0x01,
STATE_TEST = 0x02,
STATE_CHARGE = 0x04,
STATE_DISCHARGE = 0x08,
STATE_POWEROFF = 0x10,
};
static uint32_t adc_buf[4];
ISR(ADC_vect)
{
static uint8_t cnt;
/* use result of second conversion (switching ADC-gain needs time..) */
if (cnt & 0x01) {
/* moving average filter */
uint8_t ch = (cnt >> 1);
adc_buf[ch] = ((adc_buf[ch] * 7) + ((uint32_t)ADC << 8)) /8;
uint16_t value = (adc_buf[ch] >> 8) + ((adc_buf[ch] & 0x80) ? 1 : 0);
if (ch == 0 && (adc_buf[0] >= adc_buf[1])) {
/* charging: positive current = ADC * 1.25 */
adc_value[ADC_CURRENT] = value + (value >> 2);
} else if (ch == 1 && (adc_buf[0] < adc_buf[1])) {
/* discharging: negative current = ADC * -1.25 */
adc_value[ADC_CURRENT] = -(value + (value >> 2));
} else if (ch == 2) {
/* Ubat = (ADC * 21.28) - (Ishunt * 0.1) */
adc_value[ADC_UBAT] = (value * 21) + ((value * 9) >> 5) - (adc_value[ADC_CURRENT] / 10);
} else if (ch == 3) {
/* Uin = ADC * 21.28 */
adc_value[ADC_UIN] = (value * 21) + ((value * 9) >> 5);
/* all values updated */
adc_update = 1;
}
}
/* trigger next conversion */
cnt = (cnt +1) & 0x07;
ADMUX = adc_mux[cnt >> 1];
ADCSRA |= (1<<ADSC);
}
2009-03-11 21:57:57 +01:00
static uint8_t i2c_reg;
2009-03-11 21:25:34 +01:00
void usi_write(uint8_t data, uint8_t bcnt)
{
2009-03-11 21:57:57 +01:00
/* select register */
2009-03-11 21:25:34 +01:00
if (bcnt == 0) {
2009-03-11 21:57:57 +01:00
i2c_reg = data;
return;
}
bcnt--;
/* reg 0x00, lowbyte -> write control */
if (i2c_reg == 0x00 && bcnt == 0x00) {
2009-03-11 21:25:34 +01:00
/* poweroff is always allowed */
if (data & STATE_POWEROFF)
sys_state = data;
/* discharge cmd is never allowed */
else if (data & STATE_DISCHARGE)
return;
/* during discharge nothing is allowed */
else if (sys_state != STATE_DISCHARGE)
sys_state = data;
2009-03-11 21:57:57 +01:00
/* register word 0x20 - 0x26 eeprom parameters */
} else if (i2c_reg >= 0x20 && i2c_reg <= 0x26) {
2009-03-11 21:25:34 +01:00
uint8_t *ptr = (uint8_t *)&params;
2009-03-11 21:57:57 +01:00
uint8_t index = ((i2c_reg - 0x20) << 1) + (bcnt & 0x01);
ptr[index] = data;
2009-03-11 21:25:34 +01:00
2009-03-11 21:57:57 +01:00
/* crc field */
if (i2c_reg == 0x26 && bcnt == 0x01)
2009-03-11 21:25:34 +01:00
write_parameters();
}
}
uint8_t usi_read(uint8_t bcnt)
{
2009-03-11 21:57:57 +01:00
/* snapshot of adc value */
static int16_t adc_copy;
2009-03-11 21:25:34 +01:00
2009-03-11 21:57:57 +01:00
/* reg 0x00, lowbyte -> read status */
if (i2c_reg == 0 && bcnt == 0) {
2009-03-11 21:25:34 +01:00
return sys_state;
2009-03-11 21:57:57 +01:00
/* register word 0x10 - 0x12 adc values */
} else if (i2c_reg >= 0x10 && i2c_reg <= 0x12) {
if (bcnt == 0x00) {
adc_copy = adc_value[i2c_reg - 0x10];
return adc_copy & 0xFF;
2009-03-11 21:25:34 +01:00
2009-03-11 21:57:57 +01:00
} else {
return (adc_copy >> 8) & 0xFF;
}
2009-03-11 21:25:34 +01:00
2009-03-11 21:57:57 +01:00
/* register word 0x20 - 0x25 eeprom parameters */
} else if (i2c_reg >= 0x20 && i2c_reg <= 0x25) {
uint8_t *ptr = (uint8_t *)&params;
uint8_t index = ((i2c_reg - 0x20) << 1) + (bcnt & 0x01);
2009-03-11 21:25:34 +01:00
return ptr[index];
2009-03-11 21:57:57 +01:00
}
2009-03-11 21:25:34 +01:00
2009-03-15 14:41:24 +01:00
return 0x00;
2009-03-11 21:25:34 +01:00
}
/*
* the watchdog timer remains active even after a
* system reset. So disable it as soon as possible.
* automagically called on startup
*/
void disable_wdt_timer(void) __attribute__((naked, section(".init3")));
void disable_wdt_timer(void)
{
MCUSR = 0;
WDTCSR = (1<<WDCE) | (1<<WDE);
WDTCSR = (0<<WDE);
}
int main(void)
{
PORTA = I2C_SCL | I2C_SDA;
DDRA = I2C_SCL | LED_GN;
DDRB = EN_POWER | EN_CHARGER | EN_TEST;
/* Disable Digital Inputs */
DIDR0 = AIN_REF | AIN_CURRENT_N | AIN_CURRENT_P | AIN_VOLTAGE_BAT | AIN_VOLTAGE_SUP;
/* ADC: freerunning with F_CPU/128 */
ADMUX = adc_mux[0];
ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADIF) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
/* init TWI statemachine */
usi_statemachine();
/* read statemachine parameters from eeprom */
read_parameters();
sei();
/* enable power */
sys_state = STATE_IDLE;
PORTB = EN_POWER;
/* wait 1s for moving average filters */
uint8_t i;
for (i = 0; i < 50; i++)
_delay_ms(20);
while (1) {
while (!adc_update);
adc_update = 0;
/* disable interrupts while using adc_value and params */
cli();
/* loss of external power => discharge */
if (sys_state & (STATE_IDLE | STATE_TEST | STATE_CHARGE))
if (adc_value[ADC_UIN] < params.uin_loss)
sys_state = STATE_DISCHARGE;
/* external power restored => charge */
if (sys_state & (STATE_DISCHARGE | STATE_POWEROFF))
if (adc_value[ADC_UIN] > params.uin_restore)
sys_state = STATE_CHARGE;
/* battery is low => charge */
if (sys_state & (STATE_IDLE | STATE_TEST))
if (adc_value[ADC_UBAT] < params.ubat_low)
sys_state = STATE_CHARGE;
switch (sys_state) {
case STATE_IDLE:
PORTB = EN_POWER;
break;
case STATE_TEST:
PORTB = EN_POWER | EN_TEST;
break;
case STATE_CHARGE:
PORTB = EN_POWER | EN_CHARGER;
/* battery is full => idle */
if (adc_value[ADC_UBAT] > params.ubat_full && adc_value[ADC_CURRENT] < params.ibat_full)
sys_state = STATE_IDLE;
break;
case STATE_DISCHARGE:
PORTB = EN_POWER;
/* battery is critical low => poweroff */
if (adc_value[ADC_UBAT] < params.ubat_critical)
sys_state = STATE_POWEROFF;
break;
case STATE_POWEROFF:
PORTB = 0x00;
break;
default:
sys_state = STATE_IDLE;
break;
}
/* re-enable interrupts */
sei();
}
}