
/** Version 1.70 - 57.1% mem used **/ 

#define sleep()  __asm__ __volatile__ ("sleep")
#define UART_BAUD_RATE         9600         // baud rate
#define UART_BAUD_SELECT       (F_CPU/(UART_BAUD_RATE*16l)-1)
#define PRINT(string) (UART_PrintfProgStr(PSTR(string)))
#define EE_M_HI 	0
#define EE_M_LO 	1
#define EE_B 		2
#define EE_MTH 		3
#define EE_RST 		4
#define EE_MAX 		23
#define txSize		128

#define VERSION		"1.70"

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <avr/pgmspace.h> 
#include <avr/wdt.h>

void UART_PrintfProgStr(unsigned const char* pBuf);
void printu16(unsigned int x);
void txbyte(char data );
void Display(unsigned int data[]);
void ewrite(int address, char data);
unsigned char eread(int address);
unsigned int scan(void);
void measr(void);
void selmonth(void);
void dis_month(unsigned char month);
void clear_eeprom(void);

unsigned char month;			//current month
unsigned char getmonth;			//set by selmonth()
unsigned int monthtimer;			//30.41667 day timer (1/12 of 365)
unsigned char monthsec;			//minuet timer for monthtimer
unsigned char sec[18];			//Current Month's 'seconds' buffer
unsigned int crnt[18];			//Current Month's Data
unsigned int temp[18];			//Temp Month's Data
unsigned int rollavg[4];			//used to average input captures
unsigned int timer1;			// 3 hr timer
unsigned int m;				//Slope for speed calculation
unsigned int add;				//Used for month data EEPROM storage
unsigned char tempos;			//Used for month data EEPROM storage
volatile unsigned char spd;				//Current Speed
unsigned char max;				//maximum Speed for month
unsigned char rollpos;			//position of rolling average		
unsigned char b;				//Offset for speed calculation
unsigned char cal;				//Calibration mode flag
unsigned char reset;			//reset indicator
volatile unsigned char txUsed;	//Used size of the Used TX Buffer
unsigned char txDone;				//tx activity flag
unsigned char txBuff [txSize];	//Transmit Buffer


unsigned int icp_make, icp_old;


int main(void) 
{
 cli();
  //setup WatchDog Timer
 WDTCSR = 0x18;					//Allow WatchDag Chnage
 WDTCSR = 0x0F;  		  	  	//Enable WatchDog
  //Setup Ports
 PORTB = 0x01;					//Set Pull up on ICP PB0
 DDRC = _BV(PC1);
 DDRB = _BV(PB1);
 PORTB&= ~_BV(PB1);				//turn ON hall effect sensor
  //Setup UATRT
 UBRR0L = (char)UART_BAUD_SELECT;	
 UCSR0A = 0x00; 
 UCSR0B = _BV(RXCIE0)|_BV(TXCIE0)|_BV(RXEN0)|_BV(TXEN0);	//enable Rx, TX, Rx IQR, & Tx IRQ
  //Setup Timers
 TCCR1A = 0x00;
 TCCR1B = _BV(WGM12)|0x05;					//1024 Prescaller
 OCR1A = 3999;
 TIMSK1 = _BV(ICIE1) | _BV(OCIE1A);		//Enable TCO1 & ICP1
  //Setup MCU
 SMCR = _BV(SE);					//Setup Sleep mode & enable sleep
 PRR = _BV(PRTWI)| _BV(PRTIM0)| _BV(PRTIM2)| _BV(PRSPI)| _BV(PRADC);
 
 sei();
 cal = 0;
 month = eread(EE_MTH);
 getmonth = month; 
 max = eread (EE_MAX + month);
 PRINT("\fReset "VERSION);
  //Restore current month's data from EEPROM
 add=month*36;								//calc eeprom addresses based on month
 for (tempos=0;tempos<18;tempos++) {			//transfer eeprom data to array
	crnt[tempos] = 0;
	crnt[tempos]|= (int)(eread(add)) << 8;
	crnt[tempos]|= eread(add + 1);
	monthtimer+= crnt[tempos];				//get aprox monthtimer from saved data
	add+=2;
 }
  //Restore m & b for speed calc.
 m = (eread(EE_M_HI) << 8) | eread(EE_M_LO);
 b = eread(EE_B);
 
 reset = eread(EE_RST);
 if (reset < 250) {
	reset++;
	ewrite(EE_RST, reset);
 }
 
 for(;;) 
	sleep();
}

void ewrite(int address, char data) 
{
 while (EECR & _BV(EEPE));
 EEAR = address;
 EEDR = data;
 cli();
 EECR = _BV(EEMPE);
 EECR|= _BV(EEPE);
 sei();
}
 
unsigned char eread(int address) 
{
 while (EECR & _BV(EEPE));
 EEAR = address;
 EECR|= _BV(EERE);
 return EEDR;
}

SIGNAL(SIG_INPUT_CAPTURE1) 
{
 unsigned int icp_temp;
 icp_temp = ICR1;
 
 if (rollpos >=4) 
	rollpos = 0;
 
 if ((icp_temp < icp_old) && (TIFR1 & _BV(TOV1))) {
	rollavg[rollpos] = icp_make + 4000 + icp_temp - icp_old;
	icp_make = -4000;
 }
 else {
	rollavg[rollpos] = icp_make + icp_temp - icp_old;
	icp_make = 0;
 }
 icp_old = icp_temp;
 
 rollpos++;
 spd = b + m / (((long int)rollavg[0] + rollavg[1] + rollavg[2] + rollavg[3]) >> 2);
 if (spd > max) {
 	max = spd;
 	ewrite(EE_MAX + month, max);
 }
 if (cal) {
 	txbyte('\r');
 	printu16(((long int)rollavg[0] + rollavg[1] + rollavg[2] + rollavg[3]) >> 2);
 } 
}


SIGNAL(SIG_OUTPUT_COMPARE1A) 
{
 if (icp_make + 1500 < m)
	icp_make+=4000;
 else
	spd = 0;
 
 wdt_reset();
 if (spd > 17) 
		spd = 17;						//Any speed above max put in "17 & up" bin
 	if (sec[spd] < 59) 
		sec[spd]++;						//count seconds at each speed
 	else {
 		crnt[spd]++;					//incement crnt as per 60 seconds per speed
 		sec[spd] = 0;
 	}
 	if (monthsec < 59) 
		monthsec++;						//count seconds for monthtimer
 	else {
 	 	monthsec = 0;
 	 	monthtimer++;					//count minuets for monthtimer
 		if (monthtimer >= 43800) {		//True Every 30.41667 days (1/12 of 356 days)
 			monthtimer = 0;		
 			add=month*36;						//calc eeprom addresses based on month
 			for (tempos=0;tempos<18;tempos++) {		//transfer eeprom data to array
 				ewrite (add,(int)(crnt[tempos] >> 8));
 				ewrite (add + 1,crnt[tempos]);
 				add+=2;
 				crnt[tempos] = 0;
 			}
 			if (month < 12) 
				month++;
 			else 
				month = 1;
 			ewrite(EE_MTH, month);		//save which month it is
 			max = 0;					//clear maximum speed
		 	ewrite(EE_MAX + month, max);	//save cleared maximum
 		}
	}
  	timer1++;
 	if (timer1 >= 10800) {				//Save Current Data Every 3 Hours
 		timer1 = 0;
 		add=month*36;							//calc eeprom addresses based on month
 		for (tempos=0;tempos<18;tempos++) {			//transfer eeprom data to array
 			ewrite(add,(int)(crnt[tempos] >> 8));
 			ewrite(add + 1,crnt[tempos]);
 			add+=2;
 		}
 	}
}


SIGNAL(SIG_USART_RECV) 
{
 unsigned char rx_byte;
 rx_byte = UDR0;
 
 	if (rx_byte =='g') {
		 PRINT ("\fGet Data for which Month?");
		 selmonth();
		 if(getmonth != month) {						//test if requested data is for current month
		 	add=getmonth*36;						//calc eeprom addresses based on entered month
		 	for(tempos=0;tempos<18;tempos++) {			//transfer eeprom data to array
 				temp[tempos] = 0;
 				temp[tempos]|= (int)(eread (add)) << 8;
 				temp[tempos]|= eread (add + 1);
 				add+=2;
 		 	}
		 	Display(temp);
		 }
		 else Display(crnt);
		 PRINT ("\n\n\rMonth's Maximum speed = ");
		 printu16 (eread (EE_MAX + getmonth));
	}
	else if (rx_byte =='s') {
		PRINT ("\fSet Month");
		selmonth();
		month = getmonth;
		ewrite(EE_MTH, month);
		max = 0;									//clear maximum speed
		ewrite(EE_MAX + month, max);					//save cleared maximum
		monthtimer = 0;
		add=month*36;								//calc eeprom addresses based on month
		for(tempos=0;tempos<18;tempos++) {			//transfer eeprom data to array
 			ewrite (add,0);
 			ewrite (add + 1,0);
 			add+=2;
 			crnt[tempos] = 0;
 			}
 		reset = 0;
		ewrite(EE_RST, reset);
	}
	else if (rx_byte =='m') {
		PRINT("\fm = ");
		printu16(m);
		PRINT("\n\rEnter Slope ");
		m = scan();
		ewrite(EE_M_HI,(m >> 8));
		ewrite(EE_M_LO,m);
		PRINT("\rm = ");
		printu16 (m);
	}
	else if (rx_byte =='b') {
		PRINT("\fb = ");
		printu16(b);
		PRINT("\n\rEnter Offset ");
		b = scan();
		ewrite(EE_B,b);
		PRINT("\rb = ");
		printu16 (b);
	}
	else if (rx_byte =='t') {
		if (!cal) {
			cal = 1;
			PRINT ("\fCalibration Mode ON");
		 }
		 else {
		 	cal = 0;
		 	PRINT ("\fCalibration Mode OFF");
		 }
	}
	else if (rx_byte =='c') {
		clear_eeprom();
	}
	else if (rx_byte =='r') {
		WDTCSR = 0x18;							//Allow WatchDag Chnage
 		WDTCSR = 0x08;  		  	  				//Enable WatchDog
		for(;;);
	}
	else if (rx_byte =='i') {
		PRINT("\fFirmware\t= "VERSION);
		PRINT("\rLogging Month\t= ");
		dis_month(month);
		PRINT("\rDays logged\t= ");
		printu16(monthtimer / 1440);
		PRINT("\r# of resets\t= ");
		printu16(reset);
		PRINT("\rCurrent speed\t= ");
		printu16(spd);
		measr();								//display battery voltage
		PRINT("\rOffset (b)\t= ");
		printu16(b);
		PRINT("\rSlope (m)\t= ");
		printu16(m);
		
	}
	else {		
		PRINT("\fPress \r [g]et data\r [c]lear data\r [s]et month\r [i]nfo");
		PRINT("\r [m] slope\r [b] offset\r [t]oggle calibration mode\r [r]eset");
	}
}

void Display(unsigned int data[]) 
{
 PRINT("\n\rno wind   = ");
 printu16(data[0]);
 PRINT("\t9.0  -  9.9 = ");
 printu16(data[9]);
 PRINT("\r1.0 - 1.9 = ");
 printu16(data[1]);
 PRINT("\t10.0 - 10.9 = ");
 printu16(data[10]);
 PRINT("\r2.0 - 2.9 = ");
 printu16(data[2]);
 PRINT("\t11.0 - 11.9 = ");
 printu16(data[11]);
 PRINT("\r3.0 - 3.9 = ");
 printu16(data[3]);
 PRINT("\t12.0 - 12.9 = ");
 printu16(data[12]);
 PRINT("\r4.0 - 4.9 = ");
 printu16(data[4]);
 PRINT("\t13.0 - 13.9 = ");
 printu16(data[13]);
 PRINT("\r5.0 - 5.9 = ");
 printu16(data[5]);
 PRINT("\t14.0 - 14.9 = ");
 printu16(data[14]);
 PRINT("\r6.0 - 6.9 = ");
 printu16(data[6]);
 PRINT("\t15.0 - 15.9 = ");
 printu16(data[15]);
 PRINT("\r7.0 - 7.9 = ");
 printu16(data[7]);
 PRINT("\t16.0 - 16.9 = ");
 printu16(data[16]);
 PRINT("\r8.0 - 8.9 = ");
 printu16(data[8]);
 PRINT("\t17.0 & Up   = ");
 printu16(data[17]); 
}


void UART_PrintfProgStr(unsigned const char* pBuf) 
{
 wdt_reset();
 while (pgm_read_byte_near(pBuf)!=0) {
   	txbyte(pgm_read_byte_near(pBuf));
   	pBuf++;
 }
}

void printu16(unsigned int x) 
{
 txbyte(x / 10000 + 0x30);					//Add ASCI offset for digits (0-9)-> 0x30
 x=x % 10000;
 txbyte(x / 1000 + 0x30);
 x=x % 1000;
 txbyte(x / 100 + 0x30);
 x=x % 100;
 txbyte(x / 10 + 0x30);
 x=x%10;
 txbyte(x + 0x30);
}

void txbyte ( char data ) 
{
 static unsigned char txEND;
 char done=1;
 while(done) {
 	cli();
	if(txUsed < txSize) done = 0;
	sei();
 }
 cli();
 if(!txDone) {				//finished txing
	UDR0 = data;			//re-start interrupts
	txDone = 1;
 }
 else {
	txBuff [(txEND & (txSize-1))] = data;
	txEND++;
	txUsed++;
 }
 sei();
}

SIGNAL(SIG_USART_TRANS)
{
 static unsigned char txpnt;
 if(txUsed) {
	UDR0 = txBuff [(txpnt & (txSize-1))];
	txpnt++;
	txUsed--;
 }
 else txDone = 0;
}

#define ENTER 13
#define BACKSPACE 8
unsigned int scan (void) 
{
 unsigned int value;
 unsigned char indata,pos,i; 
 unsigned char data[5]; 
 UCSR0B&= ~_BV(RXCIE0);						//Disable  RX INT
 value = 0;
 pos = 0;
 i = 0;
 data[0] = 0;								//Clear Array 
 data[1] = 0; 
 data[2] = 0; 
 data[3] = 0; 
 data[4] = 0; 
 for (;;) { 
 	while (!(UCSR0A & _BV(RXC0))) 			//Wait for RX flag 
		wdt_reset();
 	indata = UDR0; 
 	if (indata == ENTER) {					//loop until ENTER is pressed 
 		while(pos > 0) {
 			value *= 10;					//Build U16 number from ASCII array
 			value += data[i++];
 			pos--;
 		}
 		UCSR0B|= _BV(RXCIE0);					//Enable RX INT
 		return(value);
 	} 
 	if (indata== BACKSPACE) {					//backspace action
 		if (pos!=0) {
 			pos--;
 			data[pos] = 0;
 			txbyte(BACKSPACE);
 			txbyte(0x20);
 			txbyte(BACKSPACE);
 		}
 	}
 	else {								//fill array with numbers only until full
 		if (pos<5 && indata>=0x30 && indata<=0x39) {
 			data[pos] = indata - 0x30;
 			pos++;
 			txbyte(indata);				//Print key just pressed 
 		}
 	}
 }
} 

void selmonth(void)
{
 unsigned char inbyte;
 UCSR0B&= ~_BV(RXCIE0);
 getmonth = month;
 inbyte = 69;
 PRINT("\n\rUse ARROW Keys to select month\r");
 for (;;) { 
  	if (inbyte>=65 && inbyte<=69) {
 		dis_month(getmonth);
 		txbyte(BACKSPACE);
 		txbyte(BACKSPACE);
 		txbyte(BACKSPACE);	
	}
	while (!(UCSR0A & _BV(RXC0))) 
		wdt_reset();			//Wait for RX flag 
 	inbyte = UDR0; 
 	if (inbyte == ENTER) {					//If ENTER is pressed 
 		UCSR0B|= _BV(RXCIE0); 
 		return;
 	}
 	if (inbyte == 66 || inbyte == 67) {			//If "<--" is pressed 
 		if (getmonth < 12) 
			getmonth++;
 		else 
			getmonth = 1;
  	}
  	if (inbyte == 65 || inbyte == 68) {			//If "-->" is pressed 
 		if (getmonth > 1) 
			getmonth--;
 		else 
			getmonth = 12;
 	}
 }
}

void dis_month(unsigned char month)
{
 switch (month) {
	case 1: PRINT("Jan");
		break;
	case 2: PRINT("Feb");
		break;
	case 3: PRINT("Mar");
		break;
	case 4: PRINT("Apr");
		break;
	case 5: PRINT("May");
		break;
	case 6: PRINT("Jun");
		break;
	case 7: PRINT("Jul");
		break;
	case 8: PRINT("Aug");
		break;
	case 9: PRINT("Sep");
		break;
	case 10: PRINT("Oct");
		 break;
	case 11: PRINT("Nov");
		 break;
	case 12: PRINT("Dec");
		 break;
 	}
}

void clear_eeprom(void)
{
 unsigned char inbyte;
 UCSR0B&= ~_BV(RXCIE0);
 PRINT("\n\rErase all logged data (y/n)");
 while (!(UCSR0A & _BV(RXC0))) 
	wdt_reset();			//Wait for RX flag 
 inbyte = UDR0; 
 if (inbyte == 'y') {					//If ENTER is pressed 
	for (add=24;add<468;add++) 
		ewrite(add,0);
 	ewrite(EE_MTH,1);			//set month = Jan
 }
 UCSR0B|= _BV(RXCIE0); 
 PRINT("\n\rDone");
}

void measr(void) 
{
 unsigned int x;
 PRR&= ~_BV(PRADC);						//turn ON ADC system clock
 PORTC|= _BV(PC1);						//turn voltage divider ON
 ADMUX = 0x40;								//Set ACDC0, 3.3v ref
 ADCSRA = 0xE7;								//Start conversion, free run, /128 ck
 while (!(ADCSRA & _BV(ADIF)));				//Wait for first conversion to complete
 ADCSRA|= _BV(ADIF);
 while (!(ADCSRA & _BV(ADIF)));				//Wait for second conversion to complete and use
 x = ADCW * 2 + ADCW / 2;
 PORTC&= ~_BV(PC1);						//turn voltage divider OFF
 ADCSRA = 0x00;								//Disable ADC to save power
 PRR|= _BV(PRADC);						//turn OFF ADC system clock
 PRINT("\rVbatt\t\t= ");
 txbyte(x / 1000 + 0x30);
 x=x % 1000;
 txbyte(x / 100 + 0x30);
 x=x % 100;
 txbyte(0x2E);
 txbyte(x / 10 + 0x30);
 x=x%10;
 txbyte(x + 0x30);
 txbyte(0x76);
}
