/*
 *	remote5.c	March 10, 2001 based on:
 *	lcdterm6.c	December 4, 2000	(c)Bob Blick
 *	Full duplex serial terminal using an LCD character display(HD44780 compatible),
 *	matrixed keypad up to 20 switches, and a PIC16F84
 *
 *	lcdterm6 adds super-multiplexing on upper 5 bits of PORTB, input and output!
 *	Minor bugfix to serial receive, centers better on bits now.
 *
 *	remote?.c leaves off one keyboard input, RA3 becomes vehicle accessory sense
 *
 *	modified:	scankb()
 *			Kb_tbl all three shortened
 *			anywhere "super" is mentioned(no supermultiplexing used)
 *			#define KB_NO_KEY	20 becomes 15
 *
 *	compile with PICCLITE from www.htsoft.com
 *	command line:
 *	picl -O -Zg9 -16F84 -V remote?.c
 *
 *	pin assignments:
 *	PORTA				4 p3	3 p2	2 p1	1 p18	0 p17
 *					SERIN	INPUT	KBI2	KBI1	KBI0
 *	
 *	PORTB	7 p13	6 p12	5 p11	4 p10	3 p9	2 p8	1 p7	0 p6
 *		LCD7	LCD6	LCD5	LCD4	LCDRS	LCDEN	SEROUT	OUTPUT
 *		KBDO7	KBDO6	KBDO5	KBDO4	KBDO3			
 *
 *	OTHER	p5	p14	p4	p15	p16
 *		Vss/GND	Vdd/+5	/MCLR	OSC2CKO	OSC1CKI
 */
#include	<pic.h>
//__CONFIG(FOSC1 | CP);	//this is for HS (more than 4 megahertz) oscillator
__CONFIG(FOSC0 | CP);	//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

#define	XTAL		4000000	// Crystal frequency (Hz).
#define	BRATE		2400L	// Baud rate. "L" prevents truncation in calculations.
#define RX_OVERSAMPLE	4	// Receive oversampling. Must be at least 4 and power of two

// Super-multiplexed outputs(upper 5 bits) of PORT B get set to this at startup
//#define SUPER_MX_OUT_INIT	0b00000000	//lower three bits ignored

/**********YOU DON'T NEED TO CHANGE ANYTHING BEYOND THIS POINT************/

// Keeping you honest :-)
#if	(RX_OVERSAMPLE-1)&RX_OVERSAMPLE
#error	RX_OVERSAMPLE_value must be a power of 2
#endif
#if (RX_OVERSAMPLE < 4)
#error RX_OVERSAMPLE must be 4 or greater
#endif
#define RX_BITCENTER	((RX_OVERSAMPLE/2) - 1)

#define byte unsigned char

// LCD pins
static bit LCD_RS	@ ((unsigned)&PORTB*8+3);	// Register select
static bit LCD_EN	@ ((unsigned)&PORTB*8+2);	// Enable

// Transmit and Receive port bits
static volatile bit	TxData @ (unsigned)&PORTB*8+1;		/* bit1 in port B */
static volatile bit	RxData @ (unsigned)&PORTA*8+4;		/* bit4 in port A */

// Don't change these
#define TIMER_VALUE	(XTAL / (4 * BRATE * RX_OVERSAMPLE))
#define TRANSMIT_NUM_BITS	13	// 1 start bit + 8 data bits + 2 stop bits + safe.
#define INT_PERIOD	(1000000 / (BRATE * RX_OVERSAMPLE)) //that is in microseconds

#if ((TIMER_VALUE) < 90)
#error baud rate or oversample too high for crystal speed
#endif

#if ((TIMER_VALUE) > 256)
#error baud rate or oversample too low for crystal speed
#endif

// 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	(120 / INT_PERIOD + 1)
#if (T_120_US < 2)
#undef T_120_US
#define T_120_US 2
#endif
#define T_1000_US	(1000 / INT_PERIOD + 1)

#define	LCD_STROBE	((LCD_EN = 1),(LCD_EN=0))

#define KB_TASK_PERIOD	(50000 / INT_PERIOD + 1)	//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	20	//scankb returns this if no key is pressed
#define KB_NO_KEY	15	//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
enum power_state {
	PS_POWER_OFF,
	PS_POWERING_UP,
	PS_OPERATING,
	PS_POWERING_DOWN
};

// Receiver states.
enum receiver_state {
	RS_HAVE_NOTHING,
	RS_WAIT_HALF_A_BIT,
	RS_HAVE_STARTBIT,
	RS_WAIT_FOR_STOP = RS_HAVE_STARTBIT+8
};

// VARIABLES
static byte	sendbuffer;		// Where the character to sent is stored.
static byte	receivebuffer;		// Where the character is stored as it is received.
static bit 	receivebufferfull;	// 1 = receivebuffer is full.

static byte	send_bitno;
static byte	receivestate; 		// Initial state of the receiver (0).
static byte	skipoversamples;	// Used to skip receive samples.
static byte	rxshift;
static bit	tx_next_bit;

static volatile byte	delaycount;		//decrements every interrupt if nonzero
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

#ifdef LCD_FORMAT
byte cursorpos;
#endif
// remote doesn't need super-multiplexing
//static byte	super_mx_in;		//super-multiplexed input PORTB 5 upper bits
//static byte	super_mx_out;		//super-multiplexed output PORTB 5 upper bits


// KEYBOARD LOOKUP TABLES
// Primary keyboard codes
const byte Kb_tbl_pri[] =  {	0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,
				0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f };
// Secondary keyboard codes
const byte Kb_tbl_sec[] =  {	0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,
				0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f };
// 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[] = {	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
				0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };	//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 byte from serial receive buffer
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 delayMs(byte t);		//delays 1 to 255 milliseconds
//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

// THE PROGRAM
void main(void) {
	init_stuff();
	lcd_init();
	lcd_clear();
	lcd_puts("remote v0.5");
	lcd_goto(0x40);
	lcd_puts("(c)2001 Bob Blick");
	delayMs(250);
	delayMs(250);
	lcd_clear();

	while(1) {
		if (receivebufferfull) {
			rs232lcd(rs232_getch());
			char_received = 1;
		}
		
		switch (powerstate) {
			case PS_POWER_OFF:
				if(RA3) {	//accessory input rise, time to power up
					powerstate++;	//move to "powering up"
					RB0 = 1;	//switch on the server
					lcd_clear();
					lcd_puts("powering up");
					powercount = POWER_UP_COUNT;	//a long time
					charcounter = 8;
					char_received = 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++;
				if(!(powercount)) {
					powerstate = PS_POWERING_DOWN;
					RB0 = 0;
					lcd_clear();
					lcd_puts("powering down");
					powercount = POWER_OFF_COUNT;
				}
				break;
			case PS_OPERATING:
				if(char_received) {
					char_received = 0;
					powercount = POWER_DOWN_COUNT;	//keep the power on
					if (RA3 == 0) {		//car is turned off
						if (onesecflag) {
							rs232_putch('Z');	//tell the server the car is off
						}
					}
				}
				break;
			case PS_POWERING_DOWN:
				if(!(powercount)) {
					powerstate = PS_POWER_OFF;
					lcd_clear();
				}
				break;
		}
		
		if (onesecflag) {
			onesecflag = 0;
			if(powercount) {
				powercount--;
			}
		}
		
		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 | (SUPER_MX_OUT_INIT & 1);	//RB0 set to lo bit of super_mx_out
	PORTB = 0;
	TRISA = 0b00011111;		// PORTA inputs.
	TRISB = 0b00000000;		// PORTB outputs

	receivestate = RS_HAVE_NOTHING;
	receivebufferfull = 0;
	skipoversamples = 1;		// check each interrupt for start bit
	kbtaskcount = KB_TASK_PERIOD;
	kbtaskflag = 0;
	kbdelaycount = KB_DELAY_LOAD;
	kbrepeatcount = KB_REPEAT_RATE;
	kboldkey = KB_NO_KEY;
	command_next = 0;
//remote inits
	oneseccount = ONE_SEC_PERIOD;
	powercount = 0;
	powerstate = 0;

//	super_mx_out = SUPER_MX_OUT_INIT;
	#ifdef LCD_FORMAT
		cursorpos = 0;
	#endif
	/* Set up the timer. */
	T0CS = 0;			// Set timer mode for Timer0
	TMR0 = (2-TIMER_VALUE);		// +2 as timer stops for 2 cycles
					//   when writing to TMR0
	T0IE = 1;			// Enable the Timer0 interrupt
	GIE = 1;			// Enable interrupts
}

void rs232_putch(byte c) {
	while(send_bitno)
		continue;
	tx_next_bit = 0;
	sendbuffer = c;
	send_bitno = TRANSMIT_NUM_BITS*RX_OVERSAMPLE;
}

byte rs232_getch(void) {
	while(!receivebufferfull)
		continue;
	receivebufferfull = 0;
	return receivebuffer;
}

/* Interrupt service routine
 * 
 * Transmits and receives characters which have been
 * "rs232_putch"ed and "rs232_getch"ed.
 * 
 * This ISR runs BRATE * RX_OVERSAMPLE times per second.
 * 
 */

interrupt void isr(void) {
	TMR0 += -TIMER_VALUE + 2;	// +2 as timer stops for 2 cycles when writing to TMR0
	T0IF = 0;

/*** RECEIVE ***/
	if( --skipoversamples == 0) {
		skipoversamples++;		// check next time
		switch(receivestate) {

		case RS_HAVE_NOTHING:
			/* Check for start bit of a received char. */
			if(RxData){
				skipoversamples = RX_BITCENTER;
				receivestate++;
			}
			break;

		case RS_WAIT_HALF_A_BIT:
			if(RxData) {	// valid start bit
				skipoversamples = RX_OVERSAMPLE;
				receivestate++;
			} else
				receivestate = RS_HAVE_NOTHING;
			break;
			
		// case RS_HAVE_STARTBIT: and subsequent values
		default:
			rxshift = (rxshift >> 1) | ((!RxData) << 7);
			skipoversamples = RX_OVERSAMPLE;
			receivestate++;
			break;

		case RS_WAIT_FOR_STOP:
			receivebuffer = rxshift;
			receivebufferfull = 1;
			receivestate = RS_HAVE_NOTHING;
			break;

		}
	}
	
/*** TRANSMIT ***/
/* This will be called every RX_OVERSAMPLEth time
 * (because the RECEIVE needs to over-sample the incoming data). */

	if(send_bitno) {
	       	if((send_bitno & (RX_OVERSAMPLE-1)) == 0) {
			TxData = !tx_next_bit;		// Send next bit.
			tx_next_bit = sendbuffer & 1;
			sendbuffer = (sendbuffer >> 1) | 0x80;
		}
		send_bitno--;	//count down until out of bits to send
	}

/*** COUNTERS ETC ***/
	if(delaycount)
		delaycount--;
	if(!(--kbtaskcount)) {
		kbtaskcount = KB_TASK_PERIOD;
		kbtaskflag = 1;
	}
}
// END of interrrupt service routine

// LCD ROUTINES FOLLOW

// Clear and home the LCD
void lcd_clear(void) {
	lcd_putcmd(0x01);
	delayMs(3);
}

// 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;
//only need these next two lines if using super multiplexed outputs
/*
	PORTB &= 0b00000111;	//drop the 5 upper pins
	PORTB |= super_mx_out & 0b11111000;	//now raise them according to super_mx_out
 */
	delaycount = T_120_US;
	while(delaycount);
}


// 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
	delayMs(50);	// power on delay. LCD spec is 15 but some don't make it
	PORTB &= 0x0F;
	PORTB |= 0x30;				//repeat this initialization 3 times
	LCD_STROBE;
	delayMs(5);
	LCD_STROBE;
	delaycount = T_120_US;
	while(delaycount);
	LCD_STROBE;
	delayMs(5);
	PORTB &= 0x0F;
	PORTB |= 0x20;				//set 4 bit mode
	LCD_STROBE;
	delaycount = T_120_US;
	while(delaycount);
	#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;
//only need these next two lines if using super multiplexed outputs
/*
	PORTB &= 0b00000111;	//drop the 5 upper pins
	PORTB |= super_mx_out & 0b11111000;	//now raise them according to super_mx_out
 */
	delaycount = T_120_US;
	while(delaycount);
}
// End of LCD functions

// Delay a number of milliseconds
void delayMs(byte t) {
	do
	{
		delaycount = T_1000_US;
		while(delaycount);
	} while(--t);
}
/*
//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 = 0b00001000;	//RB3 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 = 6;	// one more than the number of columns
	while(--columncount)
	{
		PORTB &= 0b00000111;
		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(RA0)
			break;
		key++;
	//if you don't use all rows just remove
		if(RA1)
			break;
		key++;
	//or comment out ones you don't need
		if(RA2)
			break;
		key++;
/* modification for remote
		if(RA3)
			break;
		key++;
 */
		column <<= 1;	//shift to left, energize next column
	}
// Only need these next 4 lines if using super multiplexed inputs
/*
	TRISB  = 0b11111000;	//prepare to read by making upper 5 bits input
	for (delay = 10; --delay;);	//now a short delay before reading the pins
	super_mx_in = PORTB;
	TRISB = 0;		//back to all output
// Only need these next 2 lines if using super multiplexed outputs
	PORTB &= 0b00000111;	//drop all keyboard columns
	PORTB |= super_mx_out & 0b11111000;	//raise the ones that need raisin'
*/
	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]);
			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]);
			break;
		case(KBES_KEYUP):
			rs232_putch(Kb_tbl_sec[kboldkey]);
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			kboldkey = KB_NO_KEY;
			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]);
				}
			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]);
				}
			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]);
				}
			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]);
				}
			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, to multiplexed output
				super_mx_out = rec;
				if(rec & 1)	//lo bit set
					RB0 = 1;	//make RB0 follow
				else
					RB0 = 0;
				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 (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 (0x0C):	//control-L, line feed
			case (0x0D):	//control-M, carriage return
				lcd_goto(0);		//home position
				#ifdef LCD_FORMAT
					cursorpos = 0;
				#endif
				break;
/*
			case (0x0E):	// control-N, send multiplexed inputs out rs232
				rs232_putch(super_mx_in);
				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
//END ALL

