====== AVR Programmer built from a USB Keyboard ====== Recently, my keyboard became louder and louder while typing, so I decided to buy a new one. But what to do with the old one? Ever since I saw the [[http://www.pjrc.com/hub_isp/|AVR programmer that uses a USB hub]], I thought of abusing a USB keyboard for that. ===== Theory ===== Most keyboards have at least three indicator LEDs (Num-, Caps- and Scroll-Lock), which can be controlled from the host using a HID ''Set_Report'' request, and thus can be used as general purpose outputs. The inputs are a bit more tricky, since the keyboard uses a scan matrix divided in rows and columns. Most keyboards also do some debouncing and detect rows that are 'stuck', which means that we need to simulate the keypress of a single key. If a key has been pressed, the keyboard triggers an interrupt transfer with 8 bytes of data, containing the current state of all keys. The first byte reflects the state of the modifier keys (shift, ctrl, alt, etc.) which I'll be using as inputs. Additionally, the keys can be polled using a ''Get_Report'' request to the control endpoint, but the interrupt transfers need to be handled either way, since my keyboard just locked up after the first keypress when I didn't handle them first. ===== Hardware ===== The keyboard I've been using is a Microsoft Digital Media Keyboard 3000, which contains the following PCB: {{:keyboard_controller_bottom.jpg?400|}} {{:keyboard_controller_top.jpg?400|}} The fourth LED (function lock) can't be controlled from the host (although the HID specification allows two more LEDs, 'Compose' and 'Kana'). For emulating a single keypress, I'm using an optocoupler to connect the row and column of that key. Most switching PSUs use an optocoupler for the feedback loop, so I salvaged mine ([[http://www.kingbrightusa.com/images/catalog/spec/KB817-B.pdf|KB817-B]]) from an old mobile phone charger: {{ :charger_optocoupler.jpg?300 |}} And that's basically all that's needed, here's the finished setup: {{ :hidkey_breadboard.jpg?800 |}} You can see the keyboard controller PCB, the optocoupler, an Atmel [[http://www.atmel.com/Images/doc2543.pdf|ATtiny2313]] microcontroller, and an LED for testing the simple blink program I flashed. I desoldered the LEDs and directly connected the 5V outputs (since there were already 330Ω current limiting resistors in place) as follows: ^Keyboard^ATtiny^direction^ |Num lock|SCL|out| |Caps lock|MOSI|out| |Scroll lock|Reset|out| |row/column of right shift key|MISO (through optocoupler)|in| For the optocoupler I added a 470Ω current limiting resistor. ===== Software ===== First I wrote a [[https://github.com/steve-m/hidkey_gpio|small libusb-based test utility]] for experimenting with the keyboard. I'm directly communicating with the keyboard from userspace, unloading the kernel driver first. ==== Speed ==== Just toggling the LEDs in a while-loop gives a signal fluctuating between 500 Hz and 1.2 kHz on the output, which is faster than I assumed. Unfortunately, the inputs are much slower, since the rows are only scanned at 250 Hz on my keyboard: {{ :scan_pulse.png?nolink |}} In addition to that, the keyboard seems to debounce the keys, which means that in the worst case it takes 40ms for the host to know the state of the input. That's quite a bummer, since it drastically limits the not-so-bad output speed. ==== AVR Programmer ==== After some initial tests, I wrote an interface driver for [[http://www.nongnu.org/avrdude/|avrdude]]. The code can be found on github: [[https://github.com/steve-m/avrdude|]], and can be cloned with: git clone git://github.com/steve-m/avrdude.git So far this has only been tested with Linux, but it should work fine on Windows and OS X as well. ==== Results ==== $ ./avrdude -c hidkey -C avrdude.conf -p attiny2313 -U flash:w:main.hex:i -V avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 4.17s avrdude: Device signature = 0x1e910a avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: reading input file "/home/steve/dev/hid_gpio/avr_demo/main.hex" avrdude: writing flash (86 bytes): Writing | ################################################## | 100% 123.79s avrdude: 86 bytes of flash written avrdude: verifying flash memory against /home/steve/dev/hid_gpio/avr_demo/main.hex: [skipped] Well, as you can see, programming takes quite a while, mainly because of the slow input speed I mentioned before, but is very reliable. Since I assume that many people have some old USB keyboard lying around collecting dust, it still could be used as a bootstrap flasher for solving the old chicken-and-egg problem when building a faster programmer ([[http://www.fischl.de/usbasp/|USBasp]], [[http://www.ladyada.net/make/usbtinyisp/|USBtinyISP]]) or as low-speed general purpose output for other applications. For example, many outputs could be driven using a simple serial-in, parallel-out shift register. ===== Update #1 ===== Since reading the MISO input is much slower than writing the outputs, as I mentioned above, I took a closer look at the avrdude code. As it turns out, the inputs are read every time, even if the data isn't used at all afterwards (like when writing a byte). I applied a crude hack, so that MISO is only being read if the data is used afterwards. I pushed that change to a new [[https://github.com/steve-m/avrdude/tree/speedup|speedup branch]] on github. With those changes, flashing my 86 byte demo application is now over ten times faster (11.75s vs. 123.79s)! $ ./avrdude -c hidkey -C avrdude.conf -p attiny2313 -U flash:w:main.hex:i avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 4.33s avrdude: Device signature = 0x1e910a avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: reading input file "/home/steve/dev/hid_gpio/avr_demo/main.hex" avrdude: writing flash (86 bytes): Writing | ################################################## | 100% 11.75s avrdude: 86 bytes of flash written avrdude: verifying flash memory against /home/steve/dev/hid_gpio/avr_demo/main.hex: avrdude: load data flash data from input file /home/steve/dev/hid_gpio/avr_demo/main.hex: avrdude: input file /home/steve/dev/hid_gpio/avr_demo/main.hex contains 86 bytes avrdude: reading on-chip flash data: Reading | ################################################## | 100% 124.25s avrdude: verifying ... avrdude: 86 bytes of flash verified avrdude: safemode: Fuses OK avrdude done. Thank you. Programming the whole flash of the ATiny2313 now only takes 4 minutes 37 seconds: $ ./avrdude -c hidkey -C avrdude.conf -p attiny2313 -U flash:w:/tmp/usbtiny.hex:i avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 4.33s avrdude: Device signature = 0x1e910a avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: reading input file "/tmp/usbtiny.hex" avrdude: writing flash (2046 bytes): Writing | ################################################## | 100% 276.90s avrdude: 2046 bytes of flash written avrdude: verifying flash memory against /tmp/usbtiny.hex: avrdude: load data flash data from input file /tmp/usbtiny.hex: avrdude: input file /tmp/usbtiny.hex contains 2046 bytes avrdude: reading on-chip flash data: Reading | ########## | 20% 593.63s^C I'd say that's quite an improvement, and very well acceptable for a chicken-and-egg bootstrap flasher :-) This project was featured on [[http://hackaday.com/2012/11/26/usb-keyboard-becomes-an-avr-programmer/|Hackaday]]. == Reference Literature == [[http://www.usb.org/developers/devclass_docs/HID1_11.pdf|Device Class Definition for HID 1.11 - USB Implementers’ Forum]] ~~DISCUSSION~~