Subversion Repositories Projects

Rev

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;
}