/*-----------------------------------------------------------------------------

File:		ledhot.c

Purpose:	Cree LED test bed

Version:	$Revision: 1.10 $

*/

// ------------------- includes -----------------------------------------------
#include	<p18f4550.h>				// Register definitions
#include "usb\usb.h"
#include "usb\io_cfg.h"


// ------------------- I/O Config ---------------------------------------------

#define MASK_TRISB	0xbe			// RB6,0 set to output B1011 1110
#define O_DBG_TIC	LATBbits.LATB0	// Debug of timer tic
#define O_LED1		LATBbits.LATB6	// LED on RB6

#define MASK_TRISC	0xfa			// RC2 set to output B1111 1010
#define O_TRICKLE	LATCbits.LATC0	// Trickle current output
#define O_PWM_OUT	LATCbits.LATC2	// PWM output

#define MASK_ADCON1	0x0c			// RA0-2 are analog inputs
#define ACH_LED_V	0				// A/D input 0 - Measure LED voltage
#define ACH_LED_I	2				// A/D input 2 - Measure LED current
#define ACH_TEST	1				// A/D input 1 - test pot


// ------------------- defines ------------------------------------------------

// State Machine states
typedef enum _SM_CMD {
	CMD_IDLE,		// Waiting for the start of a command
	CMD_PARAM,		// Received single letter command, waiting for param
	CMD_ERROR,		// Command error, just wait for <CR>
	CMD_COMPL,		// Complete command received, wait for main routine to process it
	CMD_SEND,
	CMD_SEND_ERR,	// Send an error
	CMD_PREP		// Prepare a status line
} SM_CMD;

#define MAX_PWM_VALUE	100
#define XMIT_TIME		2000L		// Timeout of .1s(=.1s/50us)
#define PWM_OUT_ON		1
#define PWM_OUT_OFF		!PWM_OUT_ON
#define MAX_AD_CHN		3			// Max A/D channels
struct _Status1 {
	unsigned zeroVal:1;
};

// ------------------- variable declarations ----------------------------------
unsigned int tick16;
char inpChar[1];
char inpBuf[4];
int inpBufInx = 0;
char outBuf[64];
int outBufInx = 0;
char numBuf[6];
int numBufInx = 0;
byte cmdState = CMD_IDLE;
int blinkRate = 2400;
int cmdParam;
int pwmValue = 0;
int pwmCount = 0;
long xmitTimeout = 0;
struct _Status1 Status1;

// Up to 8 channels of A/D values
unsigned char adMeasure = 0;
unsigned char adDone = 0;
int adValue[MAX_AD_CHN];
char adPrefix[MAX_AD_CHN];

// ------------------- function prototypes ------------------------------------
static void InitializeSystem(void);
void high_isr(void);
void low_isr(void);
void tickInit(void);
void UserInit(void);

void tickService(void);
void checkVoltage(void);
void checkCurrent(void);

void TaskUSBInternal(void);
void TaskBlinkLED(void);
void TaskStatusResp(void);
void TaskUsbIO(void);
void TaskCmdInput(void);

void setOK(void);
void setError(void);
void setValue(unsigned int val);
void adReadChannel(int chnNo);
void checkADInput(int chnNo);
void setChnValue(int chnNo);
void TaskADRead(void);

/////////////////////////////////////////////////
// Vector Remapping required for bootloader. Bootloader is from 0 - 0x7ff
extern void _startup (void);		// See c018i.c in your C18 compiler dir

// ------------------- interrupt vectors --------------------------------------

#pragma code _RESET_INTERRUPT_VECTOR = 0x000800

void _reset (void)
{
	_asm goto _startup _endasm
}

#pragma code

#pragma code _HIGH_INTERRUPT_VECTOR = 0x000808

void _high_ISR (void)
{
	_asm goto high_isr _endasm
}

#pragma code

#pragma code _LOW_INTERRUPT_VECTOR = 0x000818

void _low_ISR (void)
{
	_asm goto low_isr _endasm
}

#pragma code

/////////////////////////////////////////////////
//Low Interrupt ISR
#pragma interrupt low_isr
void low_isr(void) {
}

#pragma code

/////////////////////////////////////////////////
//High Interrupt ISR
#pragma interrupt high_isr
void high_isr(void) {
	//TMR2 is used for the ticks
	if (PIR1bits.TMR2IF) {   
		PIR1bits.TMR2IF = 0;
		//O_DBG_TIC = !O_DBG_TIC;
		tickService();
	}
}

// ------------------- code starts here ---------------------------------------
#pragma code

/**
 * Main function
 * Main program entry point.
 *
 */
void main(void) {
	InitializeSystem();

	//Initialize tick module
	tickInit();

	while(1) {
		TaskUSBInternal();		// Internal USB Tasks
		TaskBlinkLED();			// Blink LED
		TaskUsbIO();			// Service USB port
		TaskStatusResp();		// Send periodic status response
		TaskCmdInput();			// Process command received
		TaskADRead();			// Measure current and voltage
	}
}

/**
 * InitializeSystem is a centralize initialization routine.
 * All required USB initialization routines are called from here.
 *
 * User application initialization routine should also be
 * called from here.
 *
 */
static void InitializeSystem(void) {
	ADCON1 = MASK_ADCON1;

	T2CON = 0x0d;			//xxxx xx01 - Prescaler = 4
							//xxxx x1xx - Tmr2 on
							//x000 1xxx - Postscaler = 2 (0=1, 1=2 ... 11=12)
	// Crystal is 20MHz, prescaler set to 4, postscaler set to 2
	// Clock = 4 / 20MHz = 200ns
	// Timer period = 200ns * 4 * 2 = 1.6us
	// Interrupt period = 58us i.e. 58/1.6 = 36
	PR2 = 85;
	PIE1bits.TMR2IE = 1;	//Enable Timer2 interrupt


	/////////////////////////////////////////////////
	//USB stack defines
	#if defined(USE_USB_BUS_SENSE_IO)
	tris_usb_bus_sense = INPUT_PIN; // See io_cfg.h
	#endif
	
	#if defined(USE_SELF_POWER_SENSE_IO)
	tris_self_power = INPUT_PIN;
	#endif
	
	mInitializeUSBDriver();		// See usbdrv.h
	
	UserInit();					// See user.c & .h

	/////////////////////////////////////////////////
	//Global interrupt enable
	INTCONbits.PEIE = 1;			//Enable Peripheral interrups (TMR2, ....)
	INTCONbits.GIE = 1;			//Global interrupt enable
}

/**
 * Service loop for USB tasks.
 *
 * @PreCondition	InitializeSystem has been called.
 */
void TaskUSBInternal(void) {
	/*
	* Servicing Hardware
	*/
	USBCheckBusStatus();					// Must use polling method
	if(UCFGbits.UTEYE!=1)
		USBDriverService();				// Interrupt or polling method
	
	#if defined(USB_USE_CDC)
	CDCTxService();
	#endif
}

void UserInit(void) {
	TRISB = MASK_TRISB;
	TRISC = MASK_TRISC;

	O_TRICKLE = 0;

	adPrefix[ACH_LED_V] = 'V';
	adPrefix[ACH_LED_I] = 'I';
	adPrefix[ACH_TEST] = 'T';

	adMeasure = 0;
	adDone = 0;
}

/**
 * Initializes tick values
 */
void tickInit() {
	tick16 = blinkRate;
}

// ------------------- interrupt service routines -----------------------------

void tickService(void) {
	if (tick16 > 0) {
		tick16--;
	}

	if (++pwmCount >= MAX_PWM_VALUE) {
		pwmCount = 0;
	}
	if (adMeasure == 0) {
		// If a measurement is in progress, don't change output
		if (pwmCount >= pwmValue) {
			O_PWM_OUT = PWM_OUT_OFF;
			checkADInput(ACH_LED_V);
		}
		else {
			O_PWM_OUT = PWM_OUT_ON;
			checkADInput(ACH_LED_I);
		}
	}
	checkADInput(ACH_TEST);

	if (xmitTimeout > 0) {
		xmitTimeout--;
	}
}

void checkADInput(int chnNo) {
	if ((adDone & (1 << chnNo)) == 0 && adMeasure == 0) {
		adMeasure |= (1 << chnNo);

		if (chnNo == ACH_LED_V) {
			// Turn on trickle current output
			O_TRICKLE = 1;
		}

		// Measure test voltage
		ADCON1bits.VCFG1 = 0;	// Vref- = Vss
		ADCON1bits.VCFG0 = 0;	// Vref+ = Vdd
		ADCON0 = (chnNo << 2);	// Select channel 1
		//ADCON2 = 0b10111010;	// Right justified, 20Tad delay, Tad = Fosc/32
								// f = 20MHz/32 = 625KHz Tad = 1/625KHz = 1.6us
								// More than the required 0.7us

		ADCON2 = 0b10010101;	// Right justified, 4Tad delay, Tad = Fosc/16
								// f = 20MHz/16 = 1.25MHz Tad = 1/1.25MHz = 0.8us
								// More than the required 0.7us

		//ADCON2 = 0b10111110;	// Right justified, 20Tad delay, Tad = Fosc/64
								// f = 20MHz/64 = 312500Hz Tad = 1/312500 = 3.2us
								// More than the required 0.7us

		ADCON0bits.ADON = 1;	// Enable A/D module
		ADCON0bits.GO_DONE = 1;	// Start conversion

	}
}

void TaskBlinkLED(void) {
	if (tick16 == 0) {
		O_LED1 = !O_LED1;
		tickInit();
	}
}

void TaskCmdInput(void) {
	if (cmdState == CMD_COMPL) {
		if (inpBuf[0] == 'D') {
			cmdParam = inpBuf[1] - '0';
			if (inpBufInx > 2) {
				cmdParam *= 10;
				cmdParam += inpBuf[2] - '0';
			}
			if (cmdParam < MAX_PWM_VALUE) {
				pwmValue = cmdParam;
				setOK();
				cmdState = CMD_SEND;
			}
			else {
				setError();
				cmdState = CMD_SEND_ERR;
			}
		}
		else {
			setError();
			cmdState = CMD_SEND_ERR;
		}
	}
}

void TaskStatusResp(void) {
	if (cmdState == CMD_IDLE && xmitTimeout == 0) {
		outBuf[0] = '\r';
		outBuf[1] = '\n';
		outBuf[2] = '[';
		outBuf[3] = 'R';
		outBuf[4] = ' ';
		outBuf[5] = 'd';
		outBufInx = 6;
		setValue(pwmValue);
		setChnValue(ACH_LED_V);
		setChnValue(ACH_LED_I);
		setChnValue(ACH_TEST);
		outBuf[outBufInx++] = ']';
		outBuf[outBufInx++] = '\r';
		outBuf[outBufInx++] = '\n';
		outBuf[outBufInx++] = '\0';

		adDone = 0;
		xmitTimeout = XMIT_TIME;
		cmdState = CMD_SEND;
	}
}

void TaskADRead(void) {
	adReadChannel(ACH_LED_V);
	adReadChannel(ACH_LED_I);
	adReadChannel(ACH_TEST);
}

void adReadChannel(int chnNo) {
	if ((adMeasure & (1 << chnNo)) != 0) {
		adValue[chnNo] = ADRESL;
		adValue[chnNo] |= ((int)ADRESH << 8);
		adMeasure = 0;
		adDone |= (1 << chnNo);

		if (chnNo == ACH_LED_V) {
			O_TRICKLE = 0;
		}
	}
}

void setOK(void) {
	outBuf[0] = '\n';
	outBuf[1] = 'O';
	outBuf[2] = 'K';
	outBuf[3] = '\r';
	outBuf[4] = '\n';
	outBuf[5] = '\0';
}

void setError(void) {
	outBuf[0] = '\n';
	outBuf[1] = 'E';
	outBuf[2] = 'R';
	outBuf[3] = 'R';
	outBuf[4] = '\r';
	outBuf[5] = '\n';
	outBuf[6] = '\0';
}

void setChnValue(int chnNo) {
	outBuf[outBufInx++] = ' ';
	outBuf[outBufInx++] = adPrefix[chnNo];
	setValue(adValue[chnNo]);
}

void setValue(unsigned int val) {
	numBufInx = 0;
	if (val == 0) {
		numBuf[numBufInx++] = '0';
	}
	else {
		while (val > 0) {
			numBuf[numBufInx++] = '0' + (val % 10);
			val = val / 10;
		}
	}

	while (numBufInx > 0) {
		outBuf[outBufInx++] = numBuf[--numBufInx];
	}
}

void setValue2(int val) {
	Status1.zeroVal = 1;
	if (val >= 10000) {
		outBuf[outBufInx++] = '0' + (val / 10000);
		val = val % 10000;
		Status1.zeroVal = 0;
	}
	if (val >= 1000 || Status1.zeroVal == 0) {
		outBuf[outBufInx++] = '0' + (val / 1000);
		val = val % 1000;
		Status1.zeroVal = 0;
	}
	if (val >= 100 || Status1.zeroVal == 0) {
		outBuf[outBufInx++] = '0' + (val / 100);
		val = val % 100;
		Status1.zeroVal = 0;
	}
	if (val >= 10 || Status1.zeroVal == 0) {
		outBuf[outBufInx++] = '0' + (val / 10);
		val = val % 10;
		Status1.zeroVal = 0;
	}
	outBuf[outBufInx++] = '0' + val;
}

void TaskUsbIO(void) {
	static byte bytesRead;

	// User Application USB tasks
	if((usb_device_state < CONFIGURED_STATE)||(UCONbits.SUSPND==1)) {
		return;
	}

	switch (cmdState) {
		case CMD_IDLE:
			//Check if any data was received via the virtual serial port
			if(bytesRead = getsUSBUSART(inpChar, 1)) {
				if (inpChar[0] == 'D') {
					inpBufInx = 0;
					inpBuf[inpBufInx++] = inpChar[0];
					cmdState = CMD_PARAM;
				}
				else {
					cmdState = CMD_ERROR;
				}
			}
			else {
			}
			break;
		case CMD_PARAM:
			if(bytesRead = getsUSBUSART(inpChar, 1)) {
				if (inpChar[0] == ' ') {
				}
				else if (inpBufInx >= 4) {
					if (inpChar[0] == 0xd) {
						cmdState = CMD_SEND_ERR;
					}
					else {
						cmdState = CMD_ERROR;
					}
				}
				else if (inpChar[0] >= '0' && inpChar[0] <= '9') {
					inpBuf[inpBufInx++] = inpChar[0];
				}
				else if (inpChar[0] == 0xd) {
					cmdState = CMD_COMPL;
				}
				else {
					cmdState = CMD_ERROR;
				}
			}
			break;
		case CMD_ERROR:
			if(bytesRead = getsUSBUSART(inpChar, 1)) {
				if (inpChar[0] == 0xd) {
					cmdState = CMD_SEND_ERR;
				}
			}
			break;
		case CMD_SEND_ERR:
			setError();

			//Send contents of output buffer
			cmdState = CMD_SEND;

			break;
		case CMD_SEND:
			if(mUSBUSARTIsTxTrfReady()) {
				putsUSBUSART(outBuf);

				//Back to wait for input state
				cmdState = CMD_IDLE;
			}
			break;
	}
}
