/**
 * This example for the PIC16f628a communicates with a PS/2 keyboard.
 * The keyboard's CLOCK line is attached directly to B0, the CLOCK line 
 * to B3, a LED of some sort to B5, and some sort of serial display to 
 * B2.  The keyboard's power and ground lines must of course be attached 
 * to 5V and ground.
 *
 * Both the data and clock lines of a PS/2 keyboard are open-collector;
 * this means neither the keyboard nor the PIC ever drive high values, 
 * just low ones, with a pullup resistor producing a logic 1 whenever
 * neither end is sending anything.  This is why we keep twiddling the 
 * TRISB values instead of the PORTB ones;  PORTB is always zero for the 
 * clock and data bits, we just tristate them unless we want to express 
 * that zero.
 *
 * After a PS/2 keyboard powers on, it waits a quarter of a second then 
 * sends 0xAA to announce it is online.  Once it sees this, the PIC
 * will give the keyboard an echo request, 0xEE, and get back 0xEE
 * from the keyboard.  All transmission and reception is logged with 
 * RS232 output, with outgoing bytes surrounded by angle brackets.
 *
 * For more details on the PS/2 protocol, see Adam Chapweske's
 * very helpful page at http://computer-engineering.org/ps2protocol/
 *
 * This example has been expanded from 0012-keyboard.c into a serial 
 * byte generator using mostly numeric keypad keys.  The keys have
 * the following functions:
 * 	Keypad 0 through 9:	ASCII digits of a number.  Type '4' then 
 *		'3' to get the value 43.
 *	Keypad delete:		Undo the last digit.
 *	Keypad minus:		Zero the current value.
 *	Keypad enter:		Send the resulting number as either two 
 *		ASCII digits(default) or one literal byte.
 *	Keypad Plus:		Resend the last value.
 *	Keypad forward-slash:	Send ASCII digits on enter.
 *	Keypad asterisk:	Send literal byte on enter.
 */
#define __16f628a
#include "pic/pic16f628a.h"
#include "tsmtypes.h"

// Set the __CONFIG word:
Uint16 at 0x2007  __CONFIG = CONFIG_WORD;

// If KHZ is not specified by the makefile, assume it to be 4 MHZ
#ifndef KHZ
#define KHZ	4000
#endif

// RX_PORT, TX_PORT are fixed.  The 16f628a can use only these pins as RS232.
enum
{
	// PS2 clock attached to B0
	CLK_PORT=0,
	// RS232 receive hardwired to B1
	RX_PORT=1,
	// RS232 send hardwired to B2
	TX_PORT=2,
	// PS2 data attached to B3
	DAT_PORT=3,
	// LED attached to B5
	FIN_PORT=5,

	CLK_BIT=(1<<CLK_PORT),
	TX_BIT=(1<<TX_PORT),
	RX_BIT=(1<<RX_PORT),
	DAT_BIT=(1<<DAT_PORT),
	FIN_BIT=(1<<FIN_PORT)
};

// Twiddle these as you like BUT remember that not all values work right!
// See the datasheet for what values can work with what clock frequencies.
#define	BAUD	9600
#define BAUD_HI	1

// This section calculates the proper value for SPBRG from the given
// values of BAUD and BAUD_HI.  Derived from Microchip's datasheet.
#if	(BAUD_HI == 1)
#define	BAUD_FACTOR	(16L*BAUD)
#else
#define	BAUD_FACTOR	(64L*BAUD)
#endif
#define SPBRG_VALUE	(unsigned char)(((KHZ*1000L)-BAUD_FACTOR)/BAUD_FACTOR)

// Lookup table for hex-to-ascii macro
static const char hex[]={'0','1','2','3','4','5','6','7','8','9',
			'A','B','C','D','E','F'};

// Wait until the PIC is finished transmitting over RS232
#define FLUSH()		while(!TXIF)

// Sends a literal character down the RS232 pipe.  I.E.  SEND('Q');
#define SEND(C)		do {	FLUSH();	TXREG=C;	} while(0)

// Sends two hex chars.  I.E.  SENDHEX(0xab) will send 'A', then 'B'
#define SENDHEX(H)	do {	SEND(hex[(H)>>4]);		\
				SEND(hex[(H)&0x0f]); } while(0)

enum
{
	// Does not pull clock or data low.
	PS2_IDLE=(TX_BIT|RX_BIT|CLK_BIT|DAT_BIT),
	// Pulls clock low.
	PS2_ASSERT=(TX_BIT|RX_BIT|DAT_BIT),
	// Pulls data low.
	PS2_ZERO=(TX_BIT|RX_BIT|CLK_BIT),
	// Pulls clock low
	PS2_INHIBIT=(TX_BIT|RX_BIT|CLK_BIT)
};

// Wait for high-to-low transition on clock.
#define PS2_WAITLO()	while(PORTB&CLK_BIT)
// Wait for low-to-high transition on clock.
#define PS2_WAITHI()	while(!(PORTB&CLK_BIT))
// Wait for data to go low
#define PS2_WAITDAT()	while(PORTB&DAT_BIT)

enum
{
	PS2_BASEKEY=0xE0,
	PS2_KEYUP=0xF0,
	PS2_0=0x70,
	PS2_1=0x69,
	PS2_2=0x72,
	PS2_3=0x7a,
	PS2_4=0x6b,
	PS2_5=0x73,
	PS2_6=0x74,
	PS2_7=0x6c,
	PS2_8=0x75,
	PS2_9=0x7d,
	PS2_ENTER=0x5a,
	PS2_ESC=0x76,
	PS2_DEL=0x71,
	PS2_PLUS=0x79,
	PS2_MINUS=0x7b,
	PS2_FSLASH=0x4a,
	PS2_ASTERISK=0x7c,
};

/*	Measured these from my own PS/2 numeric keypad.
 *	Key		Press	Release
 *	===============================
 *	7/home		6c	F0,6c
 *	8		75	F0,75
 *	9/pgup		7d	F0,7d
 *	4		6b	f0,6b
 *	5		73	f0,73
 *	6		74	f0,74
 *	1		69	f0,69
 *	2		72	f0,72
 *	3		7a	f0,7a
 *	0		70	f0,70
 *	DEL		71	f0,71
 *	ENTER		E0,5A	E0,F0,5A
 *	ESC		76	f0,76
 *	+		79	F0,79
 *	-		7B	F0,7b
 *	*		7c	f0,7c
 *	/		E0,4A	E0,F0,4A
 */

//static const Uint8 inttab[]={PS2_0, PS2_1, PS2_3, PS2_4, PS2_5,
//	PS2_6, PS2_7, PS2_8, PS2_9, 0};

static const unsigned char itab[]={PS2_0,PS2_1,PS2_2,PS2_3,PS2_4,
	PS2_5,PS2_6,PS2_7,PS2_8,PS2_9, 0};

static unsigned char ps2=0;
void ps2_sendchar(unsigned char c);
void ps2_getchar(void);

void main(void)
{
	static unsigned char mode, skip, val, prev, n;

	NOT_RBPU=0;	// Enable pullups

	TRISB=PS2_IDLE;		// Setup I/O on port B
	PORTB=0;

	SPBRG=SPBRG_VALUE;	// Baud Rate register, calculated by macro
	BRGH=BAUD_HI;

	SYNC=0;			// Disable Synchronous/Enable Asynchronous
	SPEN=1;			// Enable serial port
	TXEN=1;			// Enable transmission mode

	// Wait until the keyboard initializes
	while(ps2 != 0xAA)
		ps2_getchar();

	ps2_sendchar(0xEE);
	ps2_getchar();
	if(ps2==0xEE)
		PORTB=FIN_BIT;

	skip=0;	// When we get PS2_KEYUP, we must skip the next byte.
	val=0;	// The value to be sent over RS232 when ENTER is hit
	prev=0;	// The previous byte, for resend
	mode=0;	// 0 sends two ASCII hex digits, 1 sends a literal byte

	while(1)
	{
		ps2_getchar();

		if(skip)
		{
			skip--;
			continue;
		}

		switch(ps2)
		{
		case PS2_FSLASH:
			mode=0;
			continue;
		case PS2_ASTERISK:
			mode=1;
			continue;

		case PS2_KEYUP:
			skip=1;
		case PS2_BASEKEY:
			continue;

		case PS2_PLUS:	// Resend last sent byte
			val=prev;
		case PS2_ENTER:
			if(mode)	SEND(val);	// Send literal byte
			else				// Send ASCII digits
			{
				SENDHEX(val);
				SEND('\r');
			}
			prev=val;
		case PS2_MINUS:	// Clear value
			val=0;
			continue;

		case PS2_DEL:	// Undo last keystroke
			val/=10;
			continue;

		default:	// The goggles
			break;
		}

		for(n=0; itab[n]; n++)
		{
			if(itab[n] == ps2)
			{
				val=val*10;	// Add decimal digit to value
				val+=n;
				break;
			}
		}
	}
}

void ps2_sendchar(unsigned char c)
{
	static unsigned char i, p;
	p=0;

	TRISB=PS2_ASSERT;
	for(i=0; i<15; i++);	// This loop for 4MHZ, scale accordingly
	TRISB=PS2_ZERO;

	for(i=0; i<8; i++)
	{
		PS2_WAITLO();
		if(c & 0x01)
		{
			p++;
			TRISB=PS2_IDLE;
		}
		else
			TRISB=PS2_ZERO;

		c>>=1;
		PS2_WAITHI();
	}

	PS2_WAITLO();
	if(p&1)	TRISB=PS2_ZERO;
	else	TRISB=PS2_IDLE;
	PS2_WAITHI();

	PS2_WAITLO();
	TRISB=PS2_IDLE;
	PS2_WAITHI();

	while(PORTB&DAT_BIT);
	while(PORTB&CLK_BIT);

	while(!(PORTB&DAT_BIT));
	while(!(PORTB&CLK_BIT));
}

void ps2_getchar(void)
{
	static unsigned char i;

	TRISB=PS2_IDLE;

	// Start bit
	PS2_WAITLO();
	PS2_WAITHI();

	// D0-D8
	for(i=0; i<8; i++)
	{
		ps2>>=1;
		PS2_WAITLO();
		if(PORTB&DAT_BIT)	ps2|=0x80;
		PS2_WAITHI();
	}

	// Parity bit
	PS2_WAITLO();
	PS2_WAITHI();

	//Stop bit
	PS2_WAITLO();
	if(!(PORTB&DAT_BIT))
		return;

	PS2_WAITHI();
}
