/* Name: USB_joystick.c
 * Project: hid-joystick
 * Author: Jakub Tiller
 * Description: USB HID standard joystick with x,y axes, throttle control, rudder control
 * and 2 buttons at this time ( should be expanded with more buttns)
 */

/*
This software running on ATmega8A with 3.3V VCC, clock is 16MHz
The PID, VID are set in accordance with free HID ID's
*/

#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>  
#include <util/delay.h>     
#include "./ADC_drv/adc_driver.h"   // driver for Analog to Digital Converter
#include "./KEYB_drv/keyb_drv.h"		// driver for buttons

#include <avr/pgmspace.h>   /* required by usbdrv.h */
#include "usbdrv/usbdrv.h"
//#include "usbdrv/oddebug.h"        /* This is also an example for using debug macros */

#define 	ADC_OFFSET	576
/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */

PROGMEM char usbHidReportDescriptor[68] = { /* USB report descriptor, size must match usbconfig.h */
 	0x05, 0x01,				// USAGE_PAGE (Generic Desktop)
	0x09, 0x04,				// USAGE (Joystick)
	0xA1, 0x01,				// COLLECTION (Application)
	0xA1, 0x00,  			// 	COLLECTION (Physical)
	0x09, 0x30,				//			USAGE (x)
	0x09, 0x31,				//			USAGE (y)
	0x35, 0x81,				//			PHYSICAL_MINIMUM (-127)
	0x45, 0x7F,				// 		PHUSICAL_MAXIMUM (127)
	0x75, 0x08,				// 		REPORT_SIZE (8)			
	0x95, 0x02,				// 		REPORT_COUNT (2)
	0x81, 0x02,				// 		INPUT (Data,Var,Abs)
	0x05, 0x09,				// 		USAGE (Button)
	0x19, 0x01,				// 		USAGE_MINIMUM (Button 1)
	0x29, 0x04,				//			USAGE_MAXIMUM (Button 4)
	0x15, 0x00,				//			LOGICAL_MINIMUM (0)
	0x25, 0x04,				//			LOGICAL_MAXIMUM (1)
	0x75, 0x01,				// 		REPORT_SIZE (1)				
	0x95, 0x08,				// 		REPORT_COUNT (8)
	0x81, 0x03,				//  		INPUT (Csnt, Var, Abs)
	0xC0,						//		END_COLLECTION
	0x05, 0x02,				//		USAGE_PAGE (Simulation Controls)
	0x09, 0xBB,				// 	USAGE (Throttle)
	0x15, 0x81,				//		LOGICAL_MINIMUM (-127)
	0x25, 0x7F,				//		LOGICAL_MINIMUM (127)
	0x75, 0x08,				// 	REPORT_SIZE (8)
	0x95, 0x01,				// 	REPORT_COUNT (1)
	0x81, 0x02,				// 	INPUT (Data,Var,Abs)
	0x05, 0x02,				//		USAGE_PAGE (Simulation Controls)
	0x09, 0xBA,				// 	USAGE (Rudder)
	0x15, 0x81,				//		LOGICAL_MINIMUM (-127)
	0x25, 0x7F,				//		LOGICAL_MINIMUM (127)
	0x75, 0x08,				// 	REPORT_SIZE (8)
	0x95, 0x01,				// 	REPORT_COUNT (1)
	0x81, 0x02,				// 	INPUT (Data,Var,Abs)
	0xC0,						//	END_COLLECTION
};
 
/* The data described by this descriptor consists of 5 bytes:
 *     X7 X6 X5 X4 X3 X2 X1 X0 .... 8 bit signed coordinate x
 *     Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 .... 8 bit signed coordinate y
 *      .  .  .  . B3 B2 B1 B0 .... one byte with joystick button states
 *     T7 T6 T5 T4 T3 T2 T1 T0 .... 8 bit signed throttle
 *     R7 R6 R5 R4 R3 R2 R1 R0 .... 8 bit signed rudder
 */
typedef struct{
	 char    dx;			// one byte to handle coordinate x
    char    dy;			// one byte to handle coordinate y
	 char		buttons;		// one byte to handle all buttons
	 char    throttle; 	// one byte to handle throttle
	 char 	rudder;		// one byte to handle rudder
}report_t;

static report_t reportBuffer;
static uchar    idleRate;   /* repeat rate for keyboards, never used for mice */


void hw_handle_tsk( void)
{
	uint16_t th, rd, x, y;
	uint8_t temp;
	
	// get information from ADC channels (0 - 3) and store them to temporary variables
	x = adc_get(3);
	y = adc_get(1);
	th = adc_get(2);
	rd = adc_get(0);

	// from ADC information there is simple count of position
	// offset is defined on the top of this file
	// offset is subcracted and result is devided by 4
	// to take stable information and satisfy 8-bit variable (ADC is 10-bit)
	reportBuffer.throttle = -1 * (int8_t)((th - ADC_OFFSET) >> 2);
	reportBuffer.rudder =  (int8_t)((rd - ADC_OFFSET) >> 2);
	reportBuffer.dx = (int8_t)((x - ADC_OFFSET) >> 2);
	reportBuffer.dy = (int8_t)((y - ADC_OFFSET) >> 2);
	 

	// last buttom is on PD7
	// PD7 should be input, so logical 0
	DDRD &= ~(1<<PD7);
	// set up the pull-up resistor
	PORTD |= (1<<PD7);
	// take value and store it to 4th bit in temp variable
	temp = ((PIND & (1<<PD7)) >> 4); 
	// then this bit should be inverted, its done by EX-OR
	temp ^= (1<<3);
	// take byte with information whitch buttons are pushed (0x1F is mask for lower 3 bits)
	// added value from last button whitch is stored in temp variable
	reportBuffer.buttons = (keyb_pushed() & 0x07) | temp;
		
}

/* ------------------------------------------------------------------------- */

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    /* The following requests are never used. But since they are required by
     * the specification, we implement them in this example.
     */
    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* class request type */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* we only have one report type, so don't look at wValue */
            usbMsgPtr = (void *)&reportBuffer;
            return sizeof(reportBuffer);
        }else if(rq->bRequest == USBRQ_HID_GET_IDLE){
            usbMsgPtr = &idleRate;
            return 1;
        }else if(rq->bRequest == USBRQ_HID_SET_IDLE){
            idleRate = rq->wValue.bytes[1];
        }
    }else{
        /* no vendor specific requests implemented */
    }
    return 0;   /* default for not implemented requests: return no data back to host */
}

/* ------------------------------------------------------------------------- */

int __attribute__((noreturn)) main(void)
{
uchar   i;
	
	adc_init( EXT_VREF);
	keyb_init( PULLUP_YES);
   wdt_enable(WDTO_1S);
	/* Even if you don't use the watchdog, turn it off here. On newer devices,
	* the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
	*/
	/* RESET status: all port bits are inputs without pull-up.
	* That's the way we need D+ and D-. Therefore we don't need any
	* additional hardware initialization.
	*/

	usbInit();
	usbDeviceDisconnect();  /* enforce re-enumeration, do this while interrupts are disabled! */
	i = 0;
	while(--i){             /* fake USB disconnect for > 250 ms */
	  wdt_reset();
	  _delay_ms(1);
	}
	usbDeviceConnect();
	sei();

	for(;;){                /* main event loop */
	  wdt_reset();
	  usbPoll();
	  if(usbInterruptIsReady()){
	      /* called after every poll of the interrupt endpoint */
			hw_handle_tsk();
	      usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
	  }
	}
}

/* ------------------------------------------------------------------------- */

