/*************************************************************************** * 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 #include #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< write control */ if (i2c_reg == 0x00 && bcnt == 0x00) { /* 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; /* register word 0x20 - 0x26 eeprom parameters */ } else if (i2c_reg >= 0x20 && i2c_reg <= 0x26) { uint8_t *ptr = (uint8_t *)¶ms; uint8_t index = ((i2c_reg - 0x20) << 1) + (bcnt & 0x01); ptr[index] = data; /* crc field */ if (i2c_reg == 0x26 && bcnt == 0x01) write_parameters(); } } uint8_t usi_read(uint8_t bcnt) { /* snapshot of adc value */ static int16_t adc_copy; /* reg 0x00, lowbyte -> read status */ if (i2c_reg == 0 && bcnt == 0) { return sys_state; /* 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; } else { return (adc_copy >> 8) & 0xFF; } /* register word 0x20 - 0x25 eeprom parameters */ } else if (i2c_reg >= 0x20 && i2c_reg <= 0x25) { uint8_t *ptr = (uint8_t *)¶ms; uint8_t index = ((i2c_reg - 0x20) << 1) + (bcnt & 0x01); return ptr[index]; } return 0x00; } /* * 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< 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(); } }