working with i2c ioctl

This commit is contained in:
Olaf Rempel 2009-03-11 21:25:34 +01:00
commit ccf0fbd21a
10 changed files with 755 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.o
*.elf
*.bin
*.hex
*.lst
*.map
Host/i2c-test

9
Host/Makefile Normal file
View File

@ -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

175
Host/i2c-test.c Normal file
View File

@ -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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
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 "<UNKNOWN>";
}
}
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;
}

48
Makefile Normal file
View File

@ -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

249
alix-usv.c Normal file
View File

@ -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 <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();
}
}

32
alix-usv.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef _MAIN_H_
#define _MAIN_H_
#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
#define EN_POWER (1<<PORTB0)
#define EN_CHARGER (1<<PORTB1)
#define EN_TEST (1<<PORTB2)
#define AIN_REF (1<<PINA0)
#define AIN_CURRENT_N (1<<PINA1)
#define AIN_CURRENT_P (1<<PINA2)
#define AIN_VOLTAGE_BAT (1<<PINA3)
#define I2C_SCL (1<<PORTA4)
#define LED_GN (1<<PORTA5)
#define I2C_SDA (1<<PORTA6)
#define AIN_VOLTAGE_SUP (1<<PINA7)
#define I2C_ADDRESS (0x10 << 1)
#define CH_CURRENT_P ((1<<REFS0) | 0x0d) /* current A1->A2, 20x gain */
#define CH_CURRENT_N ((1<<REFS0) | 0x2d) /* current A2->A1, 20x gain */
#define CH_VOLTAGE_BAT ((1<<REFS0) | 0x03) /* voltage A3->gnd, 1x gain */
#define CH_VOLTAGE_SUP ((1<<REFS0) | 0x07) /* voltage A7->gnd, 1x gain */
void usi_write(uint8_t data, uint8_t bcnt);
uint8_t usi_read(uint8_t bcnt);
#endif // _USI_I2C_SLAVE_H_

61
eeprom.c Normal file
View File

@ -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 <avr/eeprom.h>
#include <util/crc16.h>
#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 *)&params;
for (i = 0; i < sizeof(struct ee_param) -2; i++)
crc = _crc_ccitt_update(crc, *tmp++);
params.crc16 = crc;
eeprom_write_block(&params, &params_in_eeprom, sizeof(struct ee_param));
}
uint8_t read_parameters(void)
{
eeprom_read_block(&params, &params_in_eeprom, sizeof(struct ee_param));
uint8_t i;
uint16_t crc = 0x0000;
uint8_t *tmp = (uint8_t *)&params;
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 *)&params;
while (i--)
*dst++ = *src++;
write_parameters();
return 1;
}
return 0;
}

27
eeprom.h Normal file
View File

@ -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

141
usi-i2c-slave.c Normal file
View File

@ -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 <avr/io.h>
#include <avr/interrupt.h>
#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<<USISIE) | (1<<USIWM1) | (1<<USICS1);
/* Clear Interrupt Flags, Clear Stop Condition Flag, wait for 2 edges */
USISR = (1<<USISIF) | (1<<USIOIF) | (1<<USIPF) | ((16 -2)<<USICNT0);
} else if (state == USI_SLA) {
/* Enable Overflow Interrupt, TWI Mode, extend SCL on Overflow */
USICR = (1<<USISIE) | (1<<USIOIE) | (1<<USIWM1) | (1<<USIWM0) | (1<<USICS1);
/* Clear Interrupt Flags, Clear Stop Condition Flag, wait for 16 edges */
USISR = (1<<USISIF) | (1<<USIOIF) | (1<<USIPF) | ((16 -16)<<USICNT0);
} else {
if (usi_state & USI_MASK_OUTPUT) {
DDRA |= I2C_SDA;
} else {
DDRA &= ~I2C_SDA;
}
if (usi_state & USI_MASK_ACKBIT) {
USIDR = 0x00;
USISR = (1<<USIOIF) | ((16 -2)<<USICNT0);
} else {
USISR = (1<<USIOIF) | ((16 -16)<<USICNT0);
}
}
}
ISR(USI_START_vect)
{
usi_state = USI_START;
/* check for STOP Condition */
while (PINA & I2C_SCL) {
if (PINA & I2C_SDA) {
usi_state = USI_RESET;
break;
}
}
usi_statemachine();
}
ISR(USI_OVF_vect)
{
usi_statemachine();
}

6
usi-i2c-slave.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef _USI_I2C_SLAVE_H_
#define _USI_I2C_SLAVE_H_
void usi_statemachine(void);
#endif // _USI_I2C_SLAVE_H_