← Back to all projects

LEARN QMK KEYBOARD FIRMWARE

Learn QMK Firmware: From Zero to Keyboard Ninja

Goal: Deeply understand how keyboard firmware works—from electrical switch matrices to USB protocols to the QMK codebase itself—by building keyboards and firmware from scratch.


Why Learn QMK and Keyboard Firmware?

Every keystroke you make travels through a fascinating journey: mechanical switch → electrical matrix → microcontroller → USB protocol → operating system → application. Understanding this journey teaches you:

  • Embedded C programming on real hardware
  • USB HID protocol - the foundation of all input devices
  • Hardware/software interfaces - reading sensors, driving LEDs
  • Real-time systems - timing-critical code with strict constraints
  • Open source contribution - one of the most active hardware communities

After completing these projects, you will:

  • Understand every step from keypress to character on screen
  • Design and build your own keyboard PCB
  • Write firmware that runs on bare metal
  • Contribute to one of the largest embedded open-source projects
  • Customize keyboards beyond what any commercial product offers

Core Concept Analysis

The Journey of a Keypress

┌─────────────────────────────────────────────────────────────────────────┐
│                         THE KEYPRESS JOURNEY                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1. PHYSICAL         2. ELECTRICAL         3. FIRMWARE                  │
│  ┌─────────────┐    ┌───────────────┐    ┌─────────────────────────┐   │
│  │  Mechanical │    │ Switch closes │    │ Matrix scan detects     │   │
│  │  switch is  │───▶│ circuit in    │───▶│ row/column intersection │   │
│  │  pressed    │    │ matrix grid   │    │ Debounce algorithm runs │   │
│  └─────────────┘    └───────────────┘    └───────────┬─────────────┘   │
│                                                       │                  │
│  6. APPLICATION     5. OS DRIVER          4. USB HID                    │
│  ┌─────────────┐    ┌───────────────┐    ┌───────────▼─────────────┐   │
│  │  Character  │    │ HID driver    │    │ Firmware sends HID      │   │
│  │  appears in │◀───│ translates    │◀───│ report via USB endpoint │   │
│  │  text field │    │ to keycode    │    │ (up to 8 bytes)         │   │
│  └─────────────┘    └───────────────┘    └─────────────────────────┘   │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

The Keyboard Matrix

Without a matrix, a 60-key keyboard would need 61 wires (60 keys + ground). With a matrix, you need only ~16 wires (8 rows × 8 columns = 64 possible keys).

                    COLUMNS (active low, one at a time)
                    C0    C1    C2    C3    C4    C5    C6    C7
                    │     │     │     │     │     │     │     │
                ┌───┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴───┐
           R0 ──┤ [Q]   [W]   [E]   [R]   [T]   [Y]   [U]   [I]  │
                │  ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D │
           R1 ──┤ [A]   [S]   [D]   [F]   [G]   [H]   [J]   [K]  │
   ROWS         │  ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D │
 (read with     │                                                 │
  pull-ups) R2 ──┤ [Z]   [X]   [C]   [V]   [B]   [N]   [M]   [,]  │
                │  ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D │
           R3 ──┤[Ctrl][Alt] [Spc] [Spc] [Spc] [Alt] [Fn] [Ctrl] │
                │  ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D   ├─D │
                └─────────────────────────────────────────────────┘
                          D = Diode (prevents ghosting)

Scanning algorithm:

  1. Set all columns HIGH
  2. Set column 0 LOW
  3. Read all rows (pressed key = LOW due to internal pull-up)
  4. Set column 0 HIGH, column 1 LOW
  5. Repeat for all columns
  6. Entire scan takes ~1ms for 60 keys

USB HID (Human Interface Device)

USB HID Keyboard Report (8 bytes):
┌────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┐
│  Byte 0    │  Byte 1    │  Byte 2    │  Byte 3    │  Byte 4    │  Byte 5    │  Byte 6    │  Byte 7    │
├────────────┼────────────┼────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ Modifiers  │  Reserved  │  Keycode 1 │  Keycode 2 │  Keycode 3 │  Keycode 4 │  Keycode 5 │  Keycode 6 │
│ (bitmap)   │  (0x00)    │            │            │            │            │            │            │
└────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┘

Modifier byte bits:
  Bit 0: Left Ctrl      Bit 4: Right Ctrl
  Bit 1: Left Shift     Bit 5: Right Shift
  Bit 2: Left Alt       Bit 6: Right Alt
  Bit 3: Left GUI       Bit 7: Right GUI

Keycode examples:
  0x04 = 'a'            0x1E = '1'
  0x05 = 'b'            0x28 = Enter
  0x06 = 'c'            0x29 = Escape
  ...                   0x2C = Space

QMK Architecture

┌──────────────────────────────────────────────────────────────────────────────┐
│                           QMK FIRMWARE ARCHITECTURE                           │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                        USER LAYER (your code)                           │ │
│  │  keymap.c ─────────▶ Keymaps, layers, custom functions                  │ │
│  │  config.h ─────────▶ Hardware configuration                             │ │
│  │  rules.mk ─────────▶ Feature enables/disables                           │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                          │
│  ┌─────────────────────────────────▼───────────────────────────────────────┐ │
│  │                     QUANTUM LAYER (QMK features)                        │ │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐      │ │
│  │  │  Layers  │ │Tap Dance │ │  Macros  │ │ Combos   │ │ RGB/LED  │      │ │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘      │ │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐      │ │
│  │  │  Mouse   │ │ Unicode  │ │ Audio    │ │ OLED     │ │Bluetooth │      │ │
│  │  │  Keys    │ │ Support  │ │ Feedback │ │ Display  │ │(limited) │      │ │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘      │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                          │
│  ┌─────────────────────────────────▼───────────────────────────────────────┐ │
│  │                      TMK CORE (keyboard fundamentals)                   │ │
│  │  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐   │ │
│  │  │ Matrix Scan  │ │  Debounce    │ │  Key Process │ │   Action     │   │ │
│  │  └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘   │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                          │
│  ┌─────────────────────────────────▼───────────────────────────────────────┐ │
│  │                    PLATFORM LAYER (hardware abstraction)                │ │
│  │  ┌──────────────────────────┐  ┌──────────────────────────┐            │ │
│  │  │  ChibiOS (ARM: STM32,    │  │  LUFA (AVR: ATmega32U4,  │            │ │
│  │  │  RP2040, etc.)           │  │  ATmega32U2, etc.)       │            │ │
│  │  └──────────────────────────┘  └──────────────────────────┘            │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                          │
│  ┌─────────────────────────────────▼───────────────────────────────────────┐ │
│  │                         HARDWARE                                        │ │
│  │  ATmega32U4 │ STM32F103 │ RP2040 │ nRF52840 │ etc.                     │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                                                               │
└──────────────────────────────────────────────────────────────────────────────┘

QMK Key Processing Pipeline

┌────────────────────────────────────────────────────────────────────────────┐
│                         KEY PROCESSING PIPELINE                             │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. matrix_scan()                                                           │
│     └──▶ Scan all rows/columns, detect state changes                       │
│                │                                                            │
│  2. debounce()                                                              │
│     └──▶ Filter mechanical bounce (default: 5ms)                           │
│                │                                                            │
│  3. matrix_scan_user() / matrix_scan_kb()                                  │
│     └──▶ User hooks for custom matrix handling                             │
│                │                                                            │
│  4. process_record()                                                        │
│     └──▶ Look up keycode from keymap[layer][row][col]                      │
│                │                                                            │
│  5. process_record_user() / process_record_kb()                            │
│     └──▶ User hooks - return false to stop processing                      │
│                │                                                            │
│  6. Feature processing chain:                                               │
│     ├── process_combo()                                                     │
│     ├── process_tap_dance()                                                 │
│     ├── process_leader()                                                    │
│     ├── process_unicode()                                                   │
│     └── ... (many more)                                                     │
│                │                                                            │
│  7. register_code() / unregister_code()                                    │
│     └──▶ Add/remove keycode from HID report                                │
│                │                                                            │
│  8. send_keyboard_report()                                                  │
│     └──▶ Transmit 8-byte HID report via USB                                │
│                                                                             │
└────────────────────────────────────────────────────────────────────────────┘

Firmware Comparison

Feature QMK ZMK KMK TMK
Language C C (Zephyr) Python C
Wireless Limited Excellent No No
Compilation Required Required Not required Required
Learning curve Moderate Steep Easy Steep
Features Extensive Growing Moderate Basic
Community Huge Growing Small Original
Best for Most builds Wireless Rapid prototyping Historical interest

Project List

Projects are ordered from fundamental concepts to advanced implementations. Each builds on the previous.


Project 1: Keyboard Matrix Simulator (Understand Matrix Scanning)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C
  • Alternative Programming Languages: Python, Rust, Arduino
  • 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: Arduino/AVR simulator, Wokwi
  • Main Book: “Make: AVR Programming” by Elliot Williams

What you’ll build: A software simulation of a keyboard matrix that visualizes how row/column scanning works, demonstrates ghosting, and shows why diodes are necessary.

Why it teaches keyboard fundamentals: Before touching hardware, you need to deeply understand why keyboards use matrices and how scanning works. This simulation lets you see the electrical behavior without physical components.

Core challenges you’ll face:

  • Understanding multiplexing → maps to why 16 pins can read 64 keys
  • Simulating switch behavior → maps to pull-up resistors, active-low logic
  • Demonstrating ghosting → maps to why diodes prevent phantom keypresses
  • Implementing scan timing → maps to real-world scan frequencies

Key Concepts:

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic programming, understanding of digital logic (HIGH/LOW)

Real world outcome:

┌────────────────────────────────────────────────────────────────┐
│           KEYBOARD MATRIX SIMULATOR                             │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Matrix State (4x4):        Current Scan:                      │
│  ┌─────┬─────┬─────┬─────┐  Column: 2                          │
│  │  Q  │  W  │ [E] │  R  │  Rows reading: 0=H, 1=H, 2=L, 3=H   │
│  ├─────┼─────┼─────┼─────┤                                     │
│  │  A  │  S  │  D  │  F  │  Detected: E at (0,2)               │
│  ├─────┼─────┼─────┼─────┤                                     │
│  │  Z  │  X  │  C  │  V  │  Scan rate: 1000 Hz                 │
│  ├─────┼─────┼─────┼─────┤  Scan time: 0.4ms                   │
│  │ Spc │ Alt │ Ctrl│ Ent │                                     │
│  └─────┴─────┴─────┴─────┘                                     │
│                                                                 │
│  [Press keys to simulate]  [Toggle diodes]  [Show ghosting]    │
│                                                                 │
│  GHOSTING DEMO: Press Q + S + D simultaneously...              │
│  Without diodes: Ghost detected at A! (false positive)         │
│  With diodes: No ghost. Only Q, S, D registered.              │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

Implementation Hints:

Matrix scanning algorithm (pseudocode):

for each column c in 0..NUM_COLS:
    set_column_low(c)           // Drive this column LOW
    wait_for_settle(1us)        // Let signals stabilize

    for each row r in 0..NUM_ROWS:
        if read_row(r) == LOW:  // Pull-up pulled down = pressed
            key_pressed[r][c] = true
        else:
            key_pressed[r][c] = false

    set_column_high(c)          // Release column

Questions to guide your implementation:

  • Why do we use internal pull-up resistors on rows?
  • What happens if you scan too fast (before signals settle)?
  • How does current flow when a key is pressed without diodes?
  • Draw the current path that causes ghosting with 3 keys pressed.

Ghosting scenario to simulate:

Press these three keys:
  [Q]     [ ]
  [ ]     [S]

Without diodes, current can flow: Q→Row0→S→Col1→Row1→???
This makes the MCU think [A] (Row1, Col0) is also pressed!

Learning milestones:

  1. You understand row/column scanning → You grasp multiplexed input
  2. You can explain ghosting → You understand electrical paths
  3. You simulate real scan timing → You understand embedded constraints
  4. You add diode simulation → You understand the hardware solution

Project 2: USB HID Device from Scratch (Understand the Protocol)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Python (with PyUSB)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: USB Protocol / Embedded Systems
  • Software or Tool: V-USB or LUFA library, ATmega32U4
  • Main Book: “USB Complete” by Jan Axelson

What you’ll build: A minimal USB HID keyboard using raw USB protocol—not QMK, but the foundation QMK is built on. This teaches you exactly how USB HID works.

Why it teaches USB HID: QMK abstracts away USB complexity. Building a HID device from scratch (or near-scratch with LUFA) forces you to understand descriptors, endpoints, reports, and the enumeration process.

Core challenges you’ll face:

  • USB enumeration → maps to device descriptors, configuration descriptors
  • HID report descriptors → maps to defining what data the device sends
  • Interrupt endpoints → maps to periodic data transfer
  • Report structure → maps to the 8-byte keyboard report format

Resources for key challenges:

Key Concepts:

  • USB Fundamentals: “USB Complete” Chapters 1-4 - Jan Axelson
  • HID Class: “USB Complete” Chapter 14 - Jan Axelson
  • LUFA Library: LUFA Documentation

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Project 1, C programming, basic embedded experience

Real world outcome:

$ lsusb
Bus 001 Device 015: ID 1234:5678 My Custom Keyboard

$ sudo dmesg | tail
[12345.678] usb 1-2: new full-speed USB device number 15
[12345.789] usb 1-2: New USB device found, idVendor=1234, idProduct=5678
[12345.790] usb 1-2: Product: DIY HID Keyboard
[12345.791] input: DIY HID Keyboard as /dev/input/event5

# When you press your hardware button:
$ sudo xxd /dev/hidraw0
00000000: 00 00 04 00 00 00 00 00  # 'a' pressed (keycode 0x04)
00000008: 00 00 00 00 00 00 00 00  # 'a' released

Implementation Hints:

USB HID Device structure:

Device Descriptor
├── Configuration Descriptor
│   └── Interface Descriptor (HID class = 0x03)
│       ├── HID Descriptor
│       │   └── Report Descriptor (defines report format)
│       └── Endpoint Descriptor (Interrupt IN)

Minimal HID Report Descriptor for keyboard:

static const uint8_t keyboard_report_descriptor[] = {
    0x05, 0x01,        // Usage Page (Generic Desktop)
    0x09, 0x06,        // Usage (Keyboard)
    0xA1, 0x01,        // Collection (Application)

    // Modifier keys (byte 0)
    0x05, 0x07,        //   Usage Page (Keyboard)
    0x19, 0xE0,        //   Usage Minimum (Left Control)
    0x29, 0xE7,        //   Usage Maximum (Right GUI)
    0x15, 0x00,        //   Logical Minimum (0)
    0x25, 0x01,        //   Logical Maximum (1)
    0x75, 0x01,        //   Report Size (1 bit)
    0x95, 0x08,        //   Report Count (8 bits)
    0x81, 0x02,        //   Input (Data, Variable, Absolute)

    // Reserved byte (byte 1)
    0x95, 0x01,        //   Report Count (1)
    0x75, 0x08,        //   Report Size (8 bits)
    0x81, 0x01,        //   Input (Constant)

    // Keycodes (bytes 2-7)
    0x95, 0x06,        //   Report Count (6)
    0x75, 0x08,        //   Report Size (8 bits)
    0x15, 0x00,        //   Logical Minimum (0)
    0x25, 0x65,        //   Logical Maximum (101)
    0x05, 0x07,        //   Usage Page (Keyboard)
    0x19, 0x00,        //   Usage Minimum (0)
    0x29, 0x65,        //   Usage Maximum (101)
    0x81, 0x00,        //   Input (Data, Array)

    0xC0               // End Collection
};

Questions to guide your implementation:

  • What happens during USB enumeration (the first few milliseconds)?
  • Why does HID use interrupt endpoints, not bulk endpoints?
  • What’s the difference between boot protocol and report protocol?
  • How does the host know your keyboard is a keyboard (not a mouse)?

Learning milestones:

  1. Your device enumerates → You understand USB descriptors
  2. The OS recognizes it as a keyboard → You understand HID class
  3. Keypresses appear in applications → You understand HID reports
  4. You can send modifiers + keys → You understand report format

Project 3: 3x3 Macro Pad with Arduino (Your First Physical Keyboard)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (Arduino)
  • Alternative Programming Languages: C++ (Arduino), CircuitPython
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Embedded Systems / Hardware
  • Software or Tool: Arduino Pro Micro (ATmega32U4), Arduino IDE
  • Main Book: “Make: AVR Programming” by Elliot Williams

What you’ll build: A working 9-key macro pad with Cherry MX switches, diodes, and custom firmware—not using QMK yet, but writing the matrix scanning and USB code yourself with the Arduino HID library.

Why it teaches keyboard building: This is your first real keyboard. You’ll solder, write matrix scanning code, and deal with real-world issues like debouncing. It’s small enough to complete quickly but teaches all the fundamentals.

Core challenges you’ll face:

  • Hardware assembly → maps to soldering switches, diodes, wiring
  • Matrix scanning code → maps to reading the physical matrix
  • Debouncing → maps to handling mechanical switch bounce
  • HID library usage → maps to sending keystrokes to computer

Key Concepts:

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 1-2, basic soldering skills, Arduino Pro Micro

Real world outcome:

Physical 3x3 macro pad with Cherry MX switches:
┌─────────┬─────────┬─────────┐
│  Copy   │  Paste  │  Undo   │
│ (Ctrl+C)│ (Ctrl+V)│ (Ctrl+Z)│
├─────────┼─────────┼─────────┤
│  Cut    │  Save   │ Select  │
│ (Ctrl+X)│ (Ctrl+S)│  All    │
├─────────┼─────────┼─────────┤
│  Mute   │ Vol Down│ Vol Up  │
│         │         │         │
└─────────┴─────────┴─────────┘

When pressed, keys send actual shortcuts to your computer!

Bill of Materials:

- Arduino Pro Micro (ATmega32U4)     ~$8
- 9x Cherry MX switches               ~$5
- 9x 1N4148 diodes                    ~$1
- Hookup wire                         ~$2
- 3D printed or laser cut case        ~$10
- USB-C cable                         ~$3
                                 Total: ~$30

Implementation Hints:

Wiring diagram (simplified):

Arduino Pro Micro Pinout:
                    ┌─────────┐
                    │  USB-C  │
                    ├─────────┤
             TX1  ──┤         ├── RAW
             RX0  ──┤         ├── GND
             GND  ──┤         ├── RST
             GND  ──┤         ├── VCC
             2    ──┤  Row 0  ├── A3
             3    ──┤  Row 1  ├── A2
             4    ──┤  Row 2  ├── A1
             5    ──┤  Col 0  ├── A0
             6    ──┤  Col 1  ├── 15
             7    ──┤  Col 2  ├── 14
                    └─────────┘

Matrix wiring:
        Col0(5)   Col1(6)   Col2(7)
           │         │         │
Row0(2)────┼──[SW]───┼──[SW]───┼──[SW]──┐
           │   ├─D   │   ├─D   │   ├─D  │
Row1(3)────┼──[SW]───┼──[SW]───┼──[SW]──┤
           │   ├─D   │   ├─D   │   ├─D  │
Row2(4)────┼──[SW]───┼──[SW]───┼──[SW]──┤
           │   ├─D   │   ├─D   │   ├─D  │
           └─────────┴─────────┴────────┘

Arduino code structure (pseudocode):

#include <Keyboard.h>

#define ROWS 3
#define COLS 3

int rowPins[ROWS] = {2, 3, 4};
int colPins[COLS] = {5, 6, 7};

// Key mapping - what each position does
int keymap[ROWS][COLS] = {
    {COPY, PASTE, UNDO},
    {CUT,  SAVE,  SELECT_ALL},
    {MUTE, VOL_DOWN, VOL_UP}
};

void setup() {
    // Set rows as INPUT_PULLUP
    // Set columns as OUTPUT, initially HIGH
    Keyboard.begin();
}

void loop() {
    for (int col = 0; col < COLS; col++) {
        digitalWrite(colPins[col], LOW);
        for (int row = 0; row < ROWS; row++) {
            if (digitalRead(rowPins[row]) == LOW) {
                // Key pressed! Send appropriate keystroke
                sendKey(keymap[row][col]);
            }
        }
        digitalWrite(colPins[col], HIGH);
    }
    delay(1); // Simple debounce
}

Questions to guide your implementation:

  • Why are diodes oriented cathode-to-row (or row-to-cathode)?
  • How long should you debounce (typical switch bounce is 5-20ms)?
  • What happens if you press more than 6 keys? (HID report limit)
  • How do you send Ctrl+C vs just ‘c’?

Learning milestones:

  1. Matrix wiring complete and correct → You understand keyboard hardware
  2. Scanning detects single keypresses → You understand matrix scanning
  3. Debouncing prevents double-triggers → You understand timing
  4. Macros work correctly → You understand USB HID

Project 4: QMK “Hello World” (Your First QMK Keyboard)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: N/A (QMK is C only)
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: QMK Firmware / Configuration
  • Software or Tool: QMK CLI, QMK Toolbox
  • Main Book: “Learning eBPF” (no direct book - use official docs)

What you’ll build: Convert your macro pad from Project 3 to run QMK firmware, learning the QMK file structure, build system, and basic configuration.

Why it teaches QMK: Now that you understand what’s happening under the hood, it’s time to use QMK properly. You’ll learn the file organization, configuration options, and build process.

Core challenges you’ll face:

  • QMK environment setup → maps to qmk setup, dependencies
  • Creating keyboard definition → maps to keyboard.json, config.h
  • Defining keymap → maps to keymap.c, layers
  • Building and flashing → maps to qmk compile, qmk flash

Key Concepts:

Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 3, QMK environment installed

Real world outcome:

$ qmk compile -kb my_macropad -km default
Compiling keymap with gmake...
[OK] Compiled successfully!
Copying my_macropad_default.hex to qmk_firmware/

$ qmk flash -kb my_macropad -km default
Waiting for bootloader...
Press RESET button on keyboard...
Flashing for bootloader: caterina
avrdude: AVR device initialized
avrdude: writing flash (16384 bytes)
avrdude: 16384 bytes written
avrdude: verified
avrdude done. Thank you.

Implementation Hints:

QMK file structure for your keyboard:

qmk_firmware/
└── keyboards/
    └── my_macropad/
        ├── keyboard.json      # Hardware definition (pins, matrix, USB info)
        ├── config.h           # Optional additional configuration
        ├── rules.mk           # Optional build rules
        ├── my_macropad.c      # Optional keyboard-level code
        └── keymaps/
            └── default/
                └── keymap.c   # Your key mappings and layers

keyboard.json (data-driven config):

{
    "manufacturer": "Your Name",
    "keyboard_name": "My Macropad",
    "maintainer": "yourusername",
    "bootloader": "caterina",
    "processor": "atmega32u4",
    "usb": {
        "vid": "0xFEED",
        "pid": "0x0001",
        "device_version": "1.0.0"
    },
    "matrix_pins": {
        "cols": ["F6", "F5", "F4"],
        "rows": ["D1", "D0", "D4"]
    },
    "diode_direction": "COL2ROW",
    "layouts": {
        "LAYOUT": {
            "layout": [
                {"x": 0, "y": 0, "matrix": [0, 0]},
                {"x": 1, "y": 0, "matrix": [0, 1]},
                {"x": 2, "y": 0, "matrix": [0, 2]},
                {"x": 0, "y": 1, "matrix": [1, 0]},
                {"x": 1, "y": 1, "matrix": [1, 1]},
                {"x": 2, "y": 1, "matrix": [1, 2]},
                {"x": 0, "y": 2, "matrix": [2, 0]},
                {"x": 1, "y": 2, "matrix": [2, 1]},
                {"x": 2, "y": 2, "matrix": [2, 2]}
            ]
        }
    }
}

keymap.c:

#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [0] = LAYOUT(
        C(KC_C),  C(KC_V),  C(KC_Z),    // Copy, Paste, Undo
        C(KC_X),  C(KC_S),  C(KC_A),    // Cut, Save, Select All
        KC_MUTE,  KC_VOLD,  KC_VOLU     // Media keys
    )
};

Questions to guide your implementation:

  • What’s the difference between keyboard.json and config.h?
  • Why do you need to know your bootloader type?
  • How do pin names like “F6” map to physical pins?
  • What does C(KC_C) expand to?

Learning milestones:

  1. QMK environment works → You can compile QMK
  2. Keyboard compiles without errors → You understand file structure
  3. Keyboard flashes and types → You understand the full workflow
  4. You customize the keymap → You’re using QMK!

Project 5: Multi-Layer Keymap with Tap-Dance (QMK Features)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: QMK Advanced Features
  • Software or Tool: QMK
  • Main Book: N/A (QMK documentation)

What you’ll build: An advanced keymap with multiple layers, tap-dance keys (single tap vs double tap vs hold), layer-tap keys, and custom behavior—demonstrating QMK’s powerful features.

Why it teaches QMK features: This is where QMK shines over simple firmware. You’ll learn the layer system, timing-based features, and how to maximize a small keyboard’s functionality.

Core challenges you’ll face:

  • Layer management → maps to MO, TG, TO, LT, OSL
  • Tap-Dance configuration → maps to ACTION_TAP_DANCE_
  • Timing tuning → maps to TAPPING_TERM, PERMISSIVE_HOLD
  • Custom functions → maps to process_record_user

Key Concepts:

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 4 completed

Real world outcome:

Layer 0 (Base):                  Layer 1 (Numpad):
┌─────────┬─────────┬─────────┐  ┌─────────┬─────────┬─────────┐
│    7    │    8    │    9    │  │   F7    │   F8    │   F9    │
│         │         │         │  │         │         │         │
├─────────┼─────────┼─────────┤  ├─────────┼─────────┼─────────┤
│    4    │    5    │    6    │  │   F4    │   F5    │   F6    │
│         │         │         │  │         │         │         │
├─────────┼─────────┼─────────┤  ├─────────┼─────────┼─────────┤
│    1    │    2    │    3    │  │   F1    │   F2    │   F3    │
│Tap:1    │Tap:2    │Tap:3    │  │         │         │         │
│Hold:L1  │Hold:Shft│Hold:Ctrl│  │         │         │         │
└─────────┴─────────┴─────────┘  └─────────┴─────────┴─────────┘

Tap-Dance on key (0,0):
  1 tap  → '7'
  2 taps → '('
  hold   → Layer 1 momentary

Implementation Hints:

Layer definitions in keymap.c:

enum layers {
    _BASE,
    _NUMPAD,
    _FN
};

enum custom_keycodes {
    TD_7_PAREN = SAFE_RANGE,
};

// Tap Dance declarations
enum {
    TD_7,
    TD_8,
    TD_9,
};

// Define what tap dance does
tap_dance_action_t tap_dance_actions[] = {
    [TD_7] = ACTION_TAP_DANCE_DOUBLE(KC_7, KC_LPRN),
    [TD_8] = ACTION_TAP_DANCE_LAYER_MOVE(KC_8, _NUMPAD),
    // ...
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [_BASE] = LAYOUT(
        TD(TD_7),           TD(TD_8),        TD(TD_9),
        LT(_FN, KC_4),      LSFT_T(KC_5),    LCTL_T(KC_6),
        KC_1,               KC_2,            KC_3
    ),
    [_NUMPAD] = LAYOUT(
        KC_F7,    KC_F8,    KC_F9,
        KC_F4,    KC_F5,    KC_F6,
        KC_F1,    KC_F2,    KC_F3
    ),
    // ...
};

Key timing concepts:

// config.h
#define TAPPING_TERM 200    // ms to distinguish tap from hold
#define PERMISSIVE_HOLD     // Allow hold detection during fast typing
#define RETRO_TAPPING       // Tap after hold-time if no other key pressed

Understanding LT and MT:

LT(layer, keycode)  - Hold: activate layer, Tap: send keycode
LSFT_T(keycode)     - Hold: Left Shift, Tap: send keycode
LCTL_T(keycode)     - Hold: Left Ctrl, Tap: send keycode

Questions to guide your implementation:

  • When does QMK decide between tap and hold?
  • What’s the difference between MO(layer) and LT(layer, kc)?
  • How do you debug timing issues (key feels sluggish)?
  • Can you nest layer-tap inside tap-dance?

Learning milestones:

  1. Multiple layers work → You understand layer stacking
  2. Layer-tap feels natural → You understand timing configuration
  3. Tap-dance works reliably → You understand advanced features
  4. Custom keycodes work → You can extend QMK

Project 6: QMK with RGB and OLED (Display Features)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Hardware Peripherals / I2C/SPI
  • Software or Tool: QMK, WS2812B LEDs, SSD1306 OLED
  • Main Book: “Make: AVR Programming” by Elliot Williams

What you’ll build: Add per-key RGB lighting and an OLED display to your keyboard, showing layer status, WPM, or custom animations.

Why it teaches hardware integration: Beyond basic typing, keyboards can have rich visual feedback. This teaches you about I2C (OLED), addressable LEDs (WS2812B), and QMK’s peripheral subsystems.

Core challenges you’ll face:

  • RGB LED wiring and protocol → maps to WS2812B timing, data chain
  • OLED over I2C → maps to I2C protocol, SSD1306 driver
  • QMK RGB subsystem → maps to rgb_matrix, rgblight
  • Custom OLED rendering → maps to oled_task_user, graphics

Key Concepts:

  • RGB Matrix: QMK RGB Matrix
  • OLED Driver: QMK OLED
  • I2C Protocol: “Make: AVR Programming” Chapter 16 - Elliot Williams

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 4-5, soldering LEDs, I2C experience helpful

Real world outcome:

┌──────────────────────────────────────┐
│          OLED DISPLAY                │
│  ┌────────────────────────────────┐  │
│  │  Layer: NUMPAD                 │  │
│  │  WPM: 85                       │  │
│  │  ████████████░░░░ 75%         │  │
│  │  Caps: OFF  Num: ON           │  │
│  └────────────────────────────────┘  │
│                                      │
│  ┌─────────┬─────────┬─────────┐     │
│  │ [LED]   │ [LED]   │ [LED]   │     │
│  │ Purple  │ Purple  │ Purple  │     │
│  │  (F7)   │  (F8)   │  (F9)   │     │
│  ├─────────┼─────────┼─────────┤     │
│  │ [LED]   │ [LED]   │ [LED]   │     │
│  │ Purple  │ Purple  │ Purple  │     │
│  │  (F4)   │  (F5)   │  (F6)   │     │
│  ├─────────┼─────────┼─────────┤     │
│  │ [LED]   │ [LED]   │ [LED]   │     │
│  │ Purple  │ Purple  │ Purple  │     │
│  │  (F1)   │  (F2)   │  (F3)   │     │
│  └─────────┴─────────┴─────────┘     │
│                                      │
│  Layer 1 = Purple theme              │
│  Layer 0 = Rainbow wave animation    │
└──────────────────────────────────────┘

Bill of Materials (additions):

- 9x WS2812B LEDs (SK6812 mini-e work too)  ~$5
- SSD1306 128x32 OLED (I2C)                  ~$5
- 4.7kΩ resistors (I2C pull-ups)             ~$1

Implementation Hints:

LED data chain wiring:

MCU Pin ──▶ LED1 DOUT ──▶ LED2 DIN ──▶ LED3 DIN ──▶ ... ──▶ LED9
   (D3)           │              │              │
             (5V, GND)      (5V, GND)      (5V, GND)

OLED I2C wiring:

MCU SDA (D1) ──┬── OLED SDA
               │
MCU SCL (D0) ──┼── OLED SCL
               │
VCC ───────────┼── OLED VCC
               │
GND ───────────┴── OLED GND

Note: Add 4.7kΩ pull-up resistors on SDA and SCL to VCC

QMK configuration:

// rules.mk
RGB_MATRIX_ENABLE = yes
OLED_ENABLE = yes

// config.h
#define RGB_MATRIX_LED_COUNT 9
#define RGB_MATRIX_KEYPRESSES
#define RGB_MATRIX_FRAMEBUFFER_EFFECTS
#define I2C_DRIVER I2CD1
#define OLED_DISPLAY_128X32

// keymap.c
bool oled_task_user(void) {
    oled_write_P(PSTR("Layer: "), false);
    switch (get_highest_layer(layer_state)) {
        case _BASE:   oled_write_P(PSTR("Base\n"), false); break;
        case _NUMPAD: oled_write_P(PSTR("Numpad\n"), false); break;
        default:      oled_write_P(PSTR("???\n"), false);
    }
    return false;
}

bool rgb_matrix_indicators_user(void) {
    if (get_highest_layer(layer_state) == _NUMPAD) {
        rgb_matrix_set_color_all(128, 0, 128); // Purple
    }
    return false;
}

Questions to guide your implementation:

  • Why does WS2812B need precise timing (and how does QMK handle it)?
  • What’s the difference between rgblight and rgb_matrix?
  • How do you draw custom graphics on the OLED?
  • What’s the power consumption impact of LEDs at full brightness?

Learning milestones:

  1. LEDs light up → You understand WS2812B protocol
  2. Colors change with layers → You understand RGB callbacks
  3. OLED displays text → You understand I2C and OLED driver
  4. Custom animations work → You can extend QMK visually

Project 7: Design Your Own PCB (From Schematic to Fabrication)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (QMK), KiCad (EDA)
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 4: Expert
  • Knowledge Area: PCB Design / Electronics
  • Software or Tool: KiCad, JLCPCB/PCBWay
  • Main Book: “Designing Electronics That Work” by Hunter Scott

What you’ll build: A complete keyboard PCB designed in KiCad—schematic capture, PCB layout, manufacturing files—then fabricated and assembled into a working keyboard.

Why it teaches hardware design: This is where you go from “user” to “creator.” Understanding PCB design means you can build any keyboard you imagine, not just what others have designed.

Core challenges you’ll face:

  • Schematic capture → maps to component symbols, connections
  • PCB layout → maps to footprints, routing, design rules
  • Manufacturing files → maps to Gerbers, drill files, BOM
  • Assembly → maps to reflow soldering or hand assembly

Resources for key challenges:

Key Concepts:

Difficulty: Expert Time estimate: 4-6 weeks Prerequisites: Projects 1-6, basic electronics, KiCad familiarity helpful

Real world outcome:

Your custom PCB arrives from JLCPCB:

┌────────────────────────────────────────────────────────────────┐
│  ╔══════════════════════════════════════════════════════════╗  │
│  ║  YOUR KEYBOARD - Rev 1.0                                  ║  │
│  ║  ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐     ║  │
│  ║  │ Q  │ W  │ E  │ R  │ T  │ Y  │ U  │ I  │ O  │ P  │     ║  │
│  ║  ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤     ║  │
│  ║  │ A  │ S  │ D  │ F  │ G  │ H  │ J  │ K  │ L  │ ;  │     ║  │
│  ║  ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤     ║  │
│  ║  │ Z  │ X  │ C  │ V  │ B  │ N  │ M  │ ,  │ .  │ /  │     ║  │
│  ║  └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘     ║  │
│  ║           [Pro Micro]  ┌─USB─┐                           ║  │
│  ╚══════════════════════════════════════════════════════════╝  │
│  Professional-looking PCB with your design, your name,         │
│  manufactured in China and shipped to you for ~$20!            │
└────────────────────────────────────────────────────────────────┘

Implementation Hints:

Design workflow:

1. Layout Design (keyboard-layout-editor.com)
   └── Define key positions, sizes, layout

2. Schematic Capture (KiCad Eeschema)
   ├── Place MCU symbol
   ├── Place switch symbols in matrix
   ├── Add diodes
   ├── Connect rows/columns
   └── Add USB connector, decoupling caps, crystal

3. PCB Layout (KiCad PCB Editor)
   ├── Import schematic
   ├── Place switch footprints to match layout
   ├── Place MCU, USB connector
   ├── Route all traces
   └── Add silkscreen art, labels

4. Design Rule Check
   └── Verify no errors

5. Generate Manufacturing Files
   ├── Gerbers (copper, mask, silk)
   ├── Drill files
   └── BOM and Pick-and-place (if using assembly)

6. Order from fabricator (JLCPCB, PCBWay)

7. Assemble and test

Critical schematic elements:

MCU Power:
  VCC ──┬── [100nF cap] ── GND  (decoupling)
        └── [10µF cap] ── GND   (bulk)

USB:
  USB D+ ─── [22Ω] ─── MCU D+
  USB D- ─── [22Ω] ─── MCU D-

Crystal (if needed):
  XTAL1 ──┬── [Crystal] ──┬── XTAL2
          │               │
         [22pF]         [22pF]
          │               │
          └───── GND ─────┘

Matrix:
  Each switch has a diode in series (cathode to row)

Questions to guide your implementation:

  • What trace width do you need for USB signals?
  • Why do you need decoupling capacitors near the MCU?
  • How do you ensure switch spacing matches real keycaps?
  • What’s the difference between ENIG and HASL finish?

Learning milestones:

  1. Schematic is complete → You understand keyboard electronics
  2. PCB layout passes DRC → You understand manufacturing constraints
  3. PCB arrives and looks correct → You can generate manufacturing files
  4. Keyboard works first try → You’re a PCB designer!

Project 8: Split Keyboard (Communication Protocol)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (QMK)
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 4: Expert
  • Knowledge Area: Serial Communication / Split Keyboards
  • Software or Tool: QMK, TRRS cable or wireless
  • Main Book: N/A (QMK split keyboard docs)

What you’ll build: A two-piece split keyboard where each half has its own microcontroller, communicating over a TRRS cable (or wireless). This is the ergonomic keyboard enthusiast’s dream.

Why it teaches communication protocols: Split keyboards need to synchronize matrix data between halves. This teaches you serial communication, master/slave architecture, and the challenges of distributed embedded systems.

Core challenges you’ll face:

  • Half detection → maps to which half is master/slave
  • Serial communication → maps to UART, I2C, or soft serial
  • Matrix synchronization → maps to sending key states across halves
  • USB only on one side → maps to proper handshake

Key Concepts:

  • Split Keyboard QMK: QMK Split Keyboard
  • Serial Protocol: QMK soft serial documentation
  • TRRS Cables: Understanding TRS vs TRRS wiring

Difficulty: Expert Time estimate: 4-6 weeks Prerequisites: Projects 1-7, PCB design experience

Real world outcome:

Left Half                          Right Half
(Master - USB)                     (Slave)
┌────┬────┬────┬────┬────┐        ┌────┬────┬────┬────┬────┐
│ Q  │ W  │ E  │ R  │ T  │        │ Y  │ U  │ I  │ O  │ P  │
├────┼────┼────┼────┼────┤        ├────┼────┼────┼────┼────┤
│ A  │ S  │ D  │ F  │ G  │◄──────▶│ H  │ J  │ K  │ L  │ ;  │
├────┼────┼────┼────┼────┤  TRRS  ├────┼────┼────┼────┼────┤
│ Z  │ X  │ C  │ V  │ B  │ Cable  │ N  │ M  │ ,  │ .  │ /  │
└────┴────┴────┴────┴────┘        └────┴────┴────┴────┴────┘
    │
   USB
    │
   [Computer]

Both halves work together as one keyboard!

Implementation Hints:

Split keyboard communication:

Master (Left) ◄─────── TRRS Cable ───────▶ Slave (Right)
     │                                           │
     │  1. Master scans its local matrix         │
     │  2. Master requests slave's matrix        │
     │  3. Slave sends its key states ──────────▶│
     │  4. Master combines both matrices         │
     │  5. Master sends USB HID report           │
     │                                           │
    USB                                      (no USB)
     │
     ▼
  Computer

TRRS cable pinout:

           ┌─────────────────────┐
           │  TRRS Jack Pinout   │
           ├─────────────────────┤
Tip        │  VCC (5V or 3.3V)   │
Ring 1     │  GND                │
Ring 2     │  Serial Data        │
Sleeve     │  GND (or extra sig) │
           └─────────────────────┘

QMK split configuration:

// config.h
#define SPLIT_KEYBOARD
#define SPLIT_USB_DETECT
#define SERIAL_USART_FULL_DUPLEX  // or SOFT_SERIAL_PIN for half-duplex
#define SERIAL_USART_TX_PIN B6
#define SERIAL_USART_RX_PIN B7

// Or for soft serial (single wire):
#define SOFT_SERIAL_PIN D2

// Matrix for BOTH halves combined
#define MATRIX_ROWS 6  // 3 rows x 2 halves
#define MATRIX_COLS 10 // 5 cols x 2 halves (or just use MATRIX_COLS_RIGHT)

Questions to guide your implementation:

  • How does QMK know which half is connected to USB?
  • What happens if the TRRS cable is disconnected?
  • How do you handle layer state across both halves?
  • What’s the latency impact of serial communication?

Learning milestones:

  1. Both halves power on → You understand split power
  2. Master detects slave → You understand handshake
  3. Keypresses work from both halves → You understand matrix sync
  4. Layers sync correctly → You understand state sharing

Project 9: Bluetooth Wireless Keyboard (ZMK or Custom)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (ZMK/Zephyr or nRF SDK)
  • Alternative Programming Languages: Rust (with Embassy)
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 5: Master
  • Knowledge Area: Bluetooth Low Energy / Power Management
  • Software or Tool: ZMK, nRF52840, Zephyr RTOS
  • Main Book: “Bluetooth Low Energy: The Developer’s Handbook” by Robin Heydon

What you’ll build: A battery-powered wireless keyboard using Bluetooth Low Energy, with proper power management for months of battery life.

Why it teaches wireless: QMK doesn’t support Bluetooth well. ZMK (or custom firmware) teaches you BLE protocols, power management, battery monitoring, and the Zephyr RTOS—skills that transfer to any IoT project.

Core challenges you’ll face:

  • BLE HID profile → maps to GATT services, HID over GATT
  • Power management → maps to sleep modes, current consumption
  • Battery management → maps to voltage monitoring, charging
  • Multi-device pairing → maps to bonding, profile switching

Key Concepts:

  • ZMK Firmware: ZMK Documentation
  • BLE Basics: “Bluetooth Low Energy: The Developer’s Handbook” - Heydon
  • nRF52 Power: Nordic Semiconductor app notes

Difficulty: Master Time estimate: 6-8 weeks Prerequisites: All previous projects, RTOS experience helpful

Real world outcome:

┌────────────────────────────────────────────────────────┐
│         WIRELESS KEYBOARD STATUS                        │
├────────────────────────────────────────────────────────┤
│                                                         │
│  Battery: ████████░░ 82%                               │
│  Estimated life: 3 months                              │
│                                                         │
│  Bluetooth Status:                                      │
│    Profile 1: MacBook Pro (connected)                  │
│    Profile 2: iPad                                     │
│    Profile 3: Work Laptop                              │
│                                                         │
│  Current consumption:                                   │
│    Active typing: 2.5mA                                │
│    Idle: 15µA                                          │
│    Deep sleep: 1µA                                     │
│                                                         │
│  [No cables. No dongles. Just works.]                  │
│                                                         │
└────────────────────────────────────────────────────────┘

Implementation Hints:

Hardware requirements:

- nRF52840 (or nRF52832) module
- 3.7V LiPo battery (200-2000mAh)
- Battery charging circuit (TP4056 or similar)
- Voltage regulator (if needed)
- Low-power design considerations throughout

ZMK vs Custom:

ZMK (Recommended for most users):
  - Built on Zephyr RTOS
  - Declarative configuration (.keymap, .conf files)
  - Growing feature set
  - Active community
  - Focus on power efficiency

Custom nRF SDK:
  - Maximum control
  - Can optimize for specific use case
  - Steeper learning curve
  - Use Nordic's SoftDevice for BLE stack

Power budget calculation:

200mAh battery ÷ average current = battery life

Example:
  Typing 8 hours/day @ 2mA  = 16mAh/day
  Idle 16 hours/day @ 0.1mA = 1.6mAh/day
  Total: 17.6mAh/day
  Battery life: 200mAh ÷ 17.6 = 11+ days

With better sleep (15µA idle):
  Typing 2 hours/day @ 2mA    = 4mAh/day
  Idle 22 hours/day @ 0.015mA = 0.33mAh/day
  Total: 4.33mAh/day
  Battery life: 200mAh ÷ 4.33 = 46 days!

Questions to guide your implementation:

  • How does BLE HID differ from USB HID?
  • What’s the trade-off between connection interval and power?
  • How do you switch between paired devices?
  • What triggers the keyboard to wake from deep sleep?

Learning milestones:

  1. Keyboard pairs with phone → You understand BLE pairing
  2. Keypresses work wirelessly → You understand HID over GATT
  3. Battery lasts weeks → You understand power management
  4. Multi-device pairing works → You understand profiles and bonding

Project 10: Custom Keyboard Firmware from Scratch (The Deep Dive)

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • 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: Master
  • Knowledge Area: Firmware Development / USB / Embedded Systems
  • Software or Tool: ARM toolchain, USB library (tinyusb), bare metal
  • Main Book: “The Definitive Guide to ARM Cortex-M” by Joseph Yiu

What you’ll build: Your own keyboard firmware from the ground up—not QMK, not ZMK, but YOUR firmware. Matrix scanning, debouncing, USB stack, layers, macros—all written by you.

Why this is the ultimate project: This proves you truly understand keyboard firmware. Every line of code is intentional. Every abstraction is yours. This is how QMK and TMK started.

Core challenges you’ll face:

  • USB stack integration → maps to tinyusb, libopencm3, or custom
  • Real-time constraints → maps to scan rate, debounce timing
  • Memory constraints → maps to fitting in 32KB flash
  • Feature completeness → maps to layers, macros, RGB support

Key Concepts:

  • ARM Programming: “The Definitive Guide to ARM Cortex-M” - Yiu
  • USB Stack: TinyUSB Library
  • Bare Metal C: “Make: AVR Programming” for concepts (translate to ARM)

Difficulty: Master Time estimate: 2-3 months Prerequisites: All previous projects, strong C skills, USB knowledge

Real world outcome:

Your firmware running on bare metal:

$ arm-none-eabi-size my_firmware.elf
   text    data     bss     dec     hex filename
  12456     124    2048   14628    3924 my_firmware.elf

Features implemented:
  ✓ Matrix scanning (500Hz)
  ✓ Debouncing (symmetric eager)
  ✓ USB HID keyboard
  ✓ USB HID consumer control
  ✓ 16 layers
  ✓ Macros (text and key sequences)
  ✓ Tap-hold (home row mods)
  ✓ One-shot modifiers
  ✓ RGB LED control
  ✓ OLED display
  ✓ EEPROM settings storage

Boot time: 45ms from power-on to USB enumeration
Scan latency: <2ms from keypress to USB report

Implementation Hints:

Architecture design:

┌─────────────────────────────────────────────────────────────────┐
│                    YOUR FIRMWARE ARCHITECTURE                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  main() loop:                                                    │
│    1. matrix_scan()           // Read physical keys             │
│    2. debounce_update()       // Filter switch bounce           │
│    3. process_matrix()        // Detect key events              │
│    4. process_keycodes()      // Handle layers, tap-hold        │
│    5. hid_report_update()     // Build USB report               │
│    6. usb_task()              // TinyUSB housekeeping           │
│    7. led_task()              // RGB animations (if time)       │
│                                                                  │
│  Interrupt handlers:                                             │
│    - SysTick: timing reference (1ms)                            │
│    - USB: handled by TinyUSB                                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Minimal keymap structure:

// Define layers
enum layer {
    LAYER_BASE,
    LAYER_FN,
    LAYER_NUM,
    // ...
};

// Keycode types
typedef uint16_t keycode_t;

// Key actions
#define KC_A        0x0004
#define KC_B        0x0005
// ... (from USB HID usage tables)
#define LT(layer, kc)  (0x4000 | ((layer) << 8) | (kc))
#define MO(layer)      (0x5000 | (layer))

// Keymap storage
const keycode_t keymap[NUM_LAYERS][MATRIX_ROWS][MATRIX_COLS] = {
    [LAYER_BASE] = {
        {KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, ...},
        ...
    },
    [LAYER_FN] = {
        ...
    },
};

Questions to guide your implementation:

  • How do you handle the USB enumeration sequence?
  • What’s your debouncing algorithm? (eager vs sym-defer vs asym)
  • How do you implement tap-hold timing?
  • How do you store keymap in flash but settings in EEPROM?

Learning milestones:

  1. USB enumeration works → You understand USB at a deep level
  2. Basic typing works → You understand the full keypress pipeline
  3. Layers and modifiers work → You understand keycode processing
  4. Tap-hold feels good → You understand timing-sensitive code
  5. Everything fits in 32KB → You understand embedded constraints

Project Comparison Table

Project Difficulty Time Depth of Understanding Fun Factor
1. Matrix Simulator Weekend ⚡⚡⚡ 🎮🎮🎮
2. USB HID from Scratch ⭐⭐⭐ 2-3 weeks ⚡⚡⚡⚡⚡ 🎮🎮🎮🎮
3. 3x3 Macro Pad ⭐⭐ 1-2 weeks ⚡⚡⚡ 🎮🎮🎮🎮🎮
4. QMK Hello World ⭐⭐ Weekend ⚡⚡⚡ 🎮🎮🎮
5. Layers & Tap-Dance ⭐⭐ 1 week ⚡⚡⚡ 🎮🎮🎮🎮
6. RGB and OLED ⭐⭐⭐ 2 weeks ⚡⚡⚡⚡ 🎮🎮🎮🎮🎮
7. PCB Design ⭐⭐⭐⭐ 4-6 weeks ⚡⚡⚡⚡⚡ 🎮🎮🎮🎮🎮
8. Split Keyboard ⭐⭐⭐⭐ 4-6 weeks ⚡⚡⚡⚡ 🎮🎮🎮🎮🎮
9. Bluetooth Wireless ⭐⭐⭐⭐⭐ 6-8 weeks ⚡⚡⚡⚡⚡ 🎮🎮🎮🎮🎮
10. Firmware from Scratch ⭐⭐⭐⭐⭐ 2-3 months ⚡⚡⚡⚡⚡ 🎮🎮🎮🎮🎮

Your Starting Point

If you just want a working custom keyboard: Skip to Project 3 (Macro Pad) → Project 4 (QMK) → Project 5 (Features)

If you want to deeply understand how keyboards work: Start at Project 1 and work through sequentially.

If you’re a hardware engineer wanting firmware skills: Start at Project 2 (USB HID) → Project 4 (QMK) → Project 10 (Custom Firmware)

If you want to design and sell keyboards: Projects 3-7 (Build skills) → Project 8 (Split) → Market research!

Phase 1: Foundation (2-3 weeks)
├── Project 1: Matrix Simulator → Understand the hardware
└── Project 2: USB HID basics → Understand the protocol

Phase 2: First Physical Keyboard (2-4 weeks)
├── Project 3: Macro Pad → Build something real
└── Project 4: QMK Hello World → Learn the ecosystem

Phase 3: QMK Mastery (3-4 weeks)
├── Project 5: Layers & Tap-Dance → Advanced features
└── Project 6: RGB & OLED → Hardware peripherals

Phase 4: Hardware Design (6-8 weeks)
└── Project 7: PCB Design → Create from scratch

Phase 5: Advanced Builds (6-12 weeks)
├── Project 8: Split Keyboard → Communication protocols
└── Project 9: Bluetooth → Wireless and power management

Phase 6: Deep Understanding (2-3 months)
└── Project 10: Firmware from Scratch → Complete mastery

Final Project: Production-Ready Custom Keyboard Line

  • File: LEARN_QMK_KEYBOARD_FIRMWARE.md
  • Main Programming Language: C (QMK/custom), KiCad
  • Alternative Programming Languages: Rust, Python (tooling)
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 5. The “Industry Disruptor”
  • Difficulty: Level 5: Master
  • Knowledge Area: Product Development / Manufacturing
  • Software or Tool: KiCad, QMK, Fusion 360, JLCPCB
  • Main Book: “The Mom Test” by Rob Fitzpatrick (for business), “Designing Electronics That Work” (for hardware)

What you’ll build: A complete keyboard product line—multiple layouts (60%, 65%, TKL), with a cohesive design language, VIA/Vial support for end-user configuration, proper documentation, and ready-to-sell quality.

Why this is the ultimate project: This combines everything: hardware design, firmware, user experience, documentation, manufacturing, and potentially business. This is how companies like OLKB, ZSA, and Keychron started.

What you’ll create:

  • Multiple PCB designs sharing common elements
  • Unified firmware with VIA/Vial support
  • Case designs (3D printed or CNC aluminum)
  • Build guides and documentation
  • QC testing procedures
  • (Optional) Online store and fulfillment

Difficulty: Master Time estimate: 6-12 months Prerequisites: All 10 projects completed

Real world outcome:

YOUR KEYBOARD COMPANY

Products:
┌────────────────┬────────────────┬────────────────┐
│   Model 60     │   Model 65     │   Model TKL    │
│  60% layout    │  65% layout    │  Tenkeyless    │
│  Hotswap       │  Hotswap       │  Hotswap       │
│  RGB           │  RGB           │  RGB           │
│  USB-C         │  USB-C         │  USB-C         │
│  QMK/VIA       │  QMK/VIA       │  QMK/VIA       │
│  $129          │  $149          │  $179          │
└────────────────┴────────────────┴────────────────┘

Features across all models:
  ✓ 1000Hz polling rate
  ✓ Per-key RGB with 16 effects
  ✓ Full VIA/Vial configuration
  ✓ Gasket mount design
  ✓ South-facing switches
  ✓ Screw-in stabilizers
  ✓ Brass weight
  ✓ Carrying case included

Essential Resources

Books (Primary)

Book Author Best For
Make: AVR Programming Elliot Williams Microcontroller fundamentals
USB Complete Jan Axelson USB protocol deep dive
Designing Electronics That Work Hunter Scott PCB design
The Definitive Guide to ARM Cortex-M Joseph Yiu ARM programming

Online Resources

Resource URL Description
QMK Firmware docs.qmk.fm Official documentation
QMK GitHub github.com/qmk Source code
ai03 PCB Guide wiki.ai03.com PCB design for keyboards
ZMK Firmware zmk.dev Wireless keyboard firmware
Keyboard Layout Editor keyboard-layout-editor.com Layout design tool
GeekHack geekhack.org Community forums
r/MechanicalKeyboards reddit.com/r/MechanicalKeyboards Reddit community

Video Resources

Channel Content
Joe Scotto Handwired keyboards, macro pads
Thomas Baart Splitkb, advanced builds
Ben Vallack Ergonomic keyboards

Summary

# Project Main Language Knowledge Area
1 Matrix Simulator C Electronics / Digital I/O
2 USB HID from Scratch C USB Protocol / Embedded
3 3x3 Macro Pad C (Arduino) Hardware / Embedded
4 QMK Hello World C (QMK) QMK Configuration
5 Layers & Tap-Dance C (QMK) QMK Advanced Features
6 RGB and OLED C (QMK) Hardware Peripherals
7 PCB Design C + KiCad PCB Design / Electronics
8 Split Keyboard C (QMK) Serial Communication
9 Bluetooth Wireless C (ZMK) BLE / Power Management
10 Firmware from Scratch C/Rust Complete Firmware Development
Final Keyboard Product Line Multiple Product Development

Getting Started Checklist

Before starting Project 1:

  • Install QMK environment: qmk setup
  • Have a keyboard layout in mind (or start with 3x3 macro pad)
  • Acquire an Arduino Pro Micro or similar ATmega32U4 board (~$8)
  • Get basic soldering equipment
  • Join the community (r/MechanicalKeyboards, GeekHack, QMK Discord)
  • Read How a Matrix Works

Welcome to the custom keyboard rabbit hole! 🐰⌨️


Generated for deep understanding of QMK firmware and keyboard technology