Posts

Lightweight USB host. Part 6 – introduction to HID.

MAX3421E on a keyboard

MAX3421E on a keyboard

Human interface device AKA HID is likely the most simple class for USB host to interact with. Reports are easy to parse and timing is not critical. In addition to that, HID standard defines simplified variety of the protocol, called “boot protocol”.

Modern USB keyboards, as well as mice, support boot protocol. It was designed to be used by PC BIOS during POST setup. No report parsing is necessary; as soon as device is put into configured state, it’s interrupt IN endpoint starts generating fixed-format 8-byte packet. This packet contains basic information about peripheral events. For keyboard, such packet contains information about modifier keys (control, shift, alt ) plus keys pressed. Mouse would give state of up to three buttons, plus amount of travel in X and Y direction since last poll.

Look at the code, function HIDMprobe or HIDKprobe. This function is called during enumeration to configure device which was just attached. By default, HID peripheral is configured to talk report protocol. In order to change it to boot protocol, host has to send “Set protocol” request with single data byte set to 0. After this is done, device starts generating interrupts( i.e., host can start periodically polling interrupt endpoint for new data.

It is worth mentioning that not all HID devices support boot protocol. Interface subclass set to 1 indicates that boot protocol is supported for this interface.

The first byte in 8-byte packet from a keyboard contains states of modifier keys – Control, Shift, Alt, and Windows. The next byte is reserved, it’s state means nothing to us. Last 6 bytes is a keyboard buffer – it contains so-called HID codes of keys being pressed since last poll.

/* boot keyboard report */
typedef struct {
    struct {
        unsigned LCtrl:1;
        unsigned LShift:1;
        unsigned LAlt:1;
        unsigned LWin:1;
        /**/
        unsigned RCtrl:1;
        unsigned RShift:1;
        unsigned RAlt:1;
        unsigned RWin:1;
        } mod;
    BYTE reserved;
    BYTE keycode[ 6 ];
} BOOT_KBD_REPORT;

The keyboard interrupt endpoint works the following way: all polls between endpoint’s polling interval will receive a NAK. Polls at polling interval or longer will receive current state of the keyboard, even if nothing has changed since last poll. Thus the basic sequence of servicing HID keyboard is to get a packet, compare it with contents of the previous one and translate all newly received HID codes to ASCII, taking state of modifier keys into account.

The testKbd() function in cli.c demonstrates this. First of all, it requests idle rate and protocol for us to see. Then it starts polling the keyboard (the poll function is part of HID.c). When it gets the packet, each non-zero byte is checked against last packet buffer. Codes not present in the previous packet are converted to ASCII and printed. After all that, packet just received is saved in order to have something to compare with the next packet. Here is the code. Note that there is a rare case when 2 keyboards a involved – one is connected to MCU, another – to a PC with terminal emulator.

/* keyboard communication demo              */
void testKbd( BYTE addr )
{
  char i;
  BYTE rcode;
  char tmpbyte;
  BOOT_KBD_REPORT kbdbuf;
  BOOT_KBD_REPORT localbuf;
 
    rcode = XferGetIdle( addr, 0, hid_device.interface, 0, &tmpbyte );
    if( rcode ) {   //error handling
        send_string("\r\nGetIdle Error. Error code ");
        send_hexbyte( rcode );
    }
    else {
        send_string("\r\nUpdate rate: ");
        send_decword( tmpbyte );
    }
    send_string("\r\nProtocol: ");
    rcode = XferGetProto( addr, 0, hid_device.interface, &tmpbyte );
    if( rcode ) {   //error handling
        send_string("\r\nGetProto Error. Error code ");
        send_hexbyte( rcode );
    }
    else {
        send_decword( tmpbyte );
        send_string( crlf );
    }
    send_string("\r\nType something on PIC keyboard. Press any key on PC keyboard to stop.\r\n");
    /* Polling interrupt endpoint */
    while( !CharInQueue() ) {
        rcode = kbdPoll( &kbdbuf );
        if( rcode == hrNAK ) {          //NAK means no new data
            continue;
        }
        for( i = 0; i < 6; i++ ) {
            if( kbdbuf.keycode[ i ] == 0 ) {        //empty position means it and all subsequent positions are empty
                break;
            }
            if( prevCodeComp( kbdbuf.keycode[ i ], &localbuf ) == FALSE ) {
                sendchar( HIDtoa( &kbdbuf, i ));
            }
        }
        memcpy(( far char* )&localbuf, ( const far char* )&kbdbuf, sizeof( BOOT_KBD_REPORT ));
    }//while(CharInQueue()...
}

This sequence is short and simple. All other support functions are also short and simple, the only exception being HID to ASCII translator. There is no simple and straightforward way to translate HID codes to ASCII symbols, the only regular pattern exist for letters a to z; the rest of characters has to be translated individually using look up table or switch statement. You can see that I don’t translate all the keys – just letters, numbers, symbols on number keys, comma, dot, Enter and Escape. That’s all that is usually necessary for MCU keyboard and function can be easily expanded, if desired.

Boot mouse works differently. If mouse state has not changed since last poll, host receives a NAK. If mouse has moved, the amount of travel in X and Y direction will be returned along with buttons, if any were pressed. In addition to that, a mouse is not required to send all 8 bytes; I’ve seen several which return only 3 – buttons and two coordinates.

Another function of a keyboard is LEDs showing states of locking keys – Caps lock, Num lock, and Scroll Lock. In modern keyboards, LEDs and keys are not interconnected – if you press a locking key, host simply receives a keycode. After that, host needs to send an OUT request to the keyboard transmitting a state of the LEDs register. I left out this function for the time being, it’s not really essential for microcontroller keyboards and implementation takes program space. I’m planning to write a separate function for that. As soon as I’m done, I will post it here.

Oleg.

2 comments to Lightweight USB host. Part 6 – introduction to HID.

  • shuzon udas

    Can you publish a article to use 18f8772 and max3421 chip to communicate with a web cam and then save a image to a sd card. This will be a great article. This project can be further modified to make a wireless camera and many other things.
    Please post a article about using a webcam with pic.