I've always been a fan of the old IBM Model M keyboards. Although there are some companies out there that still make keyboards using the buckling spring mechanism that IBM pioneered, nothing quite matches the original. However, after an update to OsX a few years back, my Model M has been rendered impracicle at work. The update (I can't remember which) broke the software I used to remap individual keys. I had my right Crtl remapped to "Super" (aka "Command" on OsX), which is needed to copy and paste on the keyboard. OsX does allow all the Crtl keys to be remapped to "Super", but as a heavy terminal user this too is impractical as I still need one Crtl key.
The solution to this problem, which to me seemed that it would never again leave me at the mercy of an OS update, would be to remap the keys in hardware. So I decided to build an adapter to do it for me. I could have gone with an open source design that was already out there, but I also wanted the ability to turn the remapping off via a switch on the adapter. Although the designs out there seemed very popular, the idea of having to reverse engineer someone else's assembly code (more on that later) was not appealing. So, not wanting to compromise I did what I always do. Fire OpenSCAD, dust off the soldering iron, and get building the solution I wanted.
The hardware
I wanted this not to get too big, so I decided to base this off an Arduino Pro Micro. It has enough I/O pins to get the job done and had an appropriately small footprint. The idea was to build it onto a small bit of veroboard to give me some area to solder the various other components onto, and then put this into a 3D printed box.
The first stage of this was to establish communication with the keyboard. So, I soldered some wires onto the end of a PS/2 connector, hooked it up to and arduino and wrote a small sketch using the PS2Keyboard library. Initially this failed, and for a while I could not figure out why. So, I went back and copied another project that connects a PS/2 keyboard and found it worked. After changing the pins one by one, I found that the keyboard could not communicate to the Arduino unless the IRQ pin was connected to pin 3 on the Arduino. I found out that this was because the way that the PS2Keyboard library works is to call "attachInterrupt" (https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/), which stops processing and runs the function passed to it whenever it changes state, depending on what you pass to the function. This was something that I did not know an Arduino could do, and could be useful to me in future projects.
The circuit being prototyped
Once I had done that I put everything else together on a prototype board and updated the sketch I had started writing to test all of the components that I was adding. I knew that I was going to need another switch to enable and disable the mapping that I could mount somewhere away from the adapter, so in addition to the switch on the adapter I added a second switch that I could plug in that could be placed elsewhere.
The footswitch to switch key remapping on and off remotely
After I had finished prototyping the circuit I began building the hardware. I first soldered the Arduino onto some veroboard, leaving 3 holes on each side for putting the components into and 3 tracks behind it. Once I had this done I started making the model of the case in OpenScad. I needed to get the Arduino mounted onto the PCB first so that I could measure it to determine where all of the parts to hold it in place, and where all the holes for the connections needed to go.
Once I had 3D printed the models, corrected for some mistakes, and then 3D printed the fixed model I moved onto mounting the components onto the PCB. After connecting everything to the veroboard I tried to slide the circuit into the case and put all of the parts into place. However, I had not left enough space in the case for all of the wiring, and because I was operating under a time constraint I had to remove something from the design. I decided to remove the switch on the adapter itself, as I thought that this would get used the least. Once all of the other parts were in place I used hot glue to shut the case and make sure everything worked using the test sketch I had written earlier.
Assembling the adapter. There wasn't quite enough room for the switch on the adapter, so it had to go. Maybe with more flexible or shorter wire I could have got this all in there.
The software
Once I had the hardware complete I started looking for some software that I could modify to put on the device. There was a project called "Soarer's Converter" that I had initially thought of modifying to allow for the key mappings to be changed by a switch. However, after looking into that project I decided against this. This was because the project was written in assembly code, and I thought that the learning curve needed to learn assembly and the accompanying toolchain required to get the resulting code onto the Arduino would be much longer than modifying the PS/2 library I was using in my test sketch and the Arduino HID library that can make an Arduino Pro or Leonardo act as a USB HID device.
The main problem with both of these libraries is that they were a bit more high level than I needed. PS/2 and USB HID keybboards both use different sets of scan codes, so my intent was to map from one set to the other in software. However the Arduino Keyboard HID library did not allow you to press and release keys based on scan codes, but based on ASCII character codes. Similarly, the PS/2 library I was using would tell you what ASCII characters were available in the buffer, not which keys had been pressed. Although these are sensible design choices for both of these libraries, because for most applications people will only care about the characters that are sent and received and not the scan codes, for my application I needed to know when a particular key was pressed, not what character needed to be produced. It may seem like a subtle difference, but these layers of abstraction masked certain keys, and prevented certain key events being sent. Obviously this is going to get in the way when building a keyboard adapter.
The solution to this for both libraries was to take the original one and strip it down to allow for a lower level interaction. In the case of the Arduino keyboard HID library this was relatively easy as it already had functionality to register when a key had been pressed and released. So, all that remained was to remove the map that translated ASCII characters in to USB HID scan codes and allow the scan codes to be registered directly.
Modifying the PS/2 library was slightly more difficult because it contained a state machine that kept track of certain modifier flags and the state of the shift key so that whoever was using the library would not have to handle this detail themselves. It also dropped key up events, which I needed to know about so I could tell my USB HID library when to release a key. The library I came up with in the end worked similarly to the original one. It still requires that you call a method to see if there are any key events to handle (eventAvailable()) , but if there are you call readEvent(KeyEvent* evt) and pass in a keyEvent structure, which is populated with the details of the last key event, including the scan code,whether or not it was a keyup or keydown event and if there was a modifier flag from the keyboard.
The modifier flag was set if the scan code 0xE0 was sent from the keyboard and seemed to be used to distinguish between keys that have the same function. For example, an arrow key on the num pad, vs one of the dedicated arrow keys. It was used in the original PS2Keyboard library to distinguish between left and right control and alt keys. Knowing that I would need this information to be able to change the functionality of just the right Ctrl key I added this to the information in the KeyEvent structure that was populated by readEvent().
Once the two libraries were in place I put together a sketch that mapped all PS2 key events to a single USB key press and release just to ensure that everything was working. This sketch also included some serial output to display the incoming scan codes which I ended up leaving in simply because I liked the way it made the serial TX light on the Arduino flash when I typed.
I then put together the first of the maps. To get the first map I used the fact that in the Keybaord HID library there was a map that mapped ASCII characters to USB HID scan codes, and in the PS2Keybaord library there was a map that mapped PS/2 scan codes to ASCII characters. So in order to map PS/2 scan codes to USB HID scan codes all I needed to do was write a program that would go through the map from the PS/2 library, use the ASCII code to look up the corresponding USB Scan code in the map from the Keyboard HID library and put that into the PS/2 to USB scan code mapping. Once I had this done there were a few gaps, so I went through and put these in manually.
Once this first map was done there were a few keys that were still not behaving as they should. For example, the arrow keys were producing numbers. I found that in most cases these keys were coming through with the modifier flag set to true, so I created another map that had most values set to 0, except for a few keys that behaved differently if the modifier flag was set to true. The idea being that if the modifier flag was set, the adapter would check the modifier remapping first and if there wasn't a scan code in that map (0 meant no code) it would then go for the regular key mapping. I also created two empty maps (one for when the modifier flag was set true, the other for when it wasn't) to be used when the remapping was active which worked on the same principle. If there wasn't a scan code in either of these it would go back to the regular mappings as if remapping was not enabled.
So, once all of this was done, I populated the remapping maps with the keys I wanted to remap. Of course I put the command key in, but figuring this had all been a lot of work I also added in remaps so that F10, F11 and F12 became mute, volume up and volume down respectively.
Results and future extensions.
After finishing off the mappings and testing the keyboard out for a bit I finally have my keyboard into a state that works well with my mac at work. There are only two main bits of functionality that are missing. One is that the num lock key does not seem to work. Num lock seems to operate in a different way to caps lock. For caps lock all I need to do is send the scan code down to and the operating system seems to take care of making the characters upper case. With num lock however, this does not seem to be the case. Some more investigation on this topic will be required to figure out what is going on.
The other main piece of functionality that is missing is that none of the lights on the keyboard (num lock, caps lock or scroll lock) work. My understanding of how these lights work is that they are controlled by signals from the computer that are sent in response to the user pressing the associated lock key. So in order to make them work properly I would need to figure out how to update the KeyPress library to capture those events from the operating system, and also how to update the PS2Event library to send them on to the keyboard in the right manner. Since I rarely use these keys I probably won't do this but if anyone out there wants to all of the code for this project is on github (see below).
Another idea to extend this project would be to take a leaf out of book of the likes of Kinesis and allow the primary and default mappings in the keyboard to be changed by having the adapter go into an update mode which will present a USB mass storage device to the computer. Once this happens the mappings can be updated by updating files on that device. However, as far as I know the Arduino Pro Micro does not support this, so this will need to be done with a board like the Teensey 2.0.
For anyone else out there that has any other ideas I have put the code up on github. This code is of course complete with all of the limitations I have mentioned above. The two libraries have been put in as separate repositories and I would actually be very interested to see if anyone out there finds any use for these on their own, especially the PS2Events one, as it allows access to more keys then the original PS2Keybaord library.
That's all for now. Happy typing everyone!