250 lines
6.4 KiB
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 *)¶ms;
|
|
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 *)¶ms;
|
|
|
|
/* 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();
|
|
}
|
|
}
|