/*******************************************************************************
 *	CarLog1.c	Greenpower CarComputer DataLogger Program
 *			T.Barnaby,	BEAM Ltd,	2008-06-20
 *
 *	Copyright (c) Beam Ltd and Chipping Sodbury School
 *******************************************************************************
 *
 * Function:	Standard car data logging
 * Version:	1.0
 *
 * Infomation
 *	This code performs basic data logging functions for the car.
 *	It expects the following inputs:
 *		1. Speed sensor read switch on wheel for speed.
 *		2. Hall efect Current sensor for battery current.
 *		3. The Car Computers power supply voltage will be used
 *			as the voltage input.
 *		4. Optional temperature sensor for motor.
 *		5. It can have a battery number switche connected for charge calculations.
 *
 *	It can have an I2C 2x16 character LCD displayed for driver/pits information.
 *	It can have 3 LED's connected for driver information.
 *	It can have a set of 4 push-button switches connected for driver interaction.
 *
 *	The code will measure, calculate and log the data to FLASH memory for later recall.
 *	If an LCD display and switches are connected information to the driver and
 *	pit crew will be provided.
 *	The carmonGui program can be used to set the date/time and download the data
 *		from the FLASH memory over the RS232 port.
 *
 * Notes:
 *	1. Most values are scaled by 100 to keep reasonable resolution. For example
 *		in voltage 1000 is 10V.
 *	2. To use a 2G SDCARD set OPT_SDCARD to 1 and OPT_LOG_SECONDS to 1
 *	3. To use two EEPROMS set OPT_TWO_EEPROMS to 1
 *	4. The on-board orange LED will flash once per second to indicate that the board
 *		is up and running.
 */
#include <string.h>
#include <pic18f2520.h>
#include <picLib3.c>

#pragma stack 0x200 0x100			// Setup stack

#define	OPT_BATTERY_LOW		1		// Battery low warning
#define OPT_TWO_EEPROMS		0		// Two EEPROMS for data
#define OPT_LOG_SECONDS		8		// Log every this number of seconds (4.5 Hours)
#define OPT_SDCARD		0		// Use SDCARD

#define	TEST_SPEED		0		// Provide speed test pulse
#define	TEST_RPM		0		// Display Motor RPM for motor testing

#define	CLOCK			4000000		// Clock frequency
#define	wheelCircumference	146		// The wheel circumference in cm

#define	WARNING_BATTERY_LOW	0x01		// Battery low warning

#if OPT_SDCARD
#include <sdcard.c>

#define	LOG_START		1024		// Start of log
#define	LOG_END			4194304		// End of log (2GByte card

#else

#define	LOG_START		0		// Start of log
#if OPT_TWO_EEPROMS
#define	LOG_END		2048		// The size of the logging memory
#else
#define	LOG_END		1024		// The size of the logging memory
#endif

#endif

UInt8	battery;
UInt8	mode;
UInt8	displayMode;
UInt16	buttonsLast;
UInt16	buttonsLastTime;

// Measurement values
UInt16	voltage;
Int16	current;
UInt16	temperature;
UInt16	speed;
UInt32	distanceTicks;
UInt32	timeNextMicroSec;
UInt16	distance;
UInt16	speedTickCount;
UInt16	speedTickSTime;
UInt16	speedTickETime;
UInt32	charge;
UInt16	charge0;
UInt16	charge1;
UInt8	warnings;
UInt16	voltageMin;
UInt8	voltageCount;
UInt8	voltageWarning;
UInt16	speedPeak;
UInt16	speedPeakCurrent;
UInt32	speedAvg;
UInt32	currentAvg;
UInt16	currentAvgNum;

UInt8	buffer[32];

// EEPROM
#define	EE_LOGADDRESS_DIR	0		// Log directory
#define	EE_LOGADDRESS		2		// Log address 32 bit
#define	EE_CHARGE0		6		// Charge battery 0
#define	EE_CHARGE1		8		// Charge Battery 1
#define EE_CURRENT_AVG		10		// Average current setting
#define EE_LOG_SECONDS		12		// Number of seconds between logs

void displayDo(){
	static char	str[17];
	UInt16		t;

	switch(displayMode){
	case 0:
		strcpy(str, "S       B      %");
		strWordDec(&str[1], 2, speed);
		
		str[9] = battery + '0';
		strByte(&str[12], (charge >> 16) / 10);
		lcdWriteStr(0, 0, str);

		strcpy(str, "I       D       ");
		strWordDec(&str[1], 2, current);
		strWordDec(&str[9], 2, distance);
//		strWordDec(&str[9], 0, dvalue);
		lcdWriteStr(0, 1, str);
		break;
	case 1:
		t = (voltage / 100) * (current / 100);
		strcpy(str, "V       I       ");
		strWordDec(&str[1], 2, voltage);
		strWordDec(&str[9], 2, current);
		lcdWriteStr(0, 0, str);

		strcpy(str, "P       T       ");
		strWordDec(&str[2], 0, t);
		strWordDec(&str[10], 0, timerGetSec());
		lcdWriteStr(0, 1, str);
		break;
	case 2:
		t = (voltage / 100) * (current / 100);
		strcpy(str, "SA      IA      ");
		if(currentAvgNum){
			strWordDec(&str[1], 2, speedAvg/currentAvgNum);
			strWordDec(&str[9], 2, currentAvg/currentAvgNum);
		}
		else {
			strWordDec(&str[1], 2, 0);
			strWordDec(&str[9], 2, 0);
		}
		lcdWriteStr(0, 0, str);

		strcpy(str, "SP      IP      ");
		strWordDec(&str[1], 2, speedPeak);
		strWordDec(&str[9], 2, speedPeakCurrent);
		lcdWriteStr(0, 1, str);
		break;
	case 3:
		lcdWriteStr(0, 0, "Parameter Set   ");
		strcpy(str, "LogSeconds      ");
		t = eepromRead16(EE_LOG_SECONDS);
		strWordDec(&str[11], 0, t);
		lcdWriteStr(0, 1, str);
		break;		
	}
}

void displayWarnings(){
	if(warnings & WARNING_BATTERY_LOW){
		lcdWriteStr(0, 1, "Battery Low     ");
		PORTBbits.RB7 = 1;
	}
	else {
		PORTBbits.RB7 = 0;
	}
}

// Called on each interrupt to test for speed sensor pulse
void speedInterrupt(){
	UInt16	t;

	// Debounce
	t = timerGetMilliSec();
#if TEST_RPM
	if((t - speedTickETime) > 20){
#else
	if((t - speedTickETime) > 50){
#endif
		speedTickETime = t;

		// Add distance
		distanceTicks++;

		// Add Speed counter and time
		speedTickCount++;
	}
}

void speedCalculate(){
	UInt16	v, v1;
	
	if(speedTickCount){
#if TEST_RPM
		v = (speedTickETime - speedTickSTime) / 10;	// The time in ms / 10
		v1 = speedTickCount * 600;			
		speed = (v1 / v) * 10;				// RPM
#else
		v = (speedTickETime - speedTickSTime) / 10;	// The time in ms / 10
		v1 = speedTickCount * wheelCircumference * 10;	// Km/s
		speed = (v1 / v) * 36;
#endif
	}
	else {
		speed = 0;
	}

	if((speed > speedPeak) && (current > 1200)){
		speedPeak = speed;
		speedPeakCurrent = current;
	}

	speedTickCount = 0;
	speedTickSTime = speedTickETime;
}

void distanceCalculate(){
	distance = ((distanceTicks / 10) * wheelCircumference) / 100;
}

void chargeCalculate(){
	UInt16	c;

	// Rough battery charge calculation, this needs some work !	
	c = current + current / 4;
	c = (c * 12) / 4;
	
	if(charge > c){
		charge -= c;
	}
	else {
		charge = 0;
	}
}

// DataLoad: Reads all logging data into a buffer
void dataLoad(){
	UInt8*	p;
	UInt16	t;

	i2cRead(0xD0, 0, I2cSlow8, buffer, 3);
	i2cRead(0xD0, 4, I2cSlow8, &buffer[3], 3);
	
	p = &buffer[6];
	*p++ = voltage;				// 0
	*p++ = voltage >> 8;
	*p++ = current;
	*p++ = current >> 8;
	*p++ = charge0;
	*p++ = charge0 >> 8;
	*p++ = charge1;
	*p++ = charge1 >> 8;
	*p++ = speed;
	*p++ = speed >> 8;
	*p++ = distance;
	*p++ = distance >> 8;
	*p++ = 0;				// Throttle
	*p++ = 0;
	*p++ = temperature;
	*p++ = battery;
	*p++ = 0;				// Motor Speed
	*p++ = 0;				// 17
	t = eepromRead16(EE_CURRENT_AVG);
	*p++ = t;
	*p++ = t >> 8;				// 19
}

#if OPT_SDCARD
// Add directory entry
void dataLogDir(){
	UInt32	logAddressData;
	UInt16	logAddressDir;
	
	logAddressData = eepromRead32(EE_LOGADDRESS);
	logAddressDir = eepromRead16(EE_LOGADDRESS_DIR);
	if(logAddressDir >= LOG_START)
		logAddressDir = 0;
		
	dataLoad();
	memcpy(&buffer[6], &logAddressData, 4);

	// Save block to card
	sdcardWrite(logAddressDir, buffer);

	logAddressDir++;
	eepromWrite16(EE_LOGADDRESS_DIR, logAddressDir);
}

// DataLog: Saves all logging data in buffer to SDCARD
void dataLog(){
	UInt32	logAddress;
	
	logAddress = eepromRead32(EE_LOGADDRESS);

	if(logAddress < LOG_START)
		logAddress = LOG_START;
	if(logAddress >= LOG_END)
		logAddress = LOG_START;
		
	// Get data into buffer
	dataLoad();

	// Save block to card
	sdcardWrite(logAddress, buffer);

	if(mode & 0x02){
		// Write to serial
		usartWrite("AA", 2);
		usartWrite(buffer, 32);
	}
	
	// Update address
	logAddress++;

	eepromWrite32(EE_LOGADDRESS, logAddress);
}

// DataDump: Dumps given blocks to RS232 port
void dataDump(){
	char	c;
	UInt32	block;
	UInt16	num;
	UInt16	i;
	char*	p = (char*)&block;

	usartPutc('d');
	
	// Get Block Address
	*p++ = usartGetc();
	*p++ = usartGetc();
	*p++ = usartGetc();
	*p++ = usartGetc();

	p = (char*)&num;
	*p++ = usartGetc();
	*p++ = usartGetc();

	for(i = 0; i < num; i++){
		c = usartGetc();
		if(c == 'r')
			break;

		sdcardRead(block, buffer);

		usartWrite("AA", 2);
		if(block < LOG_START)
			usartWrite(buffer, 10);
		else
			usartWrite(buffer, 32);
		
		if(block < LOG_START){
			if(++block == LOG_START)
				block = 0;
		}
		else {
			if(++block == LOG_END)
				block = LOG_START;
		}

		_asm clrwdt _endasm;		// Clear watchdog timer
	}
}

#else

// DataLog: Saves all logging data in buffer to EEPROM
void dataLog(){
	UInt16	logAddress;
	UInt8	chipAdd = 0xA0;

	logAddress = eepromRead16(EE_LOGADDRESS);
		
	if(logAddress >= LOG_END)
		logAddress = 0;

	// Get data into buffer
	dataLoad();

#if OPT_TWO_EEPROMS
	if(logAddress >= 1024)
		chipAdd |= 0x02;
#endif

	// Write to I2C EEPROM
	i2cWrite(chipAdd, logAddress * 32, I2cFast16, buffer, 32);
	
	if(mode & 0x02){
		// Write to serial
		usartWrite("AA", 2);
		usartWrite(buffer, 32);
	}
	
	// Update address
	logAddress++;

	eepromWrite16(EE_LOGADDRESS, logAddress);
}

// DataDump: Dumps all logged data to RS232 port
void dataDump(){
	char	c;
	UInt32	block;
	UInt16	num;
	UInt16	i;
	UInt8	chipAdd = 0xA0;
	char*	p = (char*)&block;

	usartPutc('d');

	// Get Block Address
	*p++ = usartGetc();
	*p++ = usartGetc();
	*p++ = usartGetc();
	*p++ = usartGetc();

	p = (char*)&num;
	*p++ = usartGetc();
	*p++ = usartGetc();

	for(i = 0; i < num; i++){
		c = usartGetc();
		if(c == 'r')
			break;

#if OPT_TWO_EEPROMS
		if(block >= 1024)
			chipAdd = 0xA2;
		else
			chipAdd = 0xA0;
			
#endif

		i2cRead(chipAdd, block * 32, I2cFast16, buffer, 32);
		usartWrite("AA", 2);
		usartWrite(buffer, 32);
		block++;
		if(block >= LOG_END)
			block = 0;
		_asm clrwdt _endasm;		// Clear watchdog timer
	}
}
#endif


// SerialSetTime: Sets the real-time clocks time
void serialSetTime(){
	char	buf[8];
	char*	p = buf;

	usartPutc('t');
	_asm clrwdt _endasm;		// Clear watchdog timer
	*p++ = usartGetc();
	usartPutc('t');
	*p++ = usartGetc();
	usartPutc('t');
	*p++ = usartGetc();
	usartPutc('t');
	*p++ = 0x03;

	_asm clrwdt _endasm;		// Clear watchdog timer
	*p++ = usartGetc();
	usartPutc('t');
	*p++ = usartGetc();
	usartPutc('t');
	*p++ = usartGetc();
	usartPutc('t');
	*p++ = 0x10;

	_asm clrwdt _endasm;		// Clear watchdog timer
	i2cWrite(0xD0, 0, I2cSlow8, buf, 8);
}

// Reset: Resets values to initial conditions 
void reset(){
	charge0 = 1000;
	charge1 = 1000;
	charge = (UInt32)charge0 << 16;
	distanceTicks = 0;
	speedPeak = 0;
	speedPeakCurrent = 0;

	eepromWrite16(EE_LOGADDRESS, 0);
	eepromWrite16(EE_CHARGE0, charge0);
	eepromWrite16(EE_CHARGE1, charge1);
	eepromWrite16(EE_LOG_SECONDS, OPT_LOG_SECONDS);
}

// SerialProcess: Processes input from serial port
void serialProcess(){
	char	c;

	if(usartRxRdy()){
		c = usartGetc();
		switch(c){
		case 'm':
			displayMode++;
			if(displayMode > 3)
				displayMode = 0;
			break;
		case 'r':	mode = 1;	break;
		case 'c':	mode = 3;	break;
		case 'd':
			mode = 0;
			dataDump();
			break;
		case 't':
			serialSetTime();
			break;
		case 'R':
			reset();
			break;
		}
	}
}

void buttonsProcess(){
	UInt8	b;
	UInt16	t;

	// Read the buttons using an ADC
	b = (adcRead(9) + 32) / 64;
	
	if(b && (b != buttonsLast)){
		if(b == 10){
			reset();
		}
		else if(b == 1){
			displayMode++;
			if(displayMode > 3)
				displayMode = 0;
		}
		else if(b == 0x02){
			if(displayMode == 3){
				t = eepromRead16(EE_LOG_SECONDS);
				t += 1;
				eepromWrite16(EE_LOG_SECONDS, t);
			}
		}
		else if(b == 0x04){
			if(displayMode == 3){
				t = eepromRead16(EE_LOG_SECONDS);
				t -= 1;
				if(t < 1)
					t = 1;
				eepromWrite16(EE_LOG_SECONDS, t);
			}
		}
		else if(b == 0x08){
		}
	}
	buttonsLast = b;
}

// Boot Test
void bootTest(){
	lcdWriteStr(0, 0, "Car X");
	lcdWriteStr(0, 1, "Ready to Roll ....");
	delayMs(2000);
}

// Isr: Interrupt service routine
void isr(void) interrupt 1 {
	// Check timer 1 interrupt
	if(PIR2bits.TMR3IF){
		timerTick();
//		PORTBbits.RB7 = !PORTBbits.RB7;		// Flash red LED
		PIR2bits.TMR3IF = 0;			// Clear timer interrupt flag
	}
	if(INTCONbits.INT0IF){
//		PORTBbits.RB6 = !PORTBbits.RB6;		// Flash yellow LED
		speedInterrupt();
		INTCONbits.INT0IF = 0;
	}
}

#ifdef ZAP
UInt16 testTime(){
	UInt16	c;
	UInt32	t1, t2;
	UInt16	t = 0;
	
	t1 = timerGetMicroSec();
	for(c = 0; c < 100; c++){
		t = t + timerGetMicroSec();
//		t = t + timerGetMilliSec();
//		t = t + timerGetSec();
	}
	t2 = timerGetMicroSec();
	lcdWriteStr(0, 0, "                     ");
	lcdWriteStr(0, 1, "                     ");
	lcdWriteStr(0, 0, "");
	lcdPutHexByte(t1 >> 24);
	lcdPutHexByte(t1 >> 16);
	lcdPutHexByte(t1 >> 8);
	lcdPutHexByte(t1);
	lcdWriteStr(0, 1, "");
	lcdPutHexByte(t2 >> 24);
	lcdPutHexByte(t2 >> 16);
	lcdPutHexByte(t2 >> 8);
	lcdPutHexByte(t2);
	return t;
}
#endif

/* The main startup routine from processor reset */
void main(void) {
	UInt8	i;
	UInt8	t8;
	UInt16	v;
	UInt32	time;

	// Set up Oscillator
	OSCCON = 0x00;				// External 4MHZ
//	OSCCON = 0x62;				// Internal 4MHZ

	// Setup Ports
	TRISA = 0xFF;				// PORTA Input
	TRISB = 0x0F;           		// PORTB top 4 bits Output
	TRISC = 0xFF;				// PORTC Input

#if TEST_SPEED
	TRISB = 0x0E;           		// PORTB top 4 bits Output
#endif
	PORTB = 0;				// LED's off

	// Setup initial values
	mode = 1;
	displayMode = 0;
	charge0 = eepromRead16(EE_CHARGE0);
	charge1 = eepromRead16(EE_CHARGE1);
	
	speed = 0;
	temperature = 0;
	battery = 0;
	distanceTicks = 0;
	distance = 0;
	speedTickCount = 0;
	speedTickSTime = 0;PORTBbits.RB0 = 1;
	speedTickETime = 0;
	warnings = 0;
	voltageMin = 0;
	voltageCount = 0;
	voltageWarning = 0;
	speedPeak = 0;
	speedPeakCurrent = 0;
	speedAvg = 0;
	currentAvg = 0;
	currentAvgNum = 0;
	
	// Get battery number
	battery = PORTCbits.RC0;
	if(battery)
		charge = (UInt32)charge1 << 16;
	else
		charge = (UInt32)charge0 << 16;

	// Initialise system
	i2cInit();
	lcdInit();
	usartInit();
	timerInit();
	adcInit();

#if OPT_SDCARD
	spiInit();
	if(sdcardInit())
		usartPuts("No SDCARD\n\r");
	else
		usartPuts("SDCARD\n\r");
#endif

	if(RCONbits.TO)
		bootTest();			// Initial boot test, don't do this if a Watchdog reset has occurred

	WDTCONbits.SWDTEN = 1;			// Watchdog enable
	
	displayDo();

	// Enable interrupts	
	INTCON = 0;				// Clear interrupt flag bits
	INTCONbits.PEIE = 1;			// Enable peripheral interrupts
	PIE2bits.TMR3IE = 1;			// TMR3 overflow interrupt enable
	INTCON2bits.INTEDG0 = 1;		// INT0 Interrupt on +ve edge 
	INTCONbits.INT0IE = 1;			// Enable INT0 Speed interrupt
	INTCONbits.GIE = 1;			// Global interrupt enable

#ifdef ZAP
	// Test the timer
	lcdWriteStr(0, 0, "StartTesting");
	testTime();
	while(1)
		_asm clrwdt _endasm;
#endif

#if OPT_SDCARD
	// Add an entry to the data log directory
	dataLogDir();
#endif

	timerClear();
	timeNextMicroSec = 100000;
        while(1){
		// Loop every second
		PORTBbits.RB4 = !PORTBbits.RB4;		// Flash orange LED

		// Get battery number
		if(battery != PORTCbits.RC0){		// Save Values in EEPROM
			if(battery){
				charge1 = charge >> 16;
				eepromWrite16(EE_CHARGE1, charge1);
				charge = (UInt32)charge0 << 16;
			}
			else {
				charge0 = charge >> 16;
				eepromWrite16(EE_CHARGE0, charge0);
				charge = (UInt32)charge1 << 16;
			}
			battery = PORTCbits.RC0;
		}
		
		// Measure Voltage
		voltage = (adcRead(0) * 38) / 11;	// Calibrated for 33K/5K6 resistors
		
		// Sets the minium voltage depending on if 12 or 24 Volt is being used
		if(voltageMin == 0){
			if(voltage > 2000)
				voltageMin = 2100;
			else
				voltageMin = 1050;
		}
		
		for(i = 0; i < 10; i++){
			// Loop every 100ms
			while((time = timerGetMicroSec()) < timeNextMicroSec);		// Synchronise to first second, should look at RTC
			timeNextMicroSec = timeNextMicroSec + 100000;

			// Measure Current
			v = adcRead(1) - 500;			// Calibrated offset to read 0 when no current draw
			if(v > 511)				// Ignore negative charging currents
				v = 0;
			current = (v * 121) / 6;		// Calibrated current setting

#if OPT_BATTERY_LOW
			// Battery voltage limit
			if(voltage < voltageMin){
				warnings |= WARNING_BATTERY_LOW;
				voltageCount++;
				if(voltageCount > 40){
					voltageWarning = 20;
					voltageCount = 0;
				}
			}
			else if(!voltageWarning){
				warnings &= ~WARNING_BATTERY_LOW;
				voltageCount = 0;
			}
#endif				

			// Check buttons
			buttonsProcess();
			
			if(i == 1)
				speedCalculate();
			else if(i == 2)
				displayWarnings();
			else if(i == 3)
				distanceCalculate();
			else if(i == 4)
				chargeCalculate();			// Calculate battery charge
			else if(i == 5){
				t8 = eepromRead16(EE_LOG_SECONDS);
				if(((timeNextMicroSec / 1000000) % t8) == 0)
					dataLog();
			}
			else if(i == 6)
				serialProcess();
			else if(i == 9)
				displayDo();
			
#if TEST_SPEED
			PORTBbits.RB0 = 1;
			delayMs(1);
			PORTBbits.RB0 = 0;
#endif
		}
		
		// Save Values in EEPROM
		if(battery){
			charge1 = charge >> 16;
			eepromWrite16(EE_CHARGE1, charge1);
		}
		else {
			charge0 = charge >> 16;
			eepromWrite16(EE_CHARGE0, charge0);
		}
		
		// Do current/speed average when speed > 34Km/H and current > 12A
		if((speed > 3400) && (current > 1200)){
			speedAvg += speed;
			currentAvg += current;
			currentAvgNum++;
		}
			
		// Clear watchdog timer
		_asm clrwdt _endasm;
        }
}
