In my last USB video, I told you how the USB protocol worked and how you could use a special ATmega16u2 microcontroller to communicate over USB, to act as a keyboard. And while this is perphaps one of the better ways to utilize USB, since the small details are taken care of by hardware, there is another way that involves no special hardware, but rather uses firmware to run the USB protocol. And that method is known as the V-USB library. You may decide to use a library like this on devices such as the ATmega328p or more famously, an Arduino UNO, since they don’t have the necessary USB hardware to communicate. There are a few limitations though, which I will talk about during the video. I will also be showing you how the HID protocol works. This is the protocol on top of USB that we will use to run the keyboard. Without any further introduction, let’s start the video.
As I’ve just told you in the introduction, V-USB is not a hardware controlled implementation, it is one that is run inside of the firmware. This does mean that any old AVR can use USB, but it does come with a cost. If you watched the first video, you will know just how involved the USB protocol is, and there are a lot of steps and abstraction layers. Luckily, V-USB handles this for you, but keep in mind it comes at the cost of a massive CPU load. This means there isn’t much time for any processing other than the USB. It is possible to circumvent this perhaps by having another microcontroller handle most of the processing and send it over to the USB microcontroller, but that’s beyond the scope of this video. What is in the scope of this video, though, is the HID protocol, how it works, and how we can implement it using V-USB. If you were inspired by my hardware USB video, you can still learn about the HID protocol, the ideas are the exact same.
Anyways, let’s setup the circuit that we will need to create an HID keyboard. I decided to skip the breadboard and solder the circuit together this time. What you will need is a few buttons, three leds, a few resistors, a 12 MHz crystal and most importantly, your microcontroller. The crystal is used by the V-USB library to correctly time the USB data transfer. You can pick another value crystal, but keep in mind that the clock options are rather strict: you can only use 12, 12.8, 15, 16, 16.5, 18, and 20 MHz clock sources, of which all are crystals except for the 12.8 and 16.5 options. The strictness is due to the need for the clock to be precise enough to operate on the USB protocol. You will also need a 3.3 volt regulator, since while the USB voltage line is 5 volts, the data lines run on 3.3 volts. Make it easy on yourself and just power the entire circuit with 3.3 volts to begin with, because the microcontroller is capable of running on just 3.3 volts. I used an AMS1117 linear regulator to do the job, but you can use any linear regulator that fits the specs here. This little IC was tricky to solder on since it was meant to be an SMD part, but it worked out in the end. Anyways, here is the full schematic for this circuit, it is also in the description if you would prefer to see it in that way. If you want to use even more keys, you will need to use a matrix, since there aren’t enough input pins.
Anyways, with that out of the way, let’s dive into the HID protocol. This is a protocol that is typically implemented over USB, but it has also proved to be protocol agnostic, with it also being used in places like bluetooth. We will be covering HID in a USB context though. Anways, HID communication is done using two distinct forms of information, report descriptors and reports. From a top-down perspective, report descriptors simply describe how the data will look inside of the reports. This idea of a descriptor is carried down from the USB protocol. Here is a hierarchy of descriptors in a USB device. The device descriptor and configuration descriptor both describe what exactly this device is, and the interface descriptor describes what a certain subset of this device will do and how many endpoints it needs. In our case, the interface descriptor will describe this interface as an HID device. Beyond this point, we get into the HID-specific territory. The HID descriptor tells us how many report descriptors and how many physical descriptors we will have. This video will focus on report descriptors, since physical descriptors are optional and only describe which parts of the human body will interact with the device.
Report descriptors really make up the bulk of the work when using the HID protocol, since there are a lot of options. There are three types of reports described by the descriptors, and they are input, output, and feature reports. Input reports are the ones in which data is sent to the host, such as the keyboard data. Inversely, output reports are where data is sent to the device, such as toggling the CAPS LOCK LED. Feature reports are not intended for user interaction, but more for internal communcation. If you have noticed, these interactions seem familiar. And that is because they can also be somewhat represented by USB transfers, which I talked about in the last video. The HID specification even classes these as being synonymous.
Let’s take a moment to talk about the methods in which data can be transferred using USB. Each HID device can have bidirectional communication over endpoint 0. While this may be enough for a few applications, there is also another required USB endpoint, which is an interrupt IN endpoint. This will provide more bandwidth than just control transfers alone. If your device requires it, you can setup the optional interrupt OUT endpoint.
Great, but how can we determine what the data means, not just which direction. Well, for that let’s go back to the report descriptors. Like I said, they describe reports, similar to device and endpoint descriptors. There is a key difference in the layout of the data. In device and endpoint descriptors, the data is a fixed size table. Conversely, report descriptors are made of up what are called items. These items have no predefined structure, meaning that you can completely customize how the reports will look, at the cost of a little more complexity. Let’s say that we have an INPUT report planned, and we need to send over all of the control keys on a keyboard, those would be the control, alt, GUI, and shift keys. First we would need to tell the computer that we are sending those keys. And to do that we can use what us called a usage item. A usage describes what a particular set of data reprents, so in our case those control keys. We then add a few more items to format the data. We can use the logical min and logical max items to tell what the range of our data will be. So, since they are buttons, it will either be a 1 or a 0. The report size tag means how many bits each usage will use, and again since they are one-bit button presses, we will use just 1 bit. Since we have eight control keys, we will need a way to describe that too, so we will use the report count item and give it an eight. So we have just described eight bits of data that will represent our control keys.
But this is just one small portion of the entire descriptor, so let me show you the one that I have created. This chunk here is the one that I have just shown you. Anyways, let’s go from the top down. Starting at the top we have a usage page call. This is distinct from the usage tag since, it basically dictates what the usage means. There is a rather large document which I will link below that basically guides you in this. If we select the general usage page, we can select from this table of usages. And I choose the keyboard usage within that. The next item is the collection. This basically groups up different reports under one idea, which is our keyboard. Going inside of the collection, we will find another usage page, but this time its the keyboard table. This page essentially allows us to select the keys on the keyboard like described earlier. We already covered this section so let’s go to the next. It’s very similar but with a few key differences, we select the usage minimum to be the first key on the keyboard and the maximum to be the last key that we will use. We will also set the logical min and maxes to the minimum and maximum keycodes. This time, the keys will be arranged as one byte each, with a maximum of six being reported at once. We will use an array type input to return our maximum of six keys back to the computer. Finally, we have the output section, which is similar to the first one. We will use the LED page to select our Num Lock, Caps Lock, and Scroll Lock LED outputs. And finally, we end the collection. Well, now we have the human readable version, but we need to convert it to the one used by the protocol, and for that I’ve manually compiled mine. You can copy mine if you want, but if you do want to make your own simply cross reference what each item’s hex code is. For example, usage minimum is a hex 19.
And that is basically it for the HID protocol, it isn’t too bad. The hard part for us will be translating our VUSB code into the HID protocol. Let me tell you how to setup your own V-USB project. The first thing you need to do is download the V-USB library from the V-USB website, I will provide a download link in the description. From there, you should make a new folder and place and extract the V-USB library into that. Inside of the V-USB library look for a folder named usbdrv, this is what you will need to compile the V-USB library into your project. Move this folder into your project folder. Inside of the usbdrv folder, you will also find a file called usbconfig-prototype.h, copy this and place it into the project folder, also rename it to just usbconfig.h. Now let’s edit this usbconfig.h file. The first few things you will need to edit are the placements of the USB datalines. I put mine on PORTD on pins 0 and 2. Going further down, look for this line, and change it to a 1. It basically will activate our required interrupt in endpoint. Next, you will have to jump further down to the device descriptor section. If you have your own vendor id, replace it here, otherwise leave as is since obdev has kindly provided a free vendor id that we can use. You should change the device ID as necessary though, and I changed mine to the one defined as a keyboard. Next, change the vendor and device names. If you have one change the vendor name to your internet domain name. I changed mine to sinelab.net for example. The device name is up to you, but I named it as keyboard. And now for the final change in this file, the device and interface classes. Since we don’t want to define behavior at the device class level, we will put in a zero to indicate that we will be using an interface descriptor. Change the interface class to a hex 3, which stands for our HID class. Put in the size in bytes of your HID descriptor. And we are done with the usbconfig.h edits, let’s get to the fun part and write our code.
The first thing that you will need to do is include a few header files. Include the usbdrv.h, avr/io.h, avr/interruput.h, avr/pgmspace.h, and avr/wdt.h headers. Here is the bare minimum code flow that you need to include in your program, first enable the watchdog timer. This is basically used to reset the microcontroller incase there is ever a time where a bug occurs and the device can’t recover. From there you should do any device specific setup, such as pulling up your buttons. Then you need to intialize the V-USB library with the usbInit() function. Immediately after, enable interrupts. From there you should re-enummerate the USB device since the V-USB and the computer may not have the same address. To do that, simply disconnect the USB and then reconnect it after a short delay. Now you can get into your main loop. The two functions that you need in there are the watchdog reset and the usbPoll(). This is the basic structure of the program, but we are aiming to add keyboard functionality.
To do that we need a few more methods. Remember our USB interrupt IN endpoint, well we can use that to send our reports. For that we will use the usbInterruptIsReady() function to check that we are able to send data over the endpoint. If we can, then we will send our data buffer over using the usbSetInterrupt() function. The other code that I added was simply adding an idle rate that will either slow or speed up the rate of our key presses being sent. We have some other required methods that we need to define ourselves. First, we need to give the microcontroller our HID descriptor. Simply put it in as an array called usbHidReportDescriptor. We also need to define a method called usbFunctionSetup. This is basically what is called when our control endpoint needs attention. The main things we will do here are sending reports, just like the interrupt IN endpoint, and setting the idlerate. We can also redirect the output requests to another function by returning USB_NO_MSG when it is brought up. And to handle those output requests, we will define the usbFunctionWrite function to toggle our LEDs when something like CAPS LOCK is activated.
And that is basically it as far as it goes for the code, it is complicated at first, but you can definetly get used to this. If you got lost anywhere in my explaination, check out the link the description, it contains the entire project from the schematic all the way to the code. After this project, I have another small USB keyboard to play with. Anyways, if you enjoyed this video and found it helpful, please consider subscribing so that you can see my other videos. Have a good one.
Useful Links: V-USB Download: https://www.obdev.at/products/vusb/index.html Code and Schematic: https://sinelab.net/code/vusb-keyboard.zip USB Datasheet: https://sinelab.net/pdf/usb-20-specification.pdf HID Datasheet: https://sinelab.net/pdf/hid_spec.pdf Usage Tables: https://sinelab.net/pdf/hid_usage_tables.pdf Report Descriptor Checker: http://eleccelerator.com/usbdescreqparser/