0,0 → 1,628 |
/**************************************************************************** |
* 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; |
} |