/*

	Home Automation Terminal
	Version 0.1.1
	Copyright 2000, Adam Davis
	adavis@ubasics.org
	
	Code, schematic and other resources found at
	HTTP://WWW.UBASICS.COM/adam/electronics/ha

	NODE.C

	18-Apr-00	v0.1.1
		I've redone the schematic so the A/D is open for use.  The thermister
		is now on PORTA:0 as a voltage divider with a 12k resister.  The LCD
		connections are as follows:
			LCD[D0:D3]	Ground
			LCD[D4:D7]	PORTC[0:3]
			LCD[RW]		Ground (if needed, use PORTA[4])
			LCD[RS]		PORTA[2]
			LCD[EN]		PORTA[5]
		This leaves 3 A/D ports free(one being used by the thermister) for either
		A/D usage of digital i/o.  It also leave PORTA[4] free for use (if 
		LCD[RW] is tied to ground).  This is useful for timing applications, or
		other uses of an open collector output.

	26-Feb-00	v0.1.0
		Up to this point this has been a jumble of code for using the LCD,
		keypad, thermister, and basic rs-232 usage.  This is the foundation.
*/

#include "c:\progra~1\ccs\16f876.h"
#define CP_off  |= 0x3F30		// Code protect (for whatever reason) is not in the include file
										// and is defined here instead
#pragma config CP_off,PWRTE=on,WDTE=off,FOSC=HS,BODEN=on,ID=0x0001
		// No code protect, power up timer on, watch dog off, OSC=high speed, brown out detect on,
		// ID = xxyz version number expressed here, xx = device type, y is major and z is minor (0.0 to 15.15)
		// 0x001f is device type 0 (up to 256 types, 0 is undefined/preproduction) ver 1.15

#pragma bit RS @ PORTA.2		// LCD Register Select (1=data, 0=instruction)
#pragma bit EN @ PORTA.5		// LCD Enable LCD latches data at H->L transition
#pragma bit RW @ PORTA.4		// Read/Write select (1=Read, 0=Write) Tied to ground, pin not used
#pragma bit SOUND @ PORTC.4	// Speaker is connected to this pin
#pragma char KEYPAD @ PORTB	//	Keypad is connected to this port

#define ESCCHAR  0x1B	// This is the escape character to denote an instruction
								// for the LCD, or ask for a temperature measurement
								// 0x1B is 27 ascii, which is the escape character.

void delayms(uns8 ms);
void write8(uns8 byte);

//#pragma rambank 0	// Rambank 0 is assumed allready
char checksum[2];		// Two byte checksum
char packet1[64];		// First 64 bytes of packet (part 1)
uns8 lastkey;			// Last key pressed/scanned
char header[5];		// Five bytes for header
char CountPacket;		// Counter for packet
bit WOP;					//	Working on Packet flag
bit WOH;					// Working on Header flag
bit WOD;					// Working on Data flag
bit WOC;					// Working on Checksum flag
bit WFT;					// Waiting for temp measurement flag (actually, A/D, not just temp)

#pragma rambank 2		// The following variable definition(s) should be made in rambank 3
char packet2[96];		// Part two of the packet memory, 96 bytes

#pragma rambank 3		// The following variable definition(s) should be made in rambank 3
char packet3[96];		// Part three of the packet memory, 96 bytes

#pragma rambank 0		// Any following variable definitions should be made in rambank 0

char initmsg(char number)
{	skip(number);					// This is an initialization message for the device (mostly for testing)
#pragma return[16] = "Micro Basics HAT"
}

void delayms(uns8 ms)
{		// This routine delays ABOUT ms milliseconds.  NOT PRECISION.
	uns8 tempcount;				// Temporary variable for holding current count
	while(ms != 0)					// While we still need to delay (could/should probably use for here)
	{
		tempcount = 125;			// It takes about 125 of these inner loops each ms
		while(tempcount != 0)	// Each inner loop is about 8 instruction cycles (including jumps)
			tempcount--;			// End of inner loop, decrement tempcount
		ms--;							// Decrement ms.
	}
	
	return;							// All done.
}

void write4(uns8  nyble)
{		// Writes data to the LCD in 4-bit chunks
	uns8 temp;				// Holding variable
//	RW = 0;					// Set the R/W to Write (R/W is tied to ground, so we don't need to do this)
	nyble &= 0xf;			// Make sure we are only using the bottom four bits of nyble
	temp = PORTC & 0xf0;	// Copy the four high bits of PORTC into temp
	temp |= nyble;			// Put nyble and temp together, four low bits of nyble, and four high bits of PORTC
	PORTC = temp;			// Write it all to PORTC.  High 4 bits should be unchanged, lower four bits are from nyble
	EN=1;						// Bring enable high
	nop();					// Wait a teensy bit
	EN=0;						// Bring Enable Low
	return;
}

void write8(uns8 byte)
{		// Writes 8 bits to the LCD using the 4-bit interface
	uns8 nibble;						// Holding space for each nibble
	nibble = (byte & 0xf0) >> 4;	// Rotate the high nibble of byte into nibble
	write4(nibble);					// Send the high 4 bits first
	nibble = byte & 0xf;				// Copy lower four bits of byte into nibble
	write4(nibble);					// Send the lower four bits second
}

void initlcd(void)
{		// This goes through Hitachi's recomended powerup sequence to set a display to 4-bit interface
	delayms(20);	// Wait for LCD to power up ( >15ms )
						// All the delays are to let the LCD execute the instruction before moving on.
	RS=0;				// Set RS low for instruction 
	write4(3);		// Set interface to 8 bits 
	delayms(5);
	write4(3);		// Set interface to 8 bits 
	delayms(1);
	write4(3);		// Set interface to 8 bits 
	delayms(5);		// At this point we could actually start using the busy flag
						// instead of blind delays
	write4(2);		// Set the display to 4 bit interface 
	delayms(5);
						// Having gone through hitachi's suggested powerup sequence,
						// we can now treat this as a 4 bit interface.
	write8(0x28);	// Set the LCD to 4-bit interface and two lines
	delayms(5);
	write8(6);		// Curser moves right, display does not shift
	delayms(5);
	write8(1);		// Clear all DDRAM, set current position to line one column one
	delayms(5);
	write8(0xf);	// Turn display on, turn curser on, make curser blink
	delayms(5);
	return;
}

char scankp(void)
{		// Simple key scanner, checks a 3x5 matrix for key presses.  Debounces keypress, and exits
		// EVEN IF THE KEY IS STILL PRESSED.  This way it doesn't lock up the processor while the
		// user holds down a key.  There is a line commented out which will keep you in this routine
		// until the key is released, if there is one pressed.

	char row, column, tmpa, index;		// Variables to hold current row, and column
													// And other stuff
	
	TRISB=0x7;									// PORTB[7:3] need to be output
	KEYPAD=0x00;								// Set them all low

	for (tmpa = 0x8; tmpa != 0; ) 		// Start with the fifth row
	{                      
		KEYPAD = tmpa;							// Bring row high to check if key pressed on that row
		column = (KEYPAD & 0x07);			// column now holds the keypress (if one is pressed)
		if (column > 0)						// If a key is pressed, get row & save the column
		{
			row = (KEYPAD & 0xF8) >> 3;	// This is the row
			for (index = 0; index < 255; index++) ; // Delay for awhile to allow for bouncing contacts
			if ((KEYPAD & 0x07) == column)// Make sure key is still pressed.
			{
				//while (KEYPAD & 0x07);	// Wait for key release (not used here)
													// Convert from row & column to key number (1-15)
				tmpa = 1;						// Assume first column and first row
				if(row.1)						// If it was in the 2nd row, assume 4th key (first column)
					tmpa = 4;
				if(row.2)						// If it was in the 2nd row, assume 7th key (first column)
					tmpa = 7;
				if(row.3)						// If it was in the 2nd row, assume 10th key (first column)
					tmpa = 10;
				if(row.4)						// If it was in the 2nd row, assume 13th key (first column)
					tmpa = 13;
				if(column.1)					// If it was in the second column, add one key
					tmpa += 1;
				if(column.2)					// If it was in the third column, add two keys
					tmpa += 2;
				TRISB=0xff;						// Set the port to all inputs
				return tmpa;					// Return key number
			}
			else									// Key is still bouncing, was released, or was never pressed
			{
				TRISB=0xff;						// Set the port to all inputs
				return 0x00;           		// return no keys pressed
			}
		}
		tmpa = tmpa << 1;						// No keys pressed in that row, move one row over
	}
        return 0x00;							// All rows checked out, no keys pressed, exit
} 

void beep(uns8 ms)
{										// This function emits a short beep of duration ms milliseconds
										// I used to know the frequency, but don't now.
	uns8 x,y;						//	x is a temp variable to create a delay between flipping the speaker
										// y is a temp variable, it will 'flip' the speaker 2 times each ms
	for( ; ms != 0; ms--)		// Count down the number of ms we are supposed to beep
	{
		for(y=4; y!=0;y--)		// 'flip' the speaker 4 times (pull on it twice)
		{
			if(SOUND == 1)			// If the speaker is out, pull it in.
				SOUND = 0;
			else						// The speaker is in, let it go
				SOUND = 1;		
			for(x=36;x!=0;x--);	// Delay for about 1/4 ms
		}
	}
	SOUND = 1;						// Let the speaker rest (no current consumption
}

void main(void)
{
	uns8 x;						// Temporary variable
	uns8 Instruction;			// 'Last char recieved was instruction' buffer (should be bit, not byte)

	PORTA = 0b.0000.0000; 	// All ports low at start
	PORTB = 0b.0000.0000;
	PORTC = 0b.0000.0000;

	TRISA = 0b.1100.1011; 	// 7:6,3,1:0 Input(1), 5:4,2 Output(0) :Thermister,n/a,LCD[RS],n/a,n/a,LCD[EN]
	TRISB = 0b.1111.1111; 	// 7:0 Input, no output 					:Keypad[COL0:COL2][ROW0:ROW4]
	TRISC = 0b.1100.0000; 	// 7:0 Output                  			:LCD[D0:D3], Speaker, n/a,UART[TX:RX]

	// *** Setup UART ***
	SPBRG=25;  					// 25 if bgrh=1, 6 if bgrh=0 when Fosc=4MHz
	TXSTA=0x24; 				//	Enable Transmitter, async, 8bit, bgrh = 1
	RCSTA=0x90;					// Enable UART, 8bit

	// *** Setup A/D Converter ***
	ADCON1=0b.1000.1110; 	// Set PORTA:0 to analog, Right justify A/D results
	ADCON0=0b.1100.0001; 	// Set A/D clock to RC, select PORTA:0 for input to A/D, Turn A/D on
	ADIE=0;						//	Disable Interrupts from A/D
	ADIF=0;						// Clear the A/D Interrupt flag (just a good idea, not needed)
	WFT=0;						// Clear the Waiting for Temp flag

	// *** Setup LCD ***	
	initlcd(); 					// Initialize LCD
	
	// *** Send the initialization string to the LCD ***	
	RS = 1;     				// Send the following characters to the data register of the LCD
	for(x=0;x < 16;x++)		// Loop through all 16 characters
		write8(initmsg(x));	// Write the character to the LCD

	// *** Send the initialization string to the serial interface ***	
	for(x=0;x<16;x++)			// Loop through all 16 characters
	{
		TXREG = initmsg(x);	// Send the next character out
		while(!TXIF);			// Wait for the character to complete sending
	}				
	
//	RS=0;							// Next write to LCD goes to the instruction register
//	write8(0x01);				// Clear LCD
//	delayms(2);					// Let the LCD clear before we send any other commands
//	RS=1;							// Next write to the LCD goes to the data register
	
	while(1)								// This is the main program loop.  Loop forever.
	{
		uns8 key, lastkey;			// Buffers for the current key being pressed, and the
											// last key which was pressed
		PORTC = 0xff;					// Set PORTC.  Why?  I don't know... Probably due to
											// the beep routine, and earlier LED signals.

		if(WFT)							// There is an analog conversion waiting to be checked
		{
			if(!GO)						// The conversion is complete
			{
					TXREG = ADRESL;	// Send the low byte first
					delayms(1);			// 2ms is enough time to send nearly two bytes
					TXREG = ADRESH;	// Send the high byte of the conversion
					ADIF=0;				// Reset the A/D interrupt
					WFT=0;				// Reset the Waiting for Temp flag
			}
		}

		if(RCIF)							// A character has been received
		{									// We should check here for overrun and framing errors...
			uns8 regbuf;				// A place to store the received character
			regbuf = RCREG;			// Store the received character in regbuf
			if(regbuf == ESCCHAR)	// Character is escape
			{
				if(Instruction == 1)	// If the last char was esc, send the temperature
				{
					GO=1;					// Start A/D conversion
					WFT=1;				// Set conversion in progress flag
					TXREG='T';			// Send conversion in progress signal
					Instruction = 0;	// Instruction executed, next character is normal
				}
				else
				{
					Instruction = 1;	// next char is Instruction
				}
			}
			else
			{
				if(Instruction == 1)	// This char is an instruction(escaped by
				{
					RS=0;					// Send to instruction register
					write8(regbuf);
					RS=1;					// Next char to data on LCD
					Instruction = 0;	// Instruction executed, next character is normal
				}
				else						// Last character was not the escape character
				{
					write8(regbuf);	// Nothing special, send char to LCD
				}
			}
		}
		
		key = scankp();				// Scan the keypad
		if(key != 0)					// There is a key being pressed
			if(key != lastkey)		// The key being pressed is different than the previous scan
			{
				TXREG=key;				// Send key
				beep(50);				// Beep to let user know the key was accepted
			}
		lastkey = key;					// If the next scan is the same, we'll ignore it

	}
}

