RFID Keyboard

This project is an ATmega chip acting as an USB keyboard that types out the data from a RFID reader (the idea is that the ID strings can be used as login passwords). I used an ATmega168 with V-USB hardware and a ID-12 RFID reader.

This is my 2nd USB device project using AVR microcontrollers. For another project that explains how I use V-USB, see my Wii Nunchuk USB Gamepad / Mouse. (A lot of this page will refer to this as the setup procedure are very similar)

Hardware

The hardware is based around V-USB's recommended hardware, although it's been modified slightly to work at 5V. My first V-USB project, the Wii Nunchuk USB Gamepad / Mouse uses a very VERY similar circuit and on that page I explain how I came up with the circuit.

Here's the circuit for the RFID keyboard:

See? The only difference is that this project connects a RFID reader to the USART serial port instead of connecting a Nunchuk to the TWI port.

Software

The software is based around this example keyboard project: The Real USB Model-M by Chris Lee.

Once again, I use my own AVR Project IDE as the editor for this project. Setting up the project is very similar to how I setup the project for my USB Nunchuk (mentioned above). One difference is that the HID descriptor is contained in "main.c" instead of a separate file, and the report data structure is a simple array of bytes. Also, a serial port library I wrote is used ("ser.c" and "ser.h").

In "usbconfig.h", the things that are edited are the device and vendor names. Of course "USB_CFG_IOPORTNAME", "USB_CFG_DMINUS_BIT", and "USB_CFG_DPLUS_BIT" must reflect the physical circuit setup (port D, bit 4 for D- and bit 2 for D+). "USB_CFG_INTR_POLL_INTERVAL" is set to 10 ms, "USB_CFG_MAX_BUS_POWER" is set to 500 (I just prefer a higher value, don't worry about this unless you want your operating system to give you warnings when your device malfunctions), and the device is self powered (see "USB_CFG_IS_SELF_POWERED").

Pay attention around here:


#define USB_CFG_INTERFACE_CLASS     0x03    /* HID class */
#define USB_CFG_INTERFACE_SUBCLASS  0x01    /* Boot-device subclass */
#define USB_CFG_INTERFACE_PROTOCOL  0x01    /* Keyboard protocol */


This makes the device a keyboard that can be used before the operating system loads up.

In "usbconfig.h", "USB_CFG_IMPLEMENT_FN_WRITE" is set to 1, which enables you to write a function that handles incoming data from the computer. The computer would sometimes like to send data that turns on caps lock, num lock, scroll lock, and that sort of thing. We write this function to handle it:


// data from the computer is handled here
uchar usbFunctionWrite(uchar *data, uchar len)
{
    if ((expectReport) && (len == 1)) // computer wants to set LED status
    {
        LEDstate = data[0];
    }
    expectReport = 0;
    return 1;
}


V-USB also needs you to write something to handle some requests from the computer:


// computer doing some requests to your device
uchar usbFunctionSetup(uchar data[8])
{
    usbRequest_t *rq = (void *)data;
    usbMsgPtr = reportBuffer;

    if ((rq->bmRequestType & USBRQ_TYPE_MASK) != USBRQ_TYPE_CLASS)
        return 0;

    switch (rq->bRequest)
    {
        case USBRQ_HID_GET_IDLE:
            usbMsgPtr = &idleRate;
            return 1;
        case USBRQ_HID_SET_IDLE:
            idleRate = rq->wValue.bytes[1];
            return 0;
        case USBRQ_HID_GET_REPORT:
            return sizeof(reportBuffer);
        case USBRQ_HID_SET_REPORT:
            if (rq->wLength.word == 1)
                expectReport = 1;
            return expectReport == 1 ? 0xFF : 0;
        case USBRQ_HID_GET_PROTOCOL:
            if (rq->wValue.bytes[1] < 1)
                protocolVer = rq->wValue.bytes[1];
            return 0;
        case USBRQ_HID_SET_PROTOCOL:
            usbMsgPtr = &protocolVer;
            return 1;
        default:
            return 0;
    }
}


That function allows the computer to set the idle rate of the keyboard (something related to repeat keystoke setting). "protocolver" can be in boot mode or report mode. And sometimes the computer asks for the size of the report.

So the stuff related to V-USB is done. Let's move on to how to read data from the RFID reader! According to the ID-12's datasheet, if we set the "Format Select" pin to 5V, the module outputs a hexadecimal string out of a 8N1 serial port at 9600 baud.

My serial port library ("ser.c" and "ser.h") uses a circular buffer to save data from the serial port. In the code, the serial port is initialized with a baud rate of 9600 (calculated using help from "util/setbaud.h"), a RX buffer size of 64 and a small TX buffer size (TX isn't really used anyways). When a RFID tag gets near the reader, the reader will send the hexadecimal ID string to the ATmega. The ATmega's serial port received interrupt will fire and store each character into the circular buffer.

The main loop of my code will check if any characters have been received. If there are new characters, it gets translated (see "set_buffer") to keycode and stored in the report data buffer, and sent to the computer.


while (1) // main loop
{
    uint8_t read_cnt;
    uint8_t rx = ser_rx(0, &read_cnt);
    if (read_cnt > 0) // if char is received
    {
        set_buffer(rx); // sets the buffer with keycodes according to character to send
...
...
...


You will notice that timer1 (running with a prescaler of 256) of the ATmega is used to keep timing. Since the computer sets the idle rate, we should respect it and report idle once in a while. The timer runs and when enough time has passed, the code reports no keystrokes back to the computer, the timer is then reset. This is used for other timekeeping as well, when I send a keystroke, I send it for at least 60 ms, just to make sure it gets registered (and it makes a cool "typing" effect).

My code's "set_buffer" function is a really quick and dirty way of translating ASCII characters to keycodes that can be sent through a keyboard protocol. It only works for alphanumeric characters. To implement more keys, you should look up more keycodes yourself.


// basically translate ascii to a keycode and place it in the buffer if the character is allowed
void set_buffer(uint8_t c)
{
    memset(reportBuffer, 0, sizeof(reportBuffer)); // clear all of the buffer
    
    if (c < 0)
    {
        return; // nothing was received
    }
    else if (c >= 'A' && c <= 'Z') // capital letters
    {
        reportBuffer[2] = 4 + c - 'A'; // set keycode
        reportBuffer[0] = _BV(1); // left shift active
    }
    else if (c >= 'a' && c <= 'z') // lowercase letters
    {
        reportBuffer[2] = 4 + c - 'a';
    }
    else if (c >= '0' && c <= '9') // numbers
    {
        if (c == '0')
        {
            reportBuffer[2] = 0x27;
        }
        else
        {
            reportBuffer[2] = 30 + c - '1'; // set keycode 
        }
    }
}


So load up the project, compile, and build! The AVR fuses should be identical to the ones used in the USB Nunchuk.

That's basically it. This isn't really secure for logging into anything since anybody can steal your RFID tag and figure out the password. But you can be creative! I'd suggest a system where the string from one tag is used as a salt to encrypt the string from a second tag, which would increase security quite a bit (of course only if you keep the encryption technique unique and private, and never lose your tags).