/* Name: main.c
* Author: Brad Quick
* Programmed for: Atmega48
*/
#include "stdio.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
// Timer Functions
//
// To use the timer:
// starttimer(xxxx); //where xxxx is when the timer will be finished in counts. Each count is .0001024 seconds.
// if (timerdone()) // check to see if the timer has completed.
// { ... }
//
#define NUMTIMERS 2
#define GENERALTIMER 0
long timerdonetime[NUMTIMERS]; // this is where the timer data is stored
void updatetimers()
{ // this should only be called by the timer functions
unsigned int elapsedtime=TCNT1; // get the counts
if (elapsedtime>1000)
{
TCNT1=0; // reset the counts
int x;
for (x=0;x<NUMTIMERS;++x) if (timerdonetime[x]>0) timerdonetime[x]-=elapsedtime;
}
}
void starttimer(int timernumber,long donetime)
// start a timer that will be done in donetime counts. At 10 MHz, each count is .0001024 seconds.
{
updatetimers();
timerdonetime[timernumber]=TCNT1+donetime;
TCCR1B |= ((1 << CS10) | (1 << CS12)); // Set up timer at Fcpu/1024 if it's not already done
}
int timerdone(int timernumber)
{ // return 1 if the timer is done.
updatetimers();
if (TCNT1>timerdonetime[timernumber]) return(1);
else return(0);
}
#define DIGITALINPUT 0
#define DIGITALOUTPUT 1
void setupdigitalio(int pinnumber,int output)
{ // set pin pinnumber to be an output if output==1, othewise set it to be an input
if (output)
{
// this is an output. 13.2.3 of the datasheet says we shouldn't go from tri-state to output high, so we have to do pull up first
PORTB |= (1 << pinnumber); // pull up resistor on
DDRB |= (1 << pinnumber); // this is an output
}
else
{
DDRB &= ~(1 << pinnumber); // set it to an input
PORTB |= (1 << pinnumber); // pull up resistor on
}
}
int getdigitalinput(int pinnumber)
{
if (PINB & (1 << pinnumber)) return(0);
else return(1);
}
void setdigitaloutput(int pinnumber,int value)
{
if (!value) PORTB |= (1 << pinnumber);
else PORTB &= ~(1 << pinnumber);
}
// Serial Port Functions
//
// The serial port sets up interrupts on the receiving end of things. Recieved characters are put into a circular buffer.
// This way, we don't lose characters if we don't get to reading the serial port fast enough since we aren't doing handshaking.
//
// To use the serial port functions:
//
// unsigned char buffer[100]; // the receive buffer must be supplied by the main program
// unsigned int c; // we use an int for c so tha we can detect if reads time out.
//
// initserialport(9600,buffer,100); // 9600 baud, 100 byte buffer size
// serialsendchar('x');
// serialsendstring("test string");
// serialsenddata("test string",11); // send eleven characters
// if (serialnumcharsavailable()>0) // global serialnumcharsavailable can be used to see how many characters have been received that haven't been read.
// c=serialgetchar(); // read one character. This won't time out since we know there is a character available.
// c=serialgetchar(); // read one character. This may time out since we know there is a character available.
// if (c==SERIALTIMEOUT) ... // detecting a time out
#define SERIALTIMEOUT 1000 // value returned if we have to wait too long to receive a character on the serial port
void initserialport(int baud)
{ // initialize the serial port and set up a read buffer and interrupts so that we don't lose any data from reading too slowly
unsigned long baudprescale=(((F_CPU / (baud * 16UL))) - 1);
// these next 4 lines set up the input and output pins. I don't think they are necessary
DDRD &= ~(1 << 0); // set pin 0 to an input
PORTD |= (1 << 0); // pull up resistor on
PORTD |= (1 << 1); // pull up resistor on. Shouldn't go directly from tri-state to output high
DDRD |= (1 << 1); // set pin 1 as an output
// set the receive pin as in input and turn on it's pull-up resistor
UCSR0B |= (1 << RXEN0) | (1 << TXEN0); // Turn on the transmission and reception circuitry
UCSR0C |= /*(1 << URSEL0) |*/ (1 << UCSZ00) | (1 << UCSZ01); // Use 8-bit character sizes
UBRR0L = baudprescale; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
UBRR0H = (baudprescale >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
}
void serialsendchar(unsigned char c)
{ // send c to the serial port
while ((UCSR0A & (1 << UDRE0)) == 0) ; // Do nothing until UDR is ready for more data to be written to it
UDR0 = c; // send the character
}
void serialsendstring(char *string)
{ // send a null terminated string to the serial port
while (*string) serialsendchar(*string++);
}
void serialsenddata(unsigned char *data,int datalength)
{ // send datalength bytes of data to the serial port
while (datalength-- >0) serialsendchar(*data++);
}
int serialnumcharsavailable()
{ // returns 1 if there is a char available, 0 if not
return((UCSR0A & (1 << RXC0)) != 0);
}
unsigned int serialgetchar()
{ // get the next character from the serial port
// time out if we don't receive a character in approximately .1 second
starttimer(GENERALTIMER,1000);
while ((UCSR0A & (1 << RXC0)) == 0) // wait for a character to arrive
{ // the if statement may seem a litte redundant, but this way we don't use the timer if we don't need to
while (serialnumcharsavailable()==0)
{
if (timerdone(GENERALTIMER)) return(SERIALTIMEOUT);
}
}
return((unsigned int)UDR0);
}
// ADC Functions:
//
// To use the ADC functions:
//
// adcsetchannel(0,ADCREFVCC); // initializes the ADC to read on channel zero, using Vcc as the reference
// // special channel ADCCHANREF1POINT1 reads the 1.1V internal reference
// // special channel ADCCHANSLEEP puts the adc to sleep to conserve power
// adcstartreading(); // starts a reading
// unsigned int value=adcgetreading(); // gets the value when the reading is completed.
// reference voltages:
#define ADCREFVCC 0
#define ADCREF1POINT1 1
#define ADCREFEXT 2
// channels: (channels 0 throuh ? can also be used)
#define ADCCHANREF1POINT1 -1
#define ADCCHANSLEEP -2
void adcsetchannel(int channel,int reference)
{ // set up and enable the adc to read channel channel using reference reference
if (channel==ADCCHANSLEEP)
{
ADCSRA &= ~(1 << ADEN); // disable ADC to save power
}
else
{
if (reference==ADCREFVCC)
ADMUX=(1 << REFS0); // Set the reference voltage to Vcc
else if (reference==ADCREF1POINT1)
ADMUX=(1 << REFS0) | (1 << REFS1); // Set the reference voltage to 1.1v internal reference
else if (reference==ADCREFEXT)
ADMUX=0;
if (channel==ADCCHANREF1POINT1)
// ADMUX |= 0x0E; // Set ADC reference to AVCC and mux channel to 1.1v internal
ADMUX |= (1<<MUX3) | (1<<MUX2) | (1<<MUX1); // Set ADC reference to AVCC and mux channel to 1.1v internal
else
ADMUX |= channel; // Set ADC reference to AVCC and mux channel to channel
ADCSRA = (1 << ADPS2) | (1 << ADPS1); // Set ADC prescalar to 64 - 125KHz sample rate @ 8MHz. Should end up between 50KHZ and 200 KHZ CHANGE WITH F_CPU
// ADCSRA |= (1 << ADIE); // Enable ADC Interrupt
ADCSRA |= (1 << ADEN); // Enable ADC
// ADCSRA |= (1 << ADATE); // set auto trigger (free run mode)
// do one read to throw away in order to make sure we get an accurate reading
// ADCSRA |= (1 << ADSC); // start a reading
// while ((ADCSRA & (1 << ADIF))); // wait for conversion to complete
// char temp=ADCH;
// sei();
}
}
unsigned int adcgetreading()
{ // adcstartreading must be called before adcread. They are separated so that a reading can be started, you can do other stuff, then come back and get the result
ADCSRA |= (1 << ADIF); // reset the completion flag.
ADCSRA |= (1 << ADSC); // Start a conversion
// while ((ADCSRA & (1 << ADIF))) {}; // wait for conversion to complete
// temp=ADCH;
// wait for the reading to complete. this could be eliminated if we used auto run mode
while (!(ADCSRA & (1 << ADIF))) {} // wait for conversion to complete
// get the reading
unsigned int result=ADCL;
result|=ADCH<<8;
return(result);
}
// serial port messaging system:
//
// MESSAGECODEDCHAR is a special character. It is always followed by another character. The character after MESSAGECODEDCHAR represents a data character that has
// been shifted to the left one bit so as not to be confused with other special characters, namely MESSAGESTART and MESSAGEEND.
// When MESSAGECODEDCHAR or MESSAGESTART or MESSAGEEND characters appear in the data, they are encoded by sending MESSAGECODEDCHAR followed by MESSAGECODEDCHAR<<1 or MESSAGESTART<<1 or MESSAGEEND<<1
//
// a message is composed of the following (each represents one char, except message_data):
// MESSAGESTART message_type message_data... MESSAGEEND CHECKSUM
//
// CHECKSUM is a single character CHECKSUM of the message data, before the data is encoded. The CHECKSUM itself may be encoded. It appears after MESSAGEEND so that we
// can receive data and transmit it right away, then append new data on the end.
// special characters sent and received
#define MESSAGECODEDCHAR '\1'
#define MESSAGESTARTCHAR '\2'
#define MESSAGEENDCHAR '\3'
#define MESSAGEBATTERYVOLTAGECHAR 'A'
#define MESSAGECHARGINGCHAR 'B'
// special codes returned by serialgetcodedchar (int addition to SERIALTIMEOUT)
#define MESSAGESTART 1001
#define MESSAGEEND 1002
#define SERIALCHECKSUMERROR 1003
#define SERIALTOOMUCHDATAERROR 1004
void serialsendcodedchar(unsigned char c)
{
if (c>=MESSAGECODEDCHAR && c<=MESSAGEENDCHAR)
{
serialsendchar(MESSAGECODEDCHAR);
serialsendchar(c<<1);
}
else serialsendchar(c);
}
unsigned int serialgetcodedchar()
{ // return the next char in c, also return MESSAGESTART or MESSAGEEND if found
unsigned int c=serialgetchar();
if (c==SERIALTIMEOUT) return(c);
if (c==(unsigned int)MESSAGESTART) return(MESSAGESTART);
if (c==(unsigned int)MESSAGEEND) return(MESSAGEEND);
if (c==(unsigned int)MESSAGECODEDCHAR)
{
c=serialgetchar();
if (c==SERIALTIMEOUT) return(c);
else return(c>>1);
}
return(c);
}
void serialsendcodeddata(unsigned char *data,int datalength)
{ // send datalength bytes of data to the serial port
while (datalength-- >0) serialsendcodedchar(*data++);
}
void serialprintnumber(int num,int digits,int decimals)
// prints a long number, right justified, using digits # of digits, puting a
// decimal decimals places from the end, and using blank
// to fill all blank spaces
{
char stg[8];
char *ptr;
int x;
ptr=stg+7;
*ptr='\0';
for (x=1;x<=digits;++x)
{
if (num==0)
*(--ptr)=' ';
else
{
*(--ptr)=48+num-(num/10)*10;
num/=10;
}
if (x==decimals) *(--ptr)='.';
}
serialsendstring(ptr);
}
// Digital io Functions:
//
// These functions currently only work on port B, pins PB0 through PB7
//
// To use these functions:
// setupdigitialio(0,DIGITALINPUT); // sets pin zero to be an input
// setupdigitlalio(1,DIGITALOUTPUT); // sets pin 1 to be an output
// if (getdigitalinput(0)) // read input number 0
// setdigitaloutput(1,1); // turn on output number 1
void pwmsetup()
{ // set up timer2 for PWM. Operates on pin PB3
TCCR2A |=(1<<COM2A1); // mode for switching on and off
// TCCR2A |=(1<<COM2A0); // use this instead for inverted output
TCCR2A |=(1<<WGM21) | (1<<WGM20); // use fast PWM mode
TCCR2B |=/*(1<<CS22) |(1<<CS21) | */(1<<CS20); // prescaler of 128
}
void pwmsetduty(unsigned char value)
{
OCR2A=value; // from 0 to 255 to vary duty cycle
}
#define DISPLAYSERIALPORT 1
#define MESSAGESERIALPORT 2
// Define the io for this project
#define IAMMASTERINPUT 0
#define DISPLAYINHIBITOUTPUT 1
#define MESSAGEINHIBITOUTPUT 2
#define SHUNTOUTPUT 3
#define CALIBRATEINPUT 6
#define MISCOUTPUT 4
#define MODECHANGEINPUT 5
#define CHARGERENABLEOUTPUT 7
// modes
#define MODELOWESTVOLTAGE 0
#define MODEALLVOLTAGES 1
#define NUMMODES 2
#define MAXCELLS 30
#define SHUNTOFFTIME 400 // how long the shunt has to be off before we made a battery voltage reading
#define MAXDUTYVOLTAGEDIFFERENCE 200 // use maximum shunt duty cycle when a cell is .2 volts above the lowest cell
#define MAXDUTY 100 // maximum duty cycle we will use on the shunt (0 to 255)
#define SHUNTTIMER 1
void setserialoutport(int portnumber)
{ // switch the serial port to port number by inhibiting output on the other ports.
// check the state of this port's inhibit output to see if we are changing it
if ((portnumber==DISPLAYSERIALPORT && getdigitalinput(MESSAGEINHIBITOUTPUT))
|| (portnumber==MESSAGESERIALPORT && getdigitalinput(DISPLAYINHIBITOUTPUT))) return;
while ((UCSR0A & (1 << UDRE0)) == 0) ; // Do nothing until UDR is ready for more data to be written to it
// looks like we need to wait a little extra
starttimer(GENERALTIMER,100);
while (!timerdone(GENERALTIMER));
setdigitaloutput(DISPLAYINHIBITOUTPUT,1);
setdigitaloutput(MESSAGEINHIBITOUTPUT,1);
setdigitaloutput(portnumber,0); // un-inhibit the port we want to write to
}
void sendmessage(unsigned char messagetypechar,unsigned char *data,int datasize)
{
// send a battery voltage message with no data
serialsendchar(MESSAGESTARTCHAR);
serialsendchar(messagetypechar);
unsigned char checksum=messagetypechar;
while (datasize-- >0)
{
serialsendcodedchar(*data);
checksum+=*data++;
}
serialsendchar(MESSAGEENDCHAR);
serialsendcodedchar(checksum);
}
unsigned int receivemessage(unsigned char messagetypechar,unsigned char *data,int maxdatasize)
{ // we have already received MESSAGESTARTCHAR and the message type character before entering this function. This allows the data space to be
// set up in advance.
// returns number of chars received or an error code. Error codes are all >= 1000
unsigned char checksum=messagetypechar;
unsigned int count=0;
for (;;)
{
unsigned int c=serialgetcodedchar();
if (c==SERIALTIMEOUT) return(SERIALTIMEOUT);
if (c==MESSAGEENDCHAR)
{
if (checksum==serialgetcodedchar()) return(count);
else return(SERIALCHECKSUMERROR);
}
if (count++==maxdatasize) return(SERIALTOOMUCHDATAERROR);
*data++=(unsigned char)c;
checksum+=(unsigned char)c;
}
}
void watchdogsetup()
{
/* Enable the watchdog to do a system reset on timeout */
WDTCSR |= (1<<WDCE) | (1<<WDE);
/* Set new prescaler(time-out) value = 1024K cycles (~8 seconds) */
WDTCSR = (1<<WDE) | (1<<WDP3) | (1<<WDP0);
}
#define watchdogreset() __asm__ __volatile__ ("wdr")
void delay(long delaytime)
{
starttimer(GENERALTIMER,delaytime);
while (!timerdone(GENERALTIMER));
}
uint16_t EEMEM calibrationwordh;
uint16_t EEMEM calibrationwordl;
int main(void)
{
setupdigitalio(DISPLAYINHIBITOUTPUT,DIGITALOUTPUT); // set pin 1 as an output
setupdigitalio(MESSAGEINHIBITOUTPUT,DIGITALOUTPUT); // set pin 2 as an output
setupdigitalio(SHUNTOUTPUT,DIGITALOUTPUT); // set pin 3 as an output
setupdigitalio(MISCOUTPUT,DIGITALOUTPUT); // set pin 4 as an output
setupdigitalio(IAMMASTERINPUT,DIGITALINPUT);
setupdigitalio(CALIBRATEINPUT,DIGITALINPUT);
setupdigitalio(MODECHANGEINPUT,DIGITALINPUT);
initserialport(2400);
adcsetchannel(ADCCHANREF1POINT1,ADCREFVCC); // set the adc to read channel 0 with vcc as a reference
// read the calibration factor from eeprom
unsigned long calibrationfactor=((unsigned long)eeprom_read_word(&calibrationwordh))<<16 | eeprom_read_word(&calibrationwordl);
unsigned char mode=MODELOWESTVOLTAGE;
unsigned char chargingmode=0;
unsigned char shuntdutyvalue=0;
// flash the LED twice, for diagnostics sake
setdigitaloutput(MISCOUTPUT,1);
delay(500);
setdigitaloutput(MISCOUTPUT,0);
delay(500);
setdigitaloutput(MISCOUTPUT,1);
delay(500);
setdigitaloutput(MISCOUTPUT,0);
// check to see if we are the master and remember the result
int iamthemaster=0;
if (getdigitalinput(IAMMASTERINPUT))
{
iamthemaster=1;
starttimer(GENERALTIMER,5000); // wait a half second or so to make sure everybody is running
}
watchdogsetup();
for (;;)
{
watchdogreset();
// any time SHUNTTIMER times out, turn the shunt off. In order for the shunt to be on, it has to be continually set
if (shuntdutyvalue && timerdone(SHUNTTIMER))
{
shuntdutyvalue=0;
pwmsetduty(0);
// set the timer for a short period of time so that we don't try to read the ACD too soon after it's shut off
starttimer(SHUNTTIMER,SHUNTOFFTIME);
}
// see if the calibration input is on. Only calibrate if we've never been calibrated before
if (getdigitalinput(CALIBRATEINPUT) && calibrationfactor==0xFFFFFFFF)
{ // we will calibrate when the input is released
// voltages are represented as integers, in 1000's, so 4000 represents 4.0 volts.
// the calibration factor is the ADC reading times the calibration voltage. Below, we are adding up
// 3000 readings, which is the same as 3000 times one reading, assuming calibration voltage=3.0 volts
calibrationfactor=0;
int x;
for (x=0;x<3000;++x)
{
calibrationfactor+=adcgetreading();
}
// remember the calibration in eeprom
eeprom_write_word(&calibrationwordh,calibrationfactor>>16);
eeprom_write_word(&calibrationwordl,calibrationfactor & 0xFFFF);
}
// check to see if any messages are coming in on the serial port
if (serialnumcharsavailable())
{
int c=serialgetchar();
if (c==(unsigned int)MESSAGESTARTCHAR)
{ // we are receiving a new message. Get the message type
int messagetype=serialgetchar();
if (messagetype==(unsigned int)MESSAGEBATTERYVOLTAGECHAR || messagetype==(unsigned int)MESSAGECHARGINGCHAR)
{ // this is a battery voltage message. or a Charging message. Read the message and pass it along to the next processor.
// first, start measuring our batteries voltage.
unsigned int cellvoltages[MAXCELLS];
unsigned int numcells=receivemessage((unsigned char)messagetype,(unsigned char *)&cellvoltages[0],MAXCELLS*2);
if (numcells<1000) // no error
{ // we received the entire message
numcells/=2;
// change the state of the LED to indicate that we received a message
setdigitaloutput(MISCOUTPUT,!getdigitalinput(MISCOUTPUT));
// read our battery voltage
// make sure the shunt has been turned off for more than SHUNTOFFTIME before we read our own battery voltage
if (!timerdone(SHUNTTIMER))
{ // the shunt is on or was recently turned off. Wait before taking our reading
pwmsetduty(0); // turn the shunt off temporarily
starttimer(GENERALTIMER,SHUNTOFFTIME);
while (!timerdone(GENERALTIMER) && (shuntdutyvalue!=0 || !timerdone(SHUNTTIMER)));
}
// average 100 readings
unsigned long total=0;
int x;
for (x=0;x<100;++x)
total+=adcgetreading();
pwmsetduty(shuntdutyvalue); // from 0 to 255 to vary duty cycle
int voltage=100*calibrationfactor/total;
if (messagetype==(unsigned int)MESSAGECHARGINGCHAR)
{ // we got a complete charging message. See if we need to activate the shunt
starttimer(SHUNTTIMER,30000); // the shunt times out in 3 seconds
// only activate the shunt if the voltage is above 3.2 volts. Check for below 4.2 volts to eliminate acting on bad data
if (voltage>3200 && voltage<4200 && cellvoltages[0]>2400 && cellvoltages[0]<4000)
{
int difference=voltage-cellvoltages[0];
if (difference<0) shuntdutyvalue=0;
else if (difference>MAXDUTYVOLTAGEDIFFERENCE) shuntdutyvalue=MAXDUTY;
else shuntdutyvalue=MAXDUTY*difference/MAXDUTYVOLTAGEDIFFERENCE;
pwmsetduty(shuntdutyvalue);
}
if (iamthemaster) // I am the master
{ // when the highest voltage is greater than 3.6 volts, turn charging off
// move this to where we send the charging message from the master
if (cellvoltages[1]>=3600)
{
chargingmode=0;
setdigitaloutput(CHARGERENABLEOUTPUT,0);
// set the serial port up to write to the display
setserialoutport(DISPLAYSERIALPORT);
serialsendstring("?f?c0?<"); // clear the display, set to no cursor, small characters
}
}
}
else // this is a battery voltage message.
{ // add our voltage reading onto the end of the list
cellvoltages[numcells++]=voltage;
if (iamthemaster)
{
// set the serial port up to write to the display
setserialoutport(DISPLAYSERIALPORT);
unsigned int lowestbatteryvoltage=voltage; // start with our voltage
unsigned int highestbatteryvoltage=voltage; // start with our voltage
int x;
for (x=0;x<numcells;++x)
{
if (cellvoltages[x]<lowestbatteryvoltage) lowestbatteryvoltage=cellvoltages[x];
if (cellvoltages[x]>highestbatteryvoltage) highestbatteryvoltage=cellvoltages[x];
}
if (mode==MODELOWESTVOLTAGE)
{
// output the lowest voltage to the display
serialsendstring("?y1?x04Lowest: ");
serialprintnumber(lowestbatteryvoltage/10,3,2);
serialsendstring("?y2?x03Highest: ");
serialprintnumber(highestbatteryvoltage/10,3,2);
}
else if (mode==MODEALLVOLTAGES)
{
// output all voltages to the display
serialsendstring("?y0?x00");
int x;
for (x=0;x<numcells;++x)
{
serialprintnumber(cellvoltages[x]/10,4,0);
}
}
if (chargingmode)
{ // we are in charging mode. Tell each cell what the lowest voltage is
setserialoutport(MESSAGESERIALPORT);
// send a charging message with the lowest and highest voltages
cellvoltages[0]=lowestbatteryvoltage;
cellvoltages[1]=highestbatteryvoltage;
sendmessage(MESSAGECHARGINGCHAR,(unsigned char *)&cellvoltages[0],4);
}
}
}
if (!iamthemaster)
{ // forward the message to the next processor
sendmessage((unsigned char)messagetype,(unsigned char *)&cellvoltages[0],numcells*2);
}
}
else // we didn't get a complete message
{
serialsendstring("?fBad Data");
}
// wait .05 seconds and start another reading
starttimer(GENERALTIMER,500);
}
}
}
if (iamthemaster)
{ // I AM the master!
if (getdigitalinput(MODECHANGEINPUT))
{
// wait until the button is released. If the button is held for 2 seconds, toggle charging mode
starttimer(GENERALTIMER,20000);
while (getdigitalinput(MODECHANGEINPUT) && !timerdone(GENERALTIMER));
if (timerdone(GENERALTIMER))
{ // the button was held for 2 seconds
chargingmode=!chargingmode;
setdigitaloutput(CHARGERENABLEOUTPUT,chargingmode); // turn on the charger
}
else if (++mode>=NUMMODES) mode=0;
// set the serial port up to write to the display
setserialoutport(DISPLAYSERIALPORT);
serialsendstring("?f?c0?<"); // clear the display, set to no cursor, small characters
while (getdigitalinput(MODECHANGEINPUT)); // make sure he let's go of the button
if (chargingmode) serialsendstring(" *** Charging ***");
starttimer(GENERALTIMER,500);
}
if (timerdone(GENERALTIMER))
{ // it's time to trigger a new reading of battery voltages
setserialoutport(MESSAGESERIALPORT);
// send a battery voltage message with no data
sendmessage(MESSAGEBATTERYVOLTAGECHAR,0,0);
starttimer(GENERALTIMER,10000); // time out in 1 seconds
}
// read the voltage drop across the shunt and display the current
}
}
return 0; // never reached
}