Rev 902 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/****************************************************************************
* Copyright (C) 2009-2010 by Claas Anders "CaScAdE" Rathje *
* admiralcascade@gmail.com *
* Project-URL: http://www.mylifesucks.de/oss/c-strom/ *
* *
* 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; either 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. *
* *
* Thanks to: *
* Klaus "akku" Buettner for the hardware *
* All people at http://www.rn-wissen.de especially for the i2c stuff *
* *
****************************************************************************/
#include <avr/io.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include <string.h>
#include "C-Strom.h"
#include "spi_union.h"
#include "i2c_slave.h"
uint8_t EEMEM ee_checkbyte1
= CHECKBYTE1
;
uint8_t EEMEM ee_checkbyte2
= CHECKBYTE2
;
uint16_t EEMEM ee_cal_ampere
= 512;
uint8_t EEMEM ee_sensor
= 50;
uint8_t EEMEM ee_prim_r1
= 47, ee_prim_r2
= 150;
uint8_t EEMEM ee_anin_r1
= 47, ee_anin_r2
= 150;
uint8_t EEMEM ee_config
= 0;
volatile uint8_t CSTROM_FLAGS
= 0;
volatile uint8_t CSTROM_CONFIG
= 0;
// we could use ee_cal_ampere but eeprom is slow :)
volatile uint16_t cal_ampere
= 512;
volatile uint8_t sensor
= 50;
volatile uint8_t prim_r1
= 47, prim_r2
= 150;
volatile uint8_t anin_r1
= 47, anin_r2
= 150;
volatile int16_t ampere
, volt
, anin_volt
, transfer_ampere
;
volatile int32_t transfer_mah
, mah
;
volatile int16_t average_ampere
= 0;
volatile uint8_t hwver
= 10;
// global space for int conversion to string
char s
[10];
// spi buffer
union SPI_buffer_t SPI_buffer
;
// PD7 High
void PD7_H
() {
PORTD
|= (1 << PD7
);
}
// PD7 Low
void PD7_L
() {
PORTD
&= ~
(1 << PD7
);
}
void (*LED_ON
)(void) = PD7_H
;
void (*LED_OFF
)(void) = PD7_L
;
void ampere_calibrate
();
void save_eeprom
();
void help
(uint8_t);
/*ISR(__vector_default) {
asm("nop");
}*/
/**
* decimal itoa for 10th values
*/
char *itoa_dec
(int val
, char* s
) {
itoa(val
, s
, 10);
//char x = 0;
for (uint8_t i
= 0; i
< 9; i
++) {
if (s
[i
] == 0 && i
> 0) {
if (i
== 1) {
s
[i
+1] = s
[i
-1];
s
[i
-1] = '0';
s
[i
] = '.';
s
[i
+2] = 0;
} else {
s
[i
] = s
[i
-1];
s
[i
-1] = '.';
s
[i
+1] = 0;
}
break;
}
}
return s
;
}
/**
* init uart
*/
void uart_init
() {
UBRRL
= (F_CPU
/ (16UL
* BAUD_RATE
)) - 1;
// Enable receiver and transmitter; enable RX interrupt
UCSRB
= (1 << RXEN
) | (1 << TXEN
) | (1 << RXCIE
);
//asynchronous 8N1
UCSRC
= (1 << URSEL
) | (3 << UCSZ0
);
}
/**
* send a single <character> through uart
*/
void uart_putc
(unsigned char character
) {
// wait until UDR ready
while (!(UCSRA
& (1 << UDRE
)));
UDR
= character
;
}
/**
* send a <string> throught uart
*/
void uart_puts
(char *s
) {
while (*s
) {
uart_putc
(*s
);
s
++;
}
}
/**
* send a <string> from pgm space throught uart
*/
void uart_puts_pgm
(char *string
) {
while (pgm_read_byte
(string
) != 0x00)
uart_putc
(pgm_read_byte
(string
++));
}
/**
* change the sensor type
*/
void sensor_change
(uint8_t new_value
) {
if (new_value
< 10) new_value
= 0;
else if (new_value
> 250) new_value
= 250;
sensor
= new_value
;
uart_puts_pgm
(PSTR
("\r\nSensor is now: "));
uart_puts
(itoa(sensor
, s
, 10));
uart_puts
("A\r\n");
}
/**
* change the r2 value
*/
void r2_change
(uint8_t which
, uint8_t new_value
) {
if (which
== V_ANIN
) {
uart_puts_pgm
(PSTR
("\r\nANIN R2 is now: "));
anin_r2
= new_value
;
uart_puts
(itoa_dec
(anin_r2
, s
));
} else {
uart_puts_pgm
(PSTR
("\r\nPRIMARY R2 is now: "));
prim_r2
= new_value
;
uart_puts
(itoa_dec
(prim_r2
, s
));
}
uart_puts_pgm
(PSTR
("kOhm\r\n"));
}
/**
* enable/disable TWI
*/
void twi_change
() {
uart_puts_pgm
(PSTR
("\r\nTWI turned "));
if (CSTROM_CONFIG
& CSTROM_TWI
) {
uart_puts_pgm
(PSTR
("ON"));
} else {
uart_puts_pgm
(PSTR
("OFF"));
}
uart_puts_pgm
(PSTR
(". Please restart...\r\n"));
}
/**
* Interrupt handler for received data through UART1
*/
SIGNAL
(SIG_UART_RECV
) {
unsigned char c
= UDR
;
switch (c
) {
case 'c':
ampere_calibrate
();
break;
case 's':
save_eeprom
();
break;
case '+':
sensor_change
(100);
break;
case '-':
sensor_change
(50);
break;
case 'e':
if (hwver
== 11) r2_change
(V_ANIN
, anin_r2
+ 1);
break;
case 'd':
if (hwver
== 11) r2_change
(V_ANIN
, anin_r2
- 1);
break;
case 'r':
r2_change
(V_PRIMARY
, prim_r2
+ 1);
break;
case 'f':
r2_change
(V_PRIMARY
, prim_r2
- 1);
break;
case 'T':
CSTROM_CONFIG
^= CSTROM_TWI
;
twi_change
();
break;
case 'h':
help
(0);
break;
default:
asm
("nop"); // :-)
}
}
/**
* Interrupt handler for transmitting data through UART1
*/
SIGNAL
(SIG_UART_TRANS
) {
}
/**
* Read out the ADC channel <channel>
*/
uint16_t readADC
(uint8_t channel
) {
uint8_t i
;
uint16_t result
= 0;
// enable ADC and set clk div to 64
ADCSRA
= (1<<ADEN
) | (1<<ADPS2
) | (1<<ADPS1
);
_delay_us
(5);
// set up channel
ADMUX
= channel
;
// use internal reference
//ADMUX |= (1<<REFS1) | (1<<REFS0);
// init ADC for a dummy readout
ADCSRA
|= (1<<ADSC
);
// wait for conversion to be complete
while(ADCSRA
& (1<<ADSC
));
// read in three times and get the average
for(i
=0; i
<3; i
++) {
// start conversion
ADCSRA
|= (1<<ADSC
);
// wait for conversion to be complete
while(ADCSRA
& (1<<ADSC
));
// add up result
result
+= ADCW
;
}
// disable ADC
ADCSRA
&= ~
(1<<ADEN
);
// get average
result
/= 3;
return result
;
}
/**
* init SPI slave interrupt conrolled
*/
void Init_Slave_IntContr
(void) {
volatile char IOReg
;
// Set PB4(MISO) as output
DDRB
= (1<<PB4
);
// MOSI Pullup
PORTB
|= _BV
(3);
// Enable SPI Interrupt and SPI in Slave Mode
SPCR
= (1<<SPIE
)|(1<<SPE
);
IOReg
= SPSR
; // Clear SPIF bit in SPSR
IOReg
= SPDR
;
SPCR
|= _BV
(SPIE
); // duplicated
}
/**
* SPI interrupt handling
*/
ISR
(SPI_STC_vect
) {
LED_ON
();
unsigned char foo
;
foo
= SPDR
;
//uart_putc(foo);
switch (foo
) {
case 'A': // requested ampere high bits for next transmission
CSTROM_FLAGS
|= CSTROM_SPILOCKED
;
foo
= SPI_buffer.
buffer.
c[0];
break;
case 'B': // requested low bits
foo
= SPI_buffer.
buffer.
c[1];
break;
case 'C': // wasted ampere high bits in next
foo
= SPI_buffer.
buffer.
c[2];
break;
case 'D': // 2nd highest 8bits
foo
= SPI_buffer.
buffer.
c[3];
break;
case 'E': // 3rd highest 8bits
foo
= SPI_buffer.
buffer.
c[4];
break;
case 'F': // lowest 8bits
foo
= SPI_buffer.
buffer.
c[5];
break;
case 'G': // lowest 8bits
foo
= SPI_buffer.
buffer.
c[6];
break;
case 'H': // lowest 8bits
foo
= SPI_buffer.
buffer.
c[7];
break;
case 'I': // challange over
foo
= 'd'; // done :)
CSTROM_FLAGS
&= ~CSTROM_SPILOCKED
;
break;
default: // what else? nothin now
foo
= 'X';
}
// write back foo in next transmission
SPDR
= foo
;
//uart_putc(foo);
LED_OFF
();
}
/**
* read data saved in eeprom
*/
void get_eeprom
() {
if (eeprom_read_byte
(&ee_checkbyte1
) == CHECKBYTE1
&& eeprom_read_byte
(&ee_checkbyte2
) == CHECKBYTE2
) {
uart_puts
("\tLoading data from eeprom...");
sensor
= eeprom_read_byte
(&ee_sensor
);
cal_ampere
= eeprom_read_word
(&ee_cal_ampere
);
anin_r1
= eeprom_read_byte
(&ee_anin_r1
);
anin_r2
= eeprom_read_byte
(&ee_anin_r2
);
prim_r1
= eeprom_read_byte
(&ee_prim_r1
);
prim_r2
= eeprom_read_byte
(&ee_prim_r2
);
CSTROM_CONFIG
= eeprom_read_byte
(&ee_config
);
uart_puts
("done\r\n");
} else {
uart_puts
("\tNo data found in eeprom, using default data...\r\n");
}
}
/**
* save data to eeprom
*/
void save_eeprom
() {
uart_puts
("\r\nSaving data to eeprom...");
eeprom_write_byte
(&ee_checkbyte1
, CHECKBYTE1
);
eeprom_write_byte
(&ee_checkbyte2
, CHECKBYTE2
);
eeprom_write_byte
(&ee_sensor
, sensor
);
eeprom_write_word
(&ee_cal_ampere
, cal_ampere
);
//if (hwver == 11) {
// why not saving when not needed, there is space
eeprom_write_byte
(&ee_anin_r1
, anin_r1
);
eeprom_write_byte
(&ee_anin_r2
, anin_r2
);
//}
eeprom_write_byte
(&ee_prim_r1
, prim_r1
);
eeprom_write_byte
(&ee_prim_r2
, prim_r2
);
eeprom_write_byte
(&ee_config
, CSTROM_CONFIG
);
uart_puts
("done\r\n");
}
/**
* calibrate the current sensor... has to be 0A during this time!
*/
void ampere_calibrate
() {
cli
();
uart_puts
("\r\nCalibrating...");
uint16_t temp_cal
= 0;
for (uint8_t i
= 0; i
< 10; i
++) {
temp_cal
+= readADC
(0);
uart_puts
("#");
_delay_ms
(100);
}
cal_ampere
= temp_cal
/ 10;
uart_puts
("done. Offset is now: ");
uart_puts
(itoa(cal_ampere
, s
, 10));
uart_puts
("\r\n");
sei
();
}
volatile uint16_t timer
= 0, cs
= 0;
/**
* init timer0
*/
void init_timer0
(void){
// set up timer
TCCR0
|= (1 << CS00
) | (1 << CS01
); // timer0 prescaler 64
TIMSK
|= (1 << TOIE0
); // enable overflow timer0
}
/**
* timer overflow handler, should be 1ms
*/
SIGNAL
(SIG_OVERFLOW0
) {
TCNT0
= 131; // preload
timer
++;
// this should be 100ms
if (timer
== 100) {
timer
= 0;
cs
++;
average_ampere
+= ampere
;
CSTROM_FLAGS
|= CSTROM_WRITEUART
;
}
// this should be 1s
if (cs
== 10) {
cs
= 0;
mah
+= average_ampere
/ 360;
average_ampere
= 0;
}
}
/**
* write <len> through uart spaces
*/
void write_space
(uint8_t len
) {
while (len
--) {
uart_putc
(' ');
}
}
/**
* check which hardware version we have here
*/
void check_hw
() {
// check if pin was output and has pullup
uint8_t old_DDRD7
= DDRD
& (1 << PD7
);
uint8_t old_PORTD7
= PORTD
& (1 << PD7
);
// if it was, make it input
if (old_DDRD7
) DDRD
&= ~
(1 << PD7
); // PD7 input (LED)
if (!old_PORTD7
) PORTD
|= (1 << PD7
); // PD7 enable pullup (LED)
if (PIND
& (1 << PD7
)) {
hwver
= 11;
LED_ON
= PD7_L
;
LED_OFF
= PD7_H
;
}
// output again
if (!old_PORTD7
) PORTD
&= ~
(1 << PD7
); // PD7 disable pullup (LED)
if (old_DDRD7
) DDRD
|= (1 << PD7
); // PD7 output (LED)
}
/**
* call for help whenever needed
*/
void help
(uint8_t load
) {
uart_puts_pgm
(PSTR
("\r\nC-STROM\r\n\tBUILD: "));
uart_puts_pgm
(PSTR
(BUILDDATE
));
uart_puts
("\r\n\tHW: ");
uart_puts
(itoa_dec
(hwver
, s
));
uart_puts
("\r\n");
if (load
) get_eeprom
();
uart_puts_pgm
(PSTR
("\tSensor: "));
uart_puts
(itoa(sensor
, s
, 10));
uart_puts_pgm
(PSTR
("A\tCalibration: "));
uart_puts
(itoa(cal_ampere
, s
, 10));
uart_puts_pgm
(PSTR
("\r\n\tTWI is "));
if (CSTROM_CONFIG
& CSTROM_TWI
) {
uart_puts_pgm
(PSTR
("ON, SPI may not work!!!"));
} else {
uart_puts_pgm
(PSTR
("OFF"));
}
uart_puts_pgm
(PSTR
("\r\n\tPIMARY R2: "));
uart_puts
(itoa_dec
(prim_r2
, s
));
if (hwver
== 11) {
uart_puts_pgm
(PSTR
("kOhm"));
uart_puts_pgm
(PSTR
("\tANIN R2: "));
uart_puts
(itoa_dec
(anin_r2
, s
));
}
uart_puts_pgm
(PSTR
("kOhm\r\n"));
uart_puts_pgm
(PSTR
("\tCommands available:\r\n"));
uart_puts_pgm
(PSTR
("\t\th : help on commands (this)\r\n"));
uart_puts_pgm
(PSTR
("\t\tc : calibrate ampere\r\n"));
uart_puts_pgm
(PSTR
("\t\tT : toggle TWI (may break SPI communication!)\r\n"));
uart_puts_pgm
(PSTR
("\t\t+/- : to change sensor\r\n"));
uart_puts_pgm
(PSTR
("\t\tr/f : to change PRIMARY-R2 Value\r\n"));
if (hwver
== 11) {
uart_puts_pgm
(PSTR
("\t\te/d : to change ANIN-R2 Value\r\n"));
}
uart_puts_pgm
(PSTR
("\t\ts : save values\r\n"));
uart_puts_pgm
(PSTR
("\tnow enjoy it and have fun...\r\n\r\n"));
}
/**
* Main
*/
int main
(void) {
DDRD
|= (1 << PD7
); // PD7 output (LED)
check_hw
();
uart_init
();
Init_Slave_IntContr
();
init_timer0
();
sei
(); // Enable Global Interrupts
uart_puts
("\x1B[2J\x1B[H"); // clear serial
help
(1);
if (CSTROM_CONFIG
& CSTROM_TWI
) init_twi_slave
(CSTROM_I2C
);
int16_t raw_volt
= 0, raw_ampere
= 0, raw_aninvolt
= 0;
char c
[10] = " ";
c
[9] = 0;
//strom_data = *((SPI_strom_data_t*) &spi_buffer);
//*spi_buffer = *((uint8_t*) (void*) &strom_data);
LED_ON
();
while (1) { // Loop Forever
// we have got a normal voltage measuring circuit that takes the lipo-voltage
raw_volt
= readADC
(1);
/* according to what i read about voltage divider it is
Uo = Ue * (R1 / (R2 + R1))
Ue = Uo * (R2 + R1) / R1
the board has got r1 = 4.7k and r2 = 15k
but since 1step is 0,0048828125V = 4,8828125mV and not 5mV there
is some conversion to do for raw_volt --**-> Uo
this should end up in 10th of volts */
raw_volt
= ((uint32_t)raw_volt
* (uint32_t)48828) / (uint32_t)10000;
volt
= (int16_t) (((uint32_t)raw_volt
* (uint32_t)(prim_r1
+ prim_r2
)) / (uint32_t)prim_r1
) / 100;
if (volt
< 0) volt
= 0;
// and we have got a seccond voltage measuring circuit for user voltages
raw_aninvolt
= readADC
(2);
/* some conversion to do for raw_volt --**-> Uo
this should end up in 10th of volts */
raw_aninvolt
= ((uint32_t)raw_aninvolt
* (uint32_t)48828) / (uint32_t)10000;
anin_volt
= (int16_t) (((uint32_t)raw_aninvolt
* (uint32_t)(anin_r1
+ anin_r2
)) / (uint32_t)anin_r1
) / 100;
if (anin_volt
< 0) anin_volt
= 0;
raw_ampere
= readADC
(0);
/* according to datasheet sensitivity is nominal 40mV per A for the 50A chip
this would mean 50A ^= 2V since 0A is set to 2.5V output Voltage we get
a range of 0.5V till 4.5V for the full range.
the atmega ADC features 0...5V range divided into 10bit ^= 1024 steps
so 0,0048828125V, or 4,8828125mV, is one step
this leads us to 0,8192 steps per 0,1A and somehow the below formula
and i know that 32bit is evil, but what else does this device has to do? :)
this should end up in 100th of ampere */
ampere
= (int16_t) (((int32_t)(((int16_t)raw_ampere
- (int16_t)cal_ampere
)) * (int32_t)10000) / (int32_t) 819);
if (sensor
== 100) ampere
*= 2;
if ((CSTROM_FLAGS
& CSTROM_WRITEUART
)) {
uart_puts
("V: ");
uart_puts
(itoa_dec
(volt
, s
));
write_space
(10-strlen(s
));
uart_puts
("AN-IN V: ");
uart_puts
(itoa_dec
(anin_volt
, s
));
write_space
(10-strlen(s
));
uart_puts
("A: ");
uart_puts
(itoa(ampere
, s
, 10));
write_space
(10-strlen(s
));
uart_puts
("C: ");
uart_puts
(itoa(mah
, s
, 10));
write_space
(10-strlen(s
));
uart_puts
("\r");
CSTROM_FLAGS
&= ~CSTROM_WRITEUART
;
}
//spi_buff
if (!(CSTROM_FLAGS
& CSTROM_SPILOCKED
)) {
// TESTTING
if (!(CSTROM_CONFIG
& CSTROM_TWI
)) CSTROM_FLAGS
|= CSTROM_SPILOCKED
;
SPI_buffer.
data.
ampere = ampere
;
SPI_buffer.
data.
mah = mah
;
if (hwver
== 11) {
SPI_buffer.
data.
volt = anin_volt
;
} else {
SPI_buffer.
data.
volt = volt
;
}
}
}
return 0;
}