/**
 * 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/
 */
#define __16f628a
#include "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)

void ps2_sendchar(unsigned char c)
{
	static unsigned char i, p;
	p=0;
	SEND('<');	SENDHEX(c);	FLUSH();

	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));
	SEND('>');
}

unsigned char ps2_getchar(void)
{
	static unsigned char i, d;

	TRISB=PS2_IDLE;

	// Start bit
	PS2_WAITLO();
	PS2_WAITHI();

	// D0-D8
	for(i=0; i<8; i++)
	{
		d>>=1;
		PS2_WAITLO();
		if(PORTB&DAT_BIT)	d|=0x80;
		PS2_WAITHI();
	}

	// Parity bit
	PS2_WAITLO();
	PS2_WAITHI();

	//Stop bit
	PS2_WAITLO();
	if(!(PORTB&DAT_BIT))
		return(0);

	PS2_WAITHI();

	SENDHEX(d);
	SEND(' ');
//	PORTB=FIN_BIT;
	return(d);
}

void main(void)
{
	static unsigned char c=0;

//	NOT_RBPU=1;	// We do not seem to need pullups here.
	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(c != 0xAA)
		c=ps2_getchar();

	ps2_sendchar(0xEE);
	if(ps2_getchar()==0xEE)
		PORTB=FIN_BIT;

	while(ps2_getchar());
}

