/*---------------------------------------
PlayStation 2(1?) Controller Interface
For ATMega88 -Probably works on 48/88/168

Thanks to everyone on the tubes who's already played with this stuff:
http://www.curiousinventor.com/guides/ps2
http://www.gamesx.com/controldata/psxcont/psxcont.htm

Use it, share it, hack it, and don't ever pay for it.
-daPhreak
---------------------------------------*/
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

void spi_mInit();
unsigned char spi_mSend(unsigned char data);
void USART_Tx( unsigned char data );
unsigned char USART_Rx( void );
void USART_Init(unsigned int ubr_val);
void process_controller();
void send_packet();
void sleep(int);

unsigned char data[8] = {0,0,0,0,0,0,0,0};
unsigned char packet[8] = {0xFF, '!',0,0,0,0,0,0};
//global vars are messy but this was a quick project
//and I didnt feel like tossing pointers around

int main()
{
	DDRB = 0x2F; //leave MISO pin as input
	DDRD = 0x82;
	PORTB = 0x04;
	
	int x;		
	
	USART_Init(51); //initialize 9600 baud USART
	spi_mInit();	//setup SPI in master mode 
	
	sleep(50);	//why not?
	
	
	while(1)
	{
		PORTB = 0x00;		//slave select
		spi_mSend(0x01);	//see PS2 documentation for why this is sent
		data[0]=spi_mSend(0x42);
		for(x=1; x<8; x++)
		{
			data[x]=spi_mSend(0x00);
		}
		PORTB = 0x04;		//deselct slave
		
		process_controller();	//routine to put data in rover packet form and send it
		
		if((data[0]==0x73) && (data[1]==0x5A))	//status LED output
			PORTD=0x80;	//on when in analog mode and data is good
		else
			PORTD=0;
		
		sleep(10);		//havent optimized this yet... just guessing
				
	}
}

void send_packet()
{
	int x;
	
	for(x=0; x<8; x++)
	{
		USART_Tx(packet[x]);
	}	

	//probably could take this out, but LINX radios are pretty bad
	//and this seems to help keep dropped packets
	for(x=0; x<8; x++)
	{
		USART_Tx(0xFF);
	}
}

void process_controller()
{
	unsigned char rdir=0,ldir=0,lspd=0,rspd=0,checksum='!';
	int x;
/*	
	byte	MOSI	MISO
	00	0x42	0x73
	01	idle	0x5A	Bit0 Bit1 Bit2 Bit3 Bit4 Bit5 Bit6 Bit7
	02	idle	data	SLCT JOYR JOYL STRT UP   RGHT DOWN LEFT
	03	idle	data	L2   R2   L1   R1   /\   O    X    |_|
	04	idle	data	Right Joy 0x00 = Left  0xFF = Right
	05	idle	data	Right Joy 0x00 = Up    0xFF = Down
	06	idle	data	Left Joy  0x00 = Left  0xFF = Right
	07	idle	data	Left Joy  0x00 = Up    0xFF = Down
*/
	if(data[0]==0x73)
	{
		if(data[1]==0x5A)
		{
			//Could do the assignment directly but this will 
			//allow me to move to interupts in the future and 
			//keep my instructions with interupts turned off to a
			//minimum (i dont want to send incomplete packets)
			if(data[5]<0x70)
			{
				rdir=1;
				rspd=(unsigned char)(0x80-data[5]);
			}
			else if(data[5]>0x90)
			{
				rdir=0;
				rspd=(unsigned char)(data[5]-0x80);
			}
			else
			{
				rspd=0;
			}
			
			if(data[7]<0x70)
			{
				ldir=1;
				lspd=(unsigned char)(0x80-data[7]);
			}
			else if(data[7]>0x90)
			{
				ldir=0;
				lspd=(unsigned char)(data[7]-0x80);
			}
			else
			{
				lspd=0;
			}
			if(data[3] & 0x01<<1)
			{
				lights=0;
			}
			else
			{
				lights=1;
			}
	
			packet[2]=lspd+1;	//for some reason the USART
			packet[3]=rspd+1;	//freaks out when you send zeros (probably 
						//do to nature of async)
			packet[4]= (1<<6)|(rdir << 1) | (ldir) | (lights << 2);
			packet[5] = 0xFF;
			packet[6] = 0xFF;
			for(x=2; x<7; x++)
			{
				checksum ^= packet[x]; //calc checksum
			}
			packet[7]=checksum;
			send_packet();
		}
	}
}

void spi_mInit()
{
	// Enable SPI, Master, set clock rate fck/64 
	// In our case we have fclk=8MHz to spi clock will be 125KHz
	// Tried 500KHz but didnt work.
	// Also need SPI Mode 3 with LSB first.
	SPCR = (1<<SPE)|(1<<MSTR)|(2<<SPR0)|(1<<DORD)|(1<<CPHA)|(1<<CPOL);
}

unsigned char spi_mSend(unsigned char data)	//straight from documentation
{
	/* Start transmission */
	SPDR = data;
	while(!(SPSR & (1<<SPIF))) 
	;

	return SPDR;
}

void USART_Init(unsigned int ubr_val)		//straight from documentation
{
	UBRR0H = (unsigned int) (ubr_val >> 8);
	UBRR0L = (unsigned int) ubr_val;
	// Enable receiver and transmitter 
	UCSR0B = (1<<RXEN0)|(1<<TXEN0);
	// Set frame format: 8data, 1stop bit (default)
	UCSR0C = (3<<UCSZ00)|(1<<USBS0);
	
	return;
}

unsigned char USART_Rx( void )			//straight from documentation
{
	//Keep checking for data
	while ( !(UCSR0A & (1<<RXC0)) );
	//drops to here when data found
	return UDR0;
}

void USART_Tx( unsigned char data )		//straight from documentation
{
	//Wait for empty transmit buffer
	while ( !( UCSR0A & (1<<UDRE0)) );
	//Put data into buffer, sends the data
	UDR0 = data;
}

void sleep(int ms)
{
	int x;
	while(ms--)
	{
		for(x=425;x>1;x--)
		{
			asm volatile ("nop"::);	
			//approx 2.35us for a nop in a loop @ 8MHz
			//this loop gives us roughly a millisecond-ish
		}
	}
}
