Configuring the QMK firmware for my 60% keyboard layout.

Introduction

I designed a layout, and I figured out how to configure, compile and load QMK firmware on a microcontroller for an imaginary keyboard. Now it is time to create the firmware for the actual keyboard I want to build. I will call this keyboard the “Tinkboard Zero”. This keyboard will need a bit more configuration than the small numberpad in the example. In the process, I will explain how to use layers to access keystrokes that do not have a key on this small keyboard.

Matrix design

Looking at my design, obviously this keyboard will have a matrix of 14 columns and 5 rows. This immediately causes a bit of a problem though, since the ProMicro controller that I ordered only has 18 pins. For 14 rows + 5 columns I would need 19 pins. Well, that’s what you get when you decide to order the parts without too much knowledge and wing it from there.

Image

So let’s improvise: if I can halve the columns and double the rows, I can address the same number of keys. But 7 columns + 10 rows is only 17 pins, so that would fit the ProMicro with one pin to spare.

Columns

What I will do, is connect the column under the [Esc] key and the column under the [7] key. That way, they will together act as column “1”. I do the same for the other columns, so now I only have 7 columns:

Image

Rows

For the rows, I will consider the left and right parts of the keyboard to be separate rows. So I will connect [Esc] - [6] as one row, and [7] - [Backspace] as another row. I halved the columns and doubled the rows, only 17 pins required now! It will require some additional wiring but that should not be a problem.

Image

I numbered the rows left to right, then top to bottom. In the later steps this is more convenient than top to bottom, then left to right.

ProMicro pinout

The pin numbers where the rows and columns connect need to be specified. Using this schematic:

Image

Assigning a ProMicro pin to a row or column is arbitrary. But since there are 10 pins at the top and 8 at the bottom of the ProMicro, and I have 10 rows and 7 columns, I will use the top pins for the rows and the bottom pins for the columns. I initially used row number 1 to 5 for the left half and 6 to 10 for the right half of the keyboard. That turned out to cause a very muddy configuration though, so it is now left to right, then top to bottom. I already soldered the wires though, so the assignment looks a bit shuffled.

 File: config.h
#define MATRIX_ROWS 10
#define MATRIX_COLS 7

/* Row number:             1   2   3   4   5   6   7   8   9  10 */
#define MATRIX_ROW_PINS { D3, C6, D2, D7, D1, E6, D0, B4, D4, B5 }

/* Column number:          1   2   3   4   5   6   7 */
#define MATRIX_COL_PINS { B6, B2, B3, B1, F7, F6, F5 }

Putting keys on intersections

Now I have to specify on which row/column intersections there is a keyboard switch.

 File: tinkboard_zero.h
#define ___ KC_NO

#define KEYMAP( \
      K11, K12, K13, K14, K15, K16, K17,     K21, K22, K23, K24, K25, K26, K27, \
      K31, K32, K33, K34, K35, K36, K37,     K41, K42, K43, K44, K45, K46, K47, \
      K51, K52, K53, K54, K55, K56,          K61, K62, K63, K64, K65, K66, K67, \
      K71,      K73, K74, K75, K76, K77,     K81, K82, K83, K84, K85, K86,      \
      K91, K92, K93,                K97,                    KA4, KA5, KA6, KA7  \
) { \
    { K11, K12, K13, K14, K15, K16, K17 }, { K21, K22, K23, K24, K25, K26, K27 }, \
    { K31, K32, K33, K34, K35, K36, K37 }, { K41, K42, K43, K44, K45, K46, K47 }, \
    { K51, K52, K53, K54, K55, K56, ___ }, { K61, K62, K63, K64, K65, K66, K67 }, \
    { K71, ___, K73, K74, K75, K76, K77 }, { K81, K82, K83, K84, K85, K86, ___ }, \
    { K91, K92, K93, ___, ___, ___, K97 }, { ___, ___, ___, KA4, KA5, KA6, KA7 }  \
}

Here you can see why numbering the rows left to right, then top to bottom is more convenient. Rows that are next to each other on the keyboard are now next to each other in the configuration as well.

Assign base keystrokes

First I am assigning the base keystrokes. After assigning the base keystrokes I will need some additional configuration to access the missing keys (like Home and End). This I will describe later in this article.

To configure the base keymap, I can just enter the codes as a slightly formatted, boring comma separated list like in the example. Or I can go completely overboard and manually draw ASCII lines1 to make it look like a keyboard layout and try to make things a bit more readable:

 File: keymap.c
#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
   KEYMAP // LAYER 0 - Base layer
   // ╔═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════════════╗
   // ║esc    │1      │2      │3      │4      │5      │6      │7      │8      │9      │0      │─      │=      │backspace      ║
      (KC_ESC , KC_1  , KC_2  , KC_3  , KC_4  , KC_5  , KC_6  , KC_7  , KC_8  , KC_9  , KC_0  ,KC_MINS,KC_EQL ,   KC_BSPC     \
   // ╟───────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───────────╢
   // ║tab        │q      │w      │e      │r      │t      │y      │u      │i      │o      │p      │[      │del    │\          ║
      ,  KC_TAB   , KC_Q  , KC_W  , KC_E  , KC_R  , KC_T  , KC_Y  , KC_U  , KC_I  , KC_O  , KC_P  ,KC_LBRC,KC_DEL ,  KC_BSLS  \
   // ╟───────────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴───────────╢
   // ║caps         │a      │s      │d      │f      │g      │h      │j      │k      │l      │;      │'      │enter            ║
      ,  _______    , KC_A  , KC_S  , KC_D  , KC_F  , KC_G  , KC_H  , KC_J  , KC_K  , KC_L  ,KC_SCLN,KC_QUOT,     KC_ENT      \
   // ╟─────────────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴─┬───────┬───────╢
   // ║shift            │z      │x      │c      │v      │b      │n      │m      │,      │.      │/      │     │up     │       ║
      ,     KC_LSFT     , KC_Z  , KC_X  , KC_C  , KC_V  , KC_B  , KC_N  , KC_M  ,KC_COMM,KC_DOT ,KC_SLSH,       KC_UP         \
   // ╟─────────┬───────┴─┬─────┴───┬───┴───────┴───────┴───────┴───────┴───────┴─────┬─┴───────┼─────┬─┴─────┼───────┼───────╢
   // ║ctrl     │win      │alt      │ space                                           │fn       │     │left   │down   │right  ║
      , KC_LCTL , KC_LGUI , KC_LALT ,                   KC_SPC                        , _______ ,      KC_LEFT,KC_DOWN,KC_RGHT)
   // ╚═════════╧═════════╧═════════╧═════════════════════════════════════════════════╧═════════╧═════╧═══════╧═══════╧═══════╝
};

Actually, that is very readable in my opinion (though I probably invented the wheel here). Even better than the small numberpad example I used earlier, despite this being a lot larger. Remember this is C source code, all lines starting with // will be ignored by the compiler. That just leaves the lines with the comma-separated lists of keycodes that are actually processed by the compiler. This way it is both human- and computer readable.

Layers

Layers are the method used by QMK to have multiple functions on a single key. For example, my keyboard lacks a “Print screen” button, but I would still like to access that function. I will configure QMK so the “P” key has two functions: just a regular “p” when the key is pressed, but “Print Screen” when the key is pressed together with the “Fn” key.

To achieve this, I need two things:

  • A new layer with the new key bind
  • A way to activate the new layer

Create new layer

Layers are keymaps, exactly like the base layout we already defined. In fact, the base layout is also a layer, namely the one with number 0. New layers can be added in keymap.c, separated by a comma. Layers are automatically numbered, starting from 0:

#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
   KEYMAP ( /* List of keycodes for layer 0 */ ),
   KEYMAP ( /* List of keycodes for layer 1 */ ),
   KEYMAP ( /* ...etc                       */ )
}

A layer can be seen as a transparent sheet that is put on top of the layers below. Key codes on the new layer will obscure and take precedence over the key codes on the lower layer. So I will create a new layer with the KC_PSCR code assigned to the “P” key. To all the other keys on the new layer I assign the keycode _______ (7 underscores). This makes that key on the new layer transparent, so the definition on the base layer is still visible.

Activate the layer

The new layer should be activated by pressing and holding the Fn key. This is achieved by assigning the keycode MO(1)2 to the “Fn” key on layer 0.

Full keymap

Below is the final definition of my keymap, including the Print Screen mapping. Layer 1 is activated with the key marked “Caps lock” and layer 2 with the “Fn” key. I hardly ever use Caps Lock, but the key is big and in a very accessible place. I would rather see “Fn2” printed on that keycap, but that would require a more expensive keycap set, so I will have to live with the incorrect print for now.

I incorporated the following functions with layers:

Image

This translates to the following keymap configuration (with the ASCII formatting):

 File: keymap.c
#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
   KEYMAP
   // LAYER 0 - Base layer
   // ╔═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════════════╗
   // ║esc    │1      │2      │3      │4      │5      │6      │7      │8      │9      │0      │─      │=      │backspace      ║
      (KC_ESC , KC_1  , KC_2  , KC_3  , KC_4  , KC_5  , KC_6  , KC_7  , KC_8  , KC_9  , KC_0  ,KC_MINS,KC_EQL ,   KC_BSPC     \
   // ╟───────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───────────╢
   // ║tab        │q      │w      │e      │r      │t      │y      │u      │i      │o      │p      │[      │del    │\          ║
      ,  KC_TAB   , KC_Q  , KC_W  , KC_E  , KC_R  , KC_T  , KC_Y  , KC_U  , KC_I  , KC_O  , KC_P  ,KC_LBRC,KC_DEL ,  KC_BSLS  \
   // ╟───────────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴───────────╢
   // ║caps         │a      │s      │d      │f      │g      │h      │j      │k      │l      │;      │'      │enter            ║
      ,    MO(2)    , KC_A  , KC_S  , KC_D  , KC_F  , KC_G  , KC_H  , KC_J  , KC_K  , KC_L  ,KC_SCLN,KC_QUOT,     KC_ENT      \
   // ╟─────────────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴─┬───────┬───────╢
   // ║shift            │z      │x      │c      │v      │b      │n      │m      │,      │.      │/      │     │up     │       ║
      ,     KC_LSFT     , KC_Z  , KC_X  , KC_C  , KC_V  , KC_B  , KC_N  , KC_M  ,KC_COMM,KC_DOT ,KC_SLSH,       KC_UP         \
   // ╟─────────┬───────┴─┬─────┴───┬───┴───────┴───────┴───────┴───────┴───────┴─────┬─┴───────┼─────┬─┴─────┼───────┼───────╢
   // ║ctrl     │win      │alt      │ space                                           │fn       │     │left   │down   │right  ║
      , KC_LCTL , KC_LGUI , KC_LALT ,                   KC_SPC                        ,  MO(1)  ,      KC_LEFT,KC_DOWN,KC_RGHT)
   // ╚═════════╧═════════╧═════════╧═════════════════════════════════════════════════╧═════════╧═════╧═══════╧═══════╧═══════╝
   , KEYMAP
   // Layer 1 - activated with Fn Key
   // ╔═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════════════╗
   // ║esc    │1      │2      │3      │4      │5      │6      │7      │8      │9      │0      │─      │=      │backspace      ║
      (KC_GRV , KC_F1 , KC_F2 , KC_F3 , KC_F4 , KC_F5 , KC_F6 , KC_F7 , KC_F8 , KC_F9 ,KC_F10 ,KC_F11 ,KC_F12 ,    _______    \
   // ╟───────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───────────╢
   // ║tab        │q      │w      │e      │r      │t      │y      │u      │i      │o      │p      │[      │del    │\          ║
      ,  _______  ,_______,_______,_______,_______,_______,_______,_______,_______,_______,KC_PSCR,KC_RBRC,_______,  _______  \
   // ╟───────────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴───────────╢
   // ║caps         │a      │s      │d      │f      │g      │h      │j      │k      │l      │;      │'      │enter            ║
      ,   _______   ,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,     _______     \
   // ╟─────────────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴─┬───────┬───────╢
   // ║shift            │z      │x      │c      │v      │b      │n      │m      │,      │.      │/      │     │up     │       ║
      ,     _______     ,_______,_______,KC_CAPS,_______,_______,_______,_______,KC_VOLD,KC_VOLU,KC_MUTE,      _______        \
   // ╟─────────┬───────┴─┬─────┴───┬───┴───────┴───────┴───────┴───────┴───────┴─────┬─┴───────┼─────┬─┴─────┼───────┼───────╢
   // ║ctrl     │win      │alt      │ space                                           │fn       │     │left   │down   │right  ║
      , _______ , _______ , _______ ,                   _______                       , _______ ,      _______,_______,_______)
   // ╚═════════╧═════════╧═════════╧═════════════════════════════════════════════════╧═════════╧═════╧═══════╧═══════╧═══════╝
   , KEYMAP
   // Layer 2 - activated with caps Key
   // ╔═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════╤═══════════════╗
   // ║esc    │1      │2      │3      │4      │5      │6      │7      │8      │9      │0      │─      │=      │backspace      ║
      (_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,    _______    \
   // ╟───────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───────────╢
   // ║tab        │q      │w      │e      │r      │t      │y      │u      │i      │o      │p      │[      │del    │\          ║
      ,  _______  ,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,  _______  \
   // ╟───────────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴───────────╢
   // ║caps         │a      │s      │d      │f      │g      │h      │j      │k      │l      │;      │'      │enter            ║
      ,   _______   ,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,     _______     \
   // ╟─────────────┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴─┬───────┬───────╢
   // ║shift            │z      │x      │c      │v      │b      │n      │m      │,      │.      │/      │     │up     │       ║
      ,     _______     ,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,      KC_PGUP        \
   // ╟─────────┬───────┴─┬─────┴───┬───┴───────┴───────┴───────┴───────┴───────┴─────┬─┴───────┼─────┬─┴─────┼───────┼───────╢
   // ║ctrl     │win      │alt      │ space                                           │fn       │     │left   │down   │right  ║
      , _______ , _______ , _______ ,                   _______                       , _______ ,      KC_HOME,KC_PGDN,KC_END )
   // ╚═════════╧═════════╧═════════╧═════════════════════════════════════════════════╧═════════╧═════╧═══════╧═══════╧═══════╝
};

  1. It should be possible to generate this code and the ASCII lines using a JSON file from the Keyboard Layout Editor as input. That is a project I will save for a later date. ↩︎

  2. MO stands for “momentarily activate layer”. Other methods for activating layers in different ways and with different behavior can be found in the QMK documentation↩︎