USB Code for the PIC32MZ Family

Here is some minimal USB code for the MZ processor written specifically for the hobbyist or developer that does not want to use Harmony.

My code uses Microsoft OS Descriptors 1.0 to load a Winusb driver. When a USB device enumerates for WinUSB, the process starts as soon as the device is plugged in and the host detects its presence on the bus. The device first responds with basic descriptors—like the device descriptor—so Windows can identify its vendor ID, product ID, and supported USB version. Windows then requests configuration and interface descriptors, where the device advertises that it uses the WinUSB-compatible interface. If Microsoft OS descriptors are present, the device can explicitly signal that WinUSB is the preferred driver. Once Windows recognizes that the interface is WinUSB-compatible, it loads the WinUSB.sys driver, creates a device node, and exposes the endpoints for user-mode applications through the WinUSB API. The device will be listed as 'MainBrain MZ' in Device Manager under 'Universal Serial Bus Devices'

Main.c
/*********************************************************************
    FileName:           Main.c
    Dependencies:       USB_MZ.c, see includes
    Processor:          PIC32MZ
    Hardware:           MainBrain MZ
    Complier:           XC32 4.40
    Author:             Larry Knight 2023
/*********************************************************************
 
    Software License Agreement:
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 
    Description:
        Configures the PIC32MZ for USB

    File Description:

    Change History:
 
/***********************************************************************/

#pragma config DEBUG =      OFF
#pragma config JTAGEN =     OFF
#pragma config ICESEL =     ICS_PGx2
#pragma config TRCEN =      OFF
#pragma config BOOTISA =    MIPS32
#pragma config FECCCON =    OFF_UNLOCKED
#pragma config FSLEEP =     OFF
#pragma config DBGPER =     PG_ALL
#pragma config SMCLR =      MCLR_NORM
#pragma config SOSCGAIN =   GAIN_2X
#pragma config SOSCBOOST =  ON
#pragma config POSCGAIN =   GAIN_2X
#pragma config POSCBOOST =  OFF
#pragma config EJTAGBEN =   NORMAL
#pragma config CP =         OFF

/*** DEVCFG1 ***/
#pragma config FNOSC =      SPLL
#pragma config DMTINTV =    WIN_127_128
#pragma config FSOSCEN =    ON
#pragma config IESO =       OFF
#pragma config POSCMOD =    EC
#pragma config OSCIOFNC =   OFF
#pragma config FCKSM =      CSECMD
#pragma config WDTPS =      PS1048576
#pragma config WDTSPGM =    STOP
#pragma config FWDTEN =     OFF
#pragma config WINDIS =     NORMAL
#pragma config FWDTWINSZ =  WINSZ_25
#pragma config DMTCNT =     DMT31
#pragma config FDMTEN =     OFF

/*** DEVCFG2 ***/
//250 MHz Core
#pragma config FPLLIDIV =   DIV_3
#pragma config FPLLRNG =    RANGE_5_10_MHZ
#pragma config FPLLICLK =   PLL_POSC
#pragma config FPLLMULT =   MUL_58
#pragma config FPLLODIV =   DIV_2
#pragma config UPLLFSEL =   FREQ_24MHZ

/*** DEVCFG3 ***/
#pragma config USERID =     0xffff
#pragma config FMIIEN =     OFF
#pragma config FETHIO =     OFF
#pragma config PGL1WAY =    OFF
#pragma config PMDL1WAY =   OFF
#pragma config IOL1WAY =    OFF
#pragma config FUSBIDIO =   OFF

#include <xc.h>
#include "usb.h"

bool SetFreqPOSC(uint8_t f);

void SystemSetup(void);

int main(void)
{       
    while(CLKSTATbits.POSCRDY == 0);
        
    SystemSetup();
     
    //enable the interrupts
    __builtin_enable_interrupts();    
  
    //Main program Loop
    while(1)
    {           
        //Heartbeat out on pin 50
        PORTCbits.RC15 = !PORTCbits.RC15;  
    }
}

void SystemSetup(void)
{   
    //disable the interrupts
    __builtin_disable_interrupts();    
    
    // Unlock Sequence
    SYSKEY = 0xAA996655;
    SYSKEY = 0x556699AA;  
    
    //Init USB module
    USB_init();
    
    // Set multi vector interrupt mode
    INTCONbits.MVEC = 1; 
    
    unsigned int cp0;
    
    //USB
    //This allows the usage of RF3 as I/O while still using the USB module 
    //Device mode only
    USBCRCONbits.USBIDOVEN = 1;
    USBCRCONbits.USBIDVAL = 1;
    
    PRISS = 0x76543210;
    
    //PBCLK1 - System Clock
    // Peripheral Bus 1 cannot be turned off, so there's no need to turn it on
    PB1DIVbits.PBDIV = 1; // Peripheral Bus 1 Clock Divisor Control (PBCLK1 is SYSCLK divided by 1)

    //PBCLK2 - PMP
    PB2DIVbits.PBDIV = 1; // Peripheral Bus 2 Clock Divisor Control (PBCLK2 is SYSCLK divided by 1)
    PB2DIVbits.ON = 1; // Peripheral Bus 2 Output Clock Enable (Output clock is enabled)
    while (!PB2DIVbits.PBDIVRDY);

    //PBCLK3 - Feeds TMR1
    PB3DIVbits.PBDIV = 0; 
    PB3DIVbits.ON = 1; 
    while (!PB3DIVbits.PBDIVRDY);
    
    //PB4DIV
    PB4DIVbits.PBDIV = 1; // Peripheral Bus 4 Clock Divisor Control (PBCLK4 is SYSCLK divided by 1)
    PB4DIVbits.ON = 1; // Peripheral Bus 4 Output Clock Enable (Output clock is enabled)
    while (!PB4DIVbits.PBDIVRDY); // Wait until it is ready to write to

    //PB5DIV
    PB5DIVbits.PBDIV = 1; // Peripheral Bus 5 Clock Divisor Control (PBCLK5 is SYSCLK divided by 1)
    PB5DIVbits.ON = 1; // Peripheral Bus 5 Output Clock Enable (Output clock is enabled)
    while (!PB5DIVbits.PBDIVRDY); // Wait until it is ready to write to

    // PB7DIV
    PB7DIVbits.PBDIV = 0; // Peripheral Bus 7 Clock Divisor Control (PBCLK7 is SYSCLK divided by 1)
    PB7DIVbits.ON = 1; // Peripheral Bus 7 Output Clock Enable (Output clock is enabled)
    while (!PB7DIVbits.PBDIVRDY); // Wait until it is ready to write to

    //PB8DIV
    PB8DIVbits.PBDIV = 1; // Peripheral Bus 8 Clock Divisor Control (PBCLK8 is SYSCLK divided by 1)
    PB8DIVbits.ON = 1; // Peripheral Bus 8 Output Clock Enable (Output clock is enabled)
    while (!PB8DIVbits.PBDIVRDY); // Wait until it is ready to write to

    //PRECON - Set up prefetch
    PRECONbits.PFMSECEN = 0;  // Flash SEC Interrupt Enable (Do not generate an interrupt when the PFMSEC bit is set)
    PRECONbits.PREFEN = 3; // Predictive Prefetch Enable (Enable predictive prefetch for any address)
    PRECONbits.PFMWS = 2; // PFM Access Time Defined in Terms of SYSCLK Wait States (Two wait states)
    
    CFGCONbits.USBSSEN = 1;

    // Set up caching
    cp0 = _mfc0(16, 0);
    cp0 &= ~0x07;
    cp0 |= 0b011; // K0 = Cacheable, non-coherent, write-back, write allocate
    _mtc0(16, 0, cp0);  
    
    while(CLKSTATbits.DIVSPLLRDY == 0);
}

void __attribute__((nomips16)) _general_exception_handler(void)
{
   unsigned int exccode = (_CP0_GET_CAUSE() & 0x7C) >> 2;

    while(1)
    {
        //do something here
    }
}
usb.h
/*********************************************************************
    FileName:           usb.h
    Dependencies:       
    Processor:          PIC32MZ
    Hardware:           MainBrain MZ
    Complier:           XC32 4.40
    Author:             Larry Knight 2023
/*********************************************************************
 
    Software License Agreement:
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 
    Description:
        Header file for USB_MZ.c

    File Description:

    Change History:
 
/***********************************************************************/


#ifndef USB_H
#define USB_H
#include <stdbool.h>
#include <stdint.h> 

typedef enum
{
    CONNECTED =      0x00,
    DISCONNECTED =   0x01,
} DEVICE_STATE;

typedef struct
{
    volatile unsigned char bmRequestType;
    volatile unsigned char bRequest;
    volatile unsigned short wValue;
    volatile unsigned short wIndex;
    volatile unsigned short wLength;
} USB_TRANSACTION;

typedef struct
{
    volatile unsigned short rx_num_bytes;
    volatile unsigned short tx_num_bytes;
    volatile unsigned char tx_buffer[512];
    volatile unsigned char rx_buffer[512];
} USB_ENDPOINT;

void USB_init(void);

#endif  /* USB_H */


USB_MZ.c
/*********************************************************************
    FileName:           USB_MZ.c
    Dependencies:       usb.h
    Processor:          PIC32MZ
    Hardware:           non-board hardware dependent
    Complier:           XC32 4.40
    Author:             Larry Knight 2023

    Software License Agreement:
        This software is licensed under the Apache License Agreement

    Description:
        Enumerates as a High Speed interface class device,
        Uses Microsoft OS Descriptors to load a Winusb driver,
        Endpoint 1 is the receiving endpoint,
        Endpoint 2 is the transmitting endpoint,
        Host application sends commands to the device, 
        Device responds to the commands by sending requested data to the Host

    Device Interface GUID:
        2b8a8216-c82a-4a91-a8bc-a12129d2d70b
 
    References:        
        https://techcommunity.microsoft.com/t5/microsoft-usb-blog/how-does-usb-stack-enumerate-a-device/ba-p/270685#_Configuration_Descriptor_Request

    Registry entries:
        HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\120910801000
        HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_1209&PID_1080\MainBrain_MZ

    Change History:
        Basic framework completed 06/16/2024
        Updated 12/27/2025 

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

#include <xc.h>
#include <string.h>
#include <stdbool.h>
#include "usb.h"

bool isConnected = false;

USB_TRANSACTION USB_transaction;

USB_ENDPOINT EP[3];

uint8_t device_descriptor[] = 
{
    /* Descriptor Length                        */ 0x12, //Size of this descriptor in bytes
    /* DescriptorType: DEVICE                   */ 0x01,
    /* bcdUSB (ver 2.0)                         */ 0x00,0x02,
    /* bDeviceClass                             */ 0x00,
    /* bDeviceSubClass                          */ 0x00,
    /* bDeviceProtocol                          */ 0x00,
    /* bMaxPacketSize0                          */ 0x40, //0x40 for High Speed USB
    /* idVendor                                 */ 0x09,0x12, /*VID 1209*/
    /* idProduct                                */ 0x80,0x10, /*PID */
    /* bcdDevice                                */ 0x00,0x10, //revision
    /* iManufacturer                            */ 0x01,
    /* iProduct                                 */ 0x02,
    /* iSerialNumber                            */ 0x02, 
    /* bNumConfigurations                       */ 0x01
};

uint8_t config_descriptor[] = 
{
    // Configuration Descriptor
    0x09,                       //Descriptor size in bytes
    0x02,                       //Descriptor type
    0x20,0x00,                  //Total length of data
    0x01,                       //Number of interfaces
    0x01,                       //Index value of this configuration
    0x00,                       //Configuration string index
    0xc0,                       // Attributes, see usb_device.h
    0x32,                       // Max power consumption (2X mA)
                                                        
    // Interface Descriptor
    0x09,                       // Size of this descriptor in bytes
    0x04,                       // INTERFACE descriptor type
    0x00,                       // Interface Number
    0x00,                       // Alternate Setting Number
    0x02,                       // Number of endpoints in this intf
    0x00,                       // Class code
    0x00,                       // Subclass code
    0x00,                       // Protocol code
    0x00,                       // Interface string index
    
    // Endpoint Descriptor
    //EP01 OUT
    0x07,                       //Size of this descriptor in bytes
    0x05,                       //Endpoint Descriptor
    0x01,                       //EndpointAddress
    0x02,                       //Attributes
    0x40,0x00,                  //size
    0x00,                       //Interval   
    //EP02 IN                      
    0x07,                       //Size of this descriptor in bytes
    0x05,                       //Endpoint Descriptor
    0x82,                       //EndpointAddress
    0x02,                       //Attributes
    0x40,0x00,                  //size
    0x00                        //Interval
};

uint8_t device_qualifier[] =
{
    0x0a,
    0x06,
    0x00, 0x02,
    0x00,   // FIX
    0x00,   // FIX
    0x00,   // FIX
    0x40,
    0x01,
    0x00
};

uint8_t MSOSDescriptor[] =
{   
    //bLength - length of this descriptor in bytes
    0x12,                           
    //bDescriptorType - "string"
    0x03,                           
    //qwSignature - special values that specifies the OS descriptor spec version that this firmware implements
    'M',0,'S',0,'F',0,'T',0,'1',0,'0',0,'0',0,
    //bMS_VendorCode - defines the "GET_MS_DESCRIPTOR" bRequest literal value
    0xee,
    //bFlags
    //a new flags field has been added to the Microsoft OS string descriptor that can be used to indicate support for the ContainerID descriptor
    //Bit 1 of this field is used to indicate support for the ContainerID descriptor
    0x00                            
};    

//Extended Compatability ID Feature Descriptor
uint8_t ExtCompatIDFeatureDescriptor[] =
{
    0x28, 0x00, 0x00, 0x00,                             /* dwLength Length of this descriptor */
    0x00, 0x01,                                         /* bcdVersion = Version 1.0 */
    0x04, 0x00,                                         /* wIndex = 0x0004 */
    0x01,                                               /* bCount = 1 */
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,           /* Reserved */
    0x00,                                               /* Interface number = 0 */
    0x01,                                               /* Reserved */
    0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00,     /* compatibleID */ //WINUSB
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     /* subCompatibleID */
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00                  /* Reserved */
};
    
uint8_t ExtPropertyFeatureDescriptor[] =
{
    //----------Header Section--------------
    0x8e, 0x00, 0x00, 0x00,                             //dwLength (4 bytes)
    0x00, 0x01,                                         //bcdVersion = 1.00
    0x05, 0x00,                                         //wIndex
    0x01, 0x00,                                         //wCount - 0x0001 "Property Sections" implemented in this descriptor
    //----------Property Section 1----------
    0x84, 0x00, 0x00, 0x00,                             //dwSize - 132 bytes in this Property Section
    0x01,0x00, 0x00, 0x00,                              //dwPropertyDataType (Unicode string)
    0x28, 0x00,                                         //wPropertyNameLength - 40 bytes in the bPropertyName field
    'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 'I', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0,
        'e', 0, 'G', 0, 'U', 0, 'I', 0, 'D', 0,  0x00, 0x00,  //bPropertyName - "DeviceInterfaceGUID"
    0x4e, 0x00, 0x00, 0x00,                             //dwPropertyDataLength - 78 bytes in the bPropertyData field (GUID value in UNICODE formatted string, with braces and dashes)
    //Device Interface GUID
    //{2b8a8216-c82a-4a91-a8bc-a12129d2d70b}
    '{', 0, '2', 0, 'b', 0, '8', 0, 'a', 0, '8', 0, '2', 0, '1', 0, '6', 0, '-', 0, 'c', 0, '8', 0, '2', 0, 'a', 0, 
        '-', 0, '4', 0, 'a', 0, '9', 0, '1', 0, '-', 0, 'a', 0, '8', 0, 'b', 0, 'c', 0, '-', 0, 'a', 0, '1', 0, '2', 
        0, '1', 0, '2', 0, '9', 0, 'd', 0, '2', 0, 'd', 0, '7', 0, '0', 0, 'b', 0, '}', 0, 0x00, 0x00
};    

//Language - 0x0409 - English
uint8_t string0[] =  {4, 0x03, 0x09, 0x04};

//iManufacturer
uint8_t string1[] = {28, 3, 'A', 0, 'n', 0, 't', 0, 'i', 0, 'm', 0, 'a', 0, 't', 0, 't', 0, 'e', 0, 'r', 0, '.', 0, 'm', 0, 'e', 0};   

//iProduct      
uint8_t string2[] = {26, 3, 'M', 0, 'a', 0, 'i', 0, 'n', 0, 'B', 0, 'r', 0, 'a', 0, 'i', 0, 'n', 0, ' ', 0, 'M', 0, 'Z', 0};
 
//iSerialNumber 
uint8_t string3[] = {10, 3, '0', 0, '0', 0, '0', 0, '1', 0};

void EP0ControTransaction(void);
void USB_queue_EP0(uint8_t *buffer, int size, int max_size);
void EP0_RX(int length);
void EP0_TX(void);
void Host_CMDs(void);
int EP1_RX(void);
int EP2_TX(volatile uint8_t *tx_buffer);
int EP0_Wait_TXRDY(void);
int EP2_Wait_TXRDY(void);
volatile uint8_t usbAddress;
volatile bool SetAddress = true;
volatile uint8_t DeviceState;
volatile bool ep0_in_data_phase = false;

void USB_init(void)
{
    //Disable the pull-ups
    USBCSR0bits.SOFTCONN = 0;     

    //Set the initial state of the device
    DeviceState = DISCONNECTED;
    
    // Set endpoint 0 buffer to 64 bytes (multiples of 8).
    USBE0CSR0bits.TXMAXP = 64; 

    //EP 1
    USBCSR3bits.ENDPOINT = 1;
    USBOTGbits.RXFIFOSZ = 0x06;
    USBIENCSR1bits.RXMAXP = 64;
    USBIENCSR3bits.RXFIFOSZ = 0x09;
    USBFIFOAbits.RXFIFOAD = 0x0280;
    USBIENCSR3bits.PROTOCOL = 0x02;
    USBIENCSR3bits.TEP = 0x01;
    USBIENCSR1bits.FLUSH = 1;
    USBE1CSR0bits.MODE = 0;
    
    //TX
    USBCSR3bits.ENDPOINT = 2;
    USBOTGbits.TXFIFOSZ = 0x06;
    USBIENCSR0bits.TXMAXP = 64;
    USBIENCSR3bits.TXFIFOSZ = 0x09;
    USBFIFOAbits.TXFIFOAD = 0x0080;
    USBIENCSR2bits.PROTOCOL = 0x02;
    USBIENCSR2bits.TEP = 0x02;
    USBIENCSR0bits.FLUSH = 1;
    USBE2CSR0bits.MODE = 0;
    
    //Clear the address
    usbAddress = 0;                
    USBCSR0bits.FUNC = 0;   
    
    //VBUS Monitoring ON
    USBCRCONbits.VBUSMONEN = 1;
    
    //Enable the reset interrupt
    USBCSR2bits.RESETIE = 1;    
    
    //Enable the USB interrupt
    IEC4bits.USBIE = 1;    
    
    //Enable USB module interrupt
    USBCRCONbits.USBIE = 1;     
    
    //Clear the USB interrupt flag.
    IFS4bits.USBIF = 0;         
    
    //USB Interrupt Priority 7
    //Must be 7. Cannot use any other priority.
    //Internally, the USB hardware expects SRS context switching 
    //to avoid stack usage ? that?s why priority of 7 is required 
    IPC33bits.USBIP = 7;        
    IPC33bits.USBIS = 3; 
    
    //See DISNYET (same bit as PIDERR)
    USBE1CSR1bits.PIDERR = 1;   

    //Enable High Speed (480Mbps) USB mode
    USBCSR0bits.HSEN = 1;      
    
    //Enable the pull-ups
    USBCSR0bits.SOFTCONN = 1;     
}

//USB
void __attribute__((vector(_USB_VECTOR), interrupt(ipl7srs), nomips16)) USB_handler()
{       
    
    //Reset
    if(USBCSR2bits.RESETIF)
    {    
        // 1 = Endpoint is TX
        USBE1CSR0bits.MODE = 1;     
        
        // Set endpoint 0 buffer to 64 bytes (multiples of 8)
        USBE0CSR0bits.TXMAXP = 64; 
        
        // Endpoint 0 Operating Speed Control bits
        USBE0CSR2bits.SPEED = 1;
        
        USBCSR2bits.RESETIF = 0;
    }
    
    /* Endpoint 0 Interrupt Handler */
    if (USBCSR0bits.EP0IF)
    {
        /* --------------------------------------------
         * Apply SET_ADDRESS (after status stage)
         * -------------------------------------------- */
        if (SetAddress)
        {           
            USBCSR0bits.FUNC = usbAddress & 0x7F;
            SetAddress = false;
        }

        /* --------------------------------------------
         * SETUP / OUT stage
         * -------------------------------------------- */
        if (USBE0CSR0bits.RXRDY)
        {
            EP0_RX(USBE0CSR2bits.RXCNT);

            USB_transaction.bmRequestType = EP[0].rx_buffer[0];
            USB_transaction.bRequest      = EP[0].rx_buffer[1];
            USB_transaction.wValue        = ((uint16_t)EP[0].rx_buffer[3] << 8) |
                                             EP[0].rx_buffer[2];
            USB_transaction.wIndex        = ((uint16_t)EP[0].rx_buffer[5] << 8) |
                                             EP[0].rx_buffer[4];
            USB_transaction.wLength       = ((uint16_t)EP[0].rx_buffer[7] << 8) |
                                             EP[0].rx_buffer[6];

            /* Prepare control transfer */
            ep0_in_data_phase = false;

            EP0ControTransaction();

            /* If this is a DEVICE2HOST transfer, wait for IN tokens */
            if ((USB_transaction.bmRequestType & 0x80) &&
                (EP[0].tx_num_bytes > 0))
            {
                ep0_in_data_phase = true;
            }

            /* Zero-length control write */
            if (USB_transaction.wLength == 0)
            {
                USBE0CSR0bits.DATAEND = 1;
            }
        }

        /* --------------------------------------------
         * IN data stage ? ONLY when host asks
         * -------------------------------------------- */
        if (ep0_in_data_phase &&
            !USBE0CSR0bits.TXRDY)
        {
            EP0_TX();

            /* Finished? */
            if (EP[0].tx_num_bytes == 0)
            {
                ep0_in_data_phase = false;
            }
        }

        /* --------------------------------------------
         * Abort handling
         * -------------------------------------------- */
        if (USBE0CSR0bits.SETEND)
        {
            USBE0CSR0bits.SETENDC = 1;
            ep0_in_data_phase = false;
        }

        USBCSR0bits.EP0IF = 0;
    }
    
    //Endpoint 1 Interrupt Handler
    if(USBCSR1bits.EP1RXIF == 1)
    { 
        EP1_RX();
                
        Host_CMDs();
        
        USBCSR1bits.EP1RXIF = 0;
    }

    IFS4bits.USBIF = 0;   
}

void EP0ControTransaction()
{
    uint16_t length;

    if ((USB_transaction.bmRequestType == 0xC0) && (USB_transaction.bRequest == 0xee) && USB_transaction.wIndex == 0x04)
    {
       length = USB_transaction.wLength;
       if (length > sizeof(ExtCompatIDFeatureDescriptor))
       {
           length = sizeof(ExtCompatIDFeatureDescriptor);
       }
       
       USB_queue_EP0(ExtCompatIDFeatureDescriptor, sizeof(ExtCompatIDFeatureDescriptor), length); 
       
       return;
    }
    
    //Class specific, device to host, interface target
    if(USB_transaction.bmRequestType == 0xc1)    
    {
        //Check if the host is requesting an MS feature descriptor
        if(USB_transaction.bRequest == 0xee)
        {
            //Figure out which descriptor is being requested
            if(USB_transaction.wIndex == 0x05)    
            {
                //Determine number of bytes to send to host 
                //Lesser of: requested amount, or total size of the descriptor
                length = sizeof(ExtPropertyFeatureDescriptor);
                if(USB_transaction.wLength < length)
                {
                    length = USB_transaction.wLength;
                }
                
                USB_queue_EP0(ExtPropertyFeatureDescriptor, sizeof(ExtPropertyFeatureDescriptor), length);  
                        
                USBE0CSR0bits.TXRDY = 1;    
                
                return;
            }
        }
    }
    
    // We're not going to bother with whether bmRequestType is IN or OUT for the most part
    switch (USB_transaction.bRequest)
    {
        case 0xC:
        {
            USBE0CSR0bits.STALL = 1;
            break;
            
        }
        case 0x0: 
        {
            if (USB_transaction.bmRequestType == 0x80) // Get status
                USB_queue_EP0(device_descriptor, 0, 0);
            if (USB_transaction.bmRequestType == 0x00) // Select function
                USB_queue_EP0(device_descriptor, 0, 0);
            break;            
        }
        
        //Set USB address
        case 0x5: 
        {
            USBE0CSR0bits.RXRDYC = 1;
            usbAddress = EP[0].rx_buffer[2];

            SetAddress = true;
            break;
        }
        
        //Get descriptor
        case 0x6: 
        {
            switch (USB_transaction.wValue >> 8)
            {
                //Device descriptor
                case 0x1: 
                {
                    USB_queue_EP0(device_descriptor, sizeof(device_descriptor), USB_transaction.wLength);                             
                    break;
                }
                
                //Configuration descriptor
                case 0x2: 
                {
                    USB_queue_EP0(config_descriptor, sizeof(config_descriptor), USB_transaction.wLength);
                    break;
                }
                
                //String descriptors
                case 0x3: 
                {          
                    switch (USB_transaction.wValue & 0xff)
                    {
                        //String 0 - Language ID
                        case 0x0: 
                        {
                            USB_queue_EP0(string0, sizeof(string0), USB_transaction.wLength);
                            break;
                        }
                        //String 1 - iManufacturer
                        case 0x1: 
                        {
                            USB_queue_EP0(string1, sizeof(string1), USB_transaction.wLength);                           
                            break;
                        }
                        //String 2 - iProduct
                        case 0x2: 
                        {
                            USB_queue_EP0(string2, sizeof(string2), USB_transaction.wLength);
                            break;
                        }
                        //String 3 - iSerialNumber
                        case 0x3: 
                        {
                            USB_queue_EP0(string3, sizeof(string3), USB_transaction.wLength);
                            break;
                        }
                        //MS OS Descriptor Query
                        case 0xee:
                        {
                            USB_queue_EP0(MSOSDescriptor, sizeof(MSOSDescriptor), USB_transaction.wLength);
                            break;
                        }                       
                        break;
                    }  
                    break;
                }
                
                case 0x04: // Extended Compatibility ID Feature Descriptor
                if (USB_transaction.wIndex == 0x0004)
                {
                    USB_queue_EP0(ExtCompatIDFeatureDescriptor, sizeof(ExtCompatIDFeatureDescriptor), USB_transaction.wLength);
                }
                break;
                
                case 0x05: // Extended Properties Feature Descriptor
                if (USB_transaction.wIndex == 0x0005)
                {
                    USB_queue_EP0(ExtPropertyFeatureDescriptor, sizeof(ExtPropertyFeatureDescriptor), USB_transaction.wLength);
                }
                break;

            //Device Qualifier
                case 0x6: 
                {          
                    USB_queue_EP0(device_qualifier, sizeof(device_qualifier), USB_transaction.wLength);
                    break;
                }                        
            }
            break;
        }
        
        // Set configuration
        case 0x9: 
        {
            //Enumeration complete!
            
            //Enable the endpoint interrupts
            //Endpoint 1
            USBCSR2bits.EP1RXIE = 1;    
            
            // Endpoint 2:     
            USBCSR1bits.EP2TXIE = 1;

            break;
        }
        
        default: 
        {
            USBE0CSR0bits.STALL = 1;
            break;
        }  
    }
}

//USB_queue_EP0(config_descriptor, sizeof(config_descriptor), USB_transaction.wLength);
void USB_queue_EP0(uint8_t *buffer, int size, int max_size)
{
    int cnt;
    
    if (max_size < size)
        size = max_size;
    
    EP[0].tx_num_bytes = size;
    
    for (cnt = 0; cnt < size; cnt++)
    {
        EP[0].tx_buffer[cnt] = buffer[cnt];
    }       
    
    EP0_TX();
}

void EP0_RX(int length)
{
    int cnt;
    uint8_t *FIFO_buffer;
    
    // Store number of bytes received
    EP[0].rx_num_bytes = USBE0CSR2bits.RXCNT;
    
    // Get 8-bit pointer to USB FIFO for endpoint 0
    FIFO_buffer = (uint8_t *)&USBFIFO0;
    
    for(cnt = 0; cnt < length; cnt++)
    {
        // Read in one byte at a time
        EP[0].rx_buffer[cnt] = *(FIFO_buffer + (cnt & 3));
    }
     
    USBE0CSR0bits.RXRDYC = 1;
}

int EP0_Wait_TXRDY()
{
    int timeout;
    
    timeout = 0;
    
    while (USBE0CSR0bits.TXRDY)
    {
        timeout++;
        
        if (timeout > 5000)
        {
            return 1;
        }
    };
    
    return 0;
}

void EP0_TX(void)
{
    static uint16_t ep0_tx_cnt = 0;
    uint8_t *FIFO = (uint8_t *)&USBFIFO0;
    uint16_t bytes_this_pkt = 0;

    while (ep0_tx_cnt < EP[0].tx_num_bytes && bytes_this_pkt < 64)
    {
        *FIFO = EP[0].tx_buffer[ep0_tx_cnt++];
        bytes_this_pkt++;
    }

    USBE0CSR0bits.TXRDY = 1;

    /* Final packet */
    if (ep0_tx_cnt >= EP[0].tx_num_bytes)
    {
        USBE0CSR0bits.DATAEND = 1;
        ep0_tx_cnt = 0;
        EP[0].tx_num_bytes = 0;
    }
}

int EP2_TX(volatile uint8_t* tx_buffer)
{
    int cnt = 0;
    
    //Load the data to TX in array
    EP[2].tx_num_bytes = 64;
    
    for (cnt = 0; cnt < 64; cnt++)
    {
        EP[2].tx_buffer[cnt] = tx_buffer[cnt];
    }       
        
    //a pointer
    uint8_t *FIFO_buffer;

    //load the pointer with the address of the TX buffer
    FIFO_buffer = (uint8_t *)&USBFIFO2;
    
    //return if the TX buffer is empty
    if (EP2_Wait_TXRDY())
    {
        return 0;
    }
    
    //reset cnt
    cnt = 0;
    
    //send data until the TX buffer is empty
    while (cnt < EP[2].tx_num_bytes)
    {
        *FIFO_buffer = EP[2].tx_buffer[cnt]; // Send the bytes

        cnt++;
        
        // Have we sent 64 bytes?
        if ((cnt > 0) && (cnt % 64 == 0))
        {
            //Set TXRDY and wait for it to be cleared before sending any more bytes
            USBE2CSR0bits.TXPKTRDY = 1;            
            if(EP2_Wait_TXRDY())
            {
                return 0;
            }            
        }
    }

    USBE2CSR0bits.TXPKTRDY = 1;            
}

int EP1_RX()
{
    unsigned char *FIFO_buffer;
    int cnt;
    int rx_bytes;
    //get the number of bytes received
    rx_bytes = USBE1CSR2bits.RXCNT;
    
    //USB FIFO Data Register 1
    FIFO_buffer = (unsigned char *)&USBFIFO1; 
    
    //load the array with the bytes in the buffer
    for(cnt = 0; cnt < rx_bytes; cnt++)
    {
        EP[1].rx_buffer[cnt] = *(FIFO_buffer + (cnt & 3));
    }
    
    //unload the RX FIFO
    USBE1CSR1bits.RXPKTRDY = 0;
    
    return rx_bytes;
}

int EP2_Wait_TXRDY()
{
    int timeout;
    
    timeout = 0;
    
    while (USBE2CSR0bits.TXPKTRDY)
    {
        timeout++;
        
        if (timeout > 5000)
        {
            return 1;
        }
    };
    
    return 0;
}

void Host_CMDs()
{
      if(EP[1].rx_buffer[0] == 0x0)
      {
          DeviceState = CONNECTED;
      }
      else
      {
          DeviceState = DISCONNECTED;
      }

      //echo back
      EP[2].tx_buffer[0] = EP[1].rx_buffer[1];

      EP2_TX(EP[2].tx_buffer);
 }