· 7 min read

Mechanical Keyboard Primer - Firmware Edition

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Although the title says “Primer,” this article will focus more on how keyboards work under the hood and their circuitry.

Detecting Keypresses

As mentioned in the previous article, a keyboard is essentially a circuit made up of multiple switches. We can use a microcontroller’s GPIO pins to detect whether a switch has been pressed. Specifically, one end of the switch connects to power, and the other connects to ground. When a key is pressed, the circuit is completed, and the microcontroller detects this state change to send out the corresponding signal. What this signal actually looks like will be covered in later sections.

However, this approach has a drawback: every single key requires its own pin. For a 104-key keyboard, that would mean needing a microcontroller with 104 pins. Microcontrollers typically don’t have that many available pins, or some pins need to be reserved for other purposes.

Scanning and the Keyboard Matrix

In practice, we wire keyboard circuits using a matrix topology. Each row and column in the matrix connects to a microcontroller pin. This way, if you need 60 keys, a 6x10 matrix only requires 16 pins.

test1

We can sequentially set R1, R2, and R3 to high, and read the states of C1, C2, and C3 respectively to determine which key is pressed. For example, when the key in the diagram below is pressed, C1 reads a high state, allowing us to detect which switch was activated.

test3

The sequence goes like this:

  • Set R1 to HIGH
    • Read C1
    • Read C2
    • Read C3
  • Set R2 to HIGH
    • Read C1
    • Read C2
    • Read C3
  • Set R3 to HIGH
    • Read C1
    • Read C2
    • Read C3

Ideally, the matrix would have an equal number of rows and columns, but in practice, that’s not always the case. For one, microcontrollers usually have plenty of pins anyway. For another, forcing equal numbers can make PCB routing very difficult—keyboards naturally have far more columns than rows.

A question might arise: what if keys in R1 and R2 are pressed at the exact same time? Will they be missed since they are scanned sequentially rather than simultaneously? There’s no need to worry about this. Typical microcontrollers run at at least 16MHz or higher, making the scanning speed vastly faster than any human reaction limit. The microcontroller remembers the pressed state; when it reads again and notices the state has changed back, it sends a key release signal.

Ghosting

test2

The previous circuit has a flaw: if a switch in R3 is also pressed, some current can sneak through alternative paths, leading to false detections (ghosting). The solution is to add diodes. Diodes allow current to flow in only one direction, effectively preventing ghosting.

HID (Human Interface Device)

Next comes the logic of sending signals to the computer. Early keyboards used PS/2 connectors, but today USB and Bluetooth are the most common interfaces.

HID standardizes protocols for common input devices—such as keyboards, mice, and game controllers. Instead of requiring custom device drivers for everything, any device compliant with the HID specification works driver-free right out of the box.

HID involves input reports, descriptors, and output reports. Descriptors are typically used to describe what the device is, including its usage, name, vendor, product ID, and more. Once the computer reads these descriptors, it knows how to handle the device.

USB

Generally, microcontrollers come with built-in USB peripheral support to transmit data in USB format.

The same goes for HID, which is usually transmitted via USB or Bluetooth. Here are a few technical details engineers tend to care about: USB communication is split into host and device roles.

Because input devices like mice and keyboards operate much slower than CPUs, having the CPU wait on these devices would waste immense processing power. In practice, the computer’s USB controller (the host) continuously polls the device for data, and triggers a CPU interrupt only when there is data in the buffer. Therefore, the keyboard’s job is simply to keep pushing data, leaving the rest to the host.

Normally, constructing the HID format is handled by libraries, so you only need to focus on mapping physical keys to their respective keycodes.

Here is an example using the Raspberry Pi Pico:

static void send_hid_report(uint8_t report_id, uint32_t btn)
{
  // skip if hid is not ready yet
  if ( !tud_hid_ready() ) return;

  switch(report_id)
  {
    case REPORT_ID_KEYBOARD:
    {
      // use to avoid send multiple consecutive zero report for keyboard
      static bool has_keyboard_key = false;

      if ( btn )
      {
        uint8_t keycode[6] = { 0 };
        keycode[0] = HID_KEY_A;

        tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
        has_keyboard_key = true;
      }
      ...
}

This example only has one button, and it always sends the ‘A’ key. However, the code gives us a good idea of how it works. It first declares an array called keycode. A length of 6 means it can transmit up to 6 simultaneous keypresses (6KRO); here only keycode[0] is used, followed by calling tud_hid_keyboard_report to send the HID report.

PCB Design and Routing

I am by no means an expert in PCB design; my main point here is that anyone with basic knowledge can design a circuit board. In PCB design, there is a very famous open-source software tool called KiCad, which is completely free.

Using this software, you can design your schematic and route traces on a PCB layout. You can place your components and run the traces across the board.

Once everything is ready, you can export the production files (such as Gerbers) and upload them to a PCB fabrication service. You will receive your manufactured boards within a few weeks. Common, budget-friendly manufacturers include JLCPCB and PCBWay.

QMK

In the custom keyboard community, enthusiasts are obsessed with high degrees of customization—RGB lighting effects, layout remapping, rotary encoders, and even onboard LCD screens. Everyone has their own preferences, so beyond hardware craftsmanship, firmware must be highly customizable as well.

(Image source: Zoom75 official website)

Feature Case

Theoretically, all you need to write keyboard firmware is a microcontroller and a circuit. However, having to rewrite code every time you change layouts—or rewrite everything if you swap microcontrollers—is cumbersome. Because of this, developers created firmware that allows extensive configuration: define your keyboard settings, and it automatically generates the firmware image without you writing a single line of C code.

The most famous firmware in the keyboard community right now is QMK, an open-source firmware derived from TMK. It is widely adopted across custom builds, and even commercial brands like Keychron support QMK.

QMK supports almost every feature mentioned earlier: key remapping, macros, LCDs, LED control, Bluetooth, joysticks, and even MIDI. If you can think of it, QMK probably has it. It also supports an enormous range of custom keyboards; for instance, the definition files for my current daily driver, the Zoom65, can be found here.

Furthermore, what surprises me is modern support for WebUSB, which lets you remap keys directly through a web browser and update settings on the fly.

I haven’t dug deep into the underlying mechanics yet, but I imagine there is firmware code that receives data over WebUSB to modify keymaps directly in EEPROM. Those interested can check out VIA.

Conclusion

Nowadays, many open-source models and schematics are available for both PCBs and cases. While building a keyboard “from scratch” still poses a challenge, it is vastly more accessible than it used to be. Even so, just understanding the process behind it makes you appreciate that building a keyboard is no small feat.

Related Posts

Explore Other Topics