/* ps2-rf.c Radio Control transmitter 


 Interfaces Sony Playstation(TM) game controller pad to 9600 baud RF link
 for controlling combat robots.
 
 Hardware schematic: ps2-rf-xmit.sch

Atmel AVR AT90S8535 microcontroller

Compiled with avr-gcc  (See Makefile)


Note: This code is alpha.  It does not set the PSx game pad to
analog mode. The user must push the "analog" button on the pad.
There may be other bugs.  It has had limited testing.

By Dale Heatherington
Aug 25 2003

*/

/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA 

The GNU General Public License can also be found online at:
http://www.fsf.org/licenses/licenses.html#GPL

*/

 

/***************************************************************************/

//Note: CPU type defined in Makefile

#include <io.h>
#include <sig-avr.h>
#include <interrupt.h>
#include <pgmspace.h>
#include <progmem.h>


                
#define F_CPU    7372800        /* crystal frequency of CPU */

#define COUNT 1      /* offset to count field in tx/rx buffers */
#define ADDRESS 0    /* offset to address field in tx/rx buffers */
#define DATA 2       /* offset to start of data in tx/rx buffers */
#define LED_RED 2
#define LED_YELLOW 3

/* Port C interface to game pad bit defs */

#define PAD_DAT_MASK     0x01   /*in*/
#define PAD_CMD             1   /*out*/
#define PAD_SEL             2   /*out*/
#define PAD_CLK             3   /*out*/
#define PAD_ACK_MASK     0x10   /*in*/


uint8_t matrix0,matrix1;
uint16_t ad0,ad1,ad2,ad3,ad4;   //A-D converter values



uint8_t tx_buf[16];      //Serial tx buffer
uint8_t tx_ptr,tx_ctr ;
uint16_t crc;
int8_t txstate, counter,val;

int ii;
unsigned char prescale ;
volatile char prescale16ms;
volatile char flag16ms;
unsigned char prescale100;
unsigned char prescale1sec;
unsigned char prescaleMinute;
unsigned char timer10ms ;
unsigned char timer1sec;
unsigned int minutes;
char do_btu_flag;
char status;
char lan_watchdog;

void set_tx_buffer_crc();
uint16_t get_adc(uint8_t chan);
void calc_crc(char data);
void make_info_pkt();
void delay_100us(uint8_t count);

/* Analog joystick values note: x is left-right and y is up-down */
unsigned char left_x_joy;
unsigned char left_y_joy;
unsigned char right_x_joy;
unsigned char right_y_joy;


//--------------------------------------------------------------------------
/* Read the 10 bit analog to digital converter */

uint16_t get_adc(uint8_t chan)
{  
      
    outp(chan,ADMUX);               //Select channel
    sbi(ADCSR,ADSC);                //Start conversion 
    while( bit_is_clear(ADCSR,ADIF)); //Wait for ADC output registers to be loaded
    return __inw_atomic(ADCL);       //Return 16 bit value
}


//---------------------------------------------------------------------------

   // compute 16 bit CRC on 1 data byte. 
   //   crc is a  variable which accumulates the 16 bit CRC. 
   //   It must be cleared before starting to compute 16 bit CRC on a string.
   //   The final output value will be in crc.
   
void calc_crc(char data){

      uint8_t i;
     
      for (i=0;i<8;i++) {                //Do all 8 bits in the data byte
         if (data & 0x80) crc ^= 0x8000; //if data bit is 1 then flip crc msb
         data = data << 1;             // get next data bit
         if (crc & 0x8000) { //crc msb is 1
             crc = crc << 1;
             crc = crc ^ 0x8005;
          } else crc = crc << 1;
        
      }

}   

//-----------------------------------------------------------------------
void set_tx_buffer_crc(){
      int j = tx_buf[COUNT + 0] + 2; //get data size
      int i;

      crc = 0;
      for (i=0;i<j;i++) calc_crc(tx_buf[i]); //Compute crc16 over buffer
      tx_buf[i++] = (uint8_t)(crc >> 8);  //tack the crc bytes on the end of the buffer
      tx_buf[i++] = (uint8_t)(crc & 0xff);
      
}

   //-----------------------------------------------------------------------

   /* Build a packet */
  
   void make_info_pkt(){
      uint8_t i;
      i = ADDRESS;
      tx_buf[i] = 1;             // address of T-Zero battle bot
      i = DATA;
      tx_buf[i++] = 0x01;        //data type = 1
      tx_buf[i++] = (uint8_t)right_y_joy;       //1
      tx_buf[i++] = (uint8_t)right_x_joy;       //2
      tx_buf[i++] = (uint8_t)left_y_joy;        //3
      tx_buf[i++] = (uint8_t)left_x_joy;        //4
      tx_buf[i++] = matrix0;                    //5
      tx_buf[i++] = matrix1;                    //6
            
      tx_buf[COUNT] = i - DATA;       //Set byte count field to data length

      set_tx_buffer_crc();  //compute and add CRC16 bits to end of pkt
      
      txstate = 2;          //Tell scheduler it's ready to transmit
      outp(0x28,UCR);       //enable UART TX and interrupt 
      outp(0xff,UDR);       // Send a FF to get things started 
   }

//---------------------------------------------------------------------------

/* Clock data to and from the game pad.
   Data is sent and received  LSB first (right shifts)
   Clock cycle time is about 4 microseconds.
*/

char byte_to_game_pad(unsigned char c)
{
    unsigned char i,j,cmd,dat;
    cmd = c;
    dat = 0;

    cli();     //Disable interrupts
    
    for(i=0;i<8;i++){      //Shift 8 bits out and in
       if(cmd & 1) {sbi(PORTC,PAD_CMD);} else {cbi(PORTC,PAD_CMD);} //output a command bit
       cbi(PORTC,PAD_CLK); //Clock low
       cmd = cmd >> 1;     //Shift command right
       for(j=0;j<3;j++) cbi(PORTC,PAD_CLK);
       cbi(PORTC,PAD_CLK);
       cbi(PORTC,PAD_CLK);
       dat = dat >> 1;      //shift input data right
       dat = dat & 0x7f;    //clear msb
       sbi(PORTC,PAD_CLK);  //Clock high
       sbi(PORTC,PAD_CLK); 
       if(PAD_DAT_MASK & inp(PINC)) dat = dat | 0x80;  //Set input data msb if PAD bit is set
        

    } 

    sbi(PORTC,PAD_CMD);
    
    //Wait for ACK- with a timeout
    for(j=0;j<64;j++){
         if(~inp(PINC) & PAD_ACK_MASK) break; //Break out of loop when ACK- goes low
         
         }
    

    sei();     //Enable interrupts
    return dat;
}



//---------------------------------------------------------------------------
void get_game_pad_data()
{   unsigned char i, rv, id, m0,m1;

    for(i=0;i<8;i++) sbi(PORTC,PAD_CMD);  //CMD high

    for(i=0;i<8;i++) cbi(PORTC,PAD_SEL);     //SEL- low


    rv = byte_to_game_pad(0x01);    // CMD 0x01 gets it started
    id = byte_to_game_pad(0x42);    // id = 0x41 digital mode or 0x73 analog
    rv = byte_to_game_pad(0);       // ignore this byte
    m0 = ~byte_to_game_pad(0);      //Get buttons
    m1 = ~byte_to_game_pad(0);      //Get more buttons

    if(id == 0x73){   //Check for "Analog" mode and get joy stick values
      right_x_joy =   byte_to_game_pad(0);
      right_y_joy =   byte_to_game_pad(0);
      left_x_joy =    byte_to_game_pad(0);
      left_y_joy =    byte_to_game_pad(0);
    }
    else              //If not Analog mode set joy stick values to neutral.
    {  right_x_joy =  0x80;
       right_y_joy =  0x80;
       left_x_joy =   0x80;
       left_y_joy =   0x80;
    }
        
        sbi(PORTC,PAD_SEL);    //SEL- high


    matrix0 = 0;
    matrix1 = 0;

    /* Map the Sony Playstaton buttons to the same
      bits  I used for the hacked Logitech Wingman game pad */

    if(m0 & 1) matrix0 |= 0x80;   //Select -> mode
    if(m0 & 8) matrix1 |= 0x08;   //Start -> Z
    if(m0 & 0x10)  matrix0 |= 0x20; //North -> North
    if(m0 & 0x20)  matrix0 |= 0x40; // East -> East
    if(m0 & 0x40) matrix0 |= 0x08;  // South -> South
    if(m0 & 0x80) matrix0 |= 0x01;  // West -> West

    if(m1 & 0x01) matrix0 |= 0x02;  // L2 -> Left Fire
    if(m1 & 0x02) matrix1 |= 0x80;  // R2 -> Right Fire
    if(m1 & 0x04) matrix0 |= 0x02;   // L1 -> Left Fire
    if(m1 & 0x08) matrix1 |= 0x80;  // R1 -> Right Fire
    if(m1 & 0x10) matrix1 |= 0x20;  // triangle -> B
    if(m1 & 0x20) matrix1 |= 0x10;  //Circle -> C
    if(m1 & 0x40) matrix1 |= 0x02;  // X -> X
    if(m1 & 0x80) matrix1 |= 0x40;  //Square -> A

   


}
      
//---------------------------------------------------------------------------

 int main(void)
{
  outp(BV(CS01), TCCR0);      /* use CLK/8 source for counter */
  
  outp(0xff, DDRD);           /* port D all outputs */
  outp(0xff, PORTD);          /* turn all leds off  */
  outp(0x00,DDRA);            //8 bits analog inputs
  outp(0,PORTA);              //Clear port A
  outp(0x85,ADCSR);           //Enable A to D converter with /32 conv clk.

  outp(0xff,PORTC);           //Set port C bits
  outp(0xee,DDRC);            //Bits 0,4 are inputs, others outputs on PORTC
  
  outp(0x0b,TCCR2);           //clear on compare, Prescale = 32 on T/C 2
  outp(192,OCR2);             //T/C 2 compare register
  sbi(TIMSK,OCIE2);           //Enable timer 2 compare interrupts
  
  outp(47,UBRR);              /* UART baud rate set to 9600 */
  txstate = 0;
  sei();                      /* enable global interrupts */

   right_x_joy =  0x80;       
   right_y_joy =  0x80;       
   left_x_joy =   0x80;     
   left_y_joy =   0x80; 
   matrix0 = 0;
   matrix1 = 0;
        
   for(;;){                   //MAIN LOOP

    /* Get A-D converter values */
    
    //ad0 = get_adc(0) >> 2;      //Convert 10 bit value to 8 bits by right shifts
    //ad1 = get_adc(1) >> 2;
   // ad2 = get_adc(2) >> 2;
   // ad3 = get_adc(3) >> 2;
    ad4 = get_adc(4) >> 2;

    if((ad4 & 0xff) < 166){
         cbi(PORTD,LED_YELLOW);  //Yellow LED on if battery less than 6.5 volts
    }else{
       sbi(PORTD,LED_YELLOW);
    }

    
    if(flag16ms != 0) {       //Get game pad data every 16 milliseconds
        flag16ms = 0;
        get_game_pad_data();
    }
    
    while(txstate > 0) delay_100us(1) ;   //Wait for transmitter to be ready
    
    if(txstate == 0) make_info_pkt();     //Transmit data to robot


  }

}

//---------------------------------------


void delay_100us(uint8_t count)
{
  while(count){
    outp(256 - ((F_CPU/80000) -1), TCNT0);
    outp(BV(TOV0), TIFR);
    while(!(inp(TIFR) & BV(TOV0)));   
    count--;
  }
}


void delay_10ms(uint8_t count)
{
  while(count){
    delay_100us(100);
    count--;
  }
}

   

//-----------------------------------------------------------------------
/* Come here 1200 times per second */
SIGNAL(SIG_OUTPUT_COMPARE2)
{
   
   

      if(--prescale == 0){      //This executes 100 times per sec.
         prescale = 12;
         if(prescale1sec > 0) prescale1sec--;
         
      }

      if(--prescale16ms == 0){
          prescale16ms = 19;      //every 15.8 ms
          flag16ms = 1;
      }

      
     if(prescale1sec == 0){  //This executes once per second
        prescale1sec = 100;
        if(prescaleMinute > 0) prescaleMinute--;

        
     }

     if(prescaleMinute == 0){ //This executes once per minute
        prescaleMinute = 60;
        minutes++;
       
     }


}

//----------------------------------------------------------------------

/* UART interrupt handler */  
SIGNAL(SIG_UART_DATA)
{      
      switch(txstate) {
      case 0:  break;
      case 1:
      case 2:
      case 3:  outp(0xff,UDR) ; txstate++; cbi(PORTD,LED_RED);
         break;
      case 4:  outp(0xf0,UDR); txstate++;
         break;
      case 5:  outp(0x55,UDR); txstate++;
         break;

      case 6:  tx_ptr = 0;
         tx_ctr = tx_buf[COUNT] + 4; 
         txstate++;
         //fall through...

      case 7:  outp(tx_buf[tx_ptr++],UDR);  //Now send buffer contents
            tx_ctr--;
            if (tx_ctr == 0){ 
               txstate = 8;
               
            }
         
            break;

      case 8:   
               txstate = 0;
               outp(0xff,UDR);   //Feed the uart data register one last time to clear this interrupt
               outp(UCR,0);      //Stop the UART transmitter and Interrupts
               sbi(PORTD,LED_RED);     //LED off
               break;



      }

      
}


//---------------------------------------------------------------------------

/* end of file */


