Creating Your Own Keyboard From Scratch – Bluetooth HID and Key Layout Translation

Summary Page

One requirement for “TheTasTaTur Mark1” was, that it should be wireless. Because I planned to carry the keyboard around and didn’t want to crawl on the floor every time I needed to connect it to my computer. As the keys are somewhat loose (see this post for the reasons) I stopped carrying the keyboard with me and left it at my “cubicle”. Nevertheless it is still wireless (besides the cable between both parts, of course). wpid-dsc05524-e1351163397260-2012-10-25-22-12.jpg I used the BlueSmirf HID (actually an older version), which utilizes the Roving Networks RN-42 chip with a HID (Human Interface Device) profile and SPP (Serial Port Profile) firmware preloaded. I connected the hardware data flow control pins (RTS and CTS) to each other, because I didn’t want to use them.  And used the TX and RX pins (digital pins 0 and 1) of the Arduino to connect them with the RX and TX pins of the BlueSmirf (TX to RX and RX to TX). This design is somewhat tricky because to get your programs on the Arduino you have to use the hardware serial which internally uses the same pins. I made a little switch (male and female pin headers) that I could pull to power-down the BlueSmirf and then load my programs to the Arduino. Early on, I decided to use raw hid reports and soon realized that I needed a way to see what my Mac actually receives. After searching a while I found hidapi. Included is a TestGUI. It is simplistic but you can choose to sniff out any USB device connected to your computer. In the bottom is a stream of received packets listed. This way I could reverse engineer the behavior of my Mac keyboard (‹cmd› key, ‹fn› key, …) and make “TheTasTaTur Mark1” mimic this behavior as far as feasible. After all the keyboard should not only work on my Mac but also on my Windows machine, which I have to use in my dayjob from time to time, as well as Linux which I prefer to use at work. Before I could get down to programming the keyboard behavior I configured the BlueSmirf to name itself as “TheTasTaTur”. For that purpose I made (just for the main development phase) a sketch that allowed to switch back and forth between SPP and HID mode by pressing a certain key. Actually I can not remember which key this was, because after integrating the mouse buttons I rewired some rows and columns. This way I could configure the device wirelessly. Here is a gist showing the sketch: [gist 3952167/] I have found it somewhere deep in my sketchbook and I guess it wouldn’t work anymore (because of the rewiring and the newer version of the BlueSmirf). So use it with caution. After switching to SPP mode you can connect the BlueSmirf as a serial device (actually I have a strange feeling, that I had to do something more, but I can’t remember right now) and communicate with it, for example using CoolTerm or better the command line tool screen. The actual command to change the name is “SN,TheTasTaTur”. There are many more configuration parameters, like different modes of operation, but the documentation is quite good and for further information you should check there. Especially since there seemed to have changed (made better) some things in the new version of the BlueSmirf HID. As you might have read here, the left side keyboard sends its key state, whenever it has changed, to the right hand side. Actually there are only arbitrary numbers send, representing the keys that are pressed and an empty package when the last key is released. I will show the subtleties of the communication between both keyboard parts in a later post. The program in the right hand side has an array containing all left side keys. The numbers that are send represent the indexes of the keys in this array. The keys of the right are kept in a matrix, similar to matrix of the left side. The keyboard matrix polling is the same, too. But the Key struct is a little different: [gist 3952406/] Finally I know which keys are pressed on both sides. Now I have to translate the actual pressed keys to the right key codes. Remember I’m using the NEO2 layout. As it is no problem for the primary layer (no modifiers) – I can simply send the key code “as printed on the keys” – it becomes interesting when modifiers come into play. I have to remap a few keys respecting the pressed modifiers. The excerpt of the following function does exactly that: [gist 3952501/] After translating the actual keyboard state to the corresponding key codes of the standard German key layout I have to create the bluetooth HID packets and send them to the BlueSmirf through the serial interface of the Arduino. The following gist does that: [gist 3955017/]

Summary Page

Advertisements

20 thoughts on “Creating Your Own Keyboard From Scratch – Bluetooth HID and Key Layout Translation

  1. agodinhostWoody says:

    Awesome work man!!

    I’m trying to understand how the left side board “talks” with the “right” side, couldn’t figure it out looking at the gist. Any chance to see the complete code?

      1. agodinhost says:

        I tried once before with arduino + rf on this project (https://github.com/agodinhost/Arduino/tree/master/MagicWand) and it really takes a while to execute the initial setup.

        Sometimes you do loose the connection between the master and the slave and you do need to reconnect and retransmit whatever you had tried to transmit before. You can control the packages transmission with one ACK response but it’s a pain since you can lost the ACK message on the way back. IMHO RF is good for quick stateless requests and responses.

        I liked your approach, simple serial comm via TX-RX is good enough. I’m not sure how you got the readings of the second board (do you have two arduinos running in parallel or do you have a master/slave relation? If you do have it running in parallel you can face some synchronization issues). Right now I’m reading your post in order to see how you did this part of your magic.

        Your work encouraged me to start my own keyboard, not sure if it will really see the sun light though (I do already have some projects at my procrastination list, geeee)

      2. Dirk Porsche says:

        I had (and still have) only one way communication. One single sender and one single receiver, so there was no ACK message possible.

        I do run two Arduinos in parallel, they share the power source though and don’t have any sync problems so far. I thought (naively?) that the serial communication (SoftwareSerial on the receiving side) would take care of that. Since I configure both to use the same baud rate. Should there be a possible flaw?

        I’m actually making a second keyboard, with PICs and Teensy and wired.

        I will soon report the current state, as I made some progress and will finish the hardware part soon. There is a lot of waiting, though, because I resin cast the case(s) this time.

  2. agodinhost says:

    As far as I can tell your code is okay, no sync problems at all. You are actually using the master/slave approach
    : your second board only scan the keyboard after receiving the command from the first board. Even though they are running in parallel you are serializing the keyboard scan so we are okay.

    Unless you get it by a real low price I would avoid Teensy – it uses a changed version of LUFA and the guys of PJRC do not provide it’s source code (not sure). Anyway there is one new arduino version out there that most probably will replace it – the Arduino Micro – It’s basically the same uC used on Leonardo and Teensy 2 (the atmega32U4).

    Resin cast? Nordic Rich guy!!!

    1. Dirk Porsche says:

      You were hinting at a logical sync problem. I feared you meant something in the transport. I’m a software guy, so electricity is sometimes still a little mysterious.

      Actually I already have a Teensy 3.0.

      You are true. Resin casting is more expensive than I expected first. Especially the silicone rubber for making the mold.

      But I hope to be a lot cheaper and less labor intensive with this second keyboard than the first one: no bluetooth, recycled key switches, no batteries, not so much hardware experimentation, reused keycaps.

      I will provide the whole math, when I it is working. For comparison:
      https://shiggyenterprises.wordpress.com/2012/10/23/creating-your-own-keyboard-from-scratch-parts-labor-costs-and-duration/

  3. agodinhost says:

    Man, you did totally broke me – my wife will kill me when she realize the sum of money that I’m going to spend into “just one keyboard”.

    After a few google searches looking for Cherry MX Switches vendors I found two awesome forums – I had no idea of the number of guys building custom keyboards out there:

    http://deskthority.net/photos-videos-f8/356mini-t5185.html
    http://geekhack.org/index.php#c49

    My turn, take a look into these case ideas:
    http://deskthority.net/keyboards-f2/case-discussions-t4530.html

  4. Travis says:

    Im working on a project where I want to send HID data through an arduino/bluesmirf HID to a tablet in order to control multimedia on the tablet, and Im really having a hard time grasping how to send a keypress as an HID function so that its read by the tablet as a keypress. Id love to pick your brain some on this issue if you have time. Trying to understand it from the rn-42 manual is melting my brain.

    1. Dirk Porsche says:

      Hi Travis,

      sending a raw HID report means basically sending a sequence of bytes in a certain format. From the Arduino using Serial or SoftwareSerial, set up to the right baudrate, to the Smirf. To discover what you need to send, you should check out how other devices do it. For that purpose you should connect them to your PC/Mac and find out what happens using hidapi: https://github.com/signal11/hidapi. You can use the included testgui.

      At first you should check if you did get all the wiring and configuration of the BlueSmirf right. If it is properly powered you should be able to discover the device: called “Firefly …” by default, if I remember right. You should even be able to connect to it and use a terminal (screen for example) to communicate with it. There should be some time span after booting it up, where it is open for configuration, switching to control mode, by default.

      Well the configuration parameters, working modes and report formats are explained in detail in the guide: http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Wireless/Bluetooth/RN-HID-User%20Guide-1.1r.pdf

      You can use your Arduino to configure through the wire. Same command required. It works essentially the same way. Getting the wiring right is crucial. Arduino RX -> Smirf TX and Arduino TX -> Smirf RX. Smirf RTS -> Smirf CTS, as you might possibly not work with hardware flow control. If you use hardware serial of the Arduino remember to provide a possibility to take the power from the Smirf, otherwise it interferes with the bootloader, which uses internally the same pins and prohibits this way that you can send new programs to the Arduino.

      I don’t know if there is more to say. If you have a specific question feel free to ask again but provide some more background information about your current state.

      Regards
      Dirk

  5. Travis says:

    I have setup the arduino and bluetooth module correctly and am able to connect to it with my tablet, where it shows up as a bluetooth keyboard input on my tablet. I am able to send ascii by pressing a button that is wired into my circuit, and the ascii is based on what i code into the sketch. I think i am able to send plain ascii so far, but not actual keyboard keypresses. When I look at the incoming info on my tablet, what Im sending comes over without a scancode. Without a scancode, it cant pull from the keyboard layout file and determine what to do, like change the volume for example.

    Basically I need a little help getting the arduino to send the HID data so that its seen as a keyboard button press by my tablet. Previously I was using the serial.println command but I think that was just sending ascii. Someone suggested that I try serial.write with the hex code of a key, and that seems to send a letter but I havent verified that its got a scancode attached. My ultimate idea is to use buttons wired into the circuit to control volume up, volume down, track forward, etc. Im mostly there, I just need the arduino to send the button presses so they look like a keyboard key was pressed.

    I dont have any bluetooth peripherals to test with but I may have to get something and see how its buttons send over. I was hoping to avoid having to buy useless hardware since I want to use my own buttons instead of an actual bluetooth keyboard.

    This is all very confusing. The bluetooth manual goes a bit over my head too, as I dont have a lot of experience with hex/binary code and this style of communication.

    Does that help explain? id love to hear your thoughts. I can post my sketch if needed and upload pictures of what im doing to my blog as well if that will help clarify.

    1. Dirk Porsche says:

      Hi Travis,

      yes that clears it up. I guess you need a raw HID report. Which is basically an array of bytes with a predefined length and a special structure. You send this array to the Smirf using Serial.write() byte by byte. I do this in the last gist:

      byte hidReport[REPORT_LENGTH] = {0xFD,0x09,0x01,modifiers[0],0x00,0x00,0x00,0x00,0x00,0x00,0x00};

      for(int i = 0; i < keyCodeIndex; i++){
      hidReport[5 + i] = keyCodes[i];
      }

      for(int i = 0; i < REPORT_LENGTH; i++){
      Serial.write(hidReport[i]);
      }
      Serial.flush();

      The first byte introduces the HID report marking it as a raw report. The second byte indicates the length. The third the type (1 for keyboard, 2 for mouse, …). The fourth the pressed modifiers (Alt, Shift, …) and after that come the pressed keys or all zeros if there is no key pressed any longer. The guide linked above explains it from page 8 onwards.

      You don't actually need a Bluetooth device for testing and keycode discovery. Bluetooth is more or less a wireless wrapper around standard USB. So you can determine the keycodes of the multimedia buttons, through sniffing at your standard keyboard. Well, the tablet has still to understand it, the same way. As those multimedia buttons are not really standardized, as far as I know. Take a look at the USB spec. You can find it here:

      http://www.usb.org/developers/devclass_docs/Hut1_12v2.pdf

      Details regarding keyboard reports are on page 58 and the following. I sniffed at my MacBooks "volume up" and "volume down" keys and that's how the reports look:

      volume down:
      01 00 00 44 00 00 00 00 00 02

      volume up:
      01 00 00 45 00 00 00 00 00 02

      Remember the first byte of your HID reports above: "0xFD"? It is missing, since that indicates to the Smirf, that a raw report is following and never reaches the host. The first byte of my Macs report say that it is a keyboard report. Apple omitted the length in the second byte. The third indicates that there is no modifier pressed and the fourth (and the following) is the actual keycode. The last byte is something Apple specific and indicates that the key is pressed, which switches the F-key row from the normal F-key behaviour to multimedia keys.

      I haven’t done any low level programming on the host. So I cannot really help you with the scancode problem. But I think it should work with raw reports. Otherwise you might define your own keycodes for the multimedia keys, read and translate them on the tablet on the fly, and execute the desired behaviour. You could switch to consumer-reports (type 3) and change the device type, to circumvent a possible misinterpretation on the tablets side.

      Best regards,
      Dirk

  6. Travis says:

    Thanks for your help on this! Ive been busy with work and out of town a bunch, but I was able to make a little progress and get the raw HID report to at least pass data over. Its still showing up as an ascii character instead of an actual keyboard button press, but thats probably because Im using the ascii codes from the bluesmirf manual. However, when I use a modifier like shift to make a capital letter, I AM seeing a scancode come across for that, since its an HID key thats used. I just need to figure out where to find HID codes for letters, instead of the ascii codes. Its still a work in progress. If you have any input on that, Id be more than happy to listen. Currently I am just using the capital A example that is in the smirf manual, to test the passing of a raw HID report.

    As for the low level stuff on the host, thats my next bit. Android has the ability to use different key layout files based on the bluetooth device that is connecting to the OS, to define what a keypress does when its received. In theory, I can just assign any key to an action and as long as that keypress is received over bluetooth, then the action will kick off. Im working now on making a simple keylayout that my smirf will use, just havent figured that part out yet. Im pretty sure I will have to root the tablet to get it working.

  7. Travis says:

    Im not entirely sure how to use this…keep in mind, Im not a C programmer. How do I apply this so that I can see the actual codes?

    Thanks!

    1. Dirk Porsche says:

      You have to add the whole stuff to your sketch. Either put it in a separate file and include it via:

      #include “KeyCodes.h”

      or paste it to your main sketch.

      Then you can simply use KC_A somewhere in your hid report to include the key code for the a-key. Which actually is 0x04, if I glimpsed it right.

      Actually it is an enumeration. Which in C simply means: some ordinary straight counted up numbers with a name to better handle them.

      1. Travis says:

        Ah ok. I guess I still dont see how the KC_A translates into 0x04, but i will play around with this and see what I can do with it. Ill throw it into my sketch tonight and see what happens.

  8. Taylor says:

    Hi, do you know how to receive a report with RN42, I want to send a report from pc and receive it with my RN42 keyboard in order to configure some settings, but I couldn’t find the way.

    Thanks in advance.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s