Open source USB stack for PIC microcontrollers

From DP

Jump to: navigation , search


Open source USB stack for PIC microcontrollers

This stack was written by Honken and JTR. It is licensed Creative Commons Attribution. It features:

  • PIC18F, PIC24F USB support
  • CDC-ACM (virtual serial port) support
  • Single character and double-buffered reads and writes
  • Interrupt or polling modes
  • There is also a USB-CDC virtual serial port bootloader for PIC24F

It is currently used in the Bus Pirate v4 and USB Infrared Toy firmwares. A simple echo demo is available that demonstrates all the modes.

MPLAB X issues

Because of on going problems with MPLAB X that I am working with the Microchip dev tool team to sort out, mostly these projects are MPLAB 8.xx based. MPLAB X is fine for all the PIC18 projects but it tends to not want to load the BPv4 firmware.


The USB stack is organized like this.

..\[project folder]\
..\[usb stack folder]\

The goal is to completely separate the stack and the project that uses it.

Project folder

The project folder requires these files that are referenced by the usb stack:

  • prj_usb_config.h -- Unique to each project. For the PIC18F14K50 the cdc bulk endpoint size specified in prj_usb_config.h needs to be set to 32 as there is not enough usb ram for the cdc double buffering otherwise.
  • descriptors.h -- For the most it is the same file for all projects with only the strings changed. However this could need to be changed at anytime so I did it this way for forward compatibility.

Setup the USB stack

Setup include paths

Both the paths for the project and the usb stack need to be included in MPLAB's search path at least for C30 projects. C18 seems to work without them though.


Go to project -> built options -> project -> directories -> show directories for -> include search path. Add the project directory (.) and the usb stack directory (..\dp_usb).


Include USB files in the project

The project needs to be built with these files:

  • usb_stack.c
  • cdc.c

main.c setup

main.c (only!) needs to include the configwords and the descriptor header:

#include "descriptors.h"	        // JTR Only included in main.c
#include "configwords.h"	// JTR only included in main.c

Include in source

The usb stack header files need to be included in each and any module that uses the usb stack.

#include "..\dp_usb\usb_stack_globals.h"    // USB stack only defines Not function related.

Using the USB virtual serial port

The echo demo shows how to use the USB virtual serial port in normal and interrupt modes.

//USB stack
#include "..\dp_usb\usb_stack_globals.h"    // USB stack only defines Not function related.
#include "descriptors.h"    // JTR Only included in main.c
#include "configwords.h"    // JTR only included in main.c
void InterruptHandlerLow();
void USBSuspend(void);
extern BYTE usb_device_state;
#pragma code
#ifdef PIC_18F
void main(void)
int main(void)
    BYTE RecvdByte;
    initCDC(); // JTR this function has been highly modified It no longer sets up CDC endpoints.
    usb_init(cdc_device_descriptor, cdc_config_descriptor, cdc_str_descs, USB_NUM_STRINGS); // TODO: Remove magic with macro
#if defined USB_INTERRUPTS
    EnableThisUsbInterrupt(USB_TRN + USB_SOF + USB_UERR + USB_URST);
    do {
    } while (usb_device_state < CONFIGURED_STATE);
    usb_register_sof_handler(CDCFlushOnTimeout); // Register our CDC timeout handler after device configured
    do {
        //If USB_INTERRUPT is not defined each loop should have at least one additional call to the usb handler to allow for control transfers.
        // The CDC module will call usb_handler each time a BULK CDC packet is sent or received.
        if (poll_getc_cdc(&RecvdByte)) // If there is a byte ready will return with the number of bytes available and received byte in RecvdByte
            putc_cdc(RecvdByte); //
        if (peek_getc_cdc(&RecvdByte)) { // Same as poll_getc_cdc except that byte is NOT removed from queue.
            RecvdByte = getc_cdc(); // This function will wait for a byte and return and remove it from the queue when it arrives.
        if (poll_getc_cdc(&RecvdByte)) { // If there is a byte ready will return with the number of bytes available and received byte in RecvdByte
            putc_cdc(RecvdByte); //
            CDC_Flush_In_Now(); // when it has to be sent immediately and not wait for a timeout condition.
    } while (1);
} //end main
void USBSuspend(void) {}
#if defined(USB_INTERRUPTS)
//PIC24F example
#pragma interrupt _USB1Interrupt
void __attribute__((interrupt, auto_psv)) _USB1Interrupt() {
    //USB interrupt
    //IRQ enable IEC5bits.USB1IE
    //IRQ flag    IFS5bits.USB1IF
    //IRQ priority IPC21<10:8>
//PIC18F example
//USB stack on low priority interrupts,
#pragma interruptlow InterruptHandlerLow nosave= PROD, PCLATH, PCLATU, TBLPTR, TBLPTRU, TABLAT, section (".tmpdata"), section("MATH_DATA")
void InterruptHandlerLow(void) {

Best practices

PLEASE! DO NOT break this by putting project items in headers or source files belonging to the usb stack like I/We did in the past. usb_stack_globals.h is for the usb stack. Only use prj_globals.h for items belonging to the application. Based on JTR's post