The USB consortium claims that there are about 2 billion USB devices in the world. I think they’re wrong. I think it’s easily ten times that. It is truly one of the few universal success stories for interconnection between devices and computers.
When you have a look at the USB specification however, and its implementation for particular devices, you don’t have to read too much to realise that the specification has an extremely confusing nomenclature and even the standard “class” definitions work in different ways to each other. The other practical aspect that makes USB work difficult is that because everything happens in real time and we’re utilising fundamentally slow devices. So in order to make things happen quickly, everything is set up in advance, and then we’re told about the result after the fact. For example, we don’t get a request for some data, then make a call somehow to make that data available to the host. In fact, what happens is that we make the data available in advance of the host requesting it, and we find out afterwards that it grabbed it.
This means that USB debugging is a pretty tricky business. I don’t mean to dissuade you from having a go – the examples in the PicPack library work should make it pretty easy for you to get up and going pretty quickly, especially if all you need is some sort of serial connection. A lot of work has gone into the USB portion of the PicPack library to get things working and make it easy for you to incorporate USB functionality into your products.
But first, we need to understand a little about USB.
I’m not going to go into a full explanation here – there’s tonnes of information on the web and if you’re even vaguely serious about the topic, grab yourself a copy of Jan Axelson’s USB Complete book. It’s one of the best computer books I’ve ever bought. What I am going to do here is to cover the basics so you have enough information to work with.
First of all, the way that the USB system works entails the PC (or host) making requests, and the devices giving responses. Even when devices have something they need to send to the host, the host has to ask for it first. In this way, all the devices can share a common bus – they only speak when spoken to.
If you’re familiar at all with TCP/IP you’ll know that a device has an IP address (eg 192.168.1.3) and address are allocated (like DCHP) to each device. Given that a PC and its devices is a closed system, addresses start at 1 and go up to 127. This is the device number.
In TCP/IP, and each address is broken up into a number of “ports”. In USB land, this concept of ports is called “endpoints”, Endpoint 0 is a special endpoint; its purpose is (generally) to exchange information about the device itself. Other endpoints have purposes based on the device functionality. An endpoint can receive data from the host (called an OUT endpoint) or send data to the host (called an IN endpoint). These “in” and “out” definitions are named from the perspective of the host.
Endpoint 0 is a bidirectional pipe and must exist for every USB device. There can be up to 16 endpoints, but generally only a couple beyond endpoint 0 are ever actually used.
The USB system, like many systems that are designed by committee, covers many possibilities that are not often used in real life. Each device can have a number of different “configurations” of which the host can chose which one to use. While the information supplied as part of the configuration is important, I can’t think of many devices that have multiple configurations (a mobile phone may well use this if it can be a mass storage device or a modem depending on what is requested).
A given configuration can have a number of “interfaces”. In the USB serial port emulator for example, there is an interface that handles the control of the port itself (ie, baud rate, stop bits etc) and an interface for the actual exchange of RS-232 data. Each of these interfaces are associated with one or more of the endpoints available.
In summary – a device has (usually) one configuration, which has at least one interface, which has associated with it one or more endpoints.
When a USB device is first plugged in, the host and the device go through a process known as “enumeration”. This is how the host finds out about the capabilities of the device and loads whatever drivers are required; the device can also find out how the host wants to handle things. Enumeration takes place using what are called “control transfers”. These are the most complex aspect of anything to do with USB programming. Thankfully, the PicPack library handles the difficult parts of this for you.
Enumeration involves the delivery to the host of data structures that describe what a device is and how it works. These structures are called “descriptors”. You’ll find descriptors for the device as a whole, the configuration(s), interfaces and the end points themselves. Depending on the particular device, there are often extra descriptors that are specific to a particular class of device (eg, a serial port emulation, or a human interface device).
Like most of the PicPack components, pic_usb does the hard work of handling the common cases, leaving you to implement the logic that makes your device different from everything else. In the next tutorial, we’ll look at how the library works.