Learn QMK Keyboard Firmware: From Zero to Keyboard Ninja

Goal

Goal: Build a deep, end-to-end mental model of how a keypress becomes a USB HID report, how QMK turns that report into layers and behaviors, and how the hardware matrix, firmware, and host OS interact. By finishing this guide you will be able to design a keyboard matrix, wire or PCB it correctly, compile and flash QMK, and reason about latency, ghosting, debounce, and power trade-offs. You will be able to extend QMK with custom key behaviors, integrate peripherals like RGB and OLED, and debug real hardware with confidence. Finally, you will be able to build a keyboard from scratch or design a small product line that is production-ready.

Introduction

QMK (Quantum Mechanical Keyboard) firmware is an open-source firmware framework for custom keyboards that runs on a wide range of microcontrollers and turns switch matrix state into USB HID key reports. It lets you define layers, macros, tap-hold behavior, and hardware features like RGB, OLED, and encoders without rewriting low-level USB or matrix scanning code. QMK supports over 3000 keyboards and is the de facto standard for wired custom keyboards (see QMK Firmware).

What you will build (by the end of this guide):

  • A software matrix simulator that explains ghosting and diode direction.
  • A minimal USB HID keyboard device and a full QMK-powered macro pad.
  • A full custom keyboard with layers, RGB/OLED feedback, and a split design.
  • A production-grade custom keyboard firmware or product line.

Scope (what’s included):

  • Keyboard matrix design, scanning, and debouncing.
  • USB HID fundamentals and QMK architecture.
  • QMK keymaps, layers, and advanced behaviors.
  • Peripheral integration (RGB, OLED, encoders) and split keyboards.
  • Wireless concepts via ZMK for BLE keyboards.

Out of scope (for this guide):

  • Full USB host stack implementation.
  • Mechanical switch physics beyond debounce timing.
  • RF design and antenna tuning for BLE.

The Big Picture (Mental Model)

Physical Switch  ->  Electrical Matrix  ->  MCU Scan Loop  ->  QMK Key Processing  ->  USB HID Report  ->  OS Input
     |                    |                    |                   |                      |                  |
     |                    |                    |                   |                      |                  v
  Mechanical           Rows/Cols           Debounce             Layers/Macros         Interrupt IN       Application
  bounce +             + diodes            + timing             Tap-Hold, Combos       endpoint           receives text
  contact              + pull-ups          + state              Key Overrides

Key Terms You’ll See Everywhere

  • Matrix: A grid of rows and columns that reduces pin count for many keys.
  • Debounce: Filtering mechanical bounce so one press equals one event.
  • HID Report: The byte packet a keyboard sends to the host over USB.
  • Layer: A virtual keymap that changes what a physical key does.
  • Keycode: The logical code (USB usage) sent to the host.

How to Use This Guide

  1. Read the Theory Primer first. The projects assume you already understand matrix scanning, HID reports, and QMK’s flow.
  2. Build in order if you’re new. Each project introduces only one major new concept.
  3. Keep a lab notebook. Capture scan rates, debounce timings, pin mappings, and any bugs.
  4. Don’t skip the “Definition of Done”. The goal is repeatable, measurable results, not just “it works.”
  5. Use the layered hints when stuck. The goal is to learn, not to suffer.

Prerequisites & Background Knowledge

Essential Prerequisites (Must Have)

Programming Skills:

  • Basic C reading/writing (functions, arrays, structs).
  • Comfort with compiling and flashing firmware.
  • Ability to read datasheets at a high level (pinouts, power, USB).

Embedded Fundamentals:

  • Understanding of digital input/output and pull-up resistors.
  • Basic soldering and circuit safety.
  • Recommended Reading: “Making Embedded Systems, 2nd Edition” by Elecia White — Ch. 1-3.

Digital Logic Basics:

  • How a matrix reduces wiring and how diodes enforce direction.
  • Recommended Reading: “Digital Design and Computer Architecture, 2nd Edition” by Harris & Harris — Ch. 1-2.

Helpful But Not Required

USB Protocol Details:

  • Descriptor tables, endpoint types, and polling.
  • Can learn during: Project 2 and Project 10.

PCB Design:

  • Schematic capture, routing, DRC rules.
  • Can learn during: Project 7.

RTOS & BLE:

  • GATT, connection intervals, sleep states.
  • Can learn during: Project 9.

Self-Assessment Questions

  1. Can you wire a button to a microcontroller and read its state?
  2. Can you explain what a pull-up resistor does in an input circuit?
  3. Have you compiled and flashed firmware to any board before?
  4. Can you read a microcontroller pinout and power requirements?
  5. Are you comfortable with a command-line toolchain?

If you answered “no” to questions 1-3: Spend 1-2 weeks with a basic Arduino or AVR project first.

Development Environment Setup

Required Tools:

  • A Linux/macOS/Windows machine with USB access.
  • QMK CLI (Python 3.9+ required; see QMK CLI docs).
  • Git and a C toolchain (AVR or ARM depending on board).
  • A supported keyboard or microcontroller board.

Recommended Tools:

  • Logic analyzer or oscilloscope for timing verification.
  • QMK Toolbox or a serial console for flashing/debugging.
  • KiCad for PCB work (Project 7).

Testing Your Setup:

$ qmk --version
qmk 1.x.y

$ qmk doctor
☑ QMK is ready to go

Time Investment

  • Simple projects (1, 3, 4): 4-10 hours each
  • Moderate projects (2, 5, 6): 1-2 weeks each
  • Complex projects (7, 8): 4-8 weeks each
  • Advanced projects (9, 10, 11): 6 weeks to several months

Important Reality Check

Keyboard firmware looks simple but is timing-sensitive and hardware-dependent. Expect to iterate on wiring, debounce values, and pin mappings. You are learning to build a system with strict latency expectations and zero tolerance for missed inputs. This is normal. Mastery happens by building, breaking, and rebuilding.

Big Picture / Mental Model

                          QMK KEYBOARD FIRMWARE STACK

┌─────────────────────────────────────────────────────────────────────┐
│                        KEYMAP & USER LAYER                          │
│   keymap.c  |  config.h  |  rules.mk  |  custom_keycodes.c          │
└─────────────────────────────────────────────────────────────────────┘
                                   |
                                   v
┌─────────────────────────────────────────────────────────────────────┐
│                 QMK CORE (Quantum + TMK internals)                  │
│   matrix_scan -> debounce -> process_record -> feature handlers     │
└─────────────────────────────────────────────────────────────────────┘
                                   |
                                   v
┌─────────────────────────────────────────────────────────────────────┐
│                MCU + HAL (AVR/ARM, LUFA/ChibiOS/etc.)                │
│       GPIO, timers, USB interrupts, I2C/UART, DMA, PIO              │
└─────────────────────────────────────────────────────────────────────┘
                                   |
                                   v
┌─────────────────────────────────────────────────────────────────────┐
│                         USB HID / BLE HID                           │
│        HID report descriptor -> Interrupt IN -> Host polling        │
└─────────────────────────────────────────────────────────────────────┘

Theory Primer (Read This Before Coding)

This section is the mini-book. Read it once end-to-end, then use it as a reference while building projects.

Concept 1: Matrix Electrical Design, Scanning, and Ghosting

Fundamentals

A keyboard matrix is a wiring strategy that lets you read many keys using far fewer microcontroller pins. Instead of wiring every switch directly to the MCU, switches are arranged at the intersections of rows and columns. The firmware drives one column at a time (or one row at a time) and reads the other side to see which switches are closed. This reduces wiring from N keys to roughly R + C pins. The trade-off is that the firmware must actively scan the matrix and deal with electrical artifacts like ghosting and masking. Diodes are often placed in series with each switch so that current only flows in one direction, preventing phantom keypresses when multiple keys are pressed. Understanding how pull-ups, diode direction, and matrix scanning work is foundational: every other feature in QMK builds on this physical layer. QMK’s “How a Matrix Works” documentation is the canonical reference for how row/column scanning is implemented and why diode direction matters (QMK matrix docs).

Deep Dive into the Concept

A matrix is a grid: rows and columns form a set of intersections, and each switch connects one row to one column when pressed. In firmware, one side (columns or rows) is configured as outputs, and the other side is configured as inputs with pull-ups or pull-downs. Most QMK keyboards use active-low logic: the firmware drives one column LOW at a time, and the row inputs are pulled HIGH by internal pull-ups. When a key is pressed at the active column, the corresponding row reads LOW. This is the simplest scanning algorithm and the one you will implement in Project 1. The reason you scan is to disambiguate which switch is closed. If you drove all columns at once, you couldn’t tell which column caused the row change; scanning isolates one column at a time.

Without diodes, pressing multiple keys can create alternative current paths that trick the firmware into thinking a different key is pressed. This is ghosting. For example, if you press three keys that form a rectangle in the matrix, the fourth corner can appear pressed due to unintended current flow. Masking is a related phenomenon where some actual presses are not detected because the matrix wiring and the scan algorithm cannot distinguish them. Diodes fix this by forcing current to flow only from column to row (or row to column, depending on orientation). QMK requires you to specify the diode direction so it scans the matrix in the correct direction. This is why you see settings like COL2ROW or ROW2COL in QMK configurations. A diode is not a debounce device; it only constrains current paths, so you still need debounce logic in firmware. QMK provides multiple debounce algorithms that trade responsiveness for stability, configured via DEBOUNCE and DEBOUNCE_TYPE (QMK debounce options).

Matrix size and wiring trade-offs matter in real hardware. More rows and fewer columns can reduce output drive complexity but may increase the number of input pins (and vice versa). Internal pull-ups are convenient, but they vary between MCUs and may be too weak for long traces or noisy environments. Designers may add external pull-ups for signal stability. The matrix wiring also impacts routing complexity on a PCB: long row traces can pick up noise, and dense matrices can cause crosstalk or scanning errors. Understanding these electrical realities helps you interpret “random” firmware bugs that are actually wiring or impedance issues.

QMK’s matrix scanning model expects stable, predictable voltage levels. If you use a diode, you must place it in the correct orientation and ensure the firmware’s diode direction matches. If not, a pressed switch may never be detected or could be detected on the wrong scan phase. QMK documents the matrix scanning model explicitly because it influences how you define rows and columns in config.h and how you wire your PCB. This is why the first projects in this guide are about simulation and a macro pad before building a full board: it forces you to build a mental model that maps wiring to firmware behavior, and that is the single most important skill you need before diving into QMK features.

How This Fits in Projects

  • Project 1 builds a matrix simulator to visualize scanning and ghosting.
  • Project 3 uses a real 3x3 matrix and requires correct diode direction.
  • Project 7 (PCB design) uses this to design the schematic and routing.
  • Project 8 (split keyboard) uses two matrices and synchronizes their scans.
  • Project 10 requires you to implement scanning and ghost prevention yourself.

Definitions & Key Terms

  • Row/Column: The two axes of a matrix; switches connect one row to one column.
  • Active-low: A logic convention where LOW indicates an active signal.
  • Ghosting: Phantom keypresses caused by unintended current paths.
  • Masking: Missed keypresses caused by ambiguous matrix states.
  • Debounce: Filtering switch bounce into a single logical event.
  • DEBOUNCE/DEBOUNCE_TYPE: QMK settings that select debounce timing and algorithm.
  • Diode direction: The orientation (COL2ROW or ROW2COL) that determines scan polarity.
  • Pull-up: A resistor (internal or external) that biases an input to HIGH.

Mental Model Diagram

Columns driven LOW one at a time

     C0    C1    C2
     |     |     |
R0 --o-----o-----o--  inputs read
     |     |     |
R1 --o-----o-----o--  HIGH unless switch closes
     |     |     |
R2 --o-----o-----o--  LOW when active column + pressed

Diode direction (COL2ROW):
C ->|--- switch --- R

How It Works (Step-by-Step)

  1. Configure row pins as inputs with pull-ups; columns as outputs.
  2. Drive all columns HIGH (inactive).
  3. Drive column 0 LOW.
  4. Read all row inputs; any LOW means a key is pressed at that row/column.
  5. Drive column 0 HIGH, drive column 1 LOW, repeat.
  6. Build a matrix state table and pass changes to key processing.

Minimal Concrete Example

// Pseudo-C for a 2x2 matrix scan
bool matrix[2][2];

for (int col = 0; col < 2; col++) {
    set_all_cols_high();
    set_col_low(col);
    wait_us(30); // settle time
    for (int row = 0; row < 2; row++) {
        matrix[row][col] = (read_row(row) == LOW);
    }
}

Common Misconceptions

  • “Diodes fix switch bounce.” -> Diodes only prevent ghosting; debouncing is separate.
  • “More rows always equals faster scanning.” -> Pin count, trace length, and settling time still matter.
  • “Matrix scanning is purely software.” -> Wiring, diode orientation, and pull-up strength dominate reliability.

Check-Your-Understanding Questions

  1. Why does scanning one column at a time let you identify a specific key?
  2. What electrical path creates a ghosted key without diodes?
  3. How would you change the scan algorithm if the matrix is ROW2COL?

Check-Your-Understanding Answers

  1. It isolates the active column, so any row LOW must belong to that column.
  2. Pressed keys can connect rows and columns in a rectangle, creating a false path.
  3. You would drive rows and read columns, and you would flip the diode direction.

Real-World Applications

  • Keyboards, macro pads, game controllers, and industrial keypads.
  • Any device needing many buttons with minimal MCU pins.

Where You’ll Apply It

  • Project 1: Matrix simulator
  • Project 3: Macro pad
  • Project 7: PCB design
  • Project 8: Split keyboard
  • Project 10: Firmware from scratch

References

Key Insight

Key insight: A keyboard matrix is a hardware optimization that forces you to think in terms of scan timing, electrical paths, and ambiguity resolution.

Summary

The matrix is the physical foundation of keyboard firmware. The scan algorithm and diode direction create a deterministic mapping between electrical signals and logical key events. If you deeply understand this layer, every higher-level QMK feature becomes easier to reason about.

Homework/Exercises to Practice the Concept

  1. Draw a 3x3 matrix and label the rows/columns. Simulate pressing three keys that form a rectangle and identify the ghosted key.
  2. Write a short script that prints matrix state for every scan phase and injects a simulated bounce event.

Solutions to the Homework/Exercises

  1. The ghosted key is the fourth corner of the rectangle; current can flow through two pressed switches to create a false path.
  2. Add a 5ms bounce window and confirm that raw transitions collapse into a single press event.

Concept 2: USB HID and Report Descriptors

Fundamentals

USB HID (Human Interface Device) is a class specification that allows keyboards, mice, and other input devices to work without custom drivers on most operating systems. A HID device describes itself to the host using descriptors. The most important for firmware developers is the HID report descriptor, which defines the shape and meaning of the data packets (reports) that the device will send. Keyboards typically use an interrupt IN endpoint so the host can poll for key states at a predictable interval. The HID class also defines a boot protocol for keyboards, which simplifies compatibility but constrains the report format and often limits key rollover. Understanding descriptors, reports, and the host polling model is essential if you want to implement a keyboard without relying entirely on a framework like QMK. The Linux kernel HID documentation provides a clear explanation of how report descriptors define Input, Output, and Feature reports (kernel HID docs).

Deep Dive into the Concept

When a USB keyboard is plugged in, the host enumerates it: it requests device, configuration, interface, and endpoint descriptors. For HID devices, the host also requests the HID descriptor and the HID report descriptor. The report descriptor is a compact, byte-encoded program that tells the host how to parse incoming data. For a keyboard, the report usually contains modifier bits (Ctrl, Shift, Alt, GUI) and a list of pressed keycodes. The host then polls the interrupt IN endpoint at a fixed interval (commonly 1 ms for full-speed devices) and expects a report back. If no keys are pressed, the report still gets sent with zeroed fields, enabling the host to detect key releases.

The boot protocol exists to allow firmware and BIOS environments to use a simple, standard keyboard report without parsing complex report descriptors. This is why a boot keyboard is often limited to 6-key rollover (6KRO) plus modifiers. Many modern keyboards support NKRO (n-key rollover) by using a different report format, but they must still provide a boot report for compatibility. QMK can generate different report formats, but if you are building from scratch you must choose between simplicity (boot) and expressiveness (NKRO). The HID Usage Tables define the numeric usage IDs for keys (A, B, Enter, etc.) and for consumer/media controls. Even if you use QMK, understanding these usages helps you interpret custom keycodes and build accurate keymaps.

Report descriptors are structured as a sequence of items: Usage Page, Usage, Collection, Report Size, Report Count, Input/Output/Feature declarations, and so on. Each Input item defines a field in the report. A typical boot keyboard descriptor defines 8 modifier bits, one reserved byte, and 6 keycode bytes. The host uses the descriptor to parse the report into fields. If you get the descriptor wrong, the host may misinterpret your key reports or ignore them entirely. This is why a minimal HID keyboard project is so valuable: it forces you to understand enumeration, report descriptors, and the host polling model.

HID is intentionally host-driven. Unlike some protocols where the device pushes data whenever it wants, HID endpoints are interrupt endpoints that are still polled. This means your firmware must always be ready to supply the most recent key state when the host asks. Firmware must also obey USB timing rules and provide stable descriptors. If you later move to BLE, the HID over GATT profile uses similar logical concepts (reports and report maps) but transports them over GATT characteristics rather than USB endpoints.

Real keyboards also handle output reports, such as Caps Lock or Num Lock LEDs. That means your firmware must parse host-to-device reports and update LED state at the right time. If you define multiple report types (for example, keyboard + consumer controls), you may need Report IDs so the host can disambiguate them. OSes also cache HID descriptors; after firmware changes, you may need to unplug/replug or reset the device to force the host to fetch the new descriptor. When debugging, capture reports at the USB bus or use host tools to confirm the report bytes match the descriptor; mismatches are the most common source of “it enumerates but doesn’t type” bugs.

How This Fits in Projects

  • Project 2 builds a minimal HID keyboard from scratch.
  • Project 4 and Project 10 use HID concepts inside QMK or custom firmware.
  • Project 9 maps HID semantics to BLE GATT services.

Definitions & Key Terms

  • HID Report: A structured packet defined by the report descriptor.
  • Report Descriptor: Bytecode that declares report format to the host.
  • Usage Page/Usage: Identifiers that define what a field means (keyboard, consumer, etc.).
  • Interrupt IN Endpoint: USB endpoint polled by host for input data.
  • Boot Protocol: Simplified HID mode for BIOS/UEFI compatibility.

Mental Model Diagram

USB Enumeration Flow

Host                Device
 |  GET_DESCRIPTOR   |
 |-----------------> |
 |  Device/Config    |
 |<----------------- |
 |  HID Descriptor   |
 |<----------------- |
 |  Report Descriptor|
 |<----------------- |
 |  SET_PROTOCOL     |
 |-----------------> |
 |  Poll IN endpoint |
 |<----------------- |

How It Works (Step-by-Step)

  1. Device enumerates and provides device/config/interface descriptors.
  2. Host requests HID descriptor and report descriptor.
  3. Host selects boot or report protocol.
  4. Host polls the interrupt IN endpoint at a fixed interval.
  5. Device sends key state reports on each poll.

Minimal Concrete Example

// Simplified boot keyboard report (8 bytes)
typedef struct {
    uint8_t modifiers;   // bits for ctrl/shift/alt/gui
    uint8_t reserved;
    uint8_t keys[6];     // up to 6 simultaneous keys
} hid_boot_kbd_report_t;

Common Misconceptions

  • “HID devices push data whenever they want.” -> The host polls; the device responds.
  • “HID is just for keyboards.” -> HID defines input devices broadly (mouse, gamepad, etc.).
  • “NKRO is always better.” -> NKRO is more complex and must still support boot protocol for compatibility.

Check-Your-Understanding Questions

  1. Why does the host need the report descriptor?
  2. What is the practical benefit of the boot protocol?
  3. Why does a host poll a HID device instead of using interrupts from the device?

Check-Your-Understanding Answers

  1. It tells the host how to parse the report bytes into meaningful fields.
  2. It guarantees a simple report format for BIOS/UEFI and legacy environments.
  3. USB is host-controlled; polling is how the host schedules bus time.

Real-World Applications

  • USB keyboards and mice.
  • Consumer control devices (volume knobs, media keys).
  • Embedded keypads that must work without custom drivers.

Where You’ll Apply It

  • Project 2: USB HID from scratch
  • Project 4: QMK Hello World
  • Project 10: Custom firmware from scratch

References

Key Insight

Key insight: The report descriptor is the contract between your firmware and the host; everything else is an implementation detail.

Summary

USB HID is a descriptor-driven protocol. If you understand how the host learns your report format and polls for reports, you can build a keyboard on any MCU and reason about compatibility issues like boot protocol vs NKRO.

Homework/Exercises to Practice the Concept

  1. Write a minimal HID report descriptor for a 2-key keypad and annotate each item.
  2. Capture USB traffic from a keyboard using a USB analyzer or software tool and identify the report bytes.

Solutions to the Homework/Exercises

  1. Use a single Usage Page (Keyboard), define 2 key usages, set Report Size/Count accordingly, and declare an Input item.
  2. Find the interrupt IN transfers and decode the modifier byte and keycode bytes using the usage table.

Concept 3: QMK Architecture, Build System, and Flashing Toolchain

Fundamentals

QMK is not just a firmware binary; it’s a large codebase, a build system, and a set of conventions that map your keymap to a specific keyboard and microcontroller. The QMK repository contains common core logic (matrix scanning, debounce, key processing) and per-keyboard folders that define the hardware layout and pins. Your customization lives in a keymap folder with a keymap.c, config.h, and rules.mk. The QMK CLI orchestrates environment setup, compilation, and flashing, and it requires a Python environment to run. The QMK CLI documentation explicitly calls out required Python versions and the qmk setup, qmk compile, and qmk flash workflows (QMK CLI docs, CLI commands). Understanding the structure is essential because most “mystery bugs” are caused by mismatched keyboard definitions, wrong MCU settings, or incorrect build flags.

Deep Dive into the Concept

The QMK firmware repository is organized by keyboards, layouts, and features. Each keyboard folder contains the matrix pin definitions, layouts, and optional board-specific code. Each keymap is a subfolder under a keyboard and contains your keymap.c. The build system uses Make (or the QMK CLI wrapper) to compile the correct set of files for your keyboard’s MCU. The rules.mk file enables or disables features (RGB, OLED, audio) and therefore controls compile-time size and behavior. This is a crucial trade-off: enabling too many features can exceed flash size or increase scan latency. When you build, QMK combines: core firmware + keyboard definition + keymap + enabled features. The output is a firmware binary (e.g., .hex, .bin, .uf2) for flashing.

QMK CLI simplifies the build and flash process by detecting toolchains and by mapping your keyboard and keymap names to appropriate build targets. qmk setup clones the repo, configures submodules, and sets defaults; qmk compile -kb <keyboard> -km <keymap> builds; qmk flash can compile and flash in one step. For many boards, QMK can even put the device into bootloader mode automatically, but some require manual reset. The CLI provides a “doctor” command that checks your environment and informs you if your toolchain is missing. The official QMK CLI documentation is the authoritative reference for these workflows (QMK CLI docs).

QMK also integrates with the QMK Configurator, a web-based tool that lets you create keymaps without building the firmware locally. The configurator generates .hex or .bin firmware files for supported keyboards and represents keymaps as JSON internally, but it cannot include custom C logic. Understanding when to use the configurator versus a custom keymap is important: use the configurator for quick layout changes, but use a local keymap when you need custom behaviors. QMK’s documentation makes this distinction and explains the role of the configurator in the ecosystem (QMK Configurator docs, Configurator JSON format).

Flashing is the final step. Many QMK boards use common bootloaders (DFU, Caterina, UF2), and qmk flash knows how to handle several of them. The key is that you must match the correct bootloader protocol and the MCU. The QMK flashing documentation describes the expected workflow and parameters for different bootloaders (QMK CLI commands). If flashing fails, the issue is usually one of: wrong keyboard target, wrong MCU, missing udev rules (on Linux), or not entering bootloader mode properly. Knowing the build and flash pipeline makes these issues solvable instead of mysterious.

Build reproducibility matters when you start sharing firmware or shipping hardware. QMK supports userspace and shared keymap structures, but even without that, you should keep your rules.mk minimal and explicitly enable only the features you use. Link-time optimization (LTO) is a powerful tool for small MCUs, but it can complicate debugging because symbol names are stripped; you’ll want to toggle it on only after the firmware is stable. When things go wrong, qmk doctor and qmk clean can help you eliminate environmental and cached-build errors before you chase firmware bugs that are actually toolchain mismatches.

How This Fits in Projects

  • Project 3 and 4 rely on QMK CLI for build and flash.
  • Project 6 uses rules.mk to enable RGB/OLED features.
  • Project 10 mirrors the QMK build pipeline in your own firmware.

Definitions & Key Terms

  • keymap.c: Your custom keymap and user code.
  • config.h: Hardware and behavior configuration.
  • rules.mk: Feature flags that control compilation.
  • QMK CLI: The command-line tool for setup, build, and flash.
  • Bootloader: Small program that accepts new firmware images.

Mental Model Diagram

QMK Build Pipeline

Keymap + Keyboard + Core + Features -> Compile -> Firmware Binary -> Flash -> Keyboard

How It Works (Step-by-Step)

  1. Choose a keyboard definition and create a keymap folder.
  2. Edit keymap.c/config.h/rules.mk to enable features.
  3. Run qmk compile to build the firmware.
  4. Put the board in bootloader mode.
  5. Run qmk flash (or use QMK Toolbox) to program the MCU.

Minimal Concrete Example

# rules.mk
RGB_MATRIX_ENABLE = yes
OLED_ENABLE = yes
LTO_ENABLE = yes
// config.h
#define MATRIX_ROWS 4
#define MATRIX_COLS 4
#define DEBOUNCE 5

Common Misconceptions

  • “QMK Configurator replaces local builds.” -> It cannot compile custom C logic.
  • “rules.mk only affects build time.” -> It changes firmware size and runtime behavior.
  • “If it compiles, it will flash.” -> Bootloader mode and correct MCU settings still matter.

Check-Your-Understanding Questions

  1. What files does QMK combine to build a firmware image?
  2. Why would enabling too many features break a build?
  3. What is the difference between QMK Configurator and a local keymap?

Check-Your-Understanding Answers

  1. Core firmware + keyboard definition + keymap + enabled features.
  2. It can exceed flash memory or conflict with other feature settings.
  3. Configurator creates layouts but cannot include custom C code.

Real-World Applications

  • Rapidly iterating custom layouts for a production keyboard.
  • Building multiple firmware variants for different users or layouts.

Where You’ll Apply It

  • Project 3: Macro pad
  • Project 4: QMK Hello World
  • Project 6: RGB + OLED
  • Project 10: Firmware from scratch

References

Key Insight

Key insight: QMK is a build-time composition system; your firmware is the sum of definitions, features, and your keymap.

Summary

Understanding QMK’s build and flashing pipeline turns firmware development into a deterministic process rather than guesswork. If you can trace from config files to the compiled binary, you can debug almost any QMK issue.

Homework/Exercises to Practice the Concept

  1. Create a new keymap folder and build a firmware image without flashing it.
  2. Toggle a feature in rules.mk and compare firmware size with arm-none-eabi-size.

Solutions to the Homework/Exercises

  1. Use qmk new-keymap or manually create the folder, then run qmk compile.
  2. Enable/disable RGB_MATRIX and compare the text/dec size output.

Concept 4: Keymaps, Layers, and Key Processing

Fundamentals

QMK key processing turns a matrix position into a keycode by looking up the active layer stack and resolving the final action. The keymap is a 3D array: layers × rows × columns. QMK supports many layers and treats them as a stack, allowing momentary and toggle layers to overlay base layers. QMK also defines advanced keycodes such as Mod-Tap (tap for character, hold for modifier), Tap Dance (different outputs based on tap count), and Combos (pressing multiple keys at once triggers a different output). These features are implemented in the key processing pipeline, which calls user hooks like process_record_user to allow custom behavior. The QMK keymap documentation explains the layer model and how keymaps are defined (QMK keymap docs).

Deep Dive into the Concept

A QMK keymap is not just a layout; it is a runtime state machine. The firmware tracks the current layer state and processes key events with a series of feature handlers. When a key changes state, QMK looks up the keycode at [layer][row][col]. If that keycode is KC_TRNS (transparent), it falls back to the next active layer beneath it. If the keycode is a layer-tap or mod-tap, QMK begins timing logic to decide whether the user intended a tap or a hold. The core of this system is the process_record flow: it receives a keycode and a key event and then calls feature handlers (combo, tap dance, leader, etc.) before registering or unregistering the final keycode.

Layer logic can be deceptively complex. Momentary layers (MO) are active only while a key is held. Toggle layers (TG) persist until toggled off. One-shot layers (OSL) apply to the next keypress and then automatically clear. Default layers are separate from dynamic layer state; they represent the base layout and can be changed via DF (default layer) commands. The key insight is that layer state and default layer state are different and must be managed separately in firmware. QMK’s layer documentation explicitly describes these behaviors (QMK keymap docs).

Mod-tap and tap-hold features are powerful but timing-sensitive. You must choose a tapping term that feels good for your typing speed. If the term is too short, holds will be misinterpreted as taps; if too long, tapping latency increases. QMK provides configuration knobs like TAPPING_TERM and per-key overrides. Tap dance allows different outputs based on single vs double taps, which is great for dense layouts but can introduce cognitive load. Combos define chorded presses, but you must consider timing windows and key travel so combos do not trigger during normal typing. Key overrides allow changing output based on modifiers. All of these features are documented in QMK’s feature pages (Combos, Tap Dance, Mod-Tap).

Advanced key processing requires a solid mental model of the event pipeline and of how QMK builds the HID report. For example, if you register a keycode in process_record_user but return false, you block further processing of that key, which can disable features like combos or tap dance. If you use register_code/unregister_code, you must ensure the release happens or the key will get “stuck”. Many QMK “bugs” in custom keymaps are actually logic errors in the user hooks. Understanding the pipeline lets you design custom behaviors without breaking core features.

One-shot modifiers and key overrides add another layer of complexity. A one-shot modifier sets a modifier for the next keypress only, which is incredibly useful on small layouts, but it depends on a precise event order in the processing chain. Key overrides allow you to remap a key when a certain modifier is held (for example, Shift+Backspace -> Delete), which must be resolved after layer lookup but before the final report is built. When you combine one-shot modifiers, mod-tap keys, and overrides, timing and precedence become the design problem. The best approach is to define a clear priority order and test it with a small, reproducible keymap before expanding to a full layout.

How This Fits in Projects

  • Project 4 introduces a basic keymap and keycode definitions.
  • Project 5 focuses on layers, tap dance, and mod-tap.
  • Project 6 uses layers to control RGB/OLED modes.
  • Project 10 re-implements this pipeline in your own firmware.

Definitions & Key Terms

  • Layer: A virtual keymap that can be stacked and switched.
  • KC_TRNS: Transparent keycode that falls through to lower layers.
  • Mod-Tap: Tap for character, hold for modifier.
  • Tap Dance: Different actions based on tap count.
  • Combo: Simultaneous key chord that triggers a separate action.

Mental Model Diagram

Key Event -> Layer Lookup -> Feature Handlers -> Register/Unregister -> HID Report
         (stack + fallthrough)   (tap dance, combo, mod-tap)

How It Works (Step-by-Step)

  1. Matrix event occurs (press/release).
  2. Active layer stack is evaluated for the key position.
  3. Keycode is passed through feature handlers.
  4. The final keycode is registered or unregistered.
  5. The HID report is updated and sent to host.

Minimal Concrete Example

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case MY_MACRO:
            if (record->event.pressed) {
                SEND_STRING("QMK!");
            }
            return false; // Stop further processing
    }
    return true; // Continue normal processing
}

Common Misconceptions

  • “Layers are just alternative layouts.” -> They are a stack with fall-through behavior.
  • “Mod-tap is always ergonomic.” -> Poor timing settings can cause errors.
  • “Returning false is harmless.” -> It can bypass crucial QMK features.

Check-Your-Understanding Questions

  1. How does QMK decide which layer’s keycode to use?
  2. Why might a mod-tap key feel unreliable?
  3. What happens if you never unregister a key?

Check-Your-Understanding Answers

  1. It checks the active layer stack and uses the top-most non-transparent keycode.
  2. Tapping term and typing speed may misclassify taps vs holds.
  3. The key stays “stuck” in the HID report until reset.

Real-World Applications

  • Highly optimized layouts (Colemak, custom layers).
  • One-handed layers for gaming or productivity.
  • Macro-heavy workflows for CAD or video editing.

Where You’ll Apply It

  • Project 4: QMK Hello World
  • Project 5: Layers & tap dance
  • Project 6: RGB + OLED behavior
  • Project 10: Custom firmware

References

Key Insight

Key insight: The power of QMK comes from treating key processing as a programmable state machine, not a static layout.

Summary

Layers and advanced keycodes are what make QMK unique. Once you understand the layer stack and the event pipeline, you can build layouts that are both compact and expressive without sacrificing typing speed.

Homework/Exercises to Practice the Concept

  1. Create a 2-layer keymap with a momentary layer key and a tap-hold modifier.
  2. Add a combo that triggers Escape when J+K are pressed.

Solutions to the Homework/Exercises

  1. Use MO(1) for the layer key and MT(MOD_LCTL, KC_A) for mod-tap.
  2. Define a combo array with {KC_J, KC_K} and assign KC_ESC.

Concept 5: Hardware Extensions (RGB, OLED, Encoders) and Split Keyboards

Fundamentals

Modern custom keyboards are more than switches: they include RGB lighting, OLED status displays, rotary encoders, and sometimes split architectures where two halves share a single USB connection. QMK provides standardized feature modules for RGB matrix effects, underglow, OLED text rendering, and encoder mapping (RGB Matrix, RGB Light, OLED, Encoders). These features are enabled at build time through rules.mk and configured with defines in config.h. A split keyboard adds another layer of complexity: the firmware must detect which half is connected to USB, synchronize matrix states, and exchange data over a transport (UART or I2C). QMK documents these features as separate modules because each has unique electrical and timing requirements. Understanding the hardware extension layer helps you design a keyboard that is both functional and reliable, and it allows you to integrate advanced hardware without writing everything from scratch.

Deep Dive into the Concept

RGB lighting in QMK is split into two major subsystems: RGB Matrix (per-key addressable RGB controlled by drivers like IS31FL3731 or WS2812) and RGB Light (underglow or simpler LED strips). The RGB Matrix system supports complex animations but consumes more CPU and memory; the RGB Light system is simpler and uses fewer resources. QMK’s RGB documentation lists supported drivers and the configuration flags you must enable in rules.mk and config.h. The key consideration is resource trade-off: enabling RGB Matrix on an MCU with limited flash or RAM can cause compile failures or runtime instability. This is why many smaller boards stick to simple underglow while higher-end boards use full per-key RGB.

OLED support is another optional feature. QMK supports common SSD1306/SH1106 displays and expects specific screen sizes (often 128x32 or 128x64). The OLED driver integrates into the main scan loop, which means you must keep rendering lightweight to avoid scan latency spikes. You also need to decide what information to render (layer status, typing speed, lock states). The OLED docs outline the available hooks (oled_task_user) and the expected display parameters (OLED docs). A good rule: treat the OLED as a low-frequency status UI rather than a high-frequency animation target.

Rotary encoders in QMK map clockwise/counterclockwise rotation to keycodes or custom functions. QMK’s encoder documentation describes how to set the number of encoders and define their behavior, and it supports encoder maps for per-layer behavior (Encoders docs). Encoders are conceptually simple, but in practice you must debounce their signals, ensure correct pin wiring, and consider the effect of rotation events on the key processing pipeline.

Split keyboards are effectively two matrices that must be combined into a single logical keymap. QMK’s split documentation explains how to define the master half, set handedness, and choose a transport (serial or I2C) (Split keyboard docs). The serial driver documentation adds electrical details: the signal is often open-drain and requires a pull-up resistor; if the resistor is too weak or strong, communication fails (Serial driver docs). The firmware must merge scan results from both halves and keep layer state synchronized. This means your matrix scan loop now includes an interconnect step that introduces latency and potential failure modes (e.g., one half disconnecting). Designing a split keyboard requires you to think about redundancy, failure detection, and power routing across the TRRS or JST cable.

Power and signal integrity are the quiet constraints here. Per-key RGB can draw hundreds of milliamps if you run full brightness, which may exceed USB limits without proper current budgeting. OLED displays and encoders also add I2C/SPI traffic that competes with the scan loop for time. On split builds, long cables can introduce line capacitance and ringing; you may need to slow the serial clock or add stronger pull-ups. Treat every peripheral as part of a system budget: CPU time, flash space, and electrical power. That mindset keeps the keyboard responsive even as you add features.

How This Fits in Projects

  • Project 6 adds RGB and OLED features using QMK modules.
  • Project 8 implements a split keyboard transport and synchronization.
  • Project 11 uses peripherals to create a polished product.

Definitions & Key Terms

  • RGB Matrix: Per-key RGB lighting driven by a dedicated driver.
  • RGB Light: Simpler underglow lighting system.
  • OLED Task: QMK hook that updates display contents.
  • Encoder Map: Per-layer mapping from encoder steps to actions.
  • Split Transport: Communication channel between halves (UART or I2C).

Mental Model Diagram

Split Keyboard + Peripherals

Left Half (USB)             Right Half (Slave)
[Matrix + RGB + OLED]  <---- Transport ---->  [Matrix + Encoder]
         |                                       |
         +---- QMK Core merges into single keymap

How It Works (Step-by-Step)

  1. Enable features in rules.mk (RGB, OLED, ENCODER, SPLIT).
  2. Configure pin assignments and driver settings in config.h.
  3. In the scan loop, read local matrix.
  4. Exchange matrix data with the other half over serial/I2C.
  5. Merge states and run key processing.
  6. Update RGB/OLED/encoder outputs.

Minimal Concrete Example

// config.h
#define RGB_MATRIX_ENABLE
#define OLED_DISPLAY_128X32
#define ENCODERS_PAD_A { B2 }
#define ENCODERS_PAD_B { B3 }
#define SPLIT_KEYBOARD
// keymap.c
bool oled_task_user(void) {
    oled_write_P(PSTR("Layer:"), false);
    oled_write(get_u8_str(get_highest_layer(layer_state), '0'), false);
    return false;
}

Common Misconceptions

  • “RGB just costs LEDs.” -> It also costs CPU time and flash memory.
  • “OLED can update every scan.” -> Heavy rendering can add scan latency.
  • “Split halves are independent.” -> They must synchronize matrix and state.

Check-Your-Understanding Questions

  1. Why is RGB Matrix heavier than RGB Light?
  2. What happens if a split half disconnects mid-scan?
  3. Why does the split serial line need a pull-up resistor?

Check-Your-Understanding Answers

  1. It requires per-key control, driver code, and animation logic.
  2. The master must handle missing data and avoid stale state.
  3. The line is open-drain; the pull-up sets a defined idle level.

Real-World Applications

  • Keyboards with per-key RGB effects and animated layers.
  • Status displays for lock states, battery, or mode indicators.
  • Split ergonomic keyboards with independent halves.

Where You’ll Apply It

  • Project 6: RGB + OLED
  • Project 8: Split keyboard
  • Project 11: Production-ready keyboard line

References

Key Insight

Key insight: Hardware features are not “free” add-ons; they reshape the firmware timing and resource budget.

Summary

Peripherals and split designs turn a simple keyboard into a complex embedded system. QMK makes them accessible, but you must understand the electrical and timing costs to build something reliable.

Homework/Exercises to Practice the Concept

  1. Design a status OLED layout that displays layer, caps lock, and typing speed.
  2. Create a split keyboard wiring plan and specify the transport and pull-up values.

Solutions to the Homework/Exercises

  1. Use oled_task_user to render small text updates only when the state changes.
  2. Choose UART split, add a 4.7k pull-up on the data line, and map pins in config.h.

Concept 6: Wireless (BLE HID) and Power Management

Fundamentals

Wireless keyboards use Bluetooth Low Energy (BLE) to deliver HID reports without a cable. Instead of USB endpoints, BLE uses GATT services and characteristics to expose HID reports, battery level, and device information. Power management becomes central: the keyboard must sleep aggressively when idle, wake on keypress, and balance connection interval for latency vs battery life. QMK has limited BLE support; most wireless custom keyboards use ZMK firmware, which is built on Zephyr RTOS and includes native BLE HID support. ZMK’s documentation highlights its wireless-first design and configuration model (ZMK docs, Bluetooth, Battery). Understanding BLE HID concepts helps you design keyboards that can run for weeks or months on a battery.

Deep Dive into the Concept

BLE keyboards use the HID over GATT profile (HOGP). Instead of USB descriptors, the device exposes a Report Map characteristic that tells the host how to interpret reports. The host (phone or laptop) connects, negotiates security and bonding, and then subscribes to notifications for the input report characteristic. When the keyboard detects a keypress, it sends a notification containing the report data. This is conceptually similar to USB HID, but with different transport and power constraints. BLE connections operate on connection intervals (for example, 7.5 ms to 4000 ms). Short intervals reduce latency but increase current draw; longer intervals save power but make the keyboard feel sluggish. The firmware must pick intervals based on typing activity, and many stacks support “connection parameter updates” to switch between fast and slow modes.

Power management is the defining challenge. A wireless keyboard spends most of its time idle, and the MCU must enter deep sleep, waking only on GPIO events. Debounce and matrix scanning still matter, but scanning continuously will drain the battery. The firmware must scan only when necessary and use GPIO interrupts or periodic wakeups. Battery monitoring is usually done via ADC on a voltage divider, and charging circuits (like MCP73831 or TP4056) add their own system requirements. ZMK provides hooks for battery level reporting and power management, but hardware design still determines battery life.

Another core concept is device bonding and profile switching. BLE devices can store multiple bonds and switch between hosts. This is a user-experience feature that requires firmware support, non-volatile storage, and UI feedback (LED or OLED). It also introduces edge cases like stale bonds and reconnection delays. A reliable BLE keyboard must handle reconnection quickly, gracefully fall back when the host is unavailable, and avoid draining the battery while searching for a host.

ZMK is built on Zephyr, which means its configuration uses Kconfig and devicetree overlays. The learning curve is steeper than QMK, but the payoff is robust wireless support, power management APIs, and a modern RTOS. For project 9, you will likely use ZMK rather than QMK to get a production-quality wireless keyboard. Understanding the BLE HID model lets you decide when to use ZMK and when to attempt custom firmware.

BLE introduces additional compatibility factors. Different OSes enforce different security requirements, and some hosts will refuse to pair without encryption. Advertising intervals also matter: short intervals speed up reconnection but cost battery, while long intervals save power but feel sluggish. Many wireless keyboards offer multiple profiles, which requires NVM storage for bonds and a UI to switch profiles. You should test against at least two host types (phone + laptop) and validate reconnection time, wake-on-keypress reliability, and battery reporting accuracy before you consider the design “done”.\n\nFirmware scheduling is also different. A wired keyboard can scan continuously, but a wireless board often scans in bursts, wakes the radio only when needed, and defers non-critical tasks like OLED updates to maintain low average current. You may need a duty-cycled scan loop (for example, scan for 20 ms, sleep for 80 ms) and then dynamically shorten the sleep window while typing. This adaptive strategy is a key part of making wireless feel responsive without draining the battery.

How This Fits in Projects

  • Project 9 is a wireless keyboard built with ZMK.
  • Project 11 uses wireless variants for a product line.

Definitions & Key Terms

  • HOGP: HID over GATT profile.
  • Report Map: BLE equivalent of a HID report descriptor.
  • Connection Interval: Time between BLE connection events.
  • Bonding: Persisting encryption keys for fast reconnection.
  • Deep Sleep: Low-power MCU mode used during idle.

Mental Model Diagram

Keypress -> Wake MCU -> Build HID Report -> Notify Host (GATT) -> Host Updates Input
      \-> Update battery -> Possibly change connection interval for active typing

How It Works (Step-by-Step)

  1. Device advertises BLE services (HID, Battery, Device Info).
  2. Host connects and bonds.
  3. Host subscribes to input report notifications.
  4. Keyboard sends notifications on key events.
  5. Firmware manages sleep/wake and battery reporting.

Minimal Concrete Example

BLE GATT (HID) Service
- Report Map Characteristic
- Input Report Characteristic (notify)
- Output Report Characteristic (LEDs)
- Protocol Mode Characteristic

Common Misconceptions

  • “BLE is just wireless USB.” -> The transport, power model, and timing are different.
  • “Lower latency always means better.” -> It often kills battery life.
  • “Wireless keyboards are just firmware.” -> Hardware power design is equally important.

Check-Your-Understanding Questions

  1. What is the BLE equivalent of a HID report descriptor?
  2. How does connection interval affect battery life?
  3. Why is bonding important for a good user experience?

Check-Your-Understanding Answers

  1. The Report Map characteristic in the HID service.
  2. Shorter intervals increase radio activity and current draw.
  3. It allows fast reconnection without re-pairing.

Real-World Applications

  • Wireless ergonomic keyboards.
  • Portable macro pads and remote controls.
  • Battery-powered IoT input devices.

Where You’ll Apply It

  • Project 9: BLE keyboard with ZMK
  • Project 11: Wireless product variants

References

Key Insight

Key insight: Wireless keyboards are a power-management problem first, and a HID problem second.

Summary

BLE HID shifts the keyboard from a fixed, powered device to a dynamic, battery-limited system. The firmware must manage sleep, connection intervals, and bonding while still feeling as responsive as a wired board.

Homework/Exercises to Practice the Concept

  1. Estimate battery life for a 200 mAh cell given a 10 uA idle and 3 mA active current draw.
  2. Design a simple UI (LED or OLED) for showing connection status and battery level.

Solutions to the Homework/Exercises

  1. Calculate daily usage; for example 2 hours active and 22 hours idle gives about 6.2 mAh/day.
  2. Use a single LED to blink fast when unpaired, slow when connected, and solid when charging.

Glossary

  • Actuation: The moment a switch closes and registers as a press.
  • Bootloader: Small program that accepts firmware updates.
  • Debounce: Filtering switch bounce to avoid multiple events.
  • Diode Direction: The orientation of diodes in the matrix (COL2ROW or ROW2COL).
  • HID Report: The data packet describing pressed keys.
  • Layer: A virtual keymap overlay.
  • Matrix Scan: Cycling through columns/rows to detect presses.
  • NKRO: N-key rollover; support for many simultaneous keys.
  • Tap-Hold: A key that changes behavior based on tap vs hold time.

Why QMK Firmware Matters

The Modern Problem It Solves

Custom keyboards are no longer niche. Programmers, gamers, and heavy typists demand ergonomic layouts, advanced macros, and precise control over behavior. QMK solves this by giving you a firmware stack that is both open-source and widely supported, so you can customize behavior without reimplementing USB or scanning logic.

Real-world impact and adoption:

  • QMK supports over 3000 keyboards, indicating a large ecosystem of compatible hardware (QMK Firmware).
  • A 2024 market report estimates the mechanical keyboard market at USD 1.79B in 2023, projected to USD 4.99B by 2031 (CAGR 13.7%) (Consegic Business Intelligence).
OLD APPROACH                          NEW APPROACH
┌──────────────────────┐             ┌──────────────────────────┐
│ Fixed, vendor layout  │             │ Fully customizable layout│
│ No firmware access    │             │ Open firmware (QMK/ZMK)  │
│ Limited macro support │             │ Layers, combos, macros   │
└──────────────────────┘             └──────────────────────────┘

Context & Evolution (Brief)

QMK evolved from TMK to provide a more extensible, community-driven firmware with a massive library of keyboard definitions. Its growth mirrors the rise of mechanical keyboards and the maker community.

Concept Summary Table

This section provides a map of the mental models you will build during these projects.

Concept Cluster What You Need to Internalize
Matrix + Scanning How row/column wiring, diode direction, and scanning logic create deterministic key events.
USB HID How descriptors define reports and why host polling matters.
QMK Build System How keymaps, rules.mk, and QMK CLI compose a firmware image.
Layers + Key Processing How the layer stack, keycodes, and features resolve into final actions.
Peripherals + Split How RGB/OLED/encoders and split transport affect timing and reliability.
Wireless + Power How BLE HID over GATT works and why power management dominates design.

Project-to-Concept Map

Project What It Builds Primer Chapters It Uses
Project 1: Matrix Simulator Matrix scan model + ghosting demo Matrix + Scanning
Project 2: USB HID from Scratch Minimal HID keyboard USB HID
Project 3: 3x3 Macro Pad First real matrix + QMK build Matrix + Scanning, QMK Build System
Project 4: QMK Hello World Keymap build + flash QMK Build System, Layers + Key Processing
Project 5: Layers & Tap-Dance Advanced layout behaviors Layers + Key Processing
Project 6: RGB + OLED Peripherals + status UI Peripherals + Split
Project 7: PCB Design Matrix wiring + hardware constraints Matrix + Scanning
Project 8: Split Keyboard Split transport + sync Peripherals + Split
Project 9: BLE Keyboard Wireless HID Wireless + Power
Project 10: Firmware from Scratch Full custom stack All chapters
Project 11: Product Line Integration + QA All chapters

Deep Dive Reading by Concept

Fundamentals & Electrical

Concept Book & Chapter Why This Matters
Matrix + Scanning Digital Design and Computer Architecture — Ch. 1-2 Solid grounding in digital logic and signal flow.
Embedded Basics Making Embedded Systems — Ch. 1-3 Firmware mindset and real-world constraints.

Firmware & Build Systems

Concept Book & Chapter Why This Matters
Build Tooling The GNU Make Book — Ch. 2-5 Understand how QMK composes builds.
C Foundations The C Programming Language — Ch. 1-5 Essential for custom keymaps and firmware.

HID and Wireless

Concept Book & Chapter Why This Matters
USB HID USB Complete by Jan Axelson — Ch. 1-6 Deep protocol coverage for Project 2 and 10.
BLE HID Bluetooth Low Energy: The Developer’s Handbook by Robin Heydon — Ch. 10-14 BLE HID concepts and power trade-offs.

Quick Start: Your First 48 Hours

Day 1 (4 hours):

  1. Read Concept 1 and Concept 3 in the Theory Primer.
  2. Skim QMK “How a Matrix Works” and QMK CLI docs.
  3. Start Project 1 and implement a 3x3 matrix simulator.
  4. Do not worry about HID yet; focus on matrix logic.

Day 2 (4 hours):

  1. Finish Project 1 by adding ghosting and diode simulation.
  2. Install QMK CLI and run qmk setup.
  3. Build a default firmware for a known keyboard using qmk compile.
  4. Read Project 3’s “Concepts You Must Understand First”.

End of Weekend: You can explain matrix scanning and you can compile QMK firmware. That is 80% of the foundation.

Best for: Embedded beginners who want a full stack understanding.

  1. Project 1 -> Project 2 -> Project 3
  2. Project 4 -> Project 5
  3. Project 6 -> Project 7
  4. Project 8 -> Project 10 -> Project 11

Path 2: The Product Designer

Best for: Hardware builders who want to ship a keyboard.

  1. Project 3 -> Project 7 -> Project 4
  2. Project 6 -> Project 8
  3. Project 11 (product line)

Path 3: The Power User

Best for: Keyboard enthusiasts focusing on layouts and features.

  1. Project 4 -> Project 5
  2. Project 6 -> Project 8
  3. Project 3 (hardware grounding)

Path 4: The Wireless Specialist

Best for: BLE and low-power enthusiasts.

  1. Project 2 -> Project 9
  2. Project 6 -> Project 8 (peripherals + split)
  3. Project 10 (custom firmware)

Success Metrics

  • You can explain how a matrix scan translates to a HID report.
  • You can compile and flash QMK on a real keyboard.
  • You can create a 3-layer layout with mod-tap and combo behavior.
  • You can wire a 3x3 matrix with correct diode direction.
  • You can debug a split keyboard transport issue.
  • You can estimate battery life for a BLE keyboard.

Appendix: Tooling & Debugging Cheatsheet

Core QMK commands:

  • qmk setup
  • qmk compile -kb <keyboard> -km <keymap>
  • qmk flash -kb <keyboard> -km <keymap>
  • qmk doctor

Hardware debugging:

  • Use a multimeter to verify diode direction and continuity.
  • Use a logic analyzer to validate scan frequency and debounce timing.
  • Use qmk console (or a serial monitor) to see debug output.

Project Overview Table

Project Difficulty Time Focus
1. Matrix Simulator Beginner Weekend Matrix scanning + ghosting
2. USB HID from Scratch Intermediate 2-3 weeks HID descriptors + reports
3. 3x3 Macro Pad Beginner 1-2 weeks First real QMK build
4. QMK Hello World Beginner Weekend Keymap + flashing
5. Layers & Tap-Dance Intermediate 1 week Advanced key behavior
6. RGB + OLED Intermediate 2 weeks Peripherals
7. PCB Design Advanced 4-6 weeks Hardware design
8. Split Keyboard Advanced 4-6 weeks Split transport
9. BLE Keyboard Expert 6-8 weeks Wireless + power
10. Firmware from Scratch Expert 2-3 months Full stack
11. Product Line Expert 6-12 months Production readiness

Project List

Project 1: Keyboard Matrix Simulator (Understand Matrix Scanning)

  • Main Programming Language: C
  • Alternative Programming Languages: Python, Rust
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Electronics / Digital I/O
  • Software or Tool: Terminal UI (ncurses) or CLI simulation
  • Main Book: “C Programming: A Modern Approach” by K. N. King

What you’ll build: A terminal-based simulation of a keyboard matrix that scans rows and columns, shows active columns, highlights pressed keys, and demonstrates ghosting and diode prevention.

Why it teaches keyboard fundamentals: It lets you “see” electrical behavior without hardware. You will internalize how scanning works, why ghosting happens, and how firmware state changes over time.

Core challenges you’ll face:

  • Matrix modeling -> Represent row/column state transitions.
  • Ghosting detection -> Detect phantom keys and explain why.
  • Scan timing -> Simulate a realistic scan loop and debounce window.

Real World Outcome

You will run a simulator that shows the matrix, active column, and detected keys in real time.

Command Line Outcome Example:

$ ./matrix_sim --rows 3 --cols 3 --debounce 5ms --diodes col2row

[scan 1024] active_col=1 debounce=5ms
matrix:
  R0: 1 0 0
  R1: 0 1 0
  R2: 0 0 0
pressed keys: (R0,C1) (R1,C1)

[ghosting]
  detected phantom at (R0,C2) due to rectangle
  diode mode: col2row -> ghost prevented

The Core Question You’re Answering

“How can a microcontroller reliably detect which switches are pressed using far fewer pins than keys?”

This question forces you to understand matrix scanning, diode direction, and the difference between electrical state and logical key events.

Concepts You Must Understand First

  1. Matrix scanning
    • Why is scanning required instead of reading all columns at once?
    • What is active-low logic?
    • Book Reference: “Digital Design and Computer Architecture” Ch. 1-2
  2. Pull-ups and input states
    • What does a pull-up resistor do?
    • What happens if inputs float?
    • Book Reference: “Making Embedded Systems” Ch. 2
  3. C arrays and indexing
    • Can you map row/column to array indices?
    • Book Reference: “C Programming: A Modern Approach” Ch. 8-10

Questions to Guide Your Design

  1. State representation
    • How will you represent the matrix state and transitions?
    • How will you mark ghosted keys vs actual keys?
  2. Timing
    • How many scan cycles per second are realistic?
    • How will you simulate debounce delay?
  3. Visualization
    • How will the user see active columns and pressed keys?
    • How will you show ghosting events clearly?

Thinking Exercise

The “Rectangle” Problem

If three keys in a rectangle are pressed, which fourth key appears ghosted?

R0: [X] --- [X]
 |           |
R1: [X] --- [ ]  <- Which key becomes ghosted?

Questions: What electrical path causes the phantom press? How does a diode change the path?

The Interview Questions They’ll Ask

  1. “Explain why a matrix reduces pin count and how scanning works.”
  2. “What is ghosting and how do diodes prevent it?”
  3. “Why do you need pull-ups on row inputs?”
  4. “What is the difference between ghosting and key masking?”

Hints in Layers

Hint 1: Start with the matrix model

typedef struct { bool pressed; } key_t;
key_t matrix[ROWS][COLS];

Hint 2: Simulate scan columns Drive one column active at a time and read rows.

Hint 3: Add ghost detection Detect rectangles by checking multiple rows and columns with presses.

Hint 4: Validate timing

$ ./matrix_sim --scan 1000 --debounce 5
# Expect stable presses after 5 ms window

Books That Will Help

Topic Book Chapter
Digital inputs “Making Embedded Systems” by Elecia White Ch. 1-2
Digital logic “Digital Design and Computer Architecture” by Harris & Harris Ch. 1-2
C arrays “C Programming: A Modern Approach” by K. N. King Ch. 8-10

Common Pitfalls & Debugging

Problem 1: “Random keys flicker”

  • Why: Inputs are floating in the model.
  • Fix: Add pull-up logic in simulation.
  • Quick test: Ensure idle rows read HIGH consistently.

Problem 2: “Ghosting never appears”

  • Why: You aren’t simulating multiple simultaneous presses.
  • Fix: Add a multi-press test case for rectangles.
  • Quick test: Simulate R0C0, R0C1, R1C0 pressed and check R1C1.

Problem 3: “Debounce does nothing”

  • Why: You’re not tracking time since last transition.
  • Fix: Store timestamps per key.
  • Quick test: Print raw vs debounced states.

Definition of Done

  • Matrix scan loop correctly cycles through columns.
  • Ghosting is demonstrated and explained.
  • Debounce delay suppresses bouncing transitions.
  • Simulation output clearly shows active column and pressed keys.

Project 2: USB HID from Scratch (Minimal Keyboard Device)

  • Main Programming Language: C
  • Alternative Programming Languages: Rust (tinyusb), C++
  • Coolness Level: Level 4: Wizard
  • Business Potential: 2. The “Embedded Consultant”
  • Difficulty: Level 3: Intermediate
  • Knowledge Area: USB / HID Protocol
  • Software or Tool: TinyUSB, STM32 or RP2040 dev board
  • Main Book: “USB Complete” by Jan Axelson

What you’ll build: A minimal USB HID keyboard device that sends reports to the host and types a predefined string.

Why it teaches USB fundamentals: It forces you to handle descriptors, report formats, and USB polling directly.

Core challenges you’ll face:

  • Report descriptor creation -> Define a valid HID report.
  • USB enumeration -> Provide device/config/interface descriptors.
  • Timing -> Respond to host polling consistently.

Real World Outcome

You can plug in your device and see it type.

$ lsusb | grep "HID"
Bus 001 Device 012: ID 1209:0001 Custom HID Keyboard

$ sudo cat /sys/kernel/debug/usb/usbmon/1u | head -n 5
ffff88007a2a1c00 0.000000 S Ii:1:012:1 0:8 0000000000000000
ffff88007a2a1c00 0.001000 C Ii:1:012:1 0:8 0200000000000000

The Core Question You’re Answering

“How does a keyboard describe itself to the host and deliver keypresses without a driver?”

Concepts You Must Understand First

  1. HID report descriptors
    • What is a Usage Page? What is a Report Count?
    • Book Reference: “USB Complete” Ch. 3-6
  2. USB enumeration
    • What descriptors are required for a HID interface?
    • Book Reference: “USB Complete” Ch. 2-3
  3. Interrupt IN endpoints
    • Why does the host poll the device?
    • Book Reference: “Making Embedded Systems” Ch. 5

Questions to Guide Your Design

  1. What report format will you use (boot or NKRO)?
  2. How will you test that the host interprets your report correctly?
  3. How will you handle idle rate and protocol requests?

Thinking Exercise

Descriptor Trace

Write a report descriptor for 2 keys and 1 modifier. Label each item and explain what it means.

The Interview Questions They’ll Ask

  1. “What is the purpose of the HID report descriptor?”
  2. “Why is USB host-driven polling important?”
  3. “Explain the difference between boot protocol and report protocol.”
  4. “What could cause a host to ignore your HID reports?”

Hints in Layers

Hint 1: Start from a known descriptor TinyUSB includes a minimal keyboard descriptor; use it as a base.

Hint 2: Build the report struct

typedef struct { uint8_t mods; uint8_t reserved; uint8_t keys[6]; } report_t;

Hint 3: Send a single keycode Send a report with KC_A, then a zeroed report to release.

Hint 4: Validate with usbmon Use usbmon or Wireshark to verify interrupt IN transfers.

Books That Will Help

Topic Book Chapter
USB fundamentals “USB Complete” by Jan Axelson Ch. 1-6
Embedded timing “Making Embedded Systems” by Elecia White Ch. 4-5
C structures “The C Programming Language” by K&R Ch. 6

Common Pitfalls & Debugging

Problem 1: “Device enumerates but doesn’t type”

  • Why: Report descriptor mismatch.
  • Fix: Validate descriptor with a known-good example.
  • Quick test: Compare to TinyUSB demo descriptor.

Problem 2: “Keyboard repeats endlessly”

  • Why: Missing key release report.
  • Fix: Send a zero report after each press.
  • Quick test: Verify alternating report values.

Problem 3: “Host rejects device”

  • Why: Invalid descriptor lengths or endpoint types.
  • Fix: Re-check config descriptor and endpoint attributes.
  • Quick test: Use lsusb -v to inspect descriptors.

Definition of Done

  • Device enumerates as a HID keyboard.
  • Host receives correct reports for press and release.
  • Descriptor parses without errors.
  • Typing a test string works on two different OSes.

Project 3: 3x3 Macro Pad (First Physical Keyboard)

  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: None (QMK-only)
  • Coolness Level: Level 3: Useful Gadget
  • Business Potential: 2. The “Hobby Side Hustle”
  • Difficulty: Level 2: Beginner
  • Knowledge Area: Hardware + Firmware Integration
  • Software or Tool: QMK CLI, Pro Micro/ATmega32U4
  • Main Book: “Making Embedded Systems” by Elecia White

What you’ll build: A physical 3x3 macro pad with QMK firmware, custom keymap, and working HID input.

Why it teaches fundamentals: It is the smallest real keyboard that forces you to wire a matrix and flash QMK.

Core challenges you’ll face:

  • Wiring the matrix -> Correct rows/cols and diodes.
  • Building firmware -> Using QMK CLI and keymap.
  • Debugging hardware -> Diagnosing wiring mistakes.

Real World Outcome

$ qmk compile -kb handwired/3x3 -km mypad
Compiling: keyboards/handwired/3x3/keymaps/mypad/keymap.c
Linking: .build/handwired_3x3_mypad.elf
Creating hex file: .build/handwired_3x3_mypad.hex

$ qmk flash -kb handwired/3x3 -km mypad
Flashing for bootloader: Caterina
Flashing complete

The Core Question You’re Answering

“How do I go from a physical switch matrix to a working QMK keyboard?”

Concepts You Must Understand First

  1. Matrix wiring
    • Can you map each switch to row/column pins?
    • Book Reference: “Digital Design and Computer Architecture” Ch. 1-2
  2. QMK build system
    • What does rules.mk control?
    • Book Reference: “The GNU Make Book” Ch. 2-3
  3. Basic C
    • Can you edit a keymap array and compile?
    • Book Reference: “C Programming: A Modern Approach” Ch. 8

Questions to Guide Your Design

  1. Which MCU pins are easiest to wire for rows/cols?
  2. What diode direction will you use?
  3. How will you test each key independently?

Thinking Exercise

Matrix-to-Pin Mapping

Create a table mapping each of 9 keys to row/column pins. Identify any pins that are unsafe or reserved.

The Interview Questions They’ll Ask

  1. “How does QMK map physical keys to a layout?”
  2. “Why are diodes required in a macro pad?”
  3. “How would you debug a missing row?”

Hints in Layers

Hint 1: Use the QMK handwired template Start from keyboards/handwired/3x3.

Hint 2: Define pins in config.h

#define MATRIX_ROWS 3
#define MATRIX_COLS 3
#define MATRIX_ROW_PINS { D1, D0, D4 }
#define MATRIX_COL_PINS { C6, D7, E6 }
#define DIODE_DIRECTION COL2ROW

Hint 3: Test with a multimeter Ensure each switch connects the correct row/col.

Hint 4: Use QMK Console

$ qmk console
# Press each key and confirm events

Books That Will Help

Topic Book Chapter
Embedded workflow “Making Embedded Systems” by Elecia White Ch. 1-4
Digital logic “Digital Design and Computer Architecture” Ch. 1-2
C arrays “C Programming: A Modern Approach” Ch. 8-10

Common Pitfalls & Debugging

Problem 1: “A whole row is dead”

  • Why: Row pin miswired or wrong pin in config.h.
  • Fix: Check continuity and re-assign pin.
  • Quick test: Short row to col with a wire and see if it registers.

Problem 2: “Keys register on wrong column”

  • Why: Columns swapped in firmware definition.
  • Fix: Reorder MATRIX_COL_PINS.
  • Quick test: Swap pins in config and re-flash.

Problem 3: “Ghosting on multi-press”

  • Why: Missing diodes or wrong diode direction.
  • Fix: Add diodes or flip orientation.
  • Quick test: Press three keys in a rectangle and observe phantom key.

Definition of Done

  • All 9 keys register correctly in QMK console.
  • Keymap matches physical layout.
  • Diode direction is correct.
  • Firmware compiles and flashes without errors.

    Project 4: QMK Hello World (First Custom Keymap)

  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: None
  • Coolness Level: Level 2: Practical
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Firmware Configuration
  • Software or Tool: QMK CLI
  • Main Book: “The GNU Make Book” by John Graham-Cumming

What you’ll build: A custom keymap for an existing QMK keyboard that changes layout, adds a custom macro, and compiles/flashes successfully.

Why it teaches QMK: It forces you to understand the QMK file structure, how keymaps are compiled, and how to verify results.

Core challenges you’ll face:

  • Keymap editing -> Modifying the layout array correctly.
  • Custom keycodes -> Adding user-defined behavior.
  • Flashing -> Verifying the build and programming steps.

Real World Outcome

$ qmk setup
Welcome to QMK Firmware!

$ qmk compile -kb ferris/sweep -km mylayout
Compiling: keyboards/ferris/sweep/keymaps/mylayout/keymap.c
Linking: .build/ferris_sweep_mylayout.elf
Creating hex file: .build/ferris_sweep_mylayout.hex

$ qmk flash -kb ferris/sweep -km mylayout
Bootloader: DFU
Flashing complete

The Core Question You’re Answering

“How does my keymap become actual firmware running on a keyboard?”

Concepts You Must Understand First

  1. QMK build pipeline
    • What does qmk compile do under the hood?
    • Book Reference: “The GNU Make Book” Ch. 2-4
  2. Keymap array structure
    • How are rows/cols laid out in code?
    • Book Reference: “C Programming: A Modern Approach” Ch. 8
  3. Custom keycodes
    • What happens when process_record_user returns false?
    • Book Reference: “Making Embedded Systems” Ch. 5

Questions to Guide Your Design

  1. Which keys are most valuable to customize first?
  2. What macro would save you time every day?
  3. How will you verify that the macro does not interfere with normal typing?

Thinking Exercise

Keymap Diff

Take a stock keymap and list five keys you would change. Explain why each change improves your workflow.

The Interview Questions They’ll Ask

  1. “Explain how QMK compiles a keymap into firmware.”
  2. “What happens if you define a keycode in a layer but forget it in others?”
  3. “How do you debug a key that doesn’t behave as expected?”

Hints in Layers

Hint 1: Start with a copy of the default keymap Use qmk new-keymap to scaffold.

Hint 2: Add a custom keycode enum

enum custom_keycodes { MY_MACRO = SAFE_RANGE };

Hint 3: Implement the macro in process_record_user

if (keycode == MY_MACRO && record->event.pressed) {
    SEND_STRING("Hello QMK!");
    return false;
}

Hint 4: Confirm with HID output Use a text editor and confirm the output string.

Books That Will Help

Topic Book Chapter
Build systems “The GNU Make Book” Ch. 2-5
C fundamentals “C Programming: A Modern Approach” Ch. 8-10
Embedded flow “Making Embedded Systems” Ch. 4-5

Common Pitfalls & Debugging

Problem 1: “Keymap compiles but doesn’t change behavior”

  • Why: Wrong keyboard/keymap name.
  • Fix: Verify -kb and -km arguments.
  • Quick test: Delete build artifacts and recompile.

Problem 2: “Macro triggers twice”

  • Why: Missing release handling or debounce misread.
  • Fix: Only run macro on pressed event.
  • Quick test: Add debug prints to process_record_user.

Problem 3: “Keyboard not detected by QMK flash”

  • Why: Device not in bootloader mode.
  • Fix: Double-tap reset or use a reset switch.
  • Quick test: Run qmk flash and watch for USB device change.

Definition of Done

  • Keymap compiles and flashes without errors.
  • At least one macro works reliably.
  • A layer change is confirmed in practice.
  • You can explain each file you modified.

Project 5: Layers, Tap-Dance, and Advanced Behaviors

  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: None
  • Coolness Level: Level 4: Wizard
  • Business Potential: 2. The “Productivity Booster”
  • Difficulty: Level 3: Intermediate
  • Knowledge Area: Key Processing + UI/UX
  • Software or Tool: QMK CLI, QMK Configurator
  • Main Book: “Making Embedded Systems” by Elecia White

What you’ll build: A power-user keymap with three layers, mod-tap keys, tap dance actions, and combos.

Why it teaches advanced QMK: It forces you to master the layer stack and the timing-sensitive logic of QMK features.

Core challenges you’ll face:

  • Tapping term tuning -> Balancing speed vs reliability.
  • Layer stack design -> Preventing conflicts and confusion.
  • Feature interactions -> Ensuring combos and tap dance coexist.

Real World Outcome

$ qmk compile -kb lily58/rev1 -km poweruser
Compiling: keymaps/poweruser/keymap.c
Linking: .build/lily58_rev1_poweruser.elf
Creating hex file: .build/lily58_rev1_poweruser.hex

Behavior demo:
- Tap HOME key: types "h"
- Hold HOME key: activates CTRL
- Tap J+K together: sends ESC
- Double-tap ; : sends ":"

The Core Question You’re Answering

“How can a small physical keyboard feel larger than a full-size board?”

Concepts You Must Understand First

  1. Layer stack semantics
    • What is a momentary vs toggle layer?
    • Book Reference: “Making Embedded Systems” Ch. 5
  2. Tap-hold timing
    • What is a tapping term and why does it matter?
    • Book Reference: “C Programming: A Modern Approach” Ch. 8
  3. Combo detection
    • How does QMK decide a chord vs two separate keys?
    • Book Reference: “Making Embedded Systems” Ch. 6

Questions to Guide Your Design

  1. Which keys should be on the base layer vs a layer?
  2. Which keys are used most often and deserve mod-tap behavior?
  3. How will you prevent accidental combos during fast typing?

Thinking Exercise

Layer Budgeting

You only get 34 physical keys. Design a 3-layer layout that still supports symbols, numbers, and navigation.

The Interview Questions They’ll Ask

  1. “How does QMK decide between tap and hold?”
  2. “What is a transparent keycode and why is it useful?”
  3. “How do combos interact with mod-tap keys?”
  4. “What can cause a stuck modifier in QMK?”

Hints in Layers

Hint 1: Define layers clearly

enum layers { _BASE, _NAV, _SYM };

Hint 2: Add mod-tap keys

#define CTL_A MT(MOD_LCTL, KC_A)

Hint 3: Add a combo

const uint16_t PROGMEM esc_combo[] = {KC_J, KC_K, COMBO_END};

Hint 4: Tune tapping term Set TAPPING_TERM 200 and adjust after testing.

Books That Will Help

Topic Book Chapter
Embedded timing “Making Embedded Systems” Ch. 4-6
C macros “The C Programming Language” Ch. 4
Debugging “The Art of Debugging with GDB” Ch. 1-2

Common Pitfalls & Debugging

Problem 1: “Mod-tap feels inconsistent”

  • Why: Tapping term is too short for your typing speed.
  • Fix: Increase tapping term or use per-key overrides.
  • Quick test: Print debug logs for tap vs hold decisions.

Problem 2: “Combos trigger accidentally”

  • Why: Combo timeout too long.
  • Fix: Reduce combo term or adjust combo keys.
  • Quick test: Use COMBO_TERM and test typing speed.

Problem 3: “Layer stuck on”

  • Why: Missing layer-off key or toggled layer.
  • Fix: Add a clear layer toggle or reset combo.
  • Quick test: Add a debug LED or OLED indicator.

Definition of Done

  • Three-layer keymap works reliably.
  • Mod-tap keys do not misfire during fast typing.
  • Combo action triggers only when intended.
  • You can explain your layout choices to another person.

    Project 6: RGB + OLED (Feedback and Status UI)

  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: None
  • Coolness Level: Level 4: Wizard
  • Business Potential: 3. The “Premium Add-on”
  • Difficulty: Level 3: Intermediate
  • Knowledge Area: Peripheral Integration
  • Software or Tool: QMK CLI, RGB Matrix, OLED
  • Main Book: “Making Embedded Systems” by Elecia White

What you’ll build: A keyboard with per-layer RGB lighting and an OLED status display showing layer, caps lock, and typing speed.

Why it teaches hardware integration: It forces you to balance firmware features with scan timing and memory limits.

Core challenges you’ll face:

  • Driver configuration -> Selecting correct RGB/OLED drivers.
  • Performance -> Avoiding scan latency spikes.
  • UI design -> Making useful, minimal status output.

Real World Outcome

OLED Display (128x32)

Layer: NAV
WPM: 82
CAPS: off
$ qmk compile -kb kyria/rev1 -km oled_rgb
Linking: .build/kyria_rev1_oled_rgb.elf
Creating hex file: .build/kyria_rev1_oled_rgb.hex

RGB behavior:
- Base layer: soft blue
- NAV layer: bright green
- SYM layer: red pulse

The Core Question You’re Answering

“How do I add user feedback without harming scan performance?”

Concepts You Must Understand First

  1. QMK feature flags
    • How do rules.mk settings affect firmware size?
    • Book Reference: “The GNU Make Book” Ch. 2-4
  2. Timing and scan loop
    • Why do heavy animations increase latency?
    • Book Reference: “Making Embedded Systems” Ch. 4-5
  3. Display rendering
    • How often should OLED updates occur?
    • Book Reference: “Making Embedded Systems” Ch. 7

Questions to Guide Your Design

  1. Which layers need distinct visual feedback?
  2. How will you limit OLED update frequency?
  3. What RGB effects are useful vs distracting?

Thinking Exercise

UI Budget

Assume you can update the OLED at most 10 times per second. What three pieces of information should it display?

The Interview Questions They’ll Ask

  1. “What are the performance costs of RGB matrix?”
  2. “How would you throttle OLED updates?”
  3. “What is the difference between RGB Matrix and RGB Light?”

Hints in Layers

Hint 1: Enable features

RGB_MATRIX_ENABLE = yes
OLED_ENABLE = yes

Hint 2: Keep OLED updates minimal

bool oled_task_user(void) {
    if (timer_elapsed32(last_oled) < 100) return false;
    last_oled = timer_read32();
    // render small text
}

Hint 3: Use layer state to set RGB Map layer changes to RGB color.

Hint 4: Verify scan time Use QMK debug output to confirm stable scan rate.

Books That Will Help

Topic Book Chapter
Embedded timing “Making Embedded Systems” Ch. 4-6
Debugging “The Art of Debugging with GDB” Ch. 1-2
Build flags “The GNU Make Book” Ch. 3-4

Common Pitfalls & Debugging

Problem 1: “RGB flickers or is dim”

  • Why: Incorrect LED driver settings or power budget.
  • Fix: Verify driver type and voltage.
  • Quick test: Use a solid color and measure current draw.

Problem 2: “OLED slows typing”

  • Why: Too frequent updates.
  • Fix: Throttle updates to 5-10 Hz.
  • Quick test: Measure scan rate before/after OLED changes.

Problem 3: “Firmware too large”

  • Why: Features exceed flash size.
  • Fix: Disable unused features, enable LTO.
  • Quick test: Compare arm-none-eabi-size output.

Definition of Done

  • RGB changes with layers reliably.
  • OLED displays layer and caps status.
  • Scan rate remains stable under typing.
  • Firmware size fits in flash.

Project 7: PCB Design for a Custom Keyboard

  • Main Programming Language: C + KiCad
  • Alternative Programming Languages: None
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 4. The “Hardware Founder”
  • Difficulty: Level 4: Advanced
  • Knowledge Area: PCB Design / Hardware Engineering
  • Software or Tool: KiCad, ai03 guide
  • Main Book: “Designing Electronics That Work” by Hunter Scott

What you’ll build: A complete keyboard PCB design: schematic, routing, and manufacturing files.

Why it teaches hardware mastery: It forces you to translate firmware assumptions into real-world hardware constraints.

Core challenges you’ll face:

  • Matrix routing -> Efficient row/col traces with minimal noise.
  • Component selection -> MCU, diodes, connectors, ESD.
  • Manufacturing constraints -> DRC rules, footprints, tolerances.

Real World Outcome

$ kicad-cli pcb drc keyboard.kicad_pcb
DRC finished: 0 errors, 2 warnings

$ kicad-cli pcb export gerbers keyboard.kicad_pcb -o gerbers/
Exported: top.gbr, bottom.gbr, drill.drl

The Core Question You’re Answering

“How do I turn a firmware design into reliable, manufacturable hardware?”

Concepts You Must Understand First

  1. Matrix wiring
    • Can you draw the matrix schematic accurately?
    • Book Reference: “Digital Design and Computer Architecture” Ch. 1-2
  2. Power and USB routing
    • How do you place USB ESD protection and power filtering?
    • Book Reference: “Making Embedded Systems” Ch. 3
  3. MCU selection
    • What features (USB, flash size) are required?
    • Book Reference: “Making Embedded Systems” Ch. 2

Questions to Guide Your Design

  1. How many rows/cols and what diode direction will you use?
  2. Where will you place the USB connector and ESD protection?
  3. How will you expose reset/bootloader access?

Thinking Exercise

Matrix + MCU Pin Budget

Given a 5x14 matrix, how many MCU pins are required? Which MCU package fits that requirement?

The Interview Questions They’ll Ask

  1. “How do you choose diode direction and matrix layout?”
  2. “What are the trade-offs of AVR vs ARM MCUs?”
  3. “How do you ensure the PCB is manufacturable?”

Hints in Layers

Hint 1: Start with a known reference design Use a similar QMK keyboard schematic as a template.

Hint 2: Lock down the matrix Confirm rows/cols and diode direction before routing.

Hint 3: Add ESD and power filtering Place TVS diodes near USB, add decoupling caps near MCU.

Hint 4: Run DRC early Run DRC after routing each major section.

Books That Will Help

Topic Book Chapter
Hardware basics “Making Embedded Systems” Ch. 2-4
Digital logic “Digital Design and Computer Architecture” Ch. 1-2
Debugging “The Art of Debugging with GDB” Ch. 1 (hardware mindset)

Common Pitfalls & Debugging

Problem 1: “Matrix does not scan”

  • Why: Rows/cols swapped or diodes reversed.
  • Fix: Verify schematic vs config.h definitions.
  • Quick test: Use a multimeter to confirm diode direction.

Problem 2: “USB not detected”

  • Why: Missing ESD or incorrect USB D+ / D- routing.
  • Fix: Check differential pair routing and connector pins.
  • Quick test: Inspect USB pins with a continuity tester.

Problem 3: “Flashing fails”

  • Why: Reset/bootloader pins inaccessible.
  • Fix: Add a reset switch or boot pads.
  • Quick test: Trigger bootloader and confirm USB device appears.

Definition of Done

  • Schematic passes ERC.
  • PCB passes DRC with zero errors.
  • Gerbers generate and are manufacturable.
  • Board is compatible with QMK matrix definitions.

Project 8: Split Keyboard (Wired Transport)

  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: None
  • Coolness Level: Level 4: Wizard
  • Business Potential: 3. The “Ergo Builder”
  • Difficulty: Level 4: Advanced
  • Knowledge Area: Interconnects / Synchronization
  • Software or Tool: QMK Split, UART/I2C
  • Main Book: “Making Embedded Systems” by Elecia White

What you’ll build: A wired split keyboard where one half is master and the other is a slave, synchronized via serial or I2C.

Why it teaches systems thinking: It adds a communication link between two embedded systems and forces you to handle synchronization and failure modes.

Core challenges you’ll face:

  • Handedness detection -> Identifying the master half.
  • Transport reliability -> UART/I2C signal integrity.
  • State sync -> Layers and LED states across halves.

Real World Outcome

$ qmk compile -kb splitkb/kyria -km splitmaster
Creating hex file: .build/splitkb_kyria_splitmaster.hex

QMK Console:
[split] master detected on left
[split] slave connected
[split] transport: serial

The Core Question You’re Answering

“How do two separate matrices behave like one keyboard?”

Concepts You Must Understand First

  1. Split transport
    • UART vs I2C trade-offs?
    • Book Reference: “Making Embedded Systems” Ch. 7
  2. Matrix merging
    • How are two matrices merged into one logical map?
    • Book Reference: “Digital Design and Computer Architecture” Ch. 1-2
  3. Synchronization
    • How do you keep layer state in sync?
    • Book Reference: “Making Embedded Systems” Ch. 6

Questions to Guide Your Design

  1. What physical cable will connect the halves (TRRS, JST)?
  2. How will you detect the master half reliably?
  3. What happens if the cable disconnects mid-typing?

Thinking Exercise

Latency Budget

If each scan takes 1 ms and the serial link adds 0.5 ms, what is the worst-case latency for a keypress on the slave half?

The Interview Questions They’ll Ask

  1. “How does QMK decide which half is the master?”
  2. “What failure modes can happen on a split transport?”
  3. “Why might I2C be less reliable over long cables?”

Hints in Layers

Hint 1: Enable split in config.h

#define SPLIT_KEYBOARD
#define SERIAL_USART_FULL_DUPLEX

Hint 2: Define master Use MASTER_LEFT or SPLIT_USB_DETECT.

Hint 3: Test with console logs Monitor QMK console for split status.

Hint 4: Validate with a simple keymap Use a minimal keymap before adding layers.

Books That Will Help

Topic Book Chapter
Embedded communication “Making Embedded Systems” Ch. 6-7
Digital logic “Digital Design and Computer Architecture” Ch. 1-2
Debugging “The Art of Debugging with GDB” Ch. 1-2

Common Pitfalls & Debugging

Problem 1: “Slave half not detected”

  • Why: Wrong pin configuration or missing pull-up.
  • Fix: Verify pin assignments and add proper pull-up resistor.
  • Quick test: Scope the serial line for activity.

Problem 2: “Keys on slave lag”

  • Why: Serial timing too slow or scan rate too low.
  • Fix: Optimize scan and transport settings.
  • Quick test: Reduce RGB/OLED load and re-test.

Problem 3: “Layer state out of sync”

  • Why: State not transmitted or timing issues.
  • Fix: Enable split layer sync and verify.
  • Quick test: Toggle layer and check LEDs on both halves.

Definition of Done

  • Both halves detected reliably.
  • Keys on both halves work with consistent latency.
  • Layer and LED state stays synchronized.
  • Transport survives cable disconnect/reconnect.

    Project 9: Bluetooth Wireless Keyboard (ZMK)

  • Main Programming Language: C (ZMK/Zephyr)
  • Alternative Programming Languages: Rust (Embassy)
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 4. The “Wireless Premium”
  • Difficulty: Level 5: Expert
  • Knowledge Area: BLE / Power Management
  • Software or Tool: ZMK, Zephyr, nRF52840
  • Main Book: “Bluetooth Low Energy: The Developer’s Handbook” by Robin Heydon

What you’ll build: A battery-powered wireless keyboard with BLE HID, power management, and battery reporting.

Why it teaches wireless: It forces you to design for power, latency, and BLE pairing in a way that wired QMK never requires.

Core challenges you’ll face:

  • BLE HID configuration -> Report map, pairing, bonding.
  • Power management -> Sleep, wake, and battery life calculations.
  • Battery monitoring -> ADC calibration and reporting.

Real World Outcome

BLE Keyboard Status

Battery: 78%
Connection: MacBook Pro (Profile 1)
Typing latency: ~12 ms
Sleep current: 12 uA
$ west build -b nice_nano_v2
[100%] Built target zephyr/zephyr.elf

$ west flash
Flashing finished

The Core Question You’re Answering

“How do I deliver wired-like typing experience on a battery budget?”

Concepts You Must Understand First

  1. BLE HID (HOGP)
    • What is the report map in BLE?
    • Book Reference: “Bluetooth Low Energy: The Developer’s Handbook” Ch. 10-12
  2. Power budgeting
    • How do you estimate battery life?
    • Book Reference: “Making Embedded Systems” Ch. 4
  3. Zephyr basics
    • How does Kconfig and devicetree define hardware?
    • Book Reference: “Zephyr RTOS Embedded C Programming” Ch. 1-3

Questions to Guide Your Design

  1. What connection interval provides acceptable latency?
  2. How will you handle multi-device pairing?
  3. How will you wake the device without draining the battery?

Thinking Exercise

Battery Budget

Compute battery life for 200 mAh with 2 mA active for 2 hours/day and 15 uA idle for 22 hours/day.

The Interview Questions They’ll Ask

  1. “What is HID over GATT?”
  2. “Why does connection interval affect power?”
  3. “How do you handle reconnection without user frustration?”
  4. “What trade-offs exist between scan rate and battery life?”

Hints in Layers

Hint 1: Start from a known ZMK board config Use a nice_nano sample config and adapt.

Hint 2: Enable battery reporting Use ZMK’s battery service config.

Hint 3: Tune connection parameters Experiment with intervals and measure current.

Hint 4: Validate sleep Measure idle current with a multimeter.

Books That Will Help

Topic Book Chapter
BLE HID “Bluetooth Low Energy: The Developer’s Handbook” Ch. 10-14
Zephyr fundamentals “Zephyr RTOS Embedded C Programming” Ch. 1-3
Embedded power “Making Embedded Systems” Ch. 4-5

Common Pitfalls & Debugging

Problem 1: “Keyboard disconnects randomly”

  • Why: Poor connection interval or RF interference.
  • Fix: Increase supervision timeout or adjust interval.
  • Quick test: Log connection events in Zephyr console.

Problem 2: “Battery life is terrible”

  • Why: MCU not entering deep sleep or scanning too often.
  • Fix: Verify sleep state and reduce scan rate when idle.
  • Quick test: Measure current draw in idle state.

Problem 3: “Pairing fails”

  • Why: Bond data mismatch or security config error.
  • Fix: Clear bonds and re-pair.
  • Quick test: Reset bond storage in ZMK settings.

Definition of Done

  • Keyboard pairs with at least two hosts.
  • Battery reporting works on host.
  • Idle current is below 20 uA.
  • Typing latency feels comparable to wired.

Project 10: Custom Keyboard Firmware from Scratch

  • Main Programming Language: C or Rust
  • Alternative Programming Languages: Zig
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 5. The “Industry Disruptor”
  • Difficulty: Level 5: Expert
  • Knowledge Area: Bare-Metal Firmware
  • Software or Tool: TinyUSB, ARM GCC, custom HAL
  • Main Book: “The Definitive Guide to ARM Cortex-M” by Joseph Yiu

What you’ll build: A complete keyboard firmware without QMK: matrix scanning, debounce, keymap processing, and USB HID.

Why this is the ultimate project: It proves you understand the entire stack well enough to build it yourself.

Core challenges you’ll face:

  • USB integration -> Report descriptors, endpoints, timing.
  • Firmware architecture -> Scan loop, feature pipeline.
  • Memory limits -> Fit features into limited flash/RAM.

Real World Outcome

$ arm-none-eabi-size firmware.elf
   text    data     bss     dec     hex filename
  15824     256    4096   20176    4ee0 firmware.elf

Features:
- Matrix scan @ 1000 Hz
- Debounce (sym-defer)
- 8 layers
- Mod-tap + combos
- USB HID keyboard + media keys

The Core Question You’re Answering

“Can I design a reliable keyboard firmware architecture without relying on QMK?”

Concepts You Must Understand First

  1. USB HID descriptors
    • How does the host parse reports?
    • Book Reference: “USB Complete” Ch. 3-6
  2. Embedded timing
    • How do you implement a stable scan loop?
    • Book Reference: “Making Embedded Systems” Ch. 4-5
  3. Memory constraints
    • How do you manage RAM and flash in C?
    • Book Reference: “Effective C” by Robert Seacord Ch. 2-3

Questions to Guide Your Design

  1. What is the minimum viable key processing pipeline?
  2. How will you represent layers and keycodes efficiently?
  3. How will you test debounce and ghosting behavior?

Thinking Exercise

Pipeline Sketch

Draw your firmware loop. Identify which steps must run every scan and which can be deferred.

The Interview Questions They’ll Ask

  1. “How do you implement USB HID without an RTOS?”
  2. “What debounce algorithm would you choose and why?”
  3. “How do you structure your keymap for speed and memory efficiency?”
  4. “How do you validate timing and latency?”

Hints in Layers

Hint 1: Start with a minimal HID example Use TinyUSB keyboard demo as a baseline.

Hint 2: Build the matrix scan first Test raw matrix output before keymap processing.

Hint 3: Add a simple layer system Implement layer stack and transparent keycodes.

Hint 4: Measure timing Use a GPIO toggle and scope to measure scan frequency.

Books That Will Help

Topic Book Chapter
ARM fundamentals “The Definitive Guide to ARM Cortex-M” Ch. 1-3
Embedded timing “Making Embedded Systems” Ch. 4-5
C safety “Effective C” Ch. 2-4

Common Pitfalls & Debugging

Problem 1: “USB enumerates but no input”

  • Why: Report descriptor mismatch.
  • Fix: Compare with known HID descriptor.
  • Quick test: Use a USB analyzer to validate reports.

Problem 2: “Key repeats or sticks”

  • Why: Missing key release handling.
  • Fix: Ensure each press has a corresponding release.
  • Quick test: Print report bytes and check for stuck keys.

Problem 3: “Latency spikes”

  • Why: Long operations inside scan loop.
  • Fix: Move expensive operations to background tasks.
  • Quick test: Measure scan loop timing with GPIO toggles.

Definition of Done

  • Firmware enumerates as a HID keyboard.
  • Matrix scan and debounce are stable.
  • Layers and macros work reliably.
  • Latency measured < 5 ms.

Project 11: Production-Ready Custom Keyboard Line

  • Main Programming Language: C (QMK/ZMK) + KiCad
  • Alternative Programming Languages: Python (tooling)
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 5. The “Industry Disruptor”
  • Difficulty: Level 5: Expert
  • Knowledge Area: Product Engineering / Manufacturing
  • Software or Tool: QMK, ZMK, KiCad, manufacturing tools
  • Main Book: “Clean Architecture” by Robert C. Martin

What you’ll build: A complete keyboard product line with consistent hardware design, firmware variants, QA processes, and customer documentation.

Why this is the ultimate project: It combines firmware, hardware, UX, and manufacturing into one cohesive system.

Core challenges you’ll face:

  • Design consistency -> Shared footprint libraries and layout standards.
  • Firmware variants -> Supporting multiple layouts with one codebase.
  • Quality assurance -> Production test procedures and failure analysis.

Real World Outcome

Product Line Summary

Model 60: USB-C, 60% layout, QMK/VIA
Model 65: USB-C, 65% layout, QMK/VIA
Model Split: Split ergo, OLED, RGB, optional BLE

QA Checklist:
- Matrix scan verified
- USB enumeration verified
- RGB/OLED tests passed
- Firmware build reproducible

The Core Question You’re Answering

“How do I scale from a one-off keyboard to a reliable product line?”

Concepts You Must Understand First

  1. Design system for hardware
    • How do you reuse footprints and circuits safely?
    • Book Reference: “Clean Architecture” Ch. 1-4
  2. Firmware modularity
    • How do you share features across multiple keyboards?
    • Book Reference: “Clean Architecture” Ch. 5-8
  3. Manufacturing QA
    • What tests catch the most failures early?
    • Book Reference: “Making Embedded Systems” Ch. 7-8

Questions to Guide Your Design

  1. What features must be identical across the product line?
  2. What can be optional without creating support nightmares?
  3. How will you handle firmware updates post-shipment?

Thinking Exercise

Failure Modes

List the top 5 reasons a customer might return a keyboard. How will you prevent each one?

The Interview Questions They’ll Ask

  1. “How do you build a scalable firmware architecture?”
  2. “What QA tests are essential before shipping?”
  3. “How do you handle firmware updates for customers?”
  4. “What is your approach to customer-facing documentation?”

Hints in Layers

Hint 1: Standardize your MCU and pin map Reuse one MCU family across multiple boards.

Hint 2: Create a shared firmware base Use QMK userspace or a shared keymap library.

Hint 3: Build a production test jig Create a pogo-pin jig to test all keys quickly.

Hint 4: Automate firmware builds Use CI to produce signed firmware for each model.

Books That Will Help

Topic Book Chapter
Architecture “Clean Architecture” Ch. 1-8
Embedded reliability “Making Embedded Systems” Ch. 7-8
Build systems “The GNU Make Book” Ch. 6-8

Common Pitfalls & Debugging

Problem 1: “One model fails QA more often”

  • Why: Layout differences introduce new failure modes.
  • Fix: Standardize connector placement and test coverage.
  • Quick test: Compare failure rates per station.

Problem 2: “Firmware variants drift”

  • Why: Copy-paste divergence across models.
  • Fix: Use shared userspace and centralized config.
  • Quick test: Diff firmware builds regularly.

Problem 3: “Customer updates fail”

  • Why: Bootloader or flashing instructions unclear.
  • Fix: Provide a simple updater and clear docs.
  • Quick test: Run user tests on update flow.

Definition of Done

  • All models share a common hardware baseline.
  • Firmware builds are reproducible in CI.
  • QA process catches >95% of failures.
  • Documentation is clear for non-technical users.