Project 4: QMK Hello World (First Custom Keymap)

Create a custom keymap for an existing QMK keyboard, add a macro, and flash a working firmware you can explain line-by-line.

Quick Reference

Attribute Value
Difficulty Level 1: Beginner
Time Estimate 4-8 hours
Main Programming Language C (QMK)
Alternative Programming Languages None
Coolness Level Level 2: Practical
Business Potential Level 1: Resume Gold
Prerequisites QMK CLI, basic C, a supported board
Key Topics Keymap structure, custom keycodes, build/flash

1. Learning Objectives

By completing this project, you will:

  1. Understand QMK’s keymap folder structure and build flow.
  2. Modify a layout array to change key outputs.
  3. Implement a custom keycode using process_record_user.
  4. Build and flash firmware for a supported keyboard.
  5. Verify key behavior in practice and debug mis-maps.

2. All Theory Needed (Per-Concept Breakdown)

2.1 Keymap Structure and Layout Macros

Fundamentals

QMK keymaps map physical key positions to logical keycodes. This mapping is expressed as a 2D array that corresponds to the keyboard’s matrix. Most QMK keyboards define a LAYOUT(...) macro that translates the physical key order into the correct matrix indices. This allows you to think in terms of physical positions rather than row/column indices. When you create a new keymap, you are editing a C file that defines one or more layers using this layout macro. Understanding how the layout macro expands is critical to debugging when keys appear swapped or mirrored.

Deep Dive into the Concept

Every QMK keyboard defines its physical layout in a header file, typically keyboards/<board>/info.json and a matching layout.h or keymap.h. The LAYOUT macro is a convenience that orders keys in a human-friendly sequence. Internally, it expands to a 2D array ordered by matrix rows and columns. If you press a key and it outputs a different keycode, the mismatch is usually between the physical layout and the matrix ordering in that macro. This is why it’s essential to consult the keyboard’s info.json and the layout macro definition.

In a keymap file, you define layers as:

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [_BASE] = LAYOUT(...)
};

Each layer is a complete matrix; if a key is transparent (KC_TRNS), the firmware falls through to lower layers. QMK builds a layer stack where higher layers override lower ones. Even if you only use one layer in this project, understanding the layer model helps you avoid mistakes. For example, if you add a second layer but leave a key as KC_NO, it will be inactive even if lower layers define it.

The layout macro also determines the key order in keymap.c. Some keyboards have multiple layout macros for different physical configurations. If you select the wrong macro, your keymap will compile but the physical key order will be wrong. This is a common beginner mistake. Always verify which layout macro you are using and compare it with the physical legend in info.json.

Because the layout macro is a preprocessor construct, mistakes can be subtle. A missing key, duplicate key, or wrong order compiles without errors but results in confusing behavior. The QMK Configurator can help visualize the matrix, but learning to read the layout macro is what gives you full control. This project is where you build that skill.

Additional layout considerations: many keyboards include multiple physical layout options (split backspace, ISO enter, different bottom rows). The info.json file often lists several layout macros; pick the one that matches your actual plate. If you are unsure, use QMK Configurator to visualize the layout and compare it with your keyboard. Another practice is to print the keymap array indices in QMK console for a few test keys, which helps you align physical positions with matrix indices. This makes it far easier to reason about why a key might be swapped.

Extra layout hygiene: use KC_NO to explicitly disable keys you never want active on a layer, and use KC_TRNS for keys that should inherit the base layer. This makes it easy to read and maintain the keymap. If you want to visualize your keymap, QMK provides tools like qmk c2json to convert keymap.c to a JSON representation you can sanity-check in a layout viewer. This prevents off-by-one placement errors when you edit large layouts.

Additional debugging practice: keep a small “matrix map” comment block at the top of your keymap that lists row/column indices next to physical positions. This makes future edits safer and helps collaborators understand your intent. When you test changes, use qmk console to verify the exact keycode numbers emitted, not just the characters in a text editor, because modifiers and OS layouts can mask errors.

How this fits on projects

This concept is core to Project 4 and is reused in Project 5 (advanced layers) and Project 6 (status displays tied to layers).

Definitions & key terms

  • Layout macro: Macro that maps physical key order to matrix indices.
  • Layer: A complete keymap that can be stacked.
  • Transparent keycode: A key that falls through to lower layers.

Mental model diagram (ASCII)

Physical order -> LAYOUT macro -> Matrix order
[Esc][1][2]     -> LAYOUT(ESC,1,2) -> keymaps[0][row][col]

How it works (step-by-step, with invariants and failure modes)

  1. Define a keymap using the keyboard’s layout macro.
  2. QMK expands the macro into a matrix array.
  3. The matrix scan produces row/col indices.
  4. QMK looks up the keycode in the active layer.

Invariant: Layout macro and matrix definitions must match. Failure modes include using the wrong macro or mixing key positions.

Minimal concrete example

[_BASE] = LAYOUT(
  KC_Q, KC_W, KC_E,
  KC_A, KC_S, KC_D
)

Common misconceptions

  • “Keymap is independent of matrix”: It is directly tied to matrix order.
  • “All layouts use the same macro”: Many keyboards provide multiple macros.
  • “Transparent means disabled”: It means “use lower layer”.

Check-your-understanding questions

  1. What does LAYOUT expand into?
  2. Why can a keymap compile but be physically wrong?
  3. What does KC_TRNS do?

Check-your-understanding answers

  1. It expands to a matrix-ordered array of keycodes.
  2. The macro order can differ from the physical order you assume.
  3. It allows the key to fall through to a lower layer.

Real-world applications

  • Any QMK keyboard keymap.
  • Firmware layout customization for production boards.

Where you’ll apply it

  • In this project: §3.2 Functional Requirements and §5.10 Phase 1.
  • Also used in: Project 5, Project 6.

References

  • QMK docs: keymap and layout macros.
  • “The C Programming Language” Ch. 6 (arrays).

Key insights

Understanding the layout macro is the difference between guessing and controlling your keymap.

Summary

Keymaps are not just lists of keys; they are structured arrays that must align with the physical matrix. The layout macro is your translation layer.

Homework/Exercises to practice the concept

  1. Open a keyboard’s info.json and identify its layout macro.
  2. Rearrange two keys and predict how the physical output will change.

Solutions to the homework/exercises

  1. The layouts section lists the macro and key positions.
  2. Swapping positions in the macro swaps the physical output for those keys.

2.2 Custom Keycodes and the QMK Event Pipeline

Fundamentals

QMK processes every key event through a pipeline. When a key is pressed or released, QMK generates a keyrecord_t with the event state and timestamp. The process_record_user function lets you intercept these events and implement custom behavior. Custom keycodes are defined in an enum starting at SAFE_RANGE to avoid collisions with built-in keycodes. When your custom keycode is triggered, you can send strings, toggle layers, or perform complex logic. This is the foundation of macros, tap-dance, and user-defined behaviors.

Deep Dive into the Concept

The QMK event pipeline starts with the matrix scan. When a change is detected (raw state differs from debounced state), QMK generates a key event. This event is passed through a sequence of processing functions: core QMK handlers, feature handlers (e.g., combos), and finally user handlers like process_record_user. Each handler can choose to consume the event (return false) or pass it on. This means your custom keycode can override default behavior or be layered on top of it.

Custom keycodes are enumerated values that live above QMK’s built-in keycodes. This is important because QMK uses numeric ranges to identify certain features. By starting at SAFE_RANGE, you avoid accidental collisions. When a key is pressed, QMK compares the keycode in the keymap to determine which handler to invoke. If the keycode is your custom one, you can run arbitrary code. The simplest example is SEND_STRING("Hello"), which sends a sequence of keycodes via QMK’s string sending helper.

Timing matters. process_record_user is called on both press and release. If you send a string on both, you’ll duplicate output. The usual pattern is to check record->event.pressed and only act on press. Another subtlety is that macros can interact with modifiers. If you want to send a capital letter, you can either send Shift + keycode or use SS_TAP(X_LSFT) macros. QMK’s string system abstracts this but still relies on the underlying event pipeline.

Understanding the pipeline also helps you debug. If a macro doesn’t fire, it may be because a different handler consumed the event earlier (e.g., a combo or tap-dance). In this project, you keep the environment minimal so you can see how a custom keycode behaves in isolation. Later, in Project 5, you’ll deliberately mix these features and manage interactions.

Additional pipeline detail: QMK processes key events through multiple hooks (process_record_user, process_record_kb, and feature hooks). If you add features later (combos, tap dance), they may intercept the event before your custom keycode. It is useful to read the order of execution in QMK docs and to use debug prints to see the event flow. Also, remember that macros and custom keycodes can interact with modifiers in subtle ways. If you send a string with Shift held, the output might be different than expected; temporarily clear modifiers or use SEND_STRING helper sequences to force consistent output.

Extra event-flow detail: keyrecord_t includes timestamps and tap information. You can use these fields to implement more nuanced macros, such as a macro that behaves differently if the key was held longer than a threshold. QMK also provides helpers like tap_code16() and register_code() for sending keycodes without the overhead of SEND_STRING. These are useful for sending non-ASCII keys or when you want precise press/release timing, such as for shortcuts or OS-specific key sequences.

Additional maintenance tip: if your keymap grows, split custom keycodes into a separate file (e.g., custom_keycodes.c) and include it in your keymap. This keeps keymap.c readable and avoids merge conflicts when you experiment with new macros. Keep custom enums grouped and well-named so you can search and audit them quickly.

How this fits on projects

This concept is introduced in Project 4 and expanded in Project 5 (tap-dance, combos) and Project 6 (status indicators based on custom states).

Definitions & key terms

  • process_record_user: User hook for key event processing.
  • SAFE_RANGE: Starting value for custom keycodes.
  • Key event: A press or release event with timestamp and state.

Mental model diagram (ASCII)

Matrix Scan -> Debounce -> QMK Core -> Feature Handlers -> process_record_user

How it works (step-by-step, with invariants and failure modes)

  1. Key press detected in matrix scan.
  2. QMK creates a key event record.
  3. Core handlers process the event.
  4. process_record_user runs your custom logic.

Invariant: Custom keycodes must be in a safe range. Failure modes include handling both press and release, or returning false too early.

Minimal concrete example

enum custom_keycodes { MY_HELLO = SAFE_RANGE };

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  if (keycode == MY_HELLO && record->event.pressed) {
    SEND_STRING("Hello QMK!");
    return false;
  }
  return true;
}

Common misconceptions

  • “Custom keycodes are just macros”: They are full event hooks.
  • “Returning false always stops everything”: It only stops further processing for that event.
  • “SAFE_RANGE is optional”: Without it, collisions are likely.

Check-your-understanding questions

  1. Why should custom keycodes start at SAFE_RANGE?
  2. Why do you typically send macros only on press events?
  3. What happens if you return false in process_record_user?

Check-your-understanding answers

  1. To avoid overlapping built-in keycodes and feature ranges.
  2. Because release events would duplicate the macro.
  3. The event stops processing and does not reach later handlers.

Real-world applications

  • QMK macros for productivity.
  • User-defined shortcuts and sequences.

Where you’ll apply it

  • In this project: §3.2 Functional Requirements and §5.10 Phase 2.
  • Also used in: Project 5.

References

  • QMK docs: custom keycodes and process_record_user.
  • “Making Embedded Systems” Ch. 5.

Key insights

Custom keycodes give you a programmable hook into every key event.

Summary

The QMK event pipeline is extensible. With custom keycodes, you can transform a keypress into any behavior, as long as you respect the event flow.

Homework/Exercises to practice the concept

  1. Write a custom keycode that types your email address.
  2. Add a keycode that toggles a layer and prints a message.

Solutions to the homework/exercises

  1. Use SEND_STRING with your email string on press.
  2. Call layer_toggle() and SEND_STRING() within process_record_user.

3. Project Specification

3.1 What You Will Build

A custom keymap for a supported QMK keyboard that:

  • Changes at least five keys from the default layout.
  • Adds a custom macro key that types a string.
  • Compiles and flashes successfully.

3.2 Functional Requirements

  1. Keymap edits: At least five key positions changed.
  2. Custom keycode: One macro key using process_record_user.
  3. Build: qmk compile produces a firmware image.
  4. Flash: qmk flash programs the device.

3.3 Non-Functional Requirements

  • Reliability: Macro triggers once per press.
  • Usability: Build steps documented.
  • Maintainability: Code commented where necessary.

3.4 Example Usage / Output

$ qmk compile -kb ferris/sweep -km mylayout
Creating hex file: .build/ferris_sweep_mylayout.hex

3.5 Data Formats / Schemas / Protocols

  • QMK keymap arrays and custom keycode enums.

3.6 Edge Cases

  • Macro triggers twice because it fires on release.
  • Keymap compiles but uses wrong keyboard name.
  • Bootloader not detected.

3.7 Real World Outcome

Your keyboard reflects your custom layout and macro.

3.7.1 How to Run (Copy/Paste)

qmk new-keymap -kb ferris/sweep -km mylayout
qmk compile -kb ferris/sweep -km mylayout
qmk flash -kb ferris/sweep -km mylayout

3.7.2 Golden Path Demo (Deterministic)

  • Press the macro key once: it types “Hello QMK!” exactly once.
  • Press a remapped key and verify output.

3.7.3 If CLI: exact terminal transcript

$ qmk compile -kb ferris/sweep -km mylayout
Creating hex file: .build/ferris_sweep_mylayout.hex
exit_code=0

$ qmk flash -kb ferris/sweep -km mylayout
Flashing complete
exit_code=0

$ qmk flash -kb unknown/board -km mylayout
error: keyboard not found
exit_code=2

4. Solution Architecture

4.1 High-Level Design

Keymap.c -> QMK build -> Firmware -> Keyboard

4.2 Key Components

Component Responsibility Key Decisions
Keymap Map keys and macros Layout macro selection
Custom Keycodes Implement macro behavior Use SAFE_RANGE enum
Build Pipeline Compile and flash firmware Correct keyboard name

4.3 Data Structures (No Full Code)

enum custom_keycodes { MY_MACRO = SAFE_RANGE };

4.4 Algorithm Overview

Key Algorithm: Custom Macro

  1. Detect macro key press.
  2. Send string.
  3. Return false to stop further processing.

Complexity Analysis:

  • Time: O(n) for string length
  • Space: O(1)

5. Implementation Guide

5.1 Development Environment Setup

qmk setup
qmk doctor

5.2 Project Structure

keyboards/<board>/
└── keymaps/
    └── mylayout/
        ├── keymap.c
        └── rules.mk (optional)

5.3 The Core Question You’re Answering

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

5.4 Concepts You Must Understand First

  1. Layout macros and matrix mapping.
  2. Custom keycodes with process_record_user.
  3. QMK build and flash workflow.

5.5 Questions to Guide Your Design

  1. Which keys will you change and why?
  2. What macro saves you the most time?
  3. How will you verify the macro doesn’t interfere with normal typing?

5.6 Thinking Exercise

Take a default layout and list the five keys you will change. Explain the productivity benefit of each change.

5.7 The Interview Questions They’ll Ask

  1. How does QMK map a physical key to a keycode?
  2. Why use SAFE_RANGE for custom keycodes?
  3. How do you debug a key that outputs the wrong character?

5.8 Hints in Layers

Hint 1: Use QMK Configurator as a visual aid Sketch the layout before editing code.

Hint 2: Start with a copy of the default keymap Use qmk new-keymap.

Hint 3: Print debug output Enable CONSOLE_ENABLE to see events.

Hint 4: Keep changes small Validate each change before adding more.

5.9 Books That Will Help

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

5.10 Implementation Phases

Phase 1: Keymap Setup (2-3 hours)

Goals: create new keymap directory

Tasks:

  1. Run qmk new-keymap.
  2. Compile without changes.

Checkpoint: Baseline firmware compiles.

Phase 2: Customization (2-3 hours)

Goals: modify layout and add macro

Tasks:

  1. Change five keys.
  2. Add MY_MACRO and process_record_user.

Checkpoint: Macro types expected string.

Phase 3: Verification (1-2 hours)

Goals: verify behavior in real typing

Tasks:

  1. Flash firmware.
  2. Test key outputs in a text editor.

Checkpoint: All keys behave as intended.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Layout selection Multiple LAYOUT macros Use default reduces mapping errors
Macro method SEND_STRING vs tapcode SEND_STRING simplest for demo
Debugging Console vs OS testing Both verify logical + host

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Build Tests Ensure compilation works qmk compile
Functional Verify key outputs text editor typing
Edge Cases Macro double-fire press/release test

6.2 Critical Test Cases

  1. Macro key types string once per press.
  2. Remapped keys output expected characters.
  3. Flash works with correct keyboard name.

6.3 Test Data

Macro: "Hello QMK!"
Remaps: at least five keys

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Wrong keyboard name Build or flash fails Verify -kb value
Macro triggers twice Double output Only run on press event
Layout mismatch Keys swapped Check layout macro order

7.2 Debugging Strategies

  • Use QMK console to see keycodes as you press.
  • Test in a text editor to confirm OS-level output.

7.3 Performance Traps

Large macros can block scanning if they run in a tight loop. Keep macros short.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a second layer for symbols.
  • Add a reset key combo.

8.2 Intermediate Extensions

  • Add a custom tap-hold key.
  • Add per-key tapping term overrides.

8.3 Advanced Extensions

  • Add a custom key that runs a sequence of modifier taps.
  • Use dynamic macros.

9. Real-World Connections

9.1 Industry Applications

  • Custom ergonomic layouts for productivity.
  • Firmware configuration for keyboard products.
  • QMK firmware keymap repositories.
  • VIA/QMK keymap editors.

9.3 Interview Relevance

  • Demonstrates understanding of embedded build pipelines.
  • Shows ability to design and debug keymaps.

10. Resources

10.1 Essential Reading

  • QMK keymap documentation.
  • “The GNU Make Book” Ch. 2-4.

10.2 Video Resources

  • QMK keymap walkthroughs.

10.3 Tools & Documentation

  • QMK CLI, QMK Configurator.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain how layout macros map to matrix indices.
  • I can describe how custom keycodes are handled.

11.2 Implementation

  • Keymap changes are visible on the keyboard.
  • Macro key works reliably.

11.3 Growth

  • I can debug a wrong key output using QMK console.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Custom keymap with five remapped keys.
  • Macro key implemented.

Full Completion:

  • Works on a flashed keyboard.
  • Debug logs confirm correct keycodes.

Excellence (Going Above & Beyond):

  • Second layer + documented layout rationale.