I have updated this article to use the Microchip C18 compiler and to be based around my latest USB framework libraries for both the PIC and Windows (2011-10-19). This should provide a smoother path from this basic tutorial to more complex projects. As always I welcome your feedback and suggestions over in my forums.
I get a number of emails every month asking about creating USB devices using the PIC18F microcontroller. After looking at projects such as my Atari Joystick USB Adaptor and C64 VICE Front-End there seems to be a demand for more information on how to 'hack your own'.
In this article I will show how to breadboard a simple USB generic HID device, creating the PIC18F firmware and finally creating the Windows interface for the device which will allow you to control a LED from the PC and read the state of a push-button from the device. Since the advent of Windows 7 you need an expensive Microsoft validation certification to create custom USB drivers (without it most users cannot even install your software). Using the built in drivers for generic HID devices provides a simple method of creating Windows and Linux compatible devices and also makes the creation of both firmware and software far simpler. Since the HID standard does not require custom drivers you will not need to get a certificate for your driver, also both Windows and Linux have built-in libraries to help you communicate.
For this article we're going to stick to a fairly basic USB device. The device will allow you to control a LED from Windows and also see the status of a push-switch on the device. Using this the basic principals of 2-way USB communication will be made clear allowing you to progress onto more complex projects.
To keep the cost and difficulty as low as possible I will concentrate on breadboard construction of the hardware using few components, the PIC18F firmware will be based on (the freely available) MPLAB and Microchip C18 compiler, the Windows software will be created using Microsoft Visual C# 2010 express (which is also free to download).
Although this article is based around the PIC18F4550 microcontroller you can easily substitute this for the smaller and cheaper PIC18F2550 which is code compatible with the larger 4550.
If you want to follow along with this article I suggest you scroll down to the bottom and download the accompanying software. Also make sure you have MPLAB, Microchip C18 for the PIC18F and Microsoft Visual Studio 2010 express installed.
Please note that all of the host screenshots are taken from a Windows 7 machine, if you need to find the same/similar thing on an older Windows box please head over to Google where you will find plenty of information about where the items are on your WindowsME machine.
To begin with we need to build a USB device to communicate with. In the following circuit diagram you can see the minimum configuration for a usable USB device. The device includes an ICSP header (In Circuit Serial Programming) and a USB type B connection. In addition there is a single LED and a single push switch to represent the input and output devices.
The circuit is very straight forward (if you are not familiar with this level of microcontroller electronics I suggest you go ahead and build some of the many flashing LED and push button tutorials available on the web before attempting this). The PIC18F4550 will be 'bus powered'; this means that the device will draw its power from the USB host (your PC) so no power regulation is required. The 470nF capacitor (C3) is required so the PIC can operate the internal USB circuitry (it helps with regulating the USB voltages required by the on-board USB interface in the PIC) - Note that C3 can be any value between 220nF and 470nF (the datasheet recommends 220nF however I had 470nF available, it doesn't make any odds to the functioning of the circuit.
The ICSP header allows you to connect a PIC programmer, I suggest using the inexpensive PICkit3 programmer, however other ICSP compatible programmers should work just fine. An external oscillator (crystal) is required for the PIC to be able to use the on-board USB module. In these examples (and the associated firmware) a 20Mhz crystal is used (this allows the PIC to use PLL which ups the clock speed to the required 48Mhz necessary for USB communication). You can use other crystal speeds, however I recommend you use 20 Mhz to begin with since otherwise you will need to alter the firmware.
The following picture shows the circuit constructed on a hobbyist breadboard. I've added some labels to the picture to make it clear which components go where. Please note that, for programming, we will be using the 5V supply from the programmer. Since this is a bus-powered USB device the 5V lines will be connected to the USB connector also. This means that if both the programmer and the USB cable are connected simultaneously there is the potential for the programmer to supply 5V to the USB host; this is not recommended by the USB standards. I've never seen a case where this matters (for experimentation purposes), but if you want, you can add a barrier diode to the USB connector to prevent this. In my projects I typically use a 1N5817 Schottky Barrier diode for this.
If you don't have a USB plug for your breadboard you can either simply make one using a small piece of stripboard (such as my simple adaptor in the picture above) or you can cut one end of a USB cable, strip back the wires and put them directly into the breadboard. I'd recommend taking the time to make an adaptor, it reduces the risk of wires coming loose when you are plugging the USB cable in and out of your PC.
All of the resistors used are small 1/4W resistors. For all other components (such as the capacitors) anything which is rated to handle 5Vs and above will work perfectly. Also take care when reading the schematic, not all components are represented in 'pin order'. Take note of the pin numbers when connecting everything together.
If you're unsure of the wiring for the USB cable the following picture shows the pinout for a USB socket (female B type) and the standard cable colour coding:
Once you've built the circuit above be sure to check the positive and negative power connections for any shorts (use a multimeter to check for continuity between the 5Vs and ground) before plugging the device in to your PC; You don't want to damage your computer. Always be sure to check things over before connecting either the USB cable or your programmer to avoid expensive repair bills!
In order to connect your USB device to the computer you will first need to write and compile some firmware for the PIC18F4550. Microchip (the manufacture of the PIC microcontroller) supply a freely downloadable USB stack just for this purpose. To make things easy I have written some simple firmware to drive the device, you can use this to get going and also as a basis for understanding how the firmware operates. Once you've got your first device running you will find it much easier to understand how you can adapt it for more complex applications.
The firmware has to perform two important tasks which are described below.
The first is USB device enumeration - this complex sounding feat is in fact the initial communication with the USB host (your PC) when the device tells the host what it is and how it wishes to communicate. USB communication is performed using 'endpoints' which send information either to the host or to the device. As well as setting up the communication channels the device must also pass its device name and two other important values: the VID and PID.
The VID is the Vendor ID and identifies the manufacturer of the device. To get your own VID you need to pay a thousand bucks or so to the USB standards body. In this example we will use Microchip's VID to save the expense. If you are serious about producing and selling devices you will need to register one of your own.
The PID is the Product ID. Together with the VID they form a unique identifier for your device. When your device first enumerates Windows will store the VID and PID combination for the device; this is true even if you use a generic driver like the HID since it cuts down on the amount of time Windows needs to get your device ready. This is important because, if you decide to change your device's enumeration information (add more endpoints, etc.), you will also need to at least change the PID before reconnecting otherwise you will get 'Device not started' errors even if you code is flawless (from experience I've noticed that Linux is not quite as fussy and tends not to complain if you keep the same VID/PID combination).
Communication with the host
The second important task the firmware performs is the actual communication between the host and the device. Each communication is identified by a 'command'. When using the generic HID standard the 'command' tells the host and the device how to interpret the information which is passed with the command. This information could be anything (they don't call it 'generic' for nothing!) and this is how you can achieve great flexibility in the tasks your devices perform.
Once your device is enumerated the host will periodically poll the device (this is always initiated by the host and not the device (although there are exceptions later when you get more deep into the communication protocols). On each poll the host can both send a command and data to the device as well as receive a command and data from the device.
The main part of the firmware which you should look into is the section which deals with the polling requests from the host and performs the necessary actions to make the device work.
Installing and building the firmware source code
The reference firmware is targeted for the Microchip C18 compiler (and will not work with other PIC compilers without alteration). The firmware also relies on the Microchip USB stack (which is linked in enabling the code to be easily updated as new versions of the stack are release).
To install the firmware you will need to create a directory in which to place both the library and the firmware. You then have to run the Microchip Application Library installer and instruct it to install into a directory called "Microchip Solutions v2011-07-14" under the directory you created in the last step. The firmware is only tested against version 2.9a of the stack (which is contained in the v2011-07-14 installer) and you can download the library installer here (note: Microchip have a habit of moving files around... if the link breaks, please let me know over in the forums!).
Next grab a copy of the firmware zip file (at the bottom of this article) and unzip it with the application library. If all goes well you should now have two sub-directories in the same directory called "Microchip Solutions v2011-07-14" and "WFF Generic HID Demo 3". Now double-click on the "WFF Generic HID Demo 3" directory and load the project file WFF_GenericHID_Demo_3.mcp.
From within MPLAB you should be able to compile the firmware and then program it onto the PIC in the usual manner.
If you alter the relative location of either the library or the firmware you will need to change the include path in the firmware project to tell MPLAB where to find the application library. You can do this in MPLAB by going Project -> Build Options Project -> Directories -> Include search path and changing the relative path to match your set-up.
Since the VID/PID and the rest of the enumeration information is already prepared you should start by performing a build-all on the project and then download the resulting firmware to your PIC18F. Of course, you will need a sane build environment for this to work but there are plenty of resources via Google if you are having problems with your environment. Try some simple examples to make sure everything is ok before reloading this project and trying again.
Note: I have had a few comments on this section which has highlighted some issues. Unfortunately I haven't found a good work-around yet but perhaps some information will help. Firstly when you install the application library from Microchip you have to be careful to make sure it is installed in a directory called 'Microchip Solutions v2011-07-14'. Although this is the default, when you specify another install location (from the Microchip installer) you have to respecify the directory name again.
Secondly, there is no simple method of using relative paths for files in MPLAB (which is surprising since the Microchip Application Library forces you to use them). Therefore when you first open the supplied project some files may be shown as 'missing' in the project view. To solve this right-click on the missing files in the project view and select the find file option. You will then need to locate the correct file within the application library structure (if you know a good way to solve this please let me know via the forums!).
Both of these issues are due to the restrictive nature of Microchip's Application Library licensing. I encourage you all to email Microchip's customer service and ask them why the Application Library is not published as open-source using an OSI recognised licence.
Understanding the firmware source code
The firmware provides 3 commands:
0x80 - Toggle the LED
0x81 - Read the push-switch status
0x82 - Read the LED status
The code which performs these commands is located in the main.c source file in the processUsbCommands() function. This function is responsible for determining the required command and then sending and receiving data as appropriate. This is pretty straightforward since the USB stack takes care of all the underlying complexity; take a quick look at the source code and you will see how simple this really is. The only extra check performed by the function is to see if the device is in a 'configured state'; this means that the device is connected to a host and enumeration has been successful.
The main function simply calls the USB stack to perform any low-level device tasks and then the processUsbCommands function over and over again. It is possible to do this either using interrupts or a while() loop; the definition in HardwareProfile.h controls how this happens.
To understand a little more about the enumeration process take a look at usb_descriptors.c and HardwareProfile.h which contain the information that is passed to the host when the device is first connected. In the source you will find the VID and PID information for the device as well as a series of configuration descriptors which explain to the host what type of interfaces the device has and the capabilities of the interfaces. The 'endpoints' are the connectors for the 'pipes' described earlier. There are also some strings which describe the manufacturer and the product textually. Windows usually uses these strings when naming USB devices.
Understanding the enumeration process and the descriptor formats is quite complex and is covered by the various USB specifications as well as a great book by Jan Axelson called 'USB Complete - Everything you need to develop custom USB peripherals' (ISBN 978-1931448086). If you are enjoying this article and want to get more serious about USB I would highly recommend getting a copy of the book, it certainly helped me when I was learning.
Overall the firmware is quite simple, all you need to get up and running communicating to and from the host is included. Obviously you can make this as complex as you like, but for the purposes of this article (getting you going with USB) there is plenty to experiment with.
Connecting the device
Once you have followed the steps above and downloaded the firmware to your USB device you are ready to get it connected to your PC. Since we are using the generic HID USB drivers there is nothing to install on the PC before connecting. Simply plug the USB cable into your device and then plug the other end of the USB cable into your PC.
Windows 7 should detect a new device and display the usual 'installing new hardware' notice. After a few seconds you should see the following dialogue window:
If you then navigate to your start menu and select 'devices and printers' (if you have an older version of Windows you need to look elsewhere in the control panel, but the result is just the same. All these screenshots are from Windows 7) you will see the new device displayed on the screen. You should see something like the following window:
That's it, your first USB device is enumerated and ready to go! Now we can move on to the host-side of the programming and looking at how you can communicate with your device using Microsoft Visual C# 2010. Note: The default status of the LED is on, it should light up shortly after you connect the device.
The host software is based on my open-source Generic HID library. You can find full details about it and how it works here.
The host software is fairly straight-forward and basically consists of 3 parts all contained within the Form1.h file:
Monitoring the USB device to ensure it is connected (and disabling user input and device communication if it's not)
Displaying and processing the user interface form to allow the user to interact with the application
Communicating with the USB device and updating the device status
To run the host software unzip the Visual Studio 2010 express zip file and load the project into Visual Studio. Once you compile and run the code you should then be presented with the following dialogue:
To test the device detection simply unplug the USB cable from your PC. The dialogue should change to the following:
Now reconnect the USB device, wait until the status label updates (and says attached), now try clicking on the 'Toggle LED' button. You should see the LED on the breadboard turning on and off... cool huh? :)
Next make sure that the LED status label in the window matches the actual LED status. This is the 0x82 command in the firmware in action. Finally try pressing the push-button on the breadboard, you should see the push button status change accordingly in the window.
Congratulations, you are now the proud owner of your first self-made USB device!
I've included the full source code in the Visual Studio zip file, so you should be able to view the project in Visual Studio to get a better understanding of how it works.
Where to next?
The USB generic HID interface is one of many available interface types in the USB standard (although it is arguably the most useful to PIC developers). Using exactly the same techniques shown in this article you can build data-loggers, robotics interfaces, custom interface hardware, etc. etc. The list is endless.
Furthermore, if you would like to continue to experiment but would like a more powerful reference hardware environment please check out my PIC USB Development Board which allows you to easily experiment with many more USB designs and interfaces.
You are welcome to leave questions and feedback on this article in the forums.