The awesome embedded adventures logo goes here!


Configuring the PicPack USB library

As a personal preference, I think that state machines are evil and should be avoided like the plague. Mostly this is because of my view the source code should show precisely what should happen. State machines are different, since they show what should happen under certain circumstances and they specifically hide the workings of the “machine” as a whole. It means that the understanding of how the whole system works is in fact “outside of the code”. You need to know something beyond what is written to comprehend what happens and why. This makes debugging these systems extremely difficult.

Despite my misgivings, state machines are excellent at enabling us to create hardware cheaply since it doesn’t need to understand about what it is doing or why – it just needs to respond quickly. This is the world we find ourselves in when we start playing with USB. Let’s have a look at how PIC devices in combination with the PicPack library handle the tangled web that is USB.

Our first port of call is the config.h file, which is where the PicPack library finds all its configuration details – and the USB library is no exception. Let’s pull apart the USB section from the Mouse demo.

#define USB_HIGHEST_EP 1

In order to configure the appropriate data structures, the library needs to know what the highest end point number will be. In this case, it’s endpoint 1. Mice are actually about the simplest sort of device you can create in USB land.



Is the device powered from the USB supply? Select your option here. This changes what the pic reports upstream when queried.

#define USB_EP0_OUT_E_SIZE 8

#define USB_EP0_OUT_E_ADDR 0x0280

#define USB_EP0_OUT_O_SIZE 8

#define USB_EP0_OUT_O_ADDR 0x0288

#define USB_EP0_IN_SIZE 8

#define USB_EP0_IN_ADDR 0x0290

#define USB_EP1_IN_SIZE 8

#define USB_EP1_IN_ADDR 0x0298

Each endpoint for IN and OUT transactions has its own buffer. For each endpoint IN and endpoint OUT you need to decide how big transactions can be, and where the buffer will be located. You’ll need to check out the datasheet for your particular PIC.

In the case of the 18F4550, 0x0500 is a good place to start all the buffers. For the 18F14K50, which is what’s used on the PLT-1003, the USB buffers start at 0x280. You can see they run sequentially from there. Eight bytes is just plenty for a mouse.

Since we have only an IN endpoint 1 (no OUT), we only declare the IN. Note also that the OUT transactions for endpoint 0 have both odd and even buffers. This is called ping pong buffering and you can in fact enable it for all endpoints if necessary.


// if you define it, you'll need to include this routine in your code:

// void usb_SOF_callback(uns16 frame) {

// }

Each 1ms, the USB starts a new “frame”. If you want a nice clean 1ms wake up call without having to use timers, define USB_CALLBACK_ON_SOF and create a function called usb_SOF_callback in your code.


// if you define it, you'll need to include this routine in your code:

// void usb_device_configured_callback() {

// }

USB has a "configured" state. It means that the host has chosen a particular configuration (usually it has a choice of just one, but it still has to choose it) and the PicPack library has set up the endpoints ready for action. If you’d like to get notified when this state occurs, define USB_CALLBACK_ON_DEVICE_CONFIGURED in your code and declare usb_device_configured_callback() in your code.


// if you define it, you'll need to include these routines in your code:

//void usb_handle_class_ctrl_read_callback();

//void usb_handle_class_ctrl_write_callback(uns8 *data, uns16 count);

//void usb_handle_class_request_callback(setup_data_packet sdp);

A number of different types of devices are pre-defined by the USB standard. These are known as class compliant devices. Just to keep things fun, USB handles some transactions for class devices over endpoint 0 using control transfers. While the Mouse demo does indeed handle class control transfers, it doesn’t do a lot with them.

For a more complex example, including how to send data back to the host after it has made a request using a control transfer and assuming class ownership of the control transfer, see the USB to Serial project, which we’ll cover in the next tutorial. If your device needs to handle class control transfers, then define USB_CALLBACK_ON_CLASS_CTRL and declare the appropriate functions. Note that usb_handle_class_request_callback will be called when a class request has been received and needs action; usb_handle_class_ctrl_read_callback will be called when a transaction sending data to the host (IN) has completed, and usb_handle_class_ctrl_write_callback will be called when a transaction receiving data from the host has occurred.


// if you define it, you'll need to include these routines in your code:

//void usb_ep_data_out_callback(uns8 end_point, uns8 *buffer_location, uns16 byte_count);

//void usb_ep_data_in_callback(uns8 end_point, uns16 byte_count);

To handle data transactions that occur over endpoints other than endpoint 0, that is, non-control transfers, define USB_EP_DATA_CALLBACK. When a successful data transaction has occurred that received data from the host (OUT), usb_ep_data_out_callback will be called. You’ll be told the endpoint number, where the buffer is and how many bytes were transferred. Note that the endpoint will not be re-primed (armed) until you return from this function. When a successful data transaction has occurred that sent data to the host (IN), usb_ep_data_in_callback will be called. The Mouse demo doesn’t use either of these functions. To load the USB Mouse demo onto your USB to Serial PLT-1003 board, you can use the Screamer application and download the Mouse hex file to replace the firmware it came with. Plug the PLT-1003 into a USB port and low and behold you’ll have a mouse that goes left and right.