Or, how to waste an inane amount of time into converting a keyboard into… another keyboard. Because a keyboard is a keyboard is a keyboard. Or is it? Join me in my adventure modding a zalman ZM-K500 into a full featured programmable, macro capable, multi-layer NKRO monster.

A couple years ago I started delving into the depths of the keyboard enthusiast world. As a result, my curiosity towards mechanical keyboard gradually grew until I finally went on the ‘bay and got some specimens (a POS keyboard from Tipro and then a couple Cherry g80s in different form factors, all used and cheap enough to cost less than the shipping) to actually see what the fuss was all about.

I quickly learned that the major limitation of these cheap keyboards is their key rollover, or the amount of keys that can be pressed at any given time and still register properly. A typical cheapish mechanical keyboard is usually designed for a professional environment, be it a POS station or an office, and will often feature a measly 2KRO (two key rollover) as its key matrix is not really optimized for key rollover and will present some blocking. Keyboards marketed to the gamer crowd tend to behave better in this regard and often boast a 6KRO or better (and this is generally more than sufficient for the intended purpose, as you only have a hand on the keyboard and typically less than 7 fingers per hand). This can be achieved with a properly designed key matrix with diodes in series with the switches to prevent ghosting. Furthermore, the interface can also be a limiting factor. For example USB HID keyboards can report a maximum of 6 key presses and 4 modifiers at any given time, so even a properly designed keyboard will incur in this limitation if it doesn’t resort to the workaround of simulating more than one interface. Armed with this knowledge and after doing some googleing, I decided my next keyboard would be and homebrew, fully NKRO (N key roll over) capable and based on Hasu’s great TMK_keyboard project. My victim started its life as a Wyse WY-30 terminal keyboard that, once again, I found on the ‘bay for really really cheap. I chose this keyboard as a base because the switches are directly mounted to a plate and don’t need a pcb backing. I then proceeded to cut up the board (!!!) to make it a tenkeyless, wire up the switches and diodes in a freeform fashion to make a matrix and then get the controller figured out and programmed.

My only problem with this new keyboard was its unusual layout (notice in particular the absence of the F keys row, which I had to add is as in alternate key layer accessible through a modifier). TL;DR I had several crappy mechanical boards I didn’t really like and decided to mod one to meet my needs. Enters my next victim. The Zalman ZM-K500 is a fairly cheap (as in, cheap for a new mechanical keyboard, at around 50€ including shipping) chinese-made mechanical ‘gaming’ keyboard that employs a clone of the classic Cherry MX switch.

This board is sold under a variety of brands and can be found under the name of Newmen GM-10, Genius M1 and several others. I chose the zalman variant entirely because it being readily available in Europe. The switches employed in this keyboard are from Kailh and are low activation force linear non clicky switches designed as a counterpart to Cherry reds. These switches are not really that good, have a shorter lifespan and (from the little information I have been able to gather on the interwebs) seem less mechanically sound than the original. First thing I needed to do was to disassemble the keyboard, which is done by removing the screws in the back and disconnecting the usb lead, removing some of the keycaps to reveal the screw locations and then remove said screws and free the switch plate from the housing.

Next step was to remove the remaining keycaps, flip the plate over and desolder the switches from the pcb.

I then proceeded to take the switch out of the plate, as I was going to ditch them in favor of some cherry blacks I harvested off one of my old keyboards.

Cherry MX blacks are stiffer variant of the reds, the difference between the two being the heavier spring. As I was pretty curious about how reds felt I opted to swap the springs in my blacks with the ones that came in the kailh switches. While I was at it I went ahead and tossed a diode inside the switch housing to reduce the wire clutter. This was a trivial if really boring operation, as Cherry offers their switches in an optional variant with the diode already in place, so the housing was predisposed to accommodate one and it was just a matter of disassembling and reassembling.

Next up was the actual wiring of the damn thing. Not a really fun thing to do, but nevertheless quite satisfying once I was done. To make sure things wouldn’t short out during/after assembly, I stripped the insulation of the wire only where I needed to. Not one bit fun.

At this point I went on to make a board carrier for the controller board and wire it up to the matrix. I chose to use a teensy 2.0 as the controller because of the form factor and the fact that it is one of the few atmega32u4 based boards to actually break out all of the I/O pins to the header. As I’m dealing with a 18x6 matrix (and the TMK_keyboard project, to my knowledge, doesn’t support I/O expanders or shift registers) I needed 24 I/O pins out of the 25 available on a 32u4. To make the whole think fit I resorted to mount the pin headers “the wrong way around”, from the top of the board. I then slid out the black plastic that keeps them together and trimmed them to make the whole thing shorter. I also did some trimming on the plastic walls on the back of the keyboard to make sure the cable and the plug would have enough space to clear the lid.

Now that I had the whole hardware part out of the way, it was time to turn to the software side of things. The TMK_keyboard project comes with a few keyboard layouts and configurations for some of the most popular mod targets, and they are generally a great starting point for a project and can take out of the equation much of the guesswork of having to start from scratch. As I had previously dabbled with the project and I already had a working keyboard based on it, I used my previous work as a base for the new one. Some important notes on this process: Be sure to choose the proper makefile and to configure it properly to suit your needs / your hardware. I’m using the lufa makefile as the author of the project recommends it over the pjrc one. I set the mcu to atmega32u4 and the frequency to 1600000 (16mhz). As I’m using an original teensy 2.0, I also set the bootloader size to 512. In the makefile you can also select the features you wish to include in your firmware. I opted to activate everything except for the debugging console and the breathing sleep led, as I’m not planning to add a led anyway. On that note I also edited led.c to make sure no leds were defined.

# Build Options
#   comment out to disable the options.
#
BOOTMAGIC_ENABLE = yes  # Virtual DIP switch configuration(+1000)
MOUSEKEY_ENABLE = yes   # Mouse keys(+4700)
EXTRAKEY_ENABLE = yes   # Audio control and System control(+450)
#CONSOLE_ENABLE = yes   # Console for debug(+400)
COMMAND_ENABLE = yes    # Commands for debug and configuration
#SLEEP_LED_ENABLE = yes  # Breathing sleep LED during USB suspend
NKRO_ENABLE = yes       # USB Nkey Rollover - not yet supported in LUFA
I then proceeded to edit my config.h to reflect my keyboard’s key matrix size:
/* key matrix size */
#define MATRIX_ROWS 6
#define MATRIX_COLS 18

And entered some bogus info intothe USB device descriptor section:

/* USB Device descriptor parameter */
#define VENDOR_ID       0x1337
#define PRODUCT_ID      0x0006
#define DEVICE_VER      0x0001
#define MANUFACTURER    Fred
#define PRODUCT         Fred ZM-K500
#define DESCRIPTION     t.m.k. custom firmware

The last step was to edit keymap.c to suit my needs. This may take some time (and some trial and error) but is conceptually pretty straightforward, as it’s basically just defining the keys in the matrix and assigning them a value. In this file you can also define multiple layers and assign keys to momentary switch or toggle between them. I also discovered (as I should have earlier, had I read the FAQs…) that having more than 16 columns will mess things up, as the variable that stores the state of each row is by default only 16 bits wide and won’t accept bitwise shifts greater than that. Luckily in this case we can solve the issue with some simple typecasting.

static matrix_row_t read_cols(void)
{
return (PINB&(1<<4) ? 0 : ((matrix_row_t) 1<<0)) |
	   (PIND&(1<<7) ? 0 : ((matrix_row_t) 1<<1)) |
	   (PIND&(1<<6) ? 0 : ((matrix_row_t) 1<<2)) |
	   (PIND&(1<<4) ? 0 : ((matrix_row_t) 1<<3)) |
	   (PINB&(1<<5) ? 0 : ((matrix_row_t) 1<<4)) |
	   (PINB&(1<<6) ? 0 : ((matrix_row_t) 1<<5)) |
	   (PIND&(1<<5) ? 0 : ((matrix_row_t) 1<<6)) |
	   (PINC&(1<<7) ? 0 : ((matrix_row_t) 1<<7)) |
	   (PINC&(1<<6) ? 0 : ((matrix_row_t) 1<<8)) |
	   (PIND&(1<<3) ? 0 : ((matrix_row_t) 1<<9)) |
	   (PIND&(1<<2) ? 0 : ((matrix_row_t) 1<<10)) |
	   (PIND&(1<<1) ? 0 : ((matrix_row_t) 1<<11)) |
	   (PIND&(1<<0) ? 0 : ((matrix_row_t) 1<<12)) |
	   (PINB&(1<<7) ? 0 : ((matrix_row_t) 1<<13)) |
	   (PINB&(1<<3) ? 0 : ((matrix_row_t) 1<<14)) |
	   (PINB&(1<<2) ? 0 : ((matrix_row_t) 1<<15)) |
	   (PINB&(1<<1) ? 0 : ((matrix_row_t) 1<<16)) |
	   (PINB&(1<<0) ? 0 : ((matrix_row_t) 1<<17));
}

And with that the only thing left to do was to compile the firmware and load it onto the teensy using teensyloader. The first time I had to manually put the teensy in usb bootloader mode by manually shorting the reset pin to ground (as the button was on the inside of my carrier board and thus not easily accessible). To upload my firmware after that (I messed around a bit with my keymaps) all I had to do was enter the key combination “magic + pause” (“magic” being “left shift + right shift” by default, but that is easily configured from config.h). Neat!

/* key combination for command */
#define IS_COMMAND() ( \
keyboard_report->mods == (MOD_BIT(KC_LSHIFT) | MOD_BIT(KC_RSHIFT)) \
)