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:
- Understand QMK’s keymap folder structure and build flow.
- Modify a layout array to change key outputs.
- Implement a custom keycode using
process_record_user. - Build and flash firmware for a supported keyboard.
- 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)
- Define a keymap using the keyboard’s layout macro.
- QMK expands the macro into a matrix array.
- The matrix scan produces row/col indices.
- 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
- What does
LAYOUTexpand into? - Why can a keymap compile but be physically wrong?
- What does
KC_TRNSdo?
Check-your-understanding answers
- It expands to a matrix-ordered array of keycodes.
- The macro order can differ from the physical order you assume.
- 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
- Open a keyboard’s
info.jsonand identify its layout macro. - Rearrange two keys and predict how the physical output will change.
Solutions to the homework/exercises
- The
layoutssection lists the macro and key positions. - 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)
- Key press detected in matrix scan.
- QMK creates a key event record.
- Core handlers process the event.
process_record_userruns 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
- Why should custom keycodes start at
SAFE_RANGE? - Why do you typically send macros only on press events?
- What happens if you return
falseinprocess_record_user?
Check-your-understanding answers
- To avoid overlapping built-in keycodes and feature ranges.
- Because release events would duplicate the macro.
- 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
- Write a custom keycode that types your email address.
- Add a keycode that toggles a layer and prints a message.
Solutions to the homework/exercises
- Use
SEND_STRINGwith your email string on press. - Call
layer_toggle()andSEND_STRING()withinprocess_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
- Keymap edits: At least five key positions changed.
- Custom keycode: One macro key using
process_record_user. - Build:
qmk compileproduces a firmware image. - Flash:
qmk flashprograms 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
- Detect macro key press.
- Send string.
- 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
- Layout macros and matrix mapping.
- Custom keycodes with
process_record_user. - QMK build and flash workflow.
5.5 Questions to Guide Your Design
- Which keys will you change and why?
- What macro saves you the most time?
- 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
- How does QMK map a physical key to a keycode?
- Why use
SAFE_RANGEfor custom keycodes? - 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:
- Run
qmk new-keymap. - Compile without changes.
Checkpoint: Baseline firmware compiles.
Phase 2: Customization (2-3 hours)
Goals: modify layout and add macro
Tasks:
- Change five keys.
- Add
MY_MACROandprocess_record_user.
Checkpoint: Macro types expected string.
Phase 3: Verification (1-2 hours)
Goals: verify behavior in real typing
Tasks:
- Flash firmware.
- 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
- Macro key types string once per press.
- Remapped keys output expected characters.
- 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.
9.2 Related Open Source Projects
- 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.
10.4 Related Projects in This Series
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.