Now available... Including better USB documentation and a cracking USB serial (CDC) demo.
You can download from the right hand side "download" window.
You can also get PicPack from GitHub - updated regularly.
Enjoy!
26 January 2009
USB Part 5 – Using the USB Serial library
Now that we’ve explored some of the ins and outs of the PicPack USB library, it’s time to look at a more complex and probably more useful example. Up until now we’ve relied on using serial ports to act as our debug interface. It would be nice if we could use USB for this purpose entirely? This is where the Communication Device Class (CDC) comes in.
The CDC covers many things, including the thing that we’re interested in, a connection to a modem-like device. In fact, we define our “modem” as so dumb it can’t even make calls on a phone line – which sounds pretty much like a serial port to me.
If you want to get cracking, open up the usb_serial project in the demos\usb_serial directory. It’s all set for burning into a 18f4550 on a board like the TechToys USB demo board. If you burn the .hex file, then plug your device into a PC, it will ask for a driver file. Just point the install program at the picpack_cdc.inf file in the tools\usb_cdc_inf directory. For reasons best known to Microsoft, Windows requires this file, even though all it says is “this is a standard USB serial port”. Go figure. Linux doesn’t require any sort of driver information at all.
Using the PicPack CDC routines is pretty easy. You can use them just like you’ve been using the pic_serial routines up til now.
In your system setup routine, put:
In your interrupt service routine you will need to include:
which handles both reception and transmission.
In your main() routine, kick off USB negotiations using:
You can wait for the negotiations to finish using
We’ll use the callback that’s triggered when the configuration is complete to set the usb_configured variable:that we were checking in that last loop:
And that’s it. Once the link is up, you can use the usb_cdc_ routines like usb_cdc_putc just like their pic_serial equivalents. In fact, aside from these routines, you can see that the complete program looks remarkably similar to the pic_serial demo itself.
You can #define USB_DEBUG and CDC_DEBUG in the config.h file if you’d like to get more detail on the USB process itself. This is handy if you want to understand how the CDC class code and the PicPack USB stack itself work.
In the next tutorial we’ll delve a bit deeper into the CDC routines. The CDC is a more complex example than our previous USB joy mouse which sent data in only one direction, and didn’t make use of class control transfers and multiple endpoints.
The CDC covers many things, including the thing that we’re interested in, a connection to a modem-like device. In fact, we define our “modem” as so dumb it can’t even make calls on a phone line – which sounds pretty much like a serial port to me.
If you want to get cracking, open up the usb_serial project in the demos\usb_serial directory. It’s all set for burning into a 18f4550 on a board like the TechToys USB demo board. If you burn the .hex file, then plug your device into a PC, it will ask for a driver file. Just point the install program at the picpack_cdc.inf file in the tools\usb_cdc_inf directory. For reasons best known to Microsoft, Windows requires this file, even though all it says is “this is a standard USB serial port”. Go figure. Linux doesn’t require any sort of driver information at all.
Using the PicPack CDC routines is pretty easy. You can use them just like you’ve been using the pic_serial routines up til now.
In your system setup routine, put:
// Setup Communication Device Class routines
usb_cdc_setup();
// Setup USB
usb_setup();
// Turn on interrupts
turn_usb_ints_on();
turn_global_ints_on();
In your interrupt service routine you will need to include:
usb_handle_isr();
which handles both reception and transmission.
In your main() routine, kick off USB negotiations using:
usb_enable_module()
You can wait for the negotiations to finish using
while (usb_configured == 0) {
delay_ms(250);
}
We’ll use the callback that’s triggered when the configuration is complete to set the usb_configured variable:that we were checking in that last loop:
void usb_device_configured_callback() {
usb_configured = 1;
}
And that’s it. Once the link is up, you can use the usb_cdc_ routines like usb_cdc_putc just like their pic_serial equivalents. In fact, aside from these routines, you can see that the complete program looks remarkably similar to the pic_serial demo itself.
You can #define USB_DEBUG and CDC_DEBUG in the config.h file if you’d like to get more detail on the USB process itself. This is handy if you want to understand how the CDC class code and the PicPack USB stack itself work.
In the next tutorial we’ll delve a bit deeper into the CDC routines. The CDC is a more complex example than our previous USB joy mouse which sent data in only one direction, and didn’t make use of class control transfers and multiple endpoints.
12 January 2009
USB part 4 – Inner workings of the PicPack USB stack
This tutorial covers the main USB functionality and how the library actually goes about setting up the pic to carry out USB transactions and transfers. You don’t need to understand exactly how all this happens, but it’s helpful to know what the functions do and how to use them.
Our first point is to setup the USB sub-system. As is our convention with the PicPack library, naturally, you call the usb_setup routine:
void usb_setup() {
usb_state = st_POWERED;
Our state is now powered, meaning that we have power applied over the USB bus. The next task is to initialise the hardware:
// init hardware
clear_bit(ucfg, UTRDIS); // enable internal tranceiver
Most designs involving pics use the internal transceiver. You can connect the pic to an external piece of hardware, or connect it directly to the chip itself. Here we assume that you’re doing the direct connection. USB has (as of version 2.0) three speeds – low speed (around 1Mbps), full speed (around 12Mbs) and high speed (around 480Mbps). The pics that we’re playing with only operate in the low/full range, meaning that we have to select between low and full speed.
set_bit(ucfg, FSEN); // clear for low speed, set for high speed
In all the examples here, we select full speed. There’s no reason not to, really, and as it happens, selecting full speed gives us many more choices for clock frequency selection. More about that in a later tutorial.
USB indicates its speed request by pull-ups on the data pins. The pic hardware can do this function for us (meaning we don’t need to include external pull-up resistors), providing we enable the on-chip pull-ups:
set_bit (ucfg, UPUEN); // enable on-chip pull-ups
The pic USB Serial Interface Engine (SIE) can double buffer transfers, meaning that one buffer is being used by the SIE while the other is available to the microcontroller. This means faster transfer at the cost of more complex code. In the initial PicPack library, we switch double buffering off, for the sake of simplicity:
clear_bit(ucfg, PPB1); // disable double buffering
clear_bit(ucfg, PPB0);
Finally, we set up end point 0 ready to receive and send control transfers:
set_bit(uep0, EPHSHK); // EP0 handshaking on
set_bit(uep0, EPOUTEN); // EP0 OUT enable
set_bit(uep0, EPINEN); // EP0 IN enable
clear_bit(uep0, EPCONDIS); // EP0 control transfers on (and IN and OUT)
Here we enable handshaking (required for control transfers – and all endpoints type except isochronous), enable OUT (from host) and IN (to host) transfers, and enable control transfers on this endpoint.
Due to a bug in the BoostC compiler that prevents compile time initialisation of structures that don’t point to char*, we need some run-time initialisation of this structure.
ep_out_bd_location[0] = &bd0out;
This continues for all buffer descriptors for IN and OUT endpoints.
In order to kick off interrupts for the USB module, in our main program we turn on interrupts from the USB module:
turn_usb_ints_on();
turn_global_ints_on();
Finally, to actually start the USB module doing its things, we call usb_enable_module().
void usb_enable_module() {
uir = 0;
set_bit(ucon, USBEN); // enable USB serial interface engine (SIE)
usb_state = st_DEFAULT;
}
Here we clear any previously triggered interrupts, enable the USB SIE module and set our state to Default.
It’s at this point that all the fun starts. The SIE now attaches to the bus by pulling one of the data lines high, and negotiations begin.
Our first point is to setup the USB sub-system. As is our convention with the PicPack library, naturally, you call the usb_setup routine:
void usb_setup() {
usb_state = st_POWERED;
Our state is now powered, meaning that we have power applied over the USB bus. The next task is to initialise the hardware:
// init hardware
clear_bit(ucfg, UTRDIS); // enable internal tranceiver
Most designs involving pics use the internal transceiver. You can connect the pic to an external piece of hardware, or connect it directly to the chip itself. Here we assume that you’re doing the direct connection. USB has (as of version 2.0) three speeds – low speed (around 1Mbps), full speed (around 12Mbs) and high speed (around 480Mbps). The pics that we’re playing with only operate in the low/full range, meaning that we have to select between low and full speed.
set_bit(ucfg, FSEN); // clear for low speed, set for high speed
In all the examples here, we select full speed. There’s no reason not to, really, and as it happens, selecting full speed gives us many more choices for clock frequency selection. More about that in a later tutorial.
USB indicates its speed request by pull-ups on the data pins. The pic hardware can do this function for us (meaning we don’t need to include external pull-up resistors), providing we enable the on-chip pull-ups:
set_bit (ucfg, UPUEN); // enable on-chip pull-ups
The pic USB Serial Interface Engine (SIE) can double buffer transfers, meaning that one buffer is being used by the SIE while the other is available to the microcontroller. This means faster transfer at the cost of more complex code. In the initial PicPack library, we switch double buffering off, for the sake of simplicity:
clear_bit(ucfg, PPB1); // disable double buffering
clear_bit(ucfg, PPB0);
Finally, we set up end point 0 ready to receive and send control transfers:
set_bit(uep0, EPHSHK); // EP0 handshaking on
set_bit(uep0, EPOUTEN); // EP0 OUT enable
set_bit(uep0, EPINEN); // EP0 IN enable
clear_bit(uep0, EPCONDIS); // EP0 control transfers on (and IN and OUT)
Here we enable handshaking (required for control transfers – and all endpoints type except isochronous), enable OUT (from host) and IN (to host) transfers, and enable control transfers on this endpoint.
Due to a bug in the BoostC compiler that prevents compile time initialisation of structures that don’t point to char*, we need some run-time initialisation of this structure.
ep_out_bd_location[0] = &bd0out;
This continues for all buffer descriptors for IN and OUT endpoints.
In order to kick off interrupts for the USB module, in our main program we turn on interrupts from the USB module:
turn_usb_ints_on();
turn_global_ints_on();
Finally, to actually start the USB module doing its things, we call usb_enable_module().
void usb_enable_module() {
uir = 0;
set_bit(ucon, USBEN); // enable USB serial interface engine (SIE)
usb_state = st_DEFAULT;
}
Here we clear any previously triggered interrupts, enable the USB SIE module and set our state to Default.
It’s at this point that all the fun starts. The SIE now attaches to the bus by pulling one of the data lines high, and negotiations begin.
9 January 2009
USB Part 3 – 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 (unfortunately) 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 Joy 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.
// #define USB_SELF_POWERED
#define USB_BUS_POWERED
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_SIZE 8
#define USB_EP0_OUT_ADDR 0x0500
#define USB_EP0_IN_SIZE 8
#define USB_EP0_IN_ADDR 0x0508
#define USB_EP1_IN_SIZE 8
#define USB_EP1_IN_ADDR 0x0510
Here it starts to get a little more complex. 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. 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.
//#define USB_CALLBACK_ON_SOF
// 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.
//#define USB_CALLBACK_ON_DEVICE_CONFIGURED
// if you define it, you'll need to include this routine in your code:
// void usb_device_configured_callback() {
// }
USB has a state called “configured”. It means that the host has chosen a particular configuration (usually it has a choice of just one) 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.
#define USB_CALLBACK_ON_CLASS_CTRL
// 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 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 Joy 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 serial (CDC) PicPack demo 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.
//#define USB_EP_DATA_CALLBACK
// 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 Joy Mouse demo doesn’t use either of these functions.
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 (unfortunately) 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 Joy 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.
// #define USB_SELF_POWERED
#define USB_BUS_POWERED
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_SIZE 8
#define USB_EP0_OUT_ADDR 0x0500
#define USB_EP0_IN_SIZE 8
#define USB_EP0_IN_ADDR 0x0508
#define USB_EP1_IN_SIZE 8
#define USB_EP1_IN_ADDR 0x0510
Here it starts to get a little more complex. 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. 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.
//#define USB_CALLBACK_ON_SOF
// 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.
//#define USB_CALLBACK_ON_DEVICE_CONFIGURED
// if you define it, you'll need to include this routine in your code:
// void usb_device_configured_callback() {
// }
USB has a state called “configured”. It means that the host has chosen a particular configuration (usually it has a choice of just one) 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.
#define USB_CALLBACK_ON_CLASS_CTRL
// 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 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 Joy 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 serial (CDC) PicPack demo 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.
//#define USB_EP_DATA_CALLBACK
// 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 Joy Mouse demo doesn’t use either of these functions.
Subscribe to:
Posts (Atom)