#include <c8051f340.h>
#include <stddef.h>
#include "USB_API.h"

#define BUFFER_SIZE 5

/* buffer indexes */
#define REASON 0
#define ADC_STATUS 1
#define TIMER_STATUS 2
#define ADC_RESULT_LOW 3
#define ADC_RESULT_HIGH 4

/* buffer send/receive purposes */
#define GET_STATUS 1
#define TURN_ON 2
#define TURN_OFF 3
#define READ_RESULT 4
#define READ_CONTINUOUS 6
#define USE_TEMP_SENSOR 7
#define USE_ANALOG_INPUT 8
#define IGNORE_VALUE 10
#define VALID_VALUE 11

#define ON 1
#define OFF 0

sfr16 TMR2RL = 0xCA;                                            /* 16-bit timer2 reload address */
sfr16 TMR2 = 0xCC;                                              /* 16-bit timer2 address*/

sbit Led1 = P2^2;                                               /* onboard LED pin address */

volatile BYTE packetFromHost[5] = {0,0,0,0,0};                  /* packet received from host */
volatile BYTE packetToHost[5] = {0,0,0,0,0};                    /* packet to send to host */


/* USB device descriptor */
code const UINT USB_VID = 0x10C4;                               /* use default SiLabs VID */
code const UINT USB_PID = 0xEA61;                               /* associate with default USBXpress driver */
/* Manufacturer's string:  "Kevin Wu (www.wuziq.com)" */
code const BYTE USB_MfrStr[] = {50,0x03,'K',0,'e',0,'v',0,'i',0,'n',0,' ',0,'W',0,'u',0,' ',0,'(',0,'w',0,'w',0,'w',0,'.',0,'w',0,'u',0,'z',0,'i',0,'q',0,'.',0,'c',0,'o',0,'m',0,')',0};
/* Product string:  "Embedded USB Final Project" */
code const BYTE USB_ProductStr[] = {54,0x03,'E',0,'m',0,'b',0,'e',0,'d',0,'d',0,'e',0,'d',0,' ',0,'U',0,'S',0,'B',0,' ',0,'F',0,'i',0,'n',0,'a',0,'l',0,' ',0,'P',0,'r',0,'o',0,'j',0,'e',0,'c',0,'t',0};
/* Serial string:  use SiLabs default */
code const BYTE USB_SerialStr[] = {0x0A,0x03,'1',0,'2',0,'3',0,'4',0};
code const BYTE USB_MaxPower = 15;                              /* max current = 30 mA (15 * 2) */
code const BYTE USB_PwAttributes = 0x80;                        /* bus-powered, remote wakeup not supported */
code const UINT USB_bcdDevice = 0x0001;                         /* device release number 0.01 */



/*
 * initialize() sets up timer, ADC, and buffer; sets registers to initial values
 */
void initialize(void)
{
    OSCICN = 0x80;                                              /* 0b10000000, enable hf oscillator, divide by 8 */

    //AMX0P = 0x1E;                                             /* 0b00011110, select temperature sensor as ADC positive channel input */
    AMX0P = 0x09;                                               /* 0b00001001, select pin P3.5 as ADC input */
    P3MDIN = 0xDF;                                              /* 0b11011111, set pin P3.5 as analog input */
    P3SKIP = 0x20;                                              /* 0b00100000, let pin P3.5 be skipped by crossbar */

    AMX0N = 0x1F;                                               /* 0b00011111, select GND as negative channel input (sets ADC to run in single-ended mode) */
    REF0CN = 0x0E;                                              /* 0b00001110, use V_DD as voltage reference; enable temperature sensor; BIASE on, but it's turned on anyway when ADC0 is enabled; internal reference buffer disabled (??) */
    ADC0CF = 0xF8;                                              /* 0b11111000, set ADC config to defaults:  AD0SC = 0b11111, conversion results are left justified */
    ADC0CN = 0x02;                                              /* 0b00000010, set ADC0 to start conversion when timer2 overflows */
    AD0EN = 1;                                                  /* enable ADC0 */
    EIE1 |= 0x08;                                               /* 0b00001000, enables interrupt to occur when AD0INT flag is set (signifying ADC0 conversion completed) so that no polling is needed. */

    /* timer2 stuff */
    TMR2CN = 0;                                                 /* reset timer2, use system clock / 12 = 1MHz. */
    CKCON |= 0x10;                                              /* 0b00010000, timer 2 clocked by system clock */
    TMR2 = 0x0001;                                              /* timer2 initial load value (max possible reload value ) */
    TMR2RL = 0x0001;                                            /* timer2 reload value (max possible reload value) */
    ET2 = 1;                                                    /* enable timer2 interrupt */

    EA = 1;                                                     /* enable global interrupts */

    packetToHost[ADC_STATUS] = AD0EN;                           /* set initial status for packet to host */
    packetToHost[TIMER_STATUS] = TR2;
    packetToHost[REASON] = USE_ANALOG_INPUT;

    P2MDOUT |= 0x0C;                                            /* Port 2 pin 0 set high impedence for LED */
    XBR1 = 0x40;                                                /* enable crossbar */

    Led1 = 0;                                                   /* turn off LED */
}


/*
 * ADC_ConvComplete_ISR() is the service routine for when ADC completes conversion and sends interrupt
 */
void ADC_ConvComplete_ISR(void) interrupt 10
{
    packetToHost[ADC_RESULT_LOW] = ADC0L;                       /* assign result into packet to send to host */
    packetToHost[ADC_RESULT_HIGH] = ADC0H;
    AD0INT = 0;                                                 /* reset ADC interrupt flag, since it's not cleared by hardware */
}


/*
 * Timer2_ISR() is the service routine for when timer2 rolls over from 0xFFFF to 0x0000
 */
void Timer2_ISR(void) interrupt 5
{
    TF2H = 0;                                                   /* clear timer2 interrupt flag, since it's not cleared by hardware */
}


/*
 * USB_ISR() is the service routine that handles interrupts from USBXpress API.
 * It determines what actions the device should take, and it writes the outgoing packet.
 */
void USB_ISR(void) interrupt 17
{
    BYTE INTVAL = Get_Interrupt_Source();                       /* determine cause of interrupt */

    if (INTVAL & RX_COMPLETE)                                   /* a packet from the host has been received! */
    {
        /* TODO:  if (Block_Read(usbBuffer, BUFFER_SIZE) != BUFFER_SIZE) */
        Block_Read(packetFromHost, BUFFER_SIZE);                /* read the packet... */
        
        switch (packetFromHost[REASON])                         /* determine why packet was sent... */
        {
            case GET_STATUS:                                    /* either to get status... */
                if (TR2)                                        /* timer on or off? */
                    packetToHost[TIMER_STATUS] = ON;
                if (!TR2)
                    packetToHost[TIMER_STATUS] = OFF;
                if (AD0EN)                                      /* ADC on or off? */
                    packetToHost[ADC_STATUS] = ON;
                if (!AD0EN)
                    packetToHost[ADC_STATUS] = OFF;
                if (AMX0P == 0x1E)                              /* using temperature sensor? */
                    packetToHost[REASON] = USE_TEMP_SENSOR;
                if (AMX0P == 0x09)                              /* or analog input at P3.5? */
                    packetToHost[REASON] = USE_ANALOG_INPUT;
                break;

            case TURN_ON:                                       /* or to turn on timer2... */
                TR2 = ON;
                packetToHost[TIMER_STATUS] = TR2;
                break;

            case TURN_OFF:                                      /* or to turn off timer2... */
                TR2 = OFF;
                packetToHost[TIMER_STATUS] = TR2;
                break;

            case READ_RESULT:                                   /* or to just read ADC results... */
                if (AMX0P == 0x1E)                              /* using temperature sensor? */
                    packetToHost[REASON] = USE_TEMP_SENSOR;
                if (AMX0P == 0x09)                              /* or analog input at P3.5? */
                    packetToHost[REASON] = USE_ANALOG_INPUT;
                break;

            case READ_CONTINUOUS:
                if (AMX0P == 0x1E)                              /* using temperature sensor? */
                    packetToHost[REASON] = USE_TEMP_SENSOR;
                if (AMX0P == 0x09)                              /* or analog input at P3.5? */
                    packetToHost[REASON] = USE_ANALOG_INPUT;
                break;

            case USE_TEMP_SENSOR:                               /* or to switch ADC input to temperature sensor... */
                AD0EN = 0;
                AMX0P = 0x1E;
                AD0EN = 1;
                break;

            case USE_ANALOG_INPUT:                              /* or to switch ADC input to analog input */
                AD0EN = 0;
                AMX0P = 0x09;
                AD0EN = 1;
                break;

            default:
                break;
        }
        Block_Write(packetToHost, BUFFER_SIZE);                 /* for any of the above reasons, send packet to host, as it is expecting a response */
    }

    // if (INTVAL & DEV_SUSPEND)    // TODO
    //    Suspend_Device();    // TODO

    if (INTVAL & DEV_CONFIGURED)
        initialize();
}


/*
 * main() calls USB_Clock_Start(), initializes the device, and enables interrupts.  It runs the main program loop.
 */
void main(void)
{
    PCA0MD &= ~0x40;                                            /* disable watchdog timer */

    USB_Clock_Start();                                          /* enable internal oscillator; init clock multiplier; set USB clock to 48MHz */
    /* enable and config USB interface; don't touch USB0ADR or USB0DAT after this */
    USB_Init(USB_VID, USB_PID, USB_MfrStr, USB_ProductStr, USB_SerialStr, USB_MaxPower, USB_PwAttributes, USB_bcdDevice);

    initialize();                                               /* set up timer, ADC, interrupts, clock */

    USB_Int_Enable();                                           /* enable USB API interrupts */

    while (1)
    {
        if ((ADC0H >= 1) && (ADC0L >= 5))                       /* for temperature sensor:  if greater than ~70 degrees F... */
            Led1 = 1;                                           /* turn on LED */
        else
            Led1 = 0;
    }
}