USB HID keyboard with V-USB

There still seems to be a lot of traffic to my V-USB tutorials, so I thought I’d write a short follow-up post on USB keyboards. I already did a USB HID mouse post earlier, so you might want to check that out to understand a bit about HID descriptors and associated V-USB settings (in short, human interface devices send a binary descriptor to PC telling what kind of “reports” they send to the host on user activities).

As a basic setup, you’ll need a working V-USB circuit with one switch and one LED attached. Here, I’m using ATtiny2313 with the LED wired to PB0 and switch to PB1. The ATtiny is using 20 MHz crystal, so if you’re following my USB tutorial series and have that circuit at hand, remember to change that frequency in usbconfig.c before trying this out. Note the cool breadboard header I have, there will be more posts about that one to follow soon!

USB HID keyboard basics

A USB keyboard is a normal USB device, very much like a mouse, but it has an interrupt endpoint, which is used to send keyboard events to the host PC (instead of mouse movements). In this tutorial, I’m using a full “boot compliant HID” specification that can have up to six keys pressed down at a time. The bonus side is that these types of devices receive (at least on Windows, probably on Linux, not on OS X) keyboard LED state changes, so we can do cool things with our device, like I did in my USB password generator, where password generation is triggered by repeated CAPS LOCK presses.

To make the operating system recognize our device as HID keyboard, we need to make mostly the same changes to usbconfig.h as we did in my mouse tutorial. Here’s the overview of things you’ll probably need to change from my USB tutorial series:

#define USB_CFG_HAVE_INTRIN_ENDPOINT    1

#define USB_CFG_IMPLEMENT_FN_WRITE      1

#define USB_CFG_VENDOR_ID       0x42, 0x42
#define USB_CFG_DEVICE_ID       0x31, 0xe1

#define USB_CFG_DEVICE_CLASS        0
#define USB_CFG_DEVICE_SUBCLASS     0

#define USB_CFG_INTERFACE_CLASS     0x03 // HID
#define USB_CFG_INTERFACE_SUBCLASS  0x01 // Boot
#define USB_CFG_INTERFACE_PROTOCOL  0x01 // Keyboard

#define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH    63

On the main.c side, the keyboard descriptor is needed. It’s omitted here for brevity, see the project zip for details. What matters is, that the descriptor describes the following data structure that is used to send keypresses to PC:

typedef struct {
	uint8_t modifier;
	uint8_t reserved;
	uint8_t keycode[6];
} keyboard_report_t;

static keyboard_report_t keyboard_report; // sent to PC

Every time a new button is pressed, we send a report. We also need to send a report when keys are no longer pressed – so to send a single ‘a’, we first send a report containing that character, and then send a report with only zeroes to tell that letter has been released. Forget to do that, and the device floods your system with a’s – pretty hard to reflash! If you happen to make that mistake, here’s how to fix it:

  1. Unplug the device
  2. Fix the code
  3. Clear the command-line and prepare the ‘make flash’ command
  4. Plug the device in and immediately hit ‘enter’ before the PC has time to recognize the flooder
  5. Once the flashing starts, the device resets and will not send any offending letters

From the report structure you can also see that “modifier keys” such as control, alt and shift are sent separately from “normal keys” – the PC driver does the actual work so that when we send “shift” as a modifier and ‘a’ as a letter, ‘A’ will be output to any program currently active. The keycodes are also specific to the USB HID standard, for example ‘a’ is 4. You can find the full code set from “HID Usage tables” (google it or find it here), chapter 10, page 53.

Basic HID keyboard code

To pass as a functional keyboard, a few things need to be implemented in the firmware:

  1. Our device needs to be able to return the keyboard descriptor to PC when it is requested
  2. Repeat rate needs to be set and returned when asked
  3. The host can also ask the keyboard report via usbFunctionSetup, in which case we just send “no keys pressed”
  4. Also, to receive LED state changes, we need to be able to receive some data on. A custom usbFunctionWrite is used to do this.

Here’s the code to do that and keep track of LED state (and mirror the CAPS LOCK to a LED in PB0):

volatile static uchar LED_state = 0xff; // received from PC
static uchar idleRate; // repeat rate for keyboards

usbMsgLen_t usbFunctionSetup(uchar data[8]) {
    usbRequest_t *rq = (void *)data;

    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
        switch(rq->bRequest) {
        case USBRQ_HID_GET_REPORT: // send "no keys pressed" if asked here
            // wValue: ReportType (highbyte), ReportID (lowbyte)
            usbMsgPtr = (void *)&keyboard_report; // we only have this one
            keyboard_report.modifier = 0;
            keyboard_report.keycode[0] = 0;
            return sizeof(keyboard_report);
        case USBRQ_HID_SET_REPORT: // if wLength == 1, should be LED state
            return (rq->wLength.word == 1) ? USB_NO_MSG : 0;
        case USBRQ_HID_GET_IDLE: // send idle rate to PC as required by spec
            usbMsgPtr = &idleRate;
            return 1;
        case USBRQ_HID_SET_IDLE: // save idle rate as required by spec
            idleRate = rq->wValue.bytes[1];
            return 0;
        }
    }
    
    return 0; // by default don't return any data
}

#define NUM_LOCK 1
#define CAPS_LOCK 2
#define SCROLL_LOCK 4

usbMsgLen_t usbFunctionWrite(uint8_t * data, uchar len) {
    if (data[0] == LED_state)
        return 1;
    else
        LED_state = data[0];
	
    // LED state changed
    if(LED_state & CAPS_LOCK)
        PORTB |= 1 << PB0; // LED on
    else
        PORTB &= ~(1 << PB0); // LED off
	
    return 1; // Data read, not expecting more
}

Adding a switch to the mix

After fulfilling these basic requirements, the code in main method stays pretty much the same as in my V-USB tutorials. If all we wanted to do was to monitor CAPS LOCK, we’d be done by now, but let’s make the switch connected to PB1 send a letter “x” when it’s clicked. In my test circuit, I have enabled a pullup resistor at PB1 and wired to switch so that when it’s pressed, PB1 is connected to ground and PB1 bit goes from 1 to 0. So in an ideal world, the main loop would look like this (I’m using a really simple helper function buildReport() to fill keyboard_report, see the actual code for that):

while(1) {
    wdt_reset(); // keep the watchdog happy
    usbPoll();

    if(!(PINB & (1<<PB1))) // button pressed
        state = STATE_SEND_KEY;

    if(usbInterruptIsReady() && state != STATE_WAIT) {
        buildReport('x'); // fill keyboard_report with 'x' pressed
        usbSetInterrupt((void *)&keyboard_report, sizeof(keyboard_report));
        buildReport(NULL); // clear keyboard report
        usbSetInterrupt((void *)&keyboard_report, sizeof(keyboard_report));
        state = STATE_WAIT;
    }
}

Unfortunately, we don’t live in an ideal world. First of all, it takes some time after sending one report via the interrupt endpoint, so instead of two usbSetInterrupt() calls right after each other, we need to have a simple state machine inside the “if(ready)” code that first sends the key and changes state, then sends the “no keys pressed” message and changes the state. That’s rather easy to do:

if(usbInterruptIsReady() && state != STATE_WAIT && LED_state != 0xff){
    switch(state) {
    case STATE_SEND_KEY:
        buildReport('x');
        state = STATE_RELEASE_KEY; // release next
        break;
    case STATE_RELEASE_KEY:
        buildReport(NULL);
        state = STATE_WAIT; // go back to waiting
        break;
    }
}

Second problem arises from the fact that a physical switch does not toggle cleanly from 1 to 0, but flickers for several times every time when pressed/released. Therefore, the current code would probably send the ‘x’ a dozen times per switch toggle. An easy way to fix this is to have a simple release counter that counts the number of iterations since last switch release – only after enough time has elapsed, is a new event generated. Like this:

if(!(PINB & (1<<PB1))) { // button pressed
    // also check if some time has elapsed since last button press
    if(state == STATE_WAIT && button_release_counter == 255)
        state = STATE_SEND_KEY;
				
    button_release_counter = 0;
    // now button needs to be released for a while until retrigger
}
		
if(button_release_counter < 255) 
    button_release_counter++; // increase release counter

Now everything should work, more or less. For your enjoyment, I have compiled a nice little zip file also for this project, where you can find both main.c and usbconfig.h and a nice makefile to build and flash the project with a single invocation of make flash.

If you’ve done everything correctly, after flashing the MCU you should have device which toggles the LED in sync with your normal keyboard when you press CAPS LOCK on and off, and sends ‘x’ whenever you toggle the switch. This project is easily expandable to a data logger, full keyboard, or any cool project you can think of. Enjoy!

Published by

Joonas Pihlajamaa

Coding since 1990 in Basic, C/C++, Perl, Java, PHP, Ruby and Python, to name a few. Also interested in math, movies, anime, and the occasional slashdot now and then. Oh, and I also have a real life, but lets not talk about it!

35 thoughts on “USB HID keyboard with V-USB”

      1. I bread boarded the metaboard circuit. I use the pjrc teensy 2.0 to write to the 328p. I created the project in atmel studio v6. Lots of funky compiler errors that had to get fixed but finally got it compiled and it worked. Guess I could export the project and post it somewhere.

        1. Did you get errors that looked like this?
          usbdrv/usbdrv.h:455:6: error: variable ‘usbDescriptorDevice’ must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’

          That’s where I’m at right now.

          1. It seems never versions of avr-gcc don’t work that well with V-USB (or older versions of it?) and you need to add const to several places to get it to compile… Haven’t revisited these projects in a while so I don’t have first hand experience on the needed fix, and neither knowledge if V-USB guys have solved it in a newer version.

  1. Can i send some data to the avr and have it do some other thing? for example if i send data 0x01 0x03 0x04
    then it will switch a pin
    tq

    1. HID keyboards only receive keyboard LED state changes, so signalling back to device is hard. Please see my V-USB tutorial for device that could do more – you could also combine these two, so that once the device has sent the password, it waits for 30 seconds and then resets and shows itself as custom USB device, and you can then use the examples from V-USB tutorial to send any custom data to it.

      Hmm, actually now that I think more about that, V-USB probably doesn’t support two alternative descriptors out of the box, so you’d need to hack it a bit to achieve the suggested method. Another way would be to use caps lock and scroll lock to “morse” binary data to the device – if you can toggle those programmatically from windows, it might be rather straightforward.

      1. aha, morse code
        thank you sir
        nice idea :)
        that’s why when i debug it i only get 1 or 2 byte of data
        old fashion ways still do the job,hehehehe
        morse code, open up my scout guide book T_T

  2. Nice tutorials. I have learned a lot with them.
    I have a question which I could not find an answer. Do you know how to implement a descriptor that gives me a dual ‘box’ of X-Y axes?

    1. No, sorry. You’d probably need a custom descriptor, and you’ll need to dig into the USB HID spec yourself or find a good tutorial – I barely understood enough myself to copy-paste ready-made descriptor definitions to my projects. :)

  3. Hi, just build this project, but I attached the LED to PD0 and the switch to PD1.

    I had to make some variables “const” for use with PROGMEM.
    The switch is working fine, although I’d like to have that key repeated after short while.

    The status led doesn’t work, sadly.
    If I switch it on manually in the source code, it lights up when I plug the ┬ÁC in and is switched off after ~1 second.

    int main() {
    uchar i, button_release_counter = 0, state = STATE_WAIT;

    DDRD |= (1 << PD0); // PD0 as output
    PORTD |= (1 << PD1); // PD1 is input with internal pullup resistor activated
    PORTD |= 1 << PD0; // switch LED on manually
    ...
    }

    I have no idea what could be wrong, except:
    The avr-gcc isn’t very happy with some of the pointer related stuff

    main.c:74:23: warning: assignment makes integer from pointer without a cast [enabled by default]
    main.c:81:23: warning: assignment makes integer from pointer without a cast [enabled by default]
    main.c: In function 'main':
    main.c:173:5: warning: passing argument 1 of 'buildReport' makes integer from pointer without a cast [enabled by default]
    main.c:112:6: note: expected 'unsigned char' but argument is of type 'void *'

    1. Weird issue – if you make a piece of code with only those lines, does the LED stay on indefinitely? Is any other piece of code accessing PORTD that could interfere with the LED? If the standalone version works and nothing is messing with PD0, it sounds like either power issue or the device going haywire after ~1 second (in which case the keyboard functionality should also stop working).

  4. Do you know the way of detect more than 6 keys simultaneous??, I want use an atmega16 with vusb and have a lot of input keys that can be pressed simultaneously … I only see the way of send the report with status of 6 keys and no have idea how to inform to computer more keys :S

    1. Normal USB keyboard reports have a limitation of 6 simultaneous keys. I’ve understood there are options to expand it, but not sure if a driver is required (I think one of my own NKRO-capable keyboards has a separate mode for it, but Windows did recognize it automatically).

      I suggest you google for “USB HID over 6 keys” and similar ones, I’m sure this isn’t the first time this question is asked. :)

  5. Thanks for writing this awesome (and life saving) tutorial :)

    I fall into a situation to send multiple modifiers key at a time. Is it possible to send them while keeping the six keys at a time?

  6. Hi
    i’m new to this. I need to interface my mcu to pc via usb to act as hid keyboard and mouse, is there any ic or module that takes care of the interface protocol part?

    thanks

    1. The article you’re commenting on tells hwo to make a keyboard with only ATtiny, but some newer models have hardware support for much of the same (if you google around for avr and usb support, you’ll find those). Also, FTDI and other have various ICs that can handle similar tasks. You might also want to check out LUA project, it’s for AVR chips with USB support if I recall correctly.

  7. Can we make universal HID IO device that can set pins to hi/low or get pins state, even reading analog input?

    So then we can switch relay, drive motor, reading sensor, etc.

    I hope you can make tutorial about this :)

  8. This is awesome. Worked right away on an Attiny2313 and by now I’ve expanded it to 12 buttons, read in thru shift registers, further expansion pending.

    However with the original buildReport() function I believe only one keypress at a time can be transferred? I asume so because it always writes to keycode[0].

    I’m trying to get it to work to send multiple keys in parallel but I’m getting nowhere.

    I rewrote the buildReport to this:
    void buildReport(uchar keyCode, uchar offset) {
    keyboard_report.modifier = 0;
    keyboard_report.keycode[offset] = keyCode;
    }

    Note that keyCode has to be the usb key code (4 for ‘a’ etc).

    I was hope that someone might point me in the right direction how to send multiple keys with a single report?

    thanks a ton.

    1. I haven’t tried sending multiple keypresses, but note that sending ‘a’, ‘b’ and ‘c’ with single report is treated like user had simultaneously pressed all those keys down. It might be that because O/S doesn’t know if it was “abc”, “bac” or “cab”, the letters are discarded in text input type situations. This is pure speculation, though. :)

      1. Hi Joonas,

        I think I know what you mean. However I’m testing this as part of a custom game panel setup:

        For example ‘A’ moves a character left, ‘D’ moves it right.

        When I press both ‘A’ and ‘D’ on my normal (!) keyboard they are both registered and cancel each other out (left+right == stand still ;) )

        However when I press both buttons on my project controller it only registered the first key.

        This leads me to the conclusion that something is wrong with what I’m sending although I can’t see what the problem is, especially because the C64 keyboard project over at the VUSB website is using a similar approach to send multiple key presses, although the code is different.

        Oh well… guess I have to find myself an USB sniffer after all… :)

      2. consider this one closed, I had a floating input somewhere that messed things up. The code is working just fine.

        Once I got all parts done and assembled I might put a project up for the community projects list.

          1. Heh, thanks :). I know its always a bit frustrating to see the questions but not the *eureka, the sucker works* closures ;)

            I’m waiting for a few parts (namely, buttons… incredible how difficult good looking buttons with a reasonable price tag are to get).

            Might take me a bit to complete the whole thing since I have only the weekends to work on the projects but I will get back.

  9. Thanks for your excellent tutorials, VERY informative and well written !

    Just a note, with the latest version of VUSB, a small change should be done in code, by adding ‘const’ here :

    PROGMEM const char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH]

    I am compiling fine with the latest version, with only one warning (I don’t know how to fix it) :

    main.c:173: warning: passing argument 1 of ‘buildReport’ makes integer from pointer without a cast

    1. buildReport wants an uchar, that line seems to be buildReport(NULL) so the compiler is thinking (quite correctly) that NULL (probably of type void *, i.e. a void pointer) is being cast into an integer.

      NULL is (void *)0 so buildReport(0) should compile without issues. Using NULL is maybe not so elegant in any case.

    1. You can just try what happens when you send keycode for Q — does it show up as Q on screen or as an A. It might not need any changes, or then you can build a conversion table. If you are really making hardware buttons for certain keys, you’ll need to specify the code sent in any case, so I don’t really understand your problem.

      1. Thanks for reply.

        I have a french azerty keyboard and when i put the key in my computer it shows up q instead of a.

        the text “starting generation” i have to convert it in qwerty and it’s not easy with a french keyboard.

        Is there a part of the code to be modified ?

        is this part of the code make qwerty ?

        if (ascii >= ‘A’ && ascii <= 'Z')
        {
        keyboard_report.keycode[0] = 4 + ascii – 'A'; // set letter

        Regards

        1. Seems like the operating system does the conversion from keycodes sent, assuming that your device sends “qwerty” keycodes and converting them to azerty. You can check this hypothesis by switching for a second to qwerty layout and plugging in the device.

          The code you highlighted is the exact place where keycodes are calculated from ASCII values. You’d need some kind of conversion table (or just big if-set) to make the conversion. Quite painful, that’s true. :(

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload the CAPTCHA.