One of the relatively unexplored topics in this week’s lecture wasUSB, the ubiquitous protocol that allows computers to communicate withperipheral devices (containing microcontrollers). Creating a USBdevice allows any computer to talk to it without the specializedsoftware and hardware we’ve been using so far. For these two weeks, Idecided to understand the requirements to create USB-compatible devicecircuits and write the necessary software for a computer to talk tothem.
This is an epically long post about creating a USB device from scratch and programming it.
In the previous section, programming the board and restarting it made it disappear from the USB bus, and that’s because it no longer acts like a USB device when busy blinking the LED. For the board to appear as a USB device, it must use the USB protocol to be detected by and communicate with the computer. This is where the USB hardware comes in. For a system that has an embedded controller, load the Microsoft provided in-box driver, UcmUcsi.sys, that implements USB Type-C Connector System Software Interface (UCSI) Specification. Get a USB device for which you will be writing the client driver. In most cases, you are provided with a USB device and its hardware specification. The specification describes device capabilities and the supported vendor commands. Use the specification to determine the functionality of the USB driver and the related design decisions.
Making a USB device requires programming a microcontroller that cantalk to a computer with the USB protocol. So far in the class, we’vebeen writing microcontroller code that communicates with variousprotocols. It’s also possible to do this with USB, and theV-USB library implements this. However, having themicrocontroller emulate USB in software has several drawbacks,including that it’s slow, and that the microcontroller will need todevote some amount of time to USB communication.
On the other hand, microcontrollers with hardware USB supportoffload the necessary computations to dedicated circuitry. In Atmel’sAVR line, Mega and XMega devices with the
U suffix have hardware USBsupport. In order to write microcontroller code to interact with theUSB hardware, there are helper libraries such as Atmel’sUSB stack and LUFA. I’ll be exploring LUFA asit’s open source, is well documented, and has many examples.
In the class inventory, we have a couple of devices with hardware USBsupport: the ATmega16U2 and the ATXMEGA16A4U. Because LUFAsupport for the XMEGA AVRs is currentlynot well-supported, I’ve opted to go with the ATmegachip.
Given that we’ll use the ATMega16U2, what’s next? Well, it turns outthat there aren’t many examples of USB device circuits formicrocontrollers floating around online. Even the datasheet isn’t veryspecific about examples of application circuits. However, after a longsearch, I was happy to discover that the Arduino Uno revision 3 usesan ATMega16U2 chip as a USB to serial converter for the actualATmega328p being programmed. Its schematic was very helpfulas an example to build a basic circuit.
From various datasheets and other examples, I have found severalthings to be aware of when designing these circuits:
In order to use Full-speed (12 Mbit/s) USB, the microcontrollerneeds to be able to generate a precize 48 MHz clock with a deviationof no more than 0.25%. Since resonators have 0.5% tolerance, Thismeans only quartz crystals can be used, and moreover they need toevenly divide into this frequency in order for aphase-locked loop to generate this clock. For the ATMega16U2,this will require a 8MHz or 16MHz crystal - no substitutes.
Different quartz crystals at the same frequency are notinterchangeable; they are specced at a fixedload capacitance that must be matched by the circuit forthe specified accuracy. If this capacitance is far off, the crystalcan be slower or faster than intended.
The USB signaling pins require resistors forimpedance matching to optimize signal quality. Thedatasheet shows some unspecified resistors here, and I discoveredthe reason by askingthis Electronics StackExchange question.
After the due diligence on the circuit engineering, I was finallyready to design the schematic. Because I was using some non-standardparts here, I found the Ladyada andSparkfun Eagle libraries to be useful. In the circuitbelow, I’m using a 16MHz crystal in a HC-49 surface mount package, anATmega32U2 - pin compatible with the ATmega16U2, and a USB A connectordirectly on the PCB.
The Eagle libraries also include supply parts which make it easierto create
GND connections by dropping parts instead ofnaming nets. I strongly recommend this as a workflow! Note alsothe impedance matching signaling resistors, and the capacitors pairedwith the crystal, the values of which I borrowed from the Uno rev. 3because of its similar configuration.
A power LED automatically lights up when the board is powered - a nicefeature to have. The RGB LED in the circuit is attached to hardwarePWM pins, so that it can be controlled with full color by setting thecorresponding PWM rates.
The mega16U2 comes in a TQFP32 package, similar to themega328P. Compared with the pins on the tiny44 and tiny45, they aremuch closer together and smaller. In order to mill traces for them,one will either need to use a 10 mil end mill, or edit the pads toallow for more space with them. Given the overall stress caused to theclass from 10 mil end mills, I decided to edit the pads to so that Icould mill the board with the normal 1/64” end mill. Note that thismakes the pads narrower than the pins, so soldering will be harder.
To change the pad size, you can just open the library in Eagle and usethe following menu. Here, I’ve changed the TQFP32 package’s pads from
0.16 x 0.05 to
0.14 x 0.06 - slightly thinner, but longer. Inrouting the board, I used 0.14” routes when connecting to all of thepads, and widened them to the 0.16” routes as soon as they left thevicinity of the microcontroller.
This is the board, all laid out. My original design had many more LEDsand switches, but it turned out I had bit off much more than I couldchew - or lay out on a single-sided board. I removed many of the extraparts, and settled on three switches and one RGB LED. After many hoursof routing, I came up with this compact, symmetric design using theintegrated USB connector. It’s a pity that the mega16U2 doesn’t haveany ADC hardware, or I would have added several sensors to read latervia USB.
Here’s the result of milling the board. Note that there are sometraces pulling off in the center of the board, which is less thanideal. This board was exported at 1200 DPI, but that still wasn’tenough for the very fine detail in the middle - the microcontrollerpads and traces should only be slightly narrower than normal, but theyappear to be significantly narrower in some spots. If I were to dothis again, I’d export at an even higher resolution.
However, nothing essential has been damaged, and hopefully some solderwill hold it all in place.
Although I used 6 offset paths instead of the normal 4, there arestill copper traces around the edge of the board. This is a problemaround the USB connector, where we don’t want to be shorting randompins to each other. I shaved these unwanted copper parts off with anX-acto knife.
I tested the board out in a USB socket and read the pin voltages, justto make sure the connector will work:
Soldering really small packages to a board is incredibly difficult,and one of the main reasons is the initial positioning of the IC. Ididn’t want to use an initial solder joint to position the chip,because this risked damaging the delicate traces and it is very hardto position with tweezers in the first place. Nate Melenbrinksuggested putting tape down over half of the chip to position it. Thisactually worked really well, because the length of the tape creates an“arm” that makes it much easier to make small adjustments, and it canbe pressed down to hold the part in place. Masking tape works well forthis, and leaves no residue.
The traces on this chip are much too small to solderindividually. Just do them all in a row, and use a wick to soak up anyexcess solder.
At the same time, I’ve tinned the USB connection pins. This serves twopurposes. First, it protects the copper traces from oxidation, so thatthe pins don’t develop a coating that inhibits electricalcontact. Second, it thickens up the contacts a bit so that they matemore tightly with the USB socket.
Note that the schematic called for 22pF capacitors between the crystalterminals and ground, and 22 Ohm resistors on the signaling pins. Wedon’t have those in the inventory, so I approximated by soldering two10pF capacitors on top of each other for the crystal, and two 49.9 Ohmresistors on top of each other for the signaling pins. The finishedboard is below:
The power LED turns on when I plug it into the USB port, and there’sno smoke or heating. That’s a good sign…
The best sign that things are working is that the board shows upimmediately in the Windows Device Manager as a USB device called
Note that it hasn’t even been programmed yet! However, this isbecause all AVR chips with hardware USB come with a DFU (DeviceFirmware Updater) bootloader that allows them to be programmeddirectly from the USB port without needing an ISP or otherprogrammer. The DFU bootloader presents itself as a USB device thatcan be recognized by a computer.
This means that the microcontroller is able to talk to the computerand identify itself, and hopefully we are in a good position toprogram the board.
The DFU bootloader (and any bootloader in general) seems ratherconvenient for uploading code without needing an ISP. Atmel providesthe FLIP program for communicating with DFU chips, but thatlooks like it hasn’t been updated for a while. There’s also andfu-programmer, an open source project for doingthis and the analogue to avrdude for uploading over DFU.
One thing to note is that the fuses cannot be changed over the DFUprotocol; only the flash and EEPROM can. Luckily, all AVR chips withhardware USB have fuses programmed by default to use the externalcrystal oscillator as a clock, which will work immediately as long asthe crystal has a 8 MHz or 16 MHz frequency with correctly pairedcapacitors.
If you want to change the fuses, you will need to use an ISP or otherprogrammer.http://www.engbedded.com/fusecalc/is a useful website if you want to figure out fuse settings for AVRchips that you are unfamiliar with.
Note: the datasheet incorrectly specifies that the fuses are configured by default to use the internal 8MHz RC oscillator with
CKDIV8 set, so that the system clock runs 1 MHz by default. This turns out to be incorrect, and in fact the DFU bootloader would not be able to communicate over USB to the computer if this was the case. See this thread for more information.
dfu-programmer is pretty straightforward. Onwindows, it also comes with a
libusb-win32 driver so that it cantalk to the DFU bootloader over USB. Install this driver via theDevice Manager:
The board then shows up as
ATmega16U2 under libusb-win32 deviceswhen the bootloader is running.
Be sure to check out the dfu-programmer documentationbefore forging ahead with programming over USB. One important note isthat the entire chip has to be erased before you can run othercommands on it. The following command does this and clears the lockbits in flash memory:
Now, the board can be programmed. As usually recommended; I’ll startwith something simple: blinking LEDs. Here’s my
Makefile forprogramming and erasing the board over DFU:
Note that the compilation toolchain is basically the same, but the hexfile is uploaded with
dfu-programmer instead of
avrdude. After thefirst program, the flash needs to be erased before being programmedagain, so the
erase-dfu target accomplishes that. If all is working,programming is pretty straightforward:
At this point, the programming appears to be successful, but the LEDis not blinking and the device still shows up with the DFUdriver. However, there’s no need to panic here; this is because thebootloader is still running and it hasn’t changed to the program codeyet. To run the uploaded program, one can just do
dfu-programmer(target) reset or pull out the board and plug it back in.
As a result, we have a nice way to program a board directly over USB,with no other tools required. This process is basically what theArduino does, but without the nice GUI and tooling. Below, the LED isblinking after being programmed right from a USB port on mydesktop. Look ma, no ISP!
What if we want to program the board again after this? Well, when theblinking program is running, the board is not communicating over USBat all, so the system sees no device and therefore cannot program theboard. We need some way to re-activate the bootloader to program theboard. The datasheet lists three ways to do this, in Section 23.6:
HWBEprogrammed, and the
PD7pin pulled low. This just temporarilyenters the bootloader.
HWBE fuse is programmed by default to make this mode ofresetting the board easy. The rightmost button on the board connects
GND, so it can be pressed during reset to trigger thiscondition. Resetting the board is as easy as briefly touching the
GND pins together, which are adjacent on the ISPheader. Touching these two turns off the LEDs and the board shows upas a DFU device again. Now, it’s ready to accept another program.
(In my case, I didn’t actually need to press the button while doingthis. The Internet suggests that the board will always start in DFUmode if reset manually with the
HWBE fuse programmed.)
In the previous section, programming the board and restarting it madeit disappear from the USB bus, and that’s because it no longer actslike a USB device when busy blinking the LED. For the board to appearas a USB device, it must use the USB protocol to be detected by andcommunicate with the computer. This is where the USB hardware comesin. It’s possible to write low-level code to interact directly withthe USB hardware, but libraries like LUFA simplify this processsignificantly.
At a high level, LUFA provides a set of libraries for writing AVRmicrocontroller code, implementing USB devices. It does not includeany host-side (computer) code. There are manyexample applications for LUFA which can be compiledon to many common demonstration and breakout boards. LUFA focuses on amodular approach to this software, and comprises the following partsat a high level:
A completely custom LUFA application would use the low-level modulesto interact directly with hardware, and a custom driver on the host tocommunicate with the device. You can find a useful (albeit somewhatoutdated) guide for this here.
To start with something easier for this assignment, I’m going tomodify the LUFA keyboard demo to work with the board I just created,and write custom board drivers. The goal is to turn this device into a(somewhat limited) HID-compliant USB keyboard that can be plugged intoany computer and immediately recognized without the need to installdrivers.
The project files are arranged as follows. I’ll go over the differentparts below.
This Makefile uses the LUFA build system, which requires somevariables to be set properly but otherwise handles both the buildingand uploading.
A few things to note for the
MCUvariable is used in
avr-gccfor compilation, and must beset to the right microcontroller model.
BOARDvariable specifies which board driver to use. LUFA comeswith board drivers for many common boards, but I’ve specified the
USERvalue here, which indicates that I’ll be writing custom boarddrivers and that they will be found in the
F_CPUis the clock frequency, and needs to be set to the samevalue as the installed crystal for USB communication to workproperly.
TARGETis the main file to be compiled.
LUFA_PATHindicates the relative path to the LUFA library (which Icloned from GitHub on to my computer).
Board drivers allow different LUFA modules to be compiled tomicrocontrollers on different boards while abstracting away theirimplementation. LUFA contains provisions for boards with buttons,LEDs, DataFlash ICs, and a joystick. My board only has three buttonsand three LEDs, so I’ve implemented those here.
The button driver requires three functions:
Buttons_GetStatus, which are prettyself-explanatory as commented below. My buttons are connected to pins1, 4, and 7 on port D, so the implementation is straightforward.
The LED driver is a bit more complicated, because I’ve hooked up thepins of the RGB LED on different ports (the pins with PWM capabilityfrom the 16-bit counter). The LED code is shown below. Note that theRGB LED has a common anode, so the LEDs are turned on by driving theirrespective pins low. This means that the binary operations areflipped:
PORT = PIN becomes
PORT &= ~PIN and vice versa. Thesemacro definitions look confusing, but they will come in very handylater.
Note that I could not have implemented things as below if different LEDs were on the same pin number of different ports, due to the way the mask is used to select LED pins.
Finally, we are ready to implement the keyboard itself. Most of mycode is similar to the existing demo, but I’ll highlight the changesbelow.
First up is to define masks in
Keyboard.h to use the drivers wewrote earlier. LEDs 1, 2, and 3 correspond to red, green, and bluerespectively. The code below will defines the LED color to be red,yellow, and green as the device is powered on, enumerating, and ready,and purple if there is an error.
The main function calls some functions to set up the hardware andenable interrupts that call various functions we define—this is howthe LUFA library interacts with our code. The loop that follows justalternates between receiving data from the host and sending data back.
The initialization code, which calls initializationfunctions in the libraries we wrote earlier, and in the LUFA USBdevice driver. It also disables the clock prescaler, but we have to dothis manually because there is a bug where
clock_prescale_setfunction is undefined for certain microcontrollers in
The LUFA library allows various handlers to be defined that aretriggered by interrupts. The following code uses the LED masks we setearlier to set the LED color display as different USB events aretriggered. During a normal startup sequence, the board should displayred, yellow, and finally green. If there is an error, it will bepurple.
Finally, the two most interesting parts. The HID (Human InterfaceDevice) protocol requires that a device regularly report state to thehost. For the keyboard, this is simply which keys are beingpressed. The following function reads the state of the three buttons,using the
Buttons_GetStatus() function from before, and reports themas capital
C keys respectively if they are beingpressed. Basically, this is a keyboard with just three keys.
We can make the LEDs do something as well. Since there are three ofthem, the natural thing would be to use them to display the state ofNum Lock, Caps Lock, and Scroll Lock respectively. Adapting from thedemo, the code for this is pretty straightforward:
That was a lot of stuff, but if all goes well, we should have alimited functionality keyboard which is able to type three letters,and display the system lock key states!
Programming the device with LUFA’s build system is pretty easy. With
dfu-programmer installed, and the board in DFU mode, it’s just:
After the compilation is done, we see the following:
This is really nice! It erases the flash, writes the program, thenresets the board to run the newly flashed program—so it’s just onecommand to get from code to running on the board. (To program theboard again, we do have to manually reset it into DFU mode, asmentioned earlier.)
Note that the bootloader takes up 4K of the ATmega16U2’s 16K flashmemory. If a bigger program is needed, it would be time to switch toan ISP.
The first time I programmed the board, I saw the following dreadedmessage. It turns out this was a result of setting the wrong
F_CPUvalue, due to the datasheet error that caused me to misunderstand thedefault clock settings on the microcontroller. A correct value of 16MHz, and this error went away.
This has been a long post. It was quite a lot of work to create a USBdevice from scratch, but very satisfying to figure out all the quirksto make it work. The following video demonstrates how the USB boarddisplays the state of the system’s lock keys as red, green, and blue,and also using the three buttons for input. Note that when the deviceis first plugged in, it quickly flashes from red to yellow to greenbefore displaying the lock key states. When multiple lock keys are on,the LED produces additive colors.