/*
             MyUSB Library
     Copyright (C) Dean Camera, 2008.
              
  dean [at] fourwalledcubicle [dot] com
      www.fourwalledcubicle.com

 Released under the LGPL Licence, Version 3
*/

/*
	Communications Device Class demonstration application.
	This gives a simple reference application for implementing
	a CDC device acting as a virtual serial port. Joystick
	actions are transmitted to the host as strings. The device
	does not respond to serial data sent from the host.
	
	Before running, you will need to install the INF file that
	is located in the CDC project directory. This will enable
	Windows to use its inbuilt CDC drivers, negating the need
	for special Windows drivers for the device. To install,
	right-click the .INF file and choose the Install option.
*/

/*
	USB Mode:           Device
	USB Class:          Communications Device Class (CDC)
	USB Subclass:       Abstract Control Model (ACM)
	Relevant Standards: USBIF CDC Class Standard
	Usable Speeds:      Full Speed Mode
*/

#include "CDC.h"

/* Project Tags, for reading out using the ButtLoad project */
BUTTLOADTAG(ProjName,     "MyUSB CDC App");
BUTTLOADTAG(BuildTime,    __TIME__);
BUTTLOADTAG(BuildDate,    __DATE__);
BUTTLOADTAG(MyUSBVersion, "MyUSB V" MYUSB_VERSION_STRING);

/* Scheduler Task List */
TASK_LIST
{
	{ Task: USB_USBTask          , TaskStatus: TASK_STOP },
	{ Task: CycleCounter_Task    , TaskStatus: TASK_STOP },
};

/* Globals: */
CDC_Line_Coding_t LineCoding = { BaudRateBPS: 9600,
                                 CharFormat:  OneStopBit,
                                 ParityType:  Parity_None,
                                 DataBits:    8            };

char JoystickUpString[]      PROGMEM = "Joystick Up\r\n";
char JoystickDownString[]    PROGMEM = "Joystick Down\r\n";
char JoystickLeftString[]    PROGMEM = "Joystick Left\r\n";
char JoystickRightString[]   PROGMEM = "Joystick Right\r\n";
char JoystickPressedString[] PROGMEM = "Joystick Pressed\r\n";

int main(void)
{
	/* Disable watchdog if enabled by bootloader/fuses */
	MCUSR &= ~(1 << WDRF);
	wdt_disable();

	/* Disable Clock Division */
	SetSystemClockPrescaler(0);

	/* Hardware Initialization */
	Joystick_Init();
	LEDs_Init();
	
	/* Indicate USB not ready */
	LEDs_SetAllLEDs(LEDS_LED1 | LEDS_LED3);
	
	/* Initialize Scheduler so that it can be used */
	Scheduler_Init();

	/* Initialize USB Subsystem */
	USB_Init();

	EICRA = _BV(ISC01) | _BV(ISC00); /* int0 on rising edge */
	EIMSK |= _BV(INT0); /* enable int0 */;
	TCCR1B = _BV(CS20); /* /1 prescaler */

	/* Scheduling - routine never returns, so put this last in the main function */
	Scheduler_Start();
}

EVENT_HANDLER(USB_Connect)
{
	/* Start USB management task */
	Scheduler_SetTaskMode(USB_USBTask, TASK_RUN);

	/* Indicate USB enumerating */
	LEDs_SetAllLEDs(LEDS_LED1 | LEDS_LED4);
}

EVENT_HANDLER(USB_Disconnect)
{
	/* Stop running CycleCounter and USB management tasks */
	Scheduler_SetTaskMode(CycleCounter_Task, TASK_STOP);
	Scheduler_SetTaskMode(USB_USBTask, TASK_STOP);

	/* Indicate USB not ready */
	LEDs_SetAllLEDs(LEDS_LED1 | LEDS_LED3);
}

EVENT_HANDLER(USB_ConfigurationChanged)
{
	/* Setup CDC Notification, Rx and Tx Endpoints */
	Endpoint_ConfigureEndpoint(CDC_NOTIFICATION_EPNUM, EP_TYPE_INTERRUPT,
		                       ENDPOINT_DIR_IN, CDC_NOTIFICATION_EPSIZE,
	                           ENDPOINT_BANK_SINGLE);

	Endpoint_ConfigureEndpoint(CDC_TX_EPNUM, EP_TYPE_BULK,
		                       ENDPOINT_DIR_IN, CDC_TXRX_EPSIZE,
	                           ENDPOINT_BANK_SINGLE);

	Endpoint_ConfigureEndpoint(CDC_RX_EPNUM, EP_TYPE_BULK,
		                       ENDPOINT_DIR_OUT, CDC_TXRX_EPSIZE,
	                           ENDPOINT_BANK_SINGLE);

	/* Indicate USB connected and ready */
	LEDs_SetAllLEDs(LEDS_LED2 | LEDS_LED4);
	
	/* Start CycleCounter task */
	Scheduler_SetTaskMode(CycleCounter_Task, TASK_RUN);
}

EVENT_HANDLER(USB_UnhandledControlPacket)
{
	uint8_t* LineCodingData = (uint8_t*)&LineCoding;

	/* Process CDC specific control requests */
	switch (bRequest)
	{
		case GET_LINE_CODING:
			if (bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
			{	
				/* Acknowedge the SETUP packet, ready for data transfer */
				Endpoint_ClearSetupReceived();

				/* Write the line coding data to the control endpoint */
				Endpoint_Write_Control_Stream_LE(LineCodingData, sizeof(LineCoding));
				
				/* Send the line coding data to the host and clear the control endpoint */
				Endpoint_ClearSetupOUT();
			}
			
			break;
		case SET_LINE_CODING:
			if (bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
			{
				/* Acknowedge the SETUP packet, ready for data transfer */
				Endpoint_ClearSetupReceived();

				/* Read the line coding data in from the host into the global struct */
				Endpoint_Read_Control_Stream_LE(LineCodingData, sizeof(LineCoding));

				/* Send the line coding data to the host and clear the control endpoint */
				Endpoint_ClearSetupIN();
			}
	
			break;
		case SET_CONTROL_LINE_STATE:
			if (bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
			{
				/* Acknowedge the SETUP packet, ready for data transfer */
				Endpoint_ClearSetupReceived();
				
				/* Send an empty packet to acknowedge the command (currently unused) */
				Endpoint_ClearSetupIN();
			}
	
			break;
	}
}

static void put_byte(uint8_t ch) {
	Endpoint_SelectEndpoint(CDC_TX_EPNUM);
	while (!(Endpoint_ReadWriteAllowed())) {
		;
	}
	Endpoint_Write_Byte(ch);
	Endpoint_ClearCurrentBank();
}

static void print_bits(uint8_t ch) {
	char i;

	for (i = 8; i > 0; i--) {
		put_byte(ch & _BV(i-1) ? '1' : '0');
	}
}

static void reboot(void) {
	wdt_enable(WDTO_250MS);
	for (;;) {
		;
	}
}		

/* number of rising edges seen */
static volatile uint32_t ticks = 0;

/* number of errorneous pulses seen */
static volatile uint16_t errors = 0;

/* TCNT1 value of previous rising edge */ 
static volatile uint16_t prev_time = 0; 

/* TCNT1 difference between previous two rising edges */
static volatile uint16_t diff;

ISR(INT0_vect) {
	uint16_t time;
	
	time = TCNT1;
	
	if (prev_time <= time) {
		diff = time - prev_time;
	} else {
		diff = (0xffff - prev_time) + time;
	}
	prev_time = time;

	/* 8*1000*1000/10000 = 800 */
	if (diff > 810) {
		errors++;
	} else if (diff < 780) {
		errors++;
	}
	ticks++;
}

static void handle_cmd(uint8_t cmd) {
	put_byte(cmd);
	put_byte('=');
	switch (cmd) {
	case 'g':
	{
		uint32_t t;
		{
			cli();
			t = ticks;
			sei();
		}
		print_bits(t >> 24);
		print_bits(t >> 16);
		print_bits(t >> 8);
		print_bits(t >> 0);
		break;
	}
	case 'd':
	{
		uint16_t d;
		{
			cli();
			d = diff;
			sei();
		}
		print_bits(d >> 8);
		print_bits(d >> 0);
		break;
	}
	case 'e':
	{
		uint16_t e;
		{
			cli();
			e = errors;
			sei();
		}
		print_bits(e >> 8);
		print_bits(e >> 0);
		break;
	}
	case 'r':
		cli();
		ticks = 0;
		errors = 0;
		sei();
		break;
	case 'b':
		reboot();
		break;
	}
	put_byte('\r');
	put_byte('\n');
}

TASK(CycleCounter_Task) {
	if (USB_IsConnected) {
		Endpoint_SelectEndpoint(CDC_RX_EPNUM);
		if (Endpoint_ReadWriteAllowed()) {
			if (Endpoint_BytesInEndpoint()) {
				uint8_t b;

				b = Endpoint_Read_Byte();
				Endpoint_ClearCurrentBank();
				handle_cmd(b);
			}
		}
	}
}
