PIC MICRO: LCD Terminal

No replies
Michael Watterson's picture
Michael Watterson
Offline
Joined: 21 Sep 2009
Posts:

This is the very last C project I did on PIC 16F (maybe 2006). It's  a two line terminal with keypad for a Linux based ethernet Router connected to a DOCSIS modem, all in one portable battery powered case:

term-in-box

I would use C for the higher end Microchip CPUs such as PIC24, PIC32, PIC33 and dspPIC.  But for 10F, 12F, 16F, 18F families (the "real PICs") I'd only use JAL now.

1000033_pic16F876

LCD Terminal using very cheap PIC programmed in C

These are so simple that they can be made on Veroboard. The prototype was used with a Linux single Board Computer as terminals, using this LCD Terminal using very cheap PIC programmed in C

These are so simple that they can be made on Veroboard. Using this software on the host

It can use a single line 8 character up to 4 line 20 character as long as the electrical signals and command set is similar. A Graphic panel is not suitable.

The keypad can be a 3 x 4 matrix up to a 5 x 4 matrix, or a 4 x 4 keypad with four additional coloured buttons.

You need a programmer to put the compiled code into the PIC.

I used to use  ic-prog with a  simple home made programmer.

Now I would only use Pickit2. The PIC16F876 is suitable, though almost any 16F series part with enough suitable pins will do. 

Hardware

Hardware is a 2 line Text LCD, keypad and support for 8 additional buttons.  A PIC is on the back of the veroboard A 5V regulator 7805 is under the LCD to supply 5V from the battery pack used by the Linux SBC and Modem.

term-front-sml

Layoutbottom-sml

Top view layout (red are tracks, blue are wire links on top)

mirrored to allow easy cutting of track on bottom.

Layouttop-sml

Actual Veroboard

circbrdbot-sml circbrdtop-sml

Socket on bottom for PIC and sockets on top for LCD module and keypad. Crystal soldered on bottom.

New designs should use PIC18F2550, PIC18F4550 or similar, which includes USB

Possible Terminal Commands

Terminal commands
[table ! ASCII Code(s) | Description
Esc A |up

Esc B |down
Esc C |right
Esc D |left

Esc H |home

Esc I |line up

Esc J| Erase to end Screen

Esc K | Erase to EOL

Esc Ylc |Line and Column: actual numbers + 32 from 0,0 up to 223 x 223

Esc Z| Return

Esc =| Alternate keypad

Esc >| Normal keypad

Esc 1 | Graphics on

Esc 2 | Graphics off

Esc < |Ansi mode

Esc 0q |All Lights off

Esc 1q |LED 1 on

Esc 2q | LED 2 on

Esc 3q | LED 3 on

Ctrl m |start of line

Ctrl j |line down

Del | fwd delete

Ctrl h |rev delete

Ctrl i |Tab]

For this Project you need Hitech C

initports Import"initports.h"

// Peripheral initialization function
extern void initports(void);

initports.c

#include 
// htc.h is part of HiTide HiTech C

// Initialize user ID locations
//__IDLOC(0000);

/* Program device configuration word
 * Oscillator = EC I/O
 * Watchdog Timer = Off
 * Power Up Timer = On
 * Brown Out Detect = Enabled
 * Master Clear Enable = Disabled
 * Low Voltage Program = Disabled
 * Data EE Read Protect = Disabled
 * Code Protect = Off
 */
//__CONFIG(EC & WDTDIS & PWRTEN & BOREN & 0x3FDF & LVPDIS & DATUNPROT & UNPROTECT);

// Peripheral initialization function
void initports(void){
	/***** Common Code ****
	 *  Portbit7:4 interrupt-on-change disabled
	 *  Peripheral interrupts not enabled
	 *  Global interrupt disabled during initialization
	 */
	INTCON	= 0b00000000;
	/*
	 *  Weak pullup on PORT disabled
	 */
	OPTION	= 0b10000000;
	
	/***** PortA Code ****
	 *  Port directions: 1=input, 0=output
	 */
	TRISA	= 0b11111111;
	
	/***** PortB Code ****
	 *  Port directions: 1=input, 0=output
	 */
	TRISB	= 0b00000000;
	
	ei();	// Global interrupts enabled
	
}

Main Program

#include "initports.h"	// included by C-Wiz
#include 
//__CONFIG(WDTDIS & HS & UNPROTECT);	//this is for HS (more than 4 megahertz) oscillator
//__CONFIG(WDTDIS & XT & UNPROTECT);	//this is for XT (4 megahertz or less) oscillator
// Watchdog timer would be  | 0x04 but I'm not using it
__CONFIG(03f41H);
// also no MCLR and no LVP
// Mike Watterson maybe based on Bob Blick
// 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
#define LCD_RT 10 //response time for 4MHz
// 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 change later to 
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 LED_TASK_PERIOD	(1000 / INT_PERIOD + 1)	

#define	LCD_STROBE	((LCD_EN = 1),(LCD_EN = 0))
//keyboard stuff
#define KB_TASK_PERIOD	(80000 / 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_COL_DRV  5
#define KB_ROW_DRV   4
#define KB_NO_KEY	(KB_COL_DRV * KB_ROW_DRV)	//scankb returns this if no key is pressed
#define MAIN_PROG "/bin/sigmeter"
//Keyboard rows across, columns downSW, 
//the HW is c1= bottom row and
//the HW is r0= left columnn  
//	 	ro	r1	r2	r3			
// 	c0	red	grn	yel	Vio	
//  c4	1	2	3	A  noKey
//	c3	4	5	6	B
//	c2	7	8 	9	C
//	c1	*	0	#	D

//Starting with c0, r0, r1, r2, r3
const byte Kb_tbl[] =  {	
	0x52,0x47,0x59,0x56,
	0x2a,0x30,0x23,0x44,
	0x37,0x38,0x39,0x43,
	0x34,0x35,0x36,0x42,
	0x31,0x32,0x33,0x41, 0x0 };

// 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 bit  chargeon;			//1 = charge
static bit  chargetoggle;		// 0 or 1 
static volatile unsigned int	ledtaskcount;	//decrements every interrupt
static bit	ledtaskflag;		//goes high when it's time to check the keyboard


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;
#ifdef LCD_FORMAT
byte cursorpos;
#endif
/* 
static byte	super_mx_in;		//super-multiplexed input PORTB 5 upper bits
static byte	super_mx_out;		//super-multiplexed output PORTB 5 upper bits
*/

byte promptCount;


// FUNCTION PROTOTYPES
void init_stuff(void);		//sets up interrupt, pins, etc
void rs232_putch(byte c);	//puts a byte to serial transmit buffer
void rs232_puts(const byte * s); //string to serial port
byte rs232_getch(void);		//gets byte from serial receive buffer
void chargeControl(byte c);  //read battery state and ext power and charges battery
void ledtasks(void);
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

byte scankb(void);		//returns which key is depressed
byte getBatState(void);	//similar to keypad, reads four i/p muxed to RA5

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

byte batteryState =0;
byte leds =0;
byte termState =0;
byte lastState =0;
byte pulseCount =0;
#define MAX_PULSE_CYCLE 40
#define TS_NORMAL 0
#define TS_BAT_LOW 1
#define TS_BAT_PULSE 2
#define TS_CHARGING 3
#define TS_CHARGED 4

// THE PROGRAM
void main(void) {
	//initports();	// Function call inserted by C-Wiz
	lastState = TS_NORMAL;	
	termState = TS_NORMAL;
	init_stuff();
	delayMs(250);
	delayMs(250);
	lcd_init();
	lcd_clear();
	lcd_puts("Digiweb METRO");
	delayMs(250);
	delayMs(250);
	delayMs(250);
	lcd_clear();

	while(1) {
		if(kbtaskflag)
		{
			kbrs232(scankb());
			chargeControl(getBatState());
			kbtaskflag = 0;
		}
		if (ledtaskflag) {
			ledtasks();
			ledtaskflag = 0;	
		}
		if (receivebufferfull)
		{
			rs232lcd(rs232_getch());
		}
		
	} 
	 // 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
	RB0 =0;
	CMCON = 0b00000111;		// switch off comparators or else on digital on RA0 to RA3!
	TRISA = 0b00011111;		// PORTA inputs.
	TRISB = 0b00000000;		// PORTB outputs
	promptCount = 0;
	leds =0;
	receivestate = RS_HAVE_NOTHING;
	receivebufferfull = 0;
	skipoversamples = 1;		// check each interrupt for start bit
	kbtaskcount = KB_TASK_PERIOD;
	kbtaskflag = 0;
	ledtaskcount = LED_TASK_PERIOD;
	ledtaskflag = 0;
	kbdelaycount = KB_DELAY_LOAD;
	kbrepeatcount = KB_REPEAT_RATE;
	kboldkey = KB_NO_KEY;
	command_next = 0;
	chargeon = 0;
    chargetoggle = 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 chargeControl(byte batteryState) {
			// d7 = 1 normal   0 charge full
			// d6 = 1 battery  0 ext power
			// d5 = 1 low batt 0 normal
			// d4 = spare
	if ((batteryState & 0b00100000) > 0)	{
		// battery low	
		if ((batteryState & 0b01000000) > 0)	{
		// no ext power, shutdown
			if (termState == TS_NORMAL){
				rs232_putch('l');
				lastState = termState;	
				termState = TS_BAT_LOW;
				leds = 0;
				lcd_clear();
				lcd_puts("BATTERY LOW");
			}
		}else{
			//ext power do charging
			if (termState != TS_BAT_PULSE){
				lastState = termState;	
				termState = TS_BAT_PULSE;
			}
				
		} 
	}else{
		//battery	normal or charged
		if ((batteryState & 0b10000000) > 0)	{
		// battery normal
			if ((batteryState & 0b01000000) > 0)	{
			// no ext power
				if (termState != TS_NORMAL){
					if (termState ==TS_BAT_LOW){
						lcd_clear();
						rs232_putch('n');	
					}
					lastState = termState;	
					termState = TS_NORMAL;
				}
			}else{
				//ext power do charging
				if ((termState != TS_CHARGING) && (lastState !=TS_CHARGED)){
					lastState = termState;	
					termState = TS_CHARGING;
					chargeon = 1;
					
				}
			} 
		}else{
		//battery charged
			// keep turning off charger
			chargeon =0;
			if ((batteryState & 0b01000000) > 0)	{
			// no ext power
				if (termState != TS_NORMAL){
					lastState = TS_CHARGING;	
					termState = TS_NORMAL;
				}
			}else{
				//ext power do charging
				if ((termState != TS_CHARGING) && (lastState !=TS_CHARGED)){
					lastState = TS_CHARGING;	
					termState = TS_CHARGED;
				}
			} 
		} 
	} 
	if ((termState == TS_CHARGED)||(termState == TS_NORMAL)){
		leds |= 0b00001000; 
	}		
	if (termState == TS_BAT_LOW){
		leds &= 0b11110111;	
		chargeon = 0;	
	}
	if (termState == TS_BAT_PULSE) {
		pulseCount ++;
		if (pulseCount > MAX_PULSE_CYCLE){
			pulseCount = 0;
			leds &= 0b11110111;	
			chargeon = 0;	
		}else{
			if (pulseCount < (MAX_PULSE_CYCLE / 4))
				leds ^= 0b00001000;
				chargeon = 1;							
		}
	
	}
	
}

/*** LEDs & Charger ***/
void ledtasks(void){
	if (chargeon > 0) {
		leds ^= 0b00001000;
	}
	if ((chargeon > 0) || (kbtaskflag > 0)) {
		PORTB &= 0b00000110;	//drop all keyboard columns
		PORTB |= leds & 0b11111000;	//raise the ones that need raisin'
		PORTB |= 0b00000001; //latch it clock up
		PORTB &= 0b11111110; //drop clock
	}
}

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;
	}
	if(!(--ledtaskcount))
	{
		ledtaskcount = LED_TASK_PERIOD;
		ledtaskflag = 1;
	}
 

}
// END of interrrupt service routine

// LCD ROUTINES FOLLOW

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

// 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(150);	// 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(LCD_RT);
	LCD_STROBE;
	delaycount = T_120_US;
	while(delaycount);
	LCD_STROBE;
	delayMs(LCD_RT);
	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 = KB_COL_DRV+1;	// 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(RA1)
			break;
		key++;
		if(RA2)
			break;
		key++;

		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 */
	return key;
}
byte getBatState(void) {
	byte delay;
	byte batState = 0;		// Bits are set for which of the four inputs = 1
	byte column = 0b00001000;	//RB4 is starting column.
	byte columncount = KB_COL_DRV+1;	// 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(RA5)
			batState |= column;

		column <<= 1;	//shift to left, energize next column
	}
	return batState;
}
// 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;
		}
	}
	switch(kb_event_state) {
		case(KBES_NONE):
			kbdelaycount = KB_DELAY_LOAD;
			kbrepeatcount = 1;
			break;
		case(KBES_KEYDOWN):
			rs232_putch(Kb_tbl[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[kboldkey]);
				kbrepeatcount = KB_REPEAT_RATE;
				}
				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 (termState != TS_BAT_LOW) {
		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 LEDS
					rec <<= 4; // upper four bits are LEDs
					leds &= 0b00001111;
					leds |= rec ;
					rs232_putch(termState+119);
					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 (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
		}
	}

}
// End  rs232_lcd


#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

User login