commit ccf0fbd21a1fe8a93a73afd0058aa4b48fb353a4 Author: Olaf Rempel Date: Wed Mar 11 21:25:34 2009 +0100 working with i2c ioctl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f1c699 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.o +*.elf +*.bin +*.hex +*.lst +*.map +Host/i2c-test diff --git a/Host/Makefile b/Host/Makefile new file mode 100644 index 0000000..2b7ca13 --- /dev/null +++ b/Host/Makefile @@ -0,0 +1,9 @@ +all: + gcc -O2 -Wall i2c-test.c -o i2c-test + +driver: + sudo modprobe i2c-dev + +perm: + sudo chgrp dialout /dev/i2c-0 + diff --git a/Host/i2c-test.c b/Host/i2c-test.c new file mode 100644 index 0000000..44f08d6 --- /dev/null +++ b/Host/i2c-test.c @@ -0,0 +1,175 @@ +/* + * config file: + * - i2c device + * - i2c address + * - email-address + * - reporting config (when logging / mailing) + * - thresholds for do shutdown + * + * parameters: + * -d run as daemon + * -f foreground + * -v debug + * -c [idle|charge|test|poweroff] command (not with -d) + * --config + * --help + * + * in command mode try to use daemon first (via unix-socket) + * then fallback to i2c + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +struct read_data { + uint8_t sys_state; + + int16_t adc_current; + int16_t adc_ubat; + int16_t adc_uin; + + int16_t uin_loss; + int16_t uin_restore; + int16_t ubat_full; + int16_t ubat_low; + int16_t ubat_critical; + int16_t ibat_full; + uint16_t crc16; +} __attribute__((__packed__)); + +struct write_data { + uint8_t sys_state; + + int16_t uin_loss; + int16_t uin_restore; + int16_t ubat_full; + int16_t ubat_low; + int16_t ubat_critical; + int16_t ibat_full; + uint16_t crc16; +} __attribute__((__packed__)); + +enum { + STATE_IDLE = 0x01, + STATE_TEST = 0x02, + STATE_CHARGE = 0x04, + STATE_DISCHARGE = 0x08, + STATE_POWEROFF = 0x10, +}; + +static char * state2str(uint8_t state) +{ + switch (state) { + case STATE_IDLE: + return "STATE_IDLE"; + + case STATE_TEST: + return "STATE_TEST"; + + case STATE_CHARGE: + return "STATE_CHARGE"; + + case STATE_DISCHARGE: + return "STATE_DISCHARGE"; + + case STATE_POWEROFF: + return "STATE_POWEROFF"; + + default: + return ""; + } +} + +static int i2c_open(const char *path) +{ + int fd = open(path, O_RDWR); + if (fd < 0) { + perror("open()"); + return -1; + } + + unsigned long funcs; + if (ioctl(fd, I2C_FUNCS, &funcs)) { + perror("ioctl(I2C_FUNCS)"); + close(fd); + return -1; + } + + if (!(funcs & I2C_FUNC_I2C)) { + fprintf(stderr, "I2C_FUNC_I2C not supported!\n"); + close(fd); + return -1; + } + + return fd; +} + +static int i2c_setaddress(int fd, int address) +{ + if (ioctl(fd, I2C_SLAVE, address) < 0) { + perror("ioctl(I2C_SLAVE)"); + close(fd); + return -1; + } + return 0; +} + +int main(int argc, char *argv[]) +{ + int fd = i2c_open("/dev/i2c-0"); + if (fd < 0) + exit(-1); + + if (i2c_setaddress(fd, 0x10) < 0) + exit(-1); + + int cnt = 0; + + while (1) { + struct read_data rdbuf; + memset(&rdbuf, 0, sizeof(rdbuf)); + + int ret = read(fd, &rdbuf, sizeof(rdbuf)); + if (ret <= 0) + perror("read()"); + + printf("state:0x%02x I:%5dmA Ubat:%5dmV Usup:%5dmV (%s) [%5d,%5d,%5d,%5d,%5d,%5d,0x%04x]\n", + rdbuf.sys_state, rdbuf.adc_current, rdbuf.adc_ubat, + rdbuf.adc_uin, state2str(rdbuf.sys_state), + rdbuf.uin_loss, rdbuf.uin_restore, rdbuf.ubat_full, + rdbuf.ubat_low, rdbuf.ubat_critical, rdbuf.ibat_full, + rdbuf.crc16); + + sleep(1); + cnt++; + + if (cnt == 5) { + struct write_data wrbuf = { + .sys_state = STATE_CHARGE, + .uin_loss = 12000, + .uin_restore = 14000, + .ubat_full = 13300, + .ubat_low = 12000, + .ubat_critical = 10800, + .ibat_full = 150, + .crc16 = 0x0000, + }; + +// write (fd, &wrbuf, sizeof(wrbuf)); + write (fd, &wrbuf, 1); + } + } + close(fd); + + return 0; +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9760876 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +PRG = alix-usv +OBJ = alix-usv.o eeprom.o usi-i2c-slave.o +MCU_TARGET = attiny84 +OPTIMIZE = -Os + +DEFS = +LIBS = + +# Override is only needed by avr-lib build system. +override CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS) +override LDFLAGS = -Wl,-Map,$(PRG).map + +CC = avr-gcc +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +SIZE = avr-size + +all: $(PRG).elf lst text + $(SIZE) -x -A $(PRG).elf + +$(PRG).elf: $(OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) + +clean: + rm -rf *.o *.lst *.map $(PRG).elf *.hex *.bin + +lst: $(PRG).lst + +%.lst: %.elf + $(OBJDUMP) -h -S $< > $@ + +text: hex bin + +hex: $(PRG).hex +bin: $(PRG).bin + +%.hex: %.elf + $(OBJCOPY) -j .text -j .data -O ihex $< $@ + +%.bin: %.elf + $(OBJCOPY) -j .text -j .data -O binary $< $@ + +install: text + avrdude -c dragon_isp -P usb -p t84 -U flash:w:$(PRG).hex + +# no self programming, 2.7V BOD, 8MHz internal RC Osz. +fuses: + avrdude -c dragon_isp -P usb -p t84 -U lfuse:w:0xc2:m -U hfuse:w:0xdd:m -U efuse:w:0xff:m diff --git a/alix-usv.c b/alix-usv.c new file mode 100644 index 0000000..c8b99b8 --- /dev/null +++ b/alix-usv.c @@ -0,0 +1,249 @@ +/*************************************************************************** + * 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< 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(); + } +} diff --git a/alix-usv.h b/alix-usv.h new file mode 100644 index 0000000..a9eabfb --- /dev/null +++ b/alix-usv.h @@ -0,0 +1,32 @@ +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#include + +#define F_CPU 8000000 +#include + +#define EN_POWER (1<A2, 20x gain */ +#define CH_CURRENT_N ((1<A1, 20x gain */ +#define CH_VOLTAGE_BAT ((1<gnd, 1x gain */ +#define CH_VOLTAGE_SUP ((1<gnd, 1x gain */ + +void usi_write(uint8_t data, uint8_t bcnt); +uint8_t usi_read(uint8_t bcnt); + +#endif // _USI_I2C_SLAVE_H_ diff --git a/eeprom.c b/eeprom.c new file mode 100644 index 0000000..9e4fa12 --- /dev/null +++ b/eeprom.c @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 02/2008 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 "eeprom.h" + +static const struct ee_param defaults = DEFAULT_PARAMETERS; + +struct ee_param params; +struct ee_param params_in_eeprom EEMEM = DEFAULT_PARAMETERS; + +void write_parameters(void) +{ + uint8_t i; + uint16_t crc = 0x0000; + uint8_t *tmp = (uint8_t *)¶ms; + for (i = 0; i < sizeof(struct ee_param) -2; i++) + crc = _crc_ccitt_update(crc, *tmp++); + + params.crc16 = crc; + eeprom_write_block(¶ms, ¶ms_in_eeprom, sizeof(struct ee_param)); +} + +uint8_t read_parameters(void) +{ + eeprom_read_block(¶ms, ¶ms_in_eeprom, sizeof(struct ee_param)); + + uint8_t i; + uint16_t crc = 0x0000; + uint8_t *tmp = (uint8_t *)¶ms; + for (i = 0; i < sizeof(struct ee_param); i++) + crc = _crc_ccitt_update(crc, *tmp++); + + if (crc != 0x0000) { + i = sizeof(struct ee_param); + uint8_t *src = (uint8_t *)&defaults; + uint8_t *dst = (uint8_t *)¶ms; + while (i--) + *dst++ = *src++; + + write_parameters(); + return 1; + } + return 0; +} diff --git a/eeprom.h b/eeprom.h new file mode 100644 index 0000000..7bf184f --- /dev/null +++ b/eeprom.h @@ -0,0 +1,27 @@ +#ifndef _EEPROM_PARAMETERS_H_ +#define _EEPROM_PARAMETERS_H_ + +struct ee_param { + int16_t uin_loss; + int16_t uin_restore; + int16_t ubat_full; + int16_t ubat_low; + int16_t ubat_critical; + int16_t ibat_full; + + uint16_t crc16; +}; + +#define DEFAULT_PARAMETERS { \ + .uin_loss = 12000, \ + .uin_restore = 14000, \ + .ubat_full = 13300, \ + .ubat_low = 12000, \ + .ubat_critical = 11000, \ + .ibat_full = 150, \ +}; + +uint8_t read_parameters(void); +void write_parameters(void); + +#endif diff --git a/usi-i2c-slave.c b/usi-i2c-slave.c new file mode 100644 index 0000000..9f02c3c --- /dev/null +++ b/usi-i2c-slave.c @@ -0,0 +1,141 @@ +/*************************************************************************** + * 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 "usi-i2c-slave.h" + +#define USI_MASK 0x0F +#define USI_MASK_ACKBIT 0x10 /* one (ack)bit / 8bits */ +#define USI_MASK_OUTPUT 0x20 /* SDA is output / input */ + +#define USI_RESET 0x00 /* wait for Start Condition */ +#define USI_START 0x01 /* Start detected */ +#define USI_SLA 0x02 /* wait for Slave Address */ +#define USI_SLAW_ACK 0x03 /* ACK Slave Address + Write (Master writes) */ +#define USI_SLAR_ACK 0x04 /* ACK Slave Address + Read (Master reads) */ +#define USI_DATW 0x05 /* receive Data */ +#define USI_DATW_ACK 0x06 /* transmit ACK for received Data */ +#define USI_DATR 0x07 /* transmit Data */ +#define USI_DATR_ACK 0x08 /* receive ACK for transmitted Data */ + +static uint8_t usi_state; + +void usi_statemachine(void) +{ + static uint8_t bcnt; + + uint8_t tmp = USIDR; + uint8_t state = usi_state & USI_MASK; + + /* Start Condition detected */ + if (state == USI_START) { + usi_state = USI_SLA; + + /* Slave Address received => prepare ACK/NACK */ + } else if (state == USI_SLA) { + bcnt = 0; + if (tmp == (I2C_ADDRESS | 0x00)) + usi_state = USI_SLAW_ACK | USI_MASK_ACKBIT | USI_MASK_OUTPUT; + + else if (tmp == (I2C_ADDRESS | 0x01)) + usi_state = USI_SLAR_ACK | USI_MASK_ACKBIT | USI_MASK_OUTPUT; + + else + usi_state = USI_RESET; + + /* ACK after SLA+W / Data received => prepare data receive */ + } else if (state == USI_SLAW_ACK || state == USI_DATW_ACK) { + usi_state = USI_DATW; + + /* Data received => prepare ACK transmit */ + } else if (state == USI_DATW) { + usi_write(tmp, bcnt++); + usi_state = USI_DATW_ACK | USI_MASK_ACKBIT | USI_MASK_OUTPUT; + + /* ACK after SLA+R transmitted / ACK after Data received => prepare data transmit */ + } else if (state == USI_SLAR_ACK || (state == USI_DATR_ACK && tmp == 0x00)) { + USIDR = usi_read(bcnt++); + usi_state = USI_DATR | USI_MASK_OUTPUT; + + /* Data transmitted => prepare ACK receive */ + } else if (state == USI_DATR) { + usi_state = USI_DATR_ACK | USI_MASK_ACKBIT; + + /* NACK after Data received / default => go idle */ + } else { + usi_state = USI_RESET; + } + + state = usi_state & USI_MASK; + if (state == USI_RESET) { + /* input */ + DDRA &= ~I2C_SDA; + + /* Enable StartCondition Interrupt, TWI Mode, count both edges */ + USICR = (1<