alix-usv/alix-usv.c

250 lines
6.4 KiB
C

/***************************************************************************
* 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);
}
void usi_write(uint8_t data, uint8_t bcnt)
{
if (bcnt == 0) {
/* 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;
} else {
uint8_t index = (bcnt -1);
uint8_t *ptr = (uint8_t *)&params;
if (index < sizeof(params))
ptr[index] = data;
if (index == (sizeof(params) -1))
write_parameters();
}
}
uint8_t usi_read(uint8_t bcnt)
{
static int16_t adc_copy[3];
/* first byte read is sys_state */
if (bcnt == 0) {
/* take a snapshot in interrupt-mode */
adc_copy[ADC_CURRENT] = adc_value[ADC_CURRENT];
adc_copy[ADC_UBAT] = adc_value[ADC_UBAT];
adc_copy[ADC_UIN] = adc_value[ADC_UIN];
return sys_state;
}
uint8_t index = (bcnt -1);
uint8_t *ptr = (uint8_t *)adc_copy;
/* Current and Voltages (in mA/mV) */
if (index < sizeof(adc_copy))
return ptr[index];
index -= sizeof(adc_copy);
ptr = (uint8_t *)&params;
/* eeprom parameters (no snapshot needed, changed only in usi_write) */
if (index < sizeof(params))
return ptr[index];
return 0xFF;
}
/*
* 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();
}
}