/**
 * This example for the PIC16f628a communicates with a PS/2 mouse.
 * The mouse'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 mouse's power and ground lines must of course be attached 
 * to 5V and ground.
 *
 * Both the data and clock lines of a PS/2 mouse are open-collector;
 * this means neither the mouse 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 mouse powers on, it waits a quarter of a second then 
 * sends 0xAA to announce it is online, and 0x00 as its device-type.
 * Once it sees this, the PIC will give the keyboard a reset request and 
 * get back an ack from the keyboard, then another 0xAA, and another 0x00.
 *
 * Following that, we set the samplerate three times then ask for its
 * device id to find out if its an intellimouse.  Then we enable
 * streaming communication and wait to read bytes.
 *
 * It will display over its UART current button states as well as
 * cumulative x, y, and (for wheel-mice) z values.  x and y are 16-bit,
 * z is 8-bit.
 *
 * For more details on the PS/2 protocol, see Adam Chapweske's
 * very helpful page at http://computer-engineering.org/ps2protocol/
 */
#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)

static void ps2_waitnext(void);

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;

	PS2_WAITLO();

	for(i=0; i<8; i++)
	{
		if(c & 0x01)
		{
			p++;
			TRISB=PS2_IDLE;
		}
		else
			TRISB=PS2_ZERO;

		c>>=1;
		ps2_waitnext();
	}

	if(p&1)	TRISB=PS2_ZERO;
	else	TRISB=PS2_IDLE;

	ps2_waitnext();

	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;

	PS2_WAITLO();

	// Start bit shifts off and we don't care, all the livelong day
	for(i=0; i<9; i++)
	{
		d>>=1;
		if(PORTB&DAT_BIT)	d|=0x80;
		ps2_waitnext();
	}

	ps2_waitnext();
	if(!(PORTB&DAT_BIT))
		return(0);

	PS2_WAITHI();

//	SENDHEX(d);
//	SEND(' ');
	return(d);
}

enum
{
	// From computer-engineering.org/ps2mouse/
	MOUSE_RESET=0xff,
	MOUSE_POST=0xaa,
	MOUSE_READ_DATA=0xeb,
	MOUSE_ACK=0xFA,
	MOUSE_SET_STREAM=0xea,
	MOUSE_ENABLE_REPORTING=0xf4,
	MOUSE_SET_SAMPLERATE=0xf3,
	MOUSE_GET_ID=0xf2,
};

char ps2_op(unsigned char c)
{
	ps2_sendchar(c);
	if(ps2_getchar() != MOUSE_ACK)
		return(-1);

	return(0);
}

/**
 *	A wheel mouse will report a device id of 0x04 when you
 *	set its sampling rate to 200, 200, and 80 in a row.
 *	Plain PS/2 mice only report 0x00, so that's how you know it's a 
 *	wheel mouse, and how the mouse knows to send 4 bytes instead 
 *	of 3.
 */
char init_intellimouse(void)
{
	if(ps2_op(MOUSE_SET_SAMPLERATE) < 0)
		goto IM_EXIT;
	if(ps2_op(200) < 0)
		goto IM_EXIT;
	if(ps2_op(MOUSE_SET_SAMPLERATE) < 0)
		goto IM_EXIT;
	if(ps2_op(200) < 0)
		goto IM_EXIT;
	if(ps2_op(MOUSE_SET_SAMPLERATE) < 0)
		goto IM_EXIT;
	if(ps2_op(80) < 0)
		goto IM_EXIT;
	if(ps2_op(MOUSE_GET_ID) < 0)
		goto IM_EXIT;
	if(ps2_getchar() != 0x04)
		goto IM_EXIT;
	return(0);

IM_EXIT:
	return(-1);
}

#define SEND_IF(X, A, B)	\
	do {			\
	FLUSH();		\
	if(X)	TXREG=A;	\
	else	TXREG=B;	\
	} while(0)

/** For my parallax serial LCD, which is silly.  \n in most places. */
#define NEWLINE()	SEND('\r')

void main(void)
{
	static unsigned char im;	// 1 if mousewheel detected
	static unsigned char c;
	static unsigned char x,y,z;
	static char ztotal;		// Z-axis cumulative value
	static Sint16 xtotal, ytotal;	// Cumulative X and Y

	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

	c=0;
	im=0;

	// Wait until the keyboard initializes
	while(c != MOUSE_POST)
		c=ps2_getchar();

	if(ps2_op(MOUSE_RESET) < 0)	// Reset mouse
		goto ERR_EXIT;

	if(ps2_getchar()!=MOUSE_POST)	// It should POST again.
		goto ERR_EXIT;

	if(init_intellimouse() >= 0)
		im=1;

	// We set samplerates lower to make the diffs higher
	if(ps2_op(MOUSE_SET_SAMPLERATE) < 0)
		goto ERR_EXIT;

	if(ps2_op(10) < 0)
		goto ERR_EXIT;

	if(ps2_op(MOUSE_SET_STREAM) < 0)// Set stream mode
		goto ERR_EXIT;

	if(ps2_op(MOUSE_ENABLE_REPORTING) < 0) // Let it send data
		goto ERR_EXIT;

	PORTB=FIN_BIT;	// Turn on LED.

	xtotal=0;
	ytotal=0;
	ztotal=0;
	x=0;
	y=0;
	z=0;
	c=0;

	while(1)
	{
		static Sint16 tmp16;

		// Calculate new X and Y totals.
		xtotal += x;	// Data bits
		if(c&0x10)	// Sign bit
			xtotal-=0x100;

		ytotal += y;	// Data bits
		if(c&0x20)	// Sign bit
			ytotal-=0x100;

		SEND_IF(z&0x10, '4', '_');
		SEND_IF(z&0x20, '5', '_');
		SEND_IF(c&0x01, 'L', 'l');
		SEND_IF(c&0x02, 'R', 'r');
		SEND_IF(c&0x04, 'M', 'm');

		// Skip displaying this for non-scrollmice
		if(im)
		{
			// Calculate the new total for z
			ztotal+=z&0x07;	// Data bits
			ztotal-=z&0x08;	// Sign bit

			// Display new Z total
			SEND(' ');
			SEND(' ');
			SEND(' ');
			SEND('Z');

			if(ztotal < 0)
			{
				SEND('-');
				z=-ztotal;
			}
			else
			{
				SEND('+');
				z=ztotal;
			}

			SENDHEX(z);
			SEND('h');
		}

		NEWLINE();
		SEND('X');

		if(xtotal < 0)
		{
			SEND('-');
			tmp16=-xtotal;
		}
		else
		{
			SEND('+');
			tmp16=xtotal;
		}

		z=tmp16>>8;	SENDHEX(z);
		z=tmp16&0xff;	SENDHEX(z);
		SEND('h');

		SEND(' ');
		SEND('Y');

		if(ytotal < 0)
		{
			SEND('-');
			tmp16=-ytotal;
		}
		else
		{
			SEND('+');
			tmp16=ytotal;
		}

		z=tmp16>>8;	SENDHEX(z);
		z=tmp16&0xff;	SENDHEX(z);
		SEND('h');

		NEWLINE();

		c=ps2_getchar();	// Read flags
		x=ps2_getchar();	// Read X movement
		y=ps2_getchar();	// Read Y movement
		z=0;

		// Intellimice(aka wheel mice) send extra byte
		if(im)
			z=ps2_getchar();
	}

ERR_EXIT:
	SEND('!');
	PORTB=0;
	ps2_getchar();
	goto ERR_EXIT;
}

static void ps2_waitnext(void)
{
	PS2_WAITHI();
	PS2_WAITLO();
}

/**
Mouse: AA  Self-test passed
Mouse: 00  Mouse ID
Host:  FF  Reset command
Mouse: FA  Acknowledge
Mouse: AA  Self-test passed
Mouse: 00  Mouse ID
Host:  FF  Reset command
Mouse: FA  Acknowledge
Mouse: AA  Self-test passed
Mouse: 00  Mouse ID
Host:  FF  Reset command
Mouse: FA  Acknowledge
Mouse: AA  Self-test passed
Mouse: 00  Mouse ID
Host:  F3  Set Sample Rate   : Attempt to Enter Microsoft
Mouse: FA  Acknowledge       : Scrolling Mouse mode
Host:  C8  decimal 200       :
Mouse: FA  Acknowledge       :
Host:  F3  Set Sample Rate   :
Mouse: FA  Acknowledge       :
Host:  64  decimal 100       :
Mouse: FA  Acknowledge       :
Host:  F3  Set Sample Rate   :
Mouse: FA  Acknowledge       :
Host:  50  decimal 80        :
Mouse: FA  Acknowledge       :
Host:  F2  Read Device Type  :
Mouse: FA  Acknowledge       :
Mouse: 00  Mouse ID          : Response 03 if microsoft scrolling mouse
Host:  F3  Set Sample Rate 
Mouse: FA  Acknowledge
Host:  0A  decimal 10
Mouse: FA  Acknowledge
Host:  F2  Read Device Type
Mouse: FA  Acknowledge
Mouse: 00  Mouse ID
Host:  E8  Set resolution
Mouse: FA  Acknowledge
Host:  03  8 Counts/mm
Mouse: FA  Acknowledge
Host:  E6  Set Scaling 1:1
Mouse: FA  Acknowledge
Host:  F3  Set Sample Rate
Mouse: FA  Acknowledge
Host:  28  decimal 40
Mouse: FA  Acknowledge
Host:  F4  Enable
Mouse: FA  Acknowledge
Initialization complete...

If I then press the Left Button...
Mouse: 09 1 1 00001001; bit0 = Left button state; bit3 = always 1
Mouse: 00 1 1 No X-movement
Mouse: 00 1 1 No Y-movement
... and release the Left Button:
Mouse: 08 0 1 00001000 bit0 = Left button state; bit3 = always 1
Mouse: 00 1 1 No X-movement
Mouse: 00 1 1 No Y-movement
*/
