/*	
 *	rmot3mdi.c	January 8, 2002  Bob Blick based on:
 *	remote6.c which is based on:
 *	lcdterm6.c	December 4, 2000  Bob Blick
 *	Licensed under the terms of the GNU General Public License, www.gnu.org
 *	No warranties expressed or implied.
 *	uses 16F628 with hardware UART to achieve MIDI baud rate(31250)
 *	uses 4MHz crystal
 *	latest revision:
 *	added control-G for beep, control-P followed by value for backlite "on" level
 *
 *	compile with PICC from www.htsoft.com
 *	command line:
 *	picc -O -Zg9 -16F628 -V rmot?mdi.c
 *
 *	pin assignments:
 *	PORTA			5 pin4	4 pin3	3 pin2	2 pin1	1 pin18	0 pin17
 *				ON-IN	SUSPEND	ON-OUT	LCDRS	BEEP	KBIN
 *	
 *	PORTB	7 pin13	6 pin12	5 pin11	4 pin10	3 pin9	2 pin8	1 pin7	0 pin6
 *		LCD7	LCD6	LCD5	LCD4	BKLITE	SEROUT	SERIN	LCDEN
 *		KBDO7	KBDO6	KBDO5	KBDO4				
 *
 *	OTHER	pin5	pin14		pin15	pin16
 *		Vss/GND	Vdd/+5		OSC2CKO	OSC1CKI
 */
#include	<pic.h>
__CONFIG(FOSC0 | CP0 | CP1 | CPD);	//this is for XT (4 megahertz or less) oscillator
// Watchdog timer would be  | 0x04 but I'm not using it

// Change these to suit your setup

// You must use one and only one of the next four

#define CURSOR_OFF_NOBLINK		//if you want no cursor
//#define CURSOR_ON_NOBLINK		//if you want a plain cursor
//#define CURSOR_OFF_BLINK		//if you want the print position to blink
//#define CURSOR_ON_BLINK		//the cursor kitchen sink

// LCD formatting does line wraps correctly. Uses memory, you don't need it unless you want it.
// Note that 2X40 displays wrap correctly right out of the box and need no formatting.

//#define LCD_FORMAT		//if you want LCD line formatting uncomment this

// If you chose LCD_FORMAT uncomment one and only one of the following

//#define LCD_4X20		//if you have a 4x20 display and want formatting
//#define LCD_2X20
//#define LCD_2X16
//#define LCD_2X8		//this also works for cheap 1X16 displays that have one chip
//#define LCD_1X16

// LCD hardware initialization. Two line 5x7 font is the most common. You must choose one.

#define TWOLINE_5X7
//#define ONELINE_5X7	//this gives a better contrast ratio but only on one line displays
//#define ONELINE_5X10	//Many of the better 1 line displays support this.

// Uncomment to Translate ascii to use the full descender versions in the character map

//#define DESCENDERS	//if you use 5X10 font you can translate j,y,etc but uses memory

/**********YOU DON'T NEED TO CHANGE ANYTHING BEYOND THIS POINT************/

#define byte unsigned char

static bit TURNONIN	@ ((unsigned)&PORTA*8+5);	// vehicle sense
static bit SUSPENDIN	@ ((unsigned)&PORTA*8+4);	// motherboard suspend sense
static bit TURNONOUT	@ ((unsigned)&PORTA*8+3);	// power supply turnon
static bit BEEPPIN	@ ((unsigned)&PORTA*8+1);	// piezo element speaker
static bit KBIN		@ ((unsigned)&PORTA*8+0);	// keypad input

// LCD pins
static bit LCD_RS	@ ((unsigned)&PORTA*8+2);	// Register select
static bit LCD_EN	@ ((unsigned)&PORTB*8+0);	// Enable

// Line lengths for LCD formatting
#define BEGIN_LINE_1	0
#define LINE_1_MINUS_1	255
#ifdef LCD_4X20
#define END_LINE_1	19
#define BEGIN_LINE_2	64
#define END_LINE_2	83
#define BEGIN_LINE_3	20
#define END_LINE_3	39
#define BEGIN_LINE_4	84
#define END_LINE_4	103
#define LINE_2_MINUS_1	63
#define LINE_3_MINUS_1	19
#define LINE_4_MINUS_1	83
#endif
#ifdef LCD_2X20
#define END_LINE_1	19
#define BEGIN_LINE_2	64
#define END_LINE_2	83
#define LINE_2_MINUS_1	63
#endif
#ifdef LCD_2X16
#define END_LINE_1	15
#define BEGIN_LINE_2	64
#define END_LINE_2	79
#define LINE_2_MINUS_1	63
#endif
#ifdef LCD_2X8
#define END_LINE_1	7
#define BEGIN_LINE_2	64
#define END_LINE_2	71
#define LINE_2_MINUS_1	63
#endif
#ifdef LCD_1X16
#define END_LINE_1	15
#endif

// Delay constants used by LCD routines
#define T_120_US	2	// could be as low as 257 usec or as high as 511 usec
#define T_1000_US	5	// could be as low as 1025 usec or as high as 1279 usec

#define	LCD_STROBE	((LCD_EN=1),(LCD_EN=0))

#define KB_TASK_PERIOD	196	// 50000 usec = 20 per second
#define KB_DELAY_LOAD	10	// kb checks before repeat
#define KB_REPEAT_RATE	2	// about 10 per second
#define KB_NO_KEY	4	// this is for the smaller keypad

//defines added for remote
#define ONE_SEC_PERIOD		20	// one per second, change this if KB_TASK_PERIOD gets changed
#define POWER_DOWN_COUNT	45	// after no computer activity shut it off
#define POWER_UP_COUNT		900	// time to boot up 900 = 15 minutes(think about full fsck and large disk!)
#define POWER_OFF_COUNT		7	// after server power off, minimum before power up
#define CHAR_COUNT_LOAD		8	// powercount will not be reduced if a few chars received while booting
enum power_state {
	PS_POWER_OFF,
	PS_POWERING_UP,
	PS_OPERATING,
	PS_POWERING_DOWN,
	PS_USER_SHUTDOWN,
	PS_HOLD_OFF
	
};

// VARIABLES
static volatile unsigned int	kbtaskcount;	//decrements every interrupt
static bit	kbtaskflag;		//goes high when it's time to check the keyboard
byte kbdelaycount;		//timer for keypress
byte kbrepeatcount;
byte kboldkey;			//last keypress
static bit	command_next;		//next received character is special lcd data..
static byte	lcdcommand;		//so stash the command until we get the data
static byte	oneseccount;		//decrement this every keyboard check
static bit	onesecflag;		//high when it's a second

//new variables used for remote
static unsigned int	powercount;		//kill power when it zeroes
static byte	powerstate;
static byte	charcounter;		//ignore some glitches from server at powerup
static bit	char_received;		//flag we have character
byte		beep;
byte		backlite_value;		//backlite regularly updated with this value
static bit	use_suspend;		//suspend line from motherboard works

#ifdef LCD_FORMAT
byte cursorpos;
#endif

// LOOKUP TABLES
const byte Lcd_tbl[] = {	0x0E,0x11,0x17,0x19,0x17,0x11,0x0E,0x00,//copyright
				0x02,0x06,0x0E,0x1E,0x0E,0x06,0x02,0x00,//points left
				0x08,0x0C,0x0E,0x0F,0x0E,0x0C,0x08,0x00,//points right
				0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,//minutes
				0x00,0x0E,0x1F,0x1F,0x1F,0x0E,0x00,0x00};//nice dot
// Primary keyboard codes
const byte Kb_tbl_pri[] =  {	'L','R','U','D','.' };	// left right up down :-)
// Secondary keyboard codes
const byte Kb_tbl_sec[] =  {	'l','r','u','d','.' };
// mode 0: Keydown sends primary code, when held repeats primary code after KB_DELAY_LOAD delay.
// mode 1: Keydown sends primary code. Keyup sends secondary code.
// mode 2: Keyup sends primary code unless held longer than KB_DELAY_LOAD delay, send secondary.
// mode 3: Keyup sends primary code unless held longer than KB_DELAY_LOAD delay, send secondary
//         code, if held another KB_DELAY_LOAD will do repeats of secondary code.
// a non-existent mode disables a key
const byte Kb_tbl_mode[] = {	3,3,2,2,1 };	//must have one extra here

// FUNCTION PROTOTYPES
void init_stuff(void);		//sets up interrupt, pins, etc
void rs232_putch(byte c);	//puts a byte to serial transmit buffer
byte rs232_getch(void);		//gets a byte from usart and clears any errors
void lcd_clear(void);		//clear and home the LCD
void lcd_puts(const byte * s);	//put ascii string to LCD
void lcd_putch(byte c);		//put one character to LCD
void lcd_goto(byte pos);	//positions LCD cursor
void lcd_init(void);		//sets up LCD interface
void lcd_putcmd(byte c);	//send one byte to LCD command register
void lcd_chars(void);		//sends LCD custom characters. must call lcd_goto afterwards!
void delay8us(unsigned int);	//delay 8us to .528 sec using timer 1
//void rs232_puts(const byte * s);	//put ascii string to serial port, uncomment if you need
byte scankb(void);		//returns which key is depressed
void kbrs232(byte key);		//normal "keypad to rs232 out" operation
void rs232lcd(byte rec);	//sends rs232 received data to LCD or translates if needed
#ifdef LCD_FORMAT
void inc_cursor(void);		//increments the LCD cursor past line breaks
void dec_cursor(void);		//decrements
#endif
void init_pwm(void);
void backlite(byte);		//set intensity of backlite

// THE PROGRAM
void main(void) {
	byte tempkey;
	init_stuff();
	init_pwm();
	delay8us(62500);
	lcd_init();
	lcd_clear();
	backlite_value = 255;
	backlite(backlite_value);		//backlite on
	lcd_puts("remotemidi v0.3");
	lcd_chars();

	lcd_goto(0x40);
//	lcd_putch(0);			// copyright symbol
	lcd_puts("2002 Bob Blick");
	delay8us(62500);		// .5 second
	beep = 0;
	delay8us(62500);
	backlite(0);
//	lcd_clear();

	while(1) {
		if (RCIF) {
			rs232lcd(rs232_getch());
			char_received = 1;	// flag the watchdog
		}
		
		switch (powerstate) {
			case PS_POWER_OFF:
				if(TURNONIN) {	//accessory input rise, time to power up
					powerstate++;	//move to "powering up"
					TURNONOUT = 1;	//switch on the server
					lcd_clear();
					lcd_puts("Booting LINUX...");
					powercount = POWER_UP_COUNT;	//a long time
					charcounter = CHAR_COUNT_LOAD;
					char_received = 0;
					use_suspend = 0;
				}
				break;
			case PS_POWERING_UP:
				if(char_received) {
					char_received = 0;
					charcounter--;	//allow a few chars that may be glitches
					if (powercount < POWER_DOWN_COUNT)
						powercount = POWER_DOWN_COUNT;
				}
				if(!(charcounter))
					powerstate++;	//PS_OPERATING
				if(!(powercount)) {
					powerstate = PS_HOLD_OFF;
					TURNONOUT = 0;
					lcd_clear();
					lcd_puts("Boot failed.");
					lcd_goto(0x40);
					lcd_puts("Any key to retry");
				}
				if(SUSPENDIN)
					use_suspend = 1;
				break;
			case PS_OPERATING:
				if(char_received) {
					char_received = 0;
					powercount = POWER_DOWN_COUNT;	//keep the power on
				}
				if (TURNONIN == 0) {		//car is turned off
					if (onesecflag) {
						rs232_putch('Z');	//tell the server the car is off
					}
				}
				if(use_suspend && !SUSPENDIN)	// if we can use SUSPENDIN signal from
					powercount = 0;		// motherboard, accelerate shutdown
				if(!(powercount)) {
					TURNONOUT = 0;
					lcd_clear();
					lcd_puts("Power off");
					powercount = POWER_OFF_COUNT;
					if (TURNONIN) {		// car is on, system requested shutdown
						powerstate = PS_USER_SHUTDOWN;
						lcd_goto(0x40);
						lcd_puts("Any key to start");
					} else {
						powerstate = PS_POWERING_DOWN;
					}
				}
				break;
			case PS_POWERING_DOWN:
				if(!(powercount)) {
					powerstate = PS_POWER_OFF;
				}
				break;
			case PS_USER_SHUTDOWN:
				if(!powercount) {
					powerstate = PS_HOLD_OFF;
				}
				break;
			case PS_HOLD_OFF:
				if(TURNONIN) {
					tempkey = scankb();
					if (tempkey != KB_NO_KEY) {
						powerstate = PS_POWER_OFF;
						lcd_clear();
					}
				}
				break;
		}
		
		if (onesecflag) {
			onesecflag = 0;
			if(powercount) {
				powercount--;
			}
			if(TURNONIN)
				backlite(backlite_value);
			else
				backlite(0);
		}
		
		if(kbtaskflag)
		{
			kbtaskflag = 0;
			kbrs232(scankb());
			if(!(--oneseccount)) {
				oneseccount = ONE_SEC_PERIOD;
				onesecflag = 1;
			}
		}
		
	} // End of  "while(1)"
} // End of  "main()"

// THE FUNCTIONS
void init_stuff(void) {
	PORTB = 0;
	TRISA = 0b00110001;		// PORTA directions.
	TRISB = 0b00000110;		// PORTB outputs except usart pins must be set as inputs
	CMCON = 7;			// turn off comparator input on PORTA

	kbtaskcount = KB_TASK_PERIOD;
	kbtaskflag = 0;
	kbdelaycount = KB_DELAY_LOAD;
	kbrepeatcount = KB_REPEAT_RATE;
	kboldkey = KB_NO_KEY;
	command_next = 0;
	beep = 1;
//remote inits
	oneseccount = ONE_SEC_PERIOD;
	powercount = 0;
	powerstate = 0;

	#ifdef LCD_FORMAT
		cursorpos = 0;
	#endif
	/* Set up the USART for 31250 baud (with 4 mhz crystal) */
	SPBRG = 1;
	TXSTA = 0x22;	// BRGH = 0, SYNC = 0, TXEN = 1
	RCSTA = 0x90;	// SPEN = 1, CREN = 1
	/* Set up timer 0 */
	T0CS = 0;			// Set timer mode for Timer0
	T0IE = 1;			// Enable the Timer0 interrupt
	GIE = 1;			// Enable interrupts
	/* Set up timer 1 */
	T1CON = 0b00110001;		// T1 on prescale 8 internal clock
}

void rs232_putch(byte c) {
	while(!TRMT)
		continue;
	TXREG = c;
}

byte rs232_getch() {
	if(OERR || FERR) {	// clear any usart receive errors
		CREN = 0;
		CREN = 1;
	}
	return (RCREG);
}	

/* Interrupt service routine
 * This ISR runs 3906.25 times per second with a 4MHz crystal
 */

interrupt void isr(void) {
	if (!(--beep))		// to make beeping, set beep to anything but 1.
		beep++;		// beep = 0 is longest, 1/15 of a sec
	else BEEPPIN = 1;	// 255 almost as long, 64 very short etc

	T0IF = 0;

/*** COUNTERS ETC ***/
	if(!(--kbtaskcount)) {
		kbtaskcount = KB_TASK_PERIOD;
		kbtaskflag = 1;
	}
	BEEPPIN = 0;
}
// END of interrrupt service routine

// LCD ROUTINES FOLLOW

// Clear and home the LCD
void lcd_clear(void) {
	lcd_putcmd(0x01);
	delay8us(205);		//had 375 * 8
}

// write a string of chars to the LCD
void lcd_puts(const byte * s) {
	while(*s)
		lcd_putch(*s++);
}

// write one character to the LCD
void lcd_putch(byte c) {
	LCD_RS = 1;	// write characters
	PORTB &= 0x0F;
	PORTB |= c & 0xF0;
	LCD_STROBE;
	PORTB &= 0x0F;
	PORTB |= c << 4;
	LCD_STROBE;

	delay8us(6);		// had 15 * 8
}


// Go to the specified position
void lcd_goto(byte pos) {
	lcd_putcmd(0x80+pos);
}
	
// initialise the LCD - put into 4 bit mode
void lcd_init(void) {
	LCD_RS = 0;	// write control bytes
	delay8us(6250);	// power on delay. LCD spec is 15ms but some don't make it
	PORTB &= 0x0F;
	PORTB |= 0x30;				//repeat this initialization 3 times
	LCD_STROBE;
	delay8us(625);
	LCD_STROBE;
	delay8us(15);
	LCD_STROBE;
	delay8us(625);
	PORTB &= 0x0F;
	PORTB |= 0x20;				//set 4 bit mode
	LCD_STROBE;
	delay8us(15);
	#ifdef TWOLINE_5X7
		lcd_putcmd(0x28);	// 4 bit mode, 1/16 duty, 5x7 font
	#endif
	#ifdef ONELINE_5X7
		lcd_putcmd(0x20);	// 4 bit mode, 1/8 duty cycle, 5x7 font
	#endif
	#ifdef ONELINE_5X10
		lcd_putcmd(0x24);	// 4 bit mode, 1/8 duty cycle, 5x10 font
	#endif
		lcd_putcmd(0x08);	// display off
	#ifdef CURSOR_ON_NOBLINK
		lcd_putcmd(0x0E);	// display on with plain cursor
	#endif
	#ifdef CURSOR_OFF_BLINK
		lcd_putcmd(0x0D);	// display on with blink character
	#endif
	#ifdef CURSOR_OFF_NOBLINK
		lcd_putcmd(0x0C);	// display on, no cursor
	#endif
	#ifdef CURSOR_ON_BLINK
		lcd_putcmd(0x0F);	// display on, cursor, blink character
	#endif
	lcd_putcmd(0x06);	// entry mode = increment cursor, freeze display
}

// Write to a command register of LCD
void lcd_putcmd(byte c) {
	LCD_RS = 0;
	PORTB &= 0x0F;
	PORTB |= c & 0xF0;
	LCD_STROBE;
	PORTB &= 0x0F;
	PORTB |= c << 4;
	LCD_STROBE;

	delay8us(6);
}

void lcd_chars(void) {		// load the special characters into the LCD
	byte cnt;
	lcd_putcmd(0x40);	//first location of Character Generator ram
	for(cnt=0;cnt<40;cnt++)
	{
		lcd_putch(Lcd_tbl[cnt]);
	}
}
// End of LCD functions

//delay 8us to .528 sec using timer 1
void delay8us(unsigned int t) {
	TMR1ON = 0;
	t = 0 - t;
	TMR1H = t>>8;
	TMR1L = t;
	TMR1ON = 1;
	TMR1IF = 0;
	while(!TMR1IF);
}

/*
//write a string of characters out the serial port
void rs232_puts(const byte * s) {
	while(*s)
		rs232_putch(*s++);
}
*/
// Scan the keyboard, return first key found or KB_NO_KEY if none found
// if you change the number of rows or columns, change KB_NO_KEY to reflect it
byte scankb(void) {
	byte delay;
	byte key = 0;		// first key returns 0, no key returns KB_NO_KEY
	byte column = 0b00010000;	//RB4 is starting column.
//if you reduce columncount it will sequence through fewer columns - ending earlier, 
//or starting later if you choose a different start column
	byte columncount = (KB_NO_KEY + 1);	// one more than the number of columns
	while(--columncount)
	{
		PORTB &= 0b00001111;
		PORTB |= column;	//energize column but leave low 3 pins untouched
		for (delay = 10; --delay;);		//this delay rejects key capacitance
	//test each row in sequence
		if(KBIN)
			break;
		key++;
		column <<= 1;	//shift to left, energize next column
	}
	return key;
}

// Converts raw key codes to rs232 activity. Very versatile, but uses a lot of ROM
// Cutting out only one of the modes saves 50 words.
void kbrs232(byte key) {
	byte kb_event_state;
	enum kb_event_state {
		KBES_NONE,	//idle keyboard
		KBES_KEYDOWN,	//key down occurred
		KBES_KEYUP,	//key up occurred
		KBES_KEYHOLD,	//still holding, repeat counter not run out
		KBES_KEYREPEAT	//repeatcount has run out
	};
// Figure out what type of event happened	
	if(key == KB_NO_KEY)	//no keys are down
	{
		if(kboldkey == KB_NO_KEY)
			kb_event_state = KBES_NONE;
		else
			kb_event_state = KBES_KEYUP;
	}
	else	//yes there is a key down somewhere
	{
		if(kboldkey == KB_NO_KEY) {
			kb_event_state = KBES_KEYDOWN;
			kboldkey = key;
			}
		else
		{
			if(kbdelaycount)			//still counting
				kb_event_state = KBES_KEYHOLD;
			else					//count ran out
				kb_event_state = KBES_KEYREPEAT;
		}
	}
// Each key can have its own programming mode, here are 4 samples
	switch(Kb_tbl_mode[kboldkey]) {
	case (0):	//keydown=primary repeat=primary
		switch(kb_event_state) {
		case(KBES_NONE):
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			break;
		case(KBES_KEYDOWN):
			rs232_putch(Kb_tbl_pri[kboldkey]);
			beep = 0;
			break;
		case(KBES_KEYUP):
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			kboldkey = KB_NO_KEY;	
			break;
		case(KBES_KEYHOLD):
			if(kbdelaycount)
				kbdelaycount--;		//never go below zero
			break;
		case(KBES_KEYREPEAT):
			if(!(--kbrepeatcount)) {
				rs232_putch(Kb_tbl_pri[kboldkey]);
				kbrepeatcount = KB_REPEAT_RATE;
				}
				break;
		} break;
	case(1):	//keydown=primary keyup=secondary
		switch(kb_event_state) {
		case(KBES_NONE):
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			break;
		case(KBES_KEYDOWN):
			rs232_putch(Kb_tbl_pri[kboldkey]);
			beep = 0;
			break;
		case(KBES_KEYUP):
			rs232_putch(Kb_tbl_sec[kboldkey]);
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			kboldkey = KB_NO_KEY;
			beep = 0;
			break;
		case(KBES_KEYHOLD):
			break;
		case(KBES_KEYREPEAT):
			break;
		} break;
	case(2):	//keyup=primary keyhold=secondary
		switch(kb_event_state) {
		case(KBES_NONE):
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			break;
		case(KBES_KEYDOWN):
			
			break;
		case(KBES_KEYUP):
			if(kbdelaycount) {
				rs232_putch(Kb_tbl_pri[kboldkey]);
				beep = 0;
				}
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			kboldkey = KB_NO_KEY;
			break;
		case(KBES_KEYHOLD):
			if(kbdelaycount)
				kbdelaycount--;		//never go below zero
			break;
		case(KBES_KEYREPEAT):			// use kbrepeatcount to 
			if(kbrepeatcount) {		// insure this only happens once
				kbrepeatcount = 0;
				rs232_putch(Kb_tbl_sec[kboldkey]);
				beep = 0;
				}
			break;
		} break;
	case(3):	//keyup=primary keyrepeat=secondary
		switch(kb_event_state) {
		case(KBES_NONE):
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			break;
		case(KBES_KEYDOWN):
			kbrepeatcount = KB_DELAY_LOAD;	// first repeat has long delay
			break;
		case(KBES_KEYUP):
			if(kbdelaycount) {
				rs232_putch(Kb_tbl_pri[kboldkey]);
				beep = 0;
				}
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			kboldkey = KB_NO_KEY;
			break;
		case(KBES_KEYHOLD):
			if(kbdelaycount)
				kbdelaycount--;		//never go below zero
			break;
		case(KBES_KEYREPEAT):
			if(kbrepeatcount==KB_DELAY_LOAD) {	// immediately send
				rs232_putch(Kb_tbl_sec[kboldkey]);
				beep = 0;
				}
			if(!(--kbrepeatcount)) {		//then long wait until repeats
				rs232_putch(Kb_tbl_sec[kboldkey]);
				kbrepeatcount = KB_REPEAT_RATE;
				}
			break;
		} break;
	default:
		kboldkey = KB_NO_KEY;
		break;
	}
}	

// Sends rs232 received data to LCD or translates if needed
// Certain commands require a second byte for data, uses command_next as flag.
void rs232lcd(byte rec)	 {
	if (command_next) {	// extended commands, this is the data byte
		command_next = 0;
		switch (lcdcommand) {
			case(0x01):		// control-A, cursor move to
				lcd_goto(rec);
				#ifdef LCD_FORMAT
					cursorpos = rec;
				#endif
				return;
			case(0x10):		// control-P, backlite value
				backlite_value = rec;
				return;
			case(0x12):		// control-R, direct LCD command
				lcd_putcmd(rec);
				return;
			case(0x14):		// control-T, direct LCD data
				lcd_putch(rec);
				return;
			default:
				break;
		}
	}

	if(rec < 0x20) {
		switch(rec) {
			case (0x02):	//control-B, cursor left
				lcd_putcmd(0x10);	//move cursor left
				#ifdef LCD_FORMAT
					dec_cursor();
				#endif
				break;
			case (0x03):	//control-C
				lcd_clear();		//clear the LCD
				#ifdef LCD_FORMAT
					cursorpos = 0;
				#endif
				break;
			case (0x06):	//control-F, cursor right
				lcd_putcmd(0x14);	//move cursor right
				#ifdef LCD_FORMAT
					inc_cursor;
				#endif
				break;
			case (0x07):	//control-G, "bell"
				beep = 0;
				break;
			case (0x08):	//control-H, backspace
				lcd_putcmd(0x10);	//left
				#ifdef LCD_FORMAT
					dec_cursor();
				#endif
				lcd_putch(0x20);	//space
				#ifdef LCD_FORMAT
					inc_cursor();
				#endif
				lcd_putcmd(0x10);	//left
				#ifdef LCD_FORMAT
					dec_cursor();
				#endif
				break;
			case (0x0A):	//control-J, line feed
			case (0x0D):	//control-M, carriage return
				lcd_goto(0);		//home position
				#ifdef LCD_FORMAT
					cursorpos = 0;
				#endif
				break;
			default:	//codes not listed can use next byte as data
				command_next = 1;
				lcdcommand = rec;
				break;
		}
	}
	else {
		if((rec >= 0x80) && (rec < 0xA0)) {	//remapping from empty area
			rec &= 0x07;			//to custom character area
		}
		#ifdef DESCENDERS
		if(rec==0x67 || rec==0x6A || rec==0x70 || rec==0x71 || rec==0x79)
			rec += 0x80;
		#endif
		lcd_putch(rec);
		#ifdef LCD_FORMAT
			inc_cursor();
		#endif
	}
}

#if defined LCD_4X20
void inc_cursor(void) {
	cursorpos++;
	switch(cursorpos) {
		case (END_LINE_1 + 1):
			cursorpos = BEGIN_LINE_2;
			lcd_goto(cursorpos);
			break;
		case (END_LINE_2 + 1):
			cursorpos = BEGIN_LINE_3;
			lcd_goto(cursorpos);
			break;
		case (END_LINE_3 + 1):
			cursorpos = BEGIN_LINE_4;
			lcd_goto(cursorpos);
			break;
		case (END_LINE_4 + 1):
			cursorpos = BEGIN_LINE_1;
			lcd_goto(cursorpos);
			break;
	}
}
#elif defined LCD_2X8 || defined LCD_2X16 || defined LCD_2X20
void inc_cursor(void) {
	cursorpos++;
	switch(cursorpos) {
		case (END_LINE_1 + 1):
			cursorpos = BEGIN_LINE_2;
			lcd_goto(cursorpos);
			break;
		case (END_LINE_2 + 1):
			cursorpos = BEGIN_LINE_1;
			lcd_goto(cursorpos);
			break;
	}
}
#elif defined LCD_1X16
void inc_cursor(void) {
	cursorpos++;
	switch(cursorpos) {
		case (END_LINE_1 + 1):
			cursorpos = BEGIN_LINE_1;
			lcd_goto(cursorpos);
			break;
	}
}
#endif


#if defined LCD_4X20
void dec_cursor(void) {
	cursorpos--;
	switch(cursorpos) {
		case (LINE_1_MINUS_1):
			cursorpos = END_LINE_4;
			lcd_goto(cursorpos);
			break;
		case (LINE_2_MINUS_1):
			cursorpos = END_LINE_1;
			lcd_goto(cursorpos);
			break;
		case (LINE_3_MINUS_1):
			cursorpos = END_LINE_2;
			lcd_goto(cursorpos);
			break;
		case LINE_4_MINUS_1:
			cursorpos = END_LINE_3;
			lcd_goto(cursorpos);
			break;
		}
}
#elif defined LCD_2X8 || defined LCD_2X16 || defined LCD_2X20
void dec_cursor(void) {
	cursorpos--;
	switch(cursorpos) {
		case (LINE_1_MINUS_1):
			cursorpos = END_LINE_2;
			lcd_goto(cursorpos);
			break;
		case (LINE_2_MINUS_1):
			cursorpos = END_LINE_1;
			lcd_goto(cursorpos);
			break;
		}
}
#elif defined LCD_1X16
void dec_cursor(void) {
	cursorpos--;
	switch(cursorpos) {
		case (LINE_1_MINUS_1):
			cursorpos = END_LINE_1;
			lcd_goto(cursorpos);
			break;
		}
}
#endif

void backlite(byte intensity) {
	CCPR1L = intensity;
}

void init_pwm(void) {
	CCPR1L =  0;		//begin with zero duty
	T2CON =   0b00000100;	//timer 2 on
	PR2 =     0xff;		//3.9 KHz pwm
	CCP1CON = 0b00001111;	//PWM1 is now enabled
}	
//END ALL
