Project 1: Keyboard Matrix Simulator (Understand Matrix Scanning)

Build a deterministic, terminal-based simulator that models a keyboard matrix scan loop, visualizes pressed keys, and demonstrates ghosting and diode prevention.

Quick Reference

Attribute Value
Difficulty Level 1: Beginner
Time Estimate 6-10 hours
Main Programming Language C (Alternatives: Python, Rust)
Alternative Programming Languages Python, Rust
Coolness Level Level 2: Practical but Forgettable
Business Potential Level 1: Resume Gold
Prerequisites Basic C arrays, CLI usage, digital I/O basics
Key Topics Matrix scanning, ghosting, diode direction, debounce

1. Learning Objectives

By completing this project, you will:

  1. Explain how a row/column matrix reduces GPIO count and why scanning is required.
  2. Model active-low scanning, pull-ups, and diode direction in software.
  3. Detect ghosting and masking by analyzing matrix state transitions.
  4. Implement a debounce window and show its effect on event stability.
  5. Produce a deterministic CLI/TUI simulation that matches real hardware behavior.

2. All Theory Needed (Per-Concept Breakdown)

2.1 Matrix Scanning, Diode Direction, and Ghosting

Fundamentals

A keyboard matrix is an electrical grid where switches connect one row to one column. Instead of dedicating a microcontroller pin to every switch, you drive one axis (rows or columns) and read the other. This reduces the number of GPIO pins from N keys to roughly R + C. Scanning means you actively drive a single column (or row) to an active state, read the inputs, then move to the next column. Most QMK boards use active-low scanning: columns are normally high, one column is driven low at a time, and row inputs are pulled high using internal pull-ups. A pressed switch connects the active column to a row, pulling that row low during that scan. Without diodes, multiple keypresses can create alternate current paths that make unpressed keys appear pressed (ghosting). Diodes enforce one-way current flow so only the intended row/column path conducts. This concept is foundational because everything in firmware (layers, macros, combos) depends on the correctness of the raw matrix state.

Deep Dive into the Concept

Matrix scanning is a multiplexing problem. In a naive wiring, each key uses a dedicated input pin. A 60-key keyboard would require 60 pins, which is unrealistic for small MCUs. The matrix approach uses, for example, 5 rows and 12 columns to read 60 keys with 17 pins. The firmware configures one axis as outputs and the other axis as inputs. Consider a COL2ROW matrix: columns are outputs, rows are inputs with pull-ups. The scan loop sets all columns high, then pulls column 0 low. If a switch at (R1, C0) is pressed, it completes a circuit from the low column to the row input, pulling that row low. The firmware samples all rows for that column, stores the results, and moves on to the next column. The scan completes when all columns have been sampled, producing a full matrix snapshot.

Ghosting is the key electrical artifact. If you press three keys that form three corners of a rectangle (R0C0, R0C1, R1C0), then a current path can form through the pressed switches that makes the remaining corner (R1C1) appear pressed. The microcontroller sees that row R1 is low when column C1 is active, even though the physical switch is open. This happens because current can travel from the active column through a pressed switch, across another pressed switch, and back to a row input. Masking is the opposite: you press multiple keys and the firmware cannot detect one of them because the current path is ambiguous. Both are artifacts of the shared electrical paths inherent to a matrix.

Diodes fix this by enforcing directionality. A diode in series with each switch allows current to flow only from column to row (or row to column). If the diode orientation matches the scan direction (COL2ROW or ROW2COL), the current path that creates a ghost is blocked. This is why QMK requires you to specify DIODE_DIRECTION in config.h. A reversed diode orientation effectively blocks valid keypresses or moves them to the wrong scan phase, causing missed keys or false positives. In software, you can model diode direction by allowing current paths only in the allowed direction when you compute which rows become active.

In real hardware, scanning interacts with MCU electrical characteristics. Internal pull-ups vary from 10k to 50k and may be too weak for long traces. This is why some designs use external pull-ups or reduce scan speed to avoid ringing. Noise or crosstalk can appear as transient low pulses, which is why debouncing and filtering are part of the scan pipeline. The simulation should model the raw electrical state (pressed switch, diode presence) separately from debounced logical state to reflect how firmware behaves.

A robust scan model includes the following steps: (1) define a matrix with explicit row/column indices, (2) define which keys are pressed in the physical model, (3) for each active column, compute the reachable rows given diode direction and pressed switches, (4) produce a raw matrix snapshot, and (5) feed that snapshot into a debounce filter to produce the logical key events. The simulation becomes a truth table for how electrical constraints translate into firmware behavior, making it a perfect debugging tool before you wire real hardware.

How this fits on projects

You will implement this model directly in this project. The same scanning logic is the foundation for Project 3 (handwired macro pad), Project 7 (PCB design), and Project 10 (firmware from scratch). Understanding ghosting and diode direction is essential before you build anything physical.

Definitions & key terms

  • Matrix scanning: Sequentially driving one axis and reading the other to identify pressed keys.
  • Active-low: A logic convention where a low voltage indicates an active signal.
  • Ghosting: False keypresses caused by unintended current paths in a matrix.
  • Masking: Missed keypresses due to ambiguous current paths.
  • Diode direction: The allowed current flow direction (COL2ROW or ROW2COL).
  • Pull-up: A resistor that biases an input to high when floating.

Mental model diagram (ASCII)

COL2ROW scan

C0 ->|---(switch)--- R0   (pressed closes circuit)
C1 ->|---(switch)--- R0
C0 ->|---(switch)--- R1
C1 ->|---(switch)--- R1

Scan loop:
C0 LOW, C1 HIGH -> read R0/R1
C0 HIGH, C1 LOW -> read R0/R1

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

  1. Configure rows as inputs with pull-ups; columns as outputs.
  2. Set all columns inactive (HIGH for active-low).
  3. For each column: drive it LOW, wait a short settle time, read rows.
  4. Record which rows read LOW for that column.
  5. Restore column to HIGH and move to the next column.
  6. Debounce the raw matrix by requiring stability for N ms.

Invariants: only one column is active at a time; rows are never actively driven. Failure modes include floating inputs (no pull-ups), reversed diode direction, and reading rows too quickly before signals settle.

Minimal concrete example

for (int c = 0; c < cols; c++) {
    drive_col(c, LOW);
    delay_us(30);
    for (int r = 0; r < rows; r++) {
        raw[r][c] = (read_row(r) == LOW);
    }
    drive_col(c, HIGH);
}

Common misconceptions

  • “Diodes debounce switches”: Diodes only block current direction; debounce is time-based filtering.
  • “Ghosting only happens with many keys”: Three keys in a rectangle are enough.
  • “If it works in software, hardware will match”: Noise, pull-up strength, and wiring quality matter.

Check-your-understanding questions

  1. Why can a matrix read many keys with fewer pins?
  2. How does active-low scanning work step by step?
  3. Explain a ghosting scenario with three pressed keys.
  4. What changes in the scan algorithm when diode direction is reversed?
  5. Why must only one column be active at a time?

Check-your-understanding answers

  1. The matrix uses shared row/column wires so each key is identified by its row/column intersection, requiring only R + C pins.
  2. The firmware drives one column low, reads rows, then moves to the next column while rows are inputs with pull-ups.
  3. Pressing three keys that form three corners of a rectangle creates a current path that makes the fourth corner appear pressed.
  4. The direction of current flow changes which side is driven and which side is read, so the firmware must scan in the direction that matches diode orientation.
  5. If multiple columns are active, the firmware cannot disambiguate which column caused a row to change.

Real-world applications

  • QMK and TMK firmware matrix scanning.
  • Laptop keyboard controller design.
  • Control panels using multiplexed button matrices.

Where you’ll apply it

References

  • QMK Documentation: “How a Matrix Works”.
  • “Digital Design and Computer Architecture” by Harris & Harris, Ch. 1-2.

Key insights

A keyboard matrix is a controlled ambiguity: scanning and diodes are the rules that make it unambiguous.

Summary

Matrix scanning is the electrical foundation of every keyboard firmware. By understanding how rows, columns, and diodes interact, you can predict key behavior and prevent ghosting before you ever solder a switch.

Homework/Exercises to practice the concept

  1. Draw a 3x3 matrix and mark three pressed keys that cause a ghost.
  2. Sketch the current path with and without diodes.
  3. Write a small program that prints the scan order for a 4x4 matrix.

Solutions to the homework/exercises

  1. Any three keys that form a rectangle produce a ghost at the fourth corner.
  2. Without diodes, current can flow both directions; with diodes, the path is blocked in one direction.
  3. The scan order is a loop: for each column, read all rows, then advance to the next column.

2.2 Debounce, Scan Timing, and Event Stability

Fundamentals

Mechanical switches do not transition cleanly from open to closed. When a key is pressed, the contacts bounce for a few milliseconds, producing rapid on/off transitions. If firmware treats every transition as a keypress, one physical press can generate multiple events. Debounce is the process of filtering these transitions so only a single press and release are registered. In a scan-based keyboard, debounce is applied to the matrix state over time. Timing matters because scan loops run at hundreds or thousands of Hertz, which is much faster than the bounce period. A debounce algorithm must balance responsiveness (low latency) with stability (no repeated keys). QMK offers multiple debounce algorithms, but the core idea is to require a stable state for a certain duration before accepting it.

Deep Dive into the Concept

Debounce is fundamentally a time-domain filter applied to a sampled digital signal. In a keyboard matrix, the signal is sampled once per scan; if the scan rate is 1 kHz, then each sample represents 1 ms. A typical mechanical switch may bounce for 5-15 ms. If you accept a key change immediately, you can get a press, release, press sequence even though the user only pressed once. Debounce algorithms avoid this by requiring the sampled state to remain stable for a defined window. The simplest method is “defer”: when a state change is detected, you start a timer and only commit the new state if the input remains unchanged for N milliseconds. QMK’s “sym-defer” algorithm uses a symmetric approach for press and release and is a good default.

Scan timing interacts with debounce in subtle ways. If you scan too slowly (e.g., 50 Hz), then your samples are 20 ms apart, and bounce might be “invisible” because the switch has already settled. This can seem good, but it increases latency. If you scan too fast (e.g., 5 kHz), then you capture many bounce transitions and rely entirely on the debounce algorithm to filter them. Most keyboards aim for 500-1000 Hz scanning, yielding 1-2 ms latency while still manageable for debounce. The simulator should allow you to set both scan rate and debounce window to show the trade-offs. It should also model a “bouncing” key by toggling raw state at a set frequency for a few milliseconds.

A key insight is that debounce is per-key stateful logic, not a global delay. You should track each key’s last stable state, last change time, and current raw state. The algorithm compares raw samples to stable state, and only when the raw state is stable for N ms does it change the logical state. This prevents one key from delaying the entire matrix. In a simulator, this means each key needs its own timer or counter. A simple approach is to store a “debounce counter” per key that increments when raw != stable and resets when raw == stable; when the counter reaches a threshold, commit the change.

Failure modes include “chatter” if you set debounce too low, and perceived sluggishness if you set it too high. Another failure mode is inconsistent behavior if your scan rate isn’t stable. If the scan loop jitter is large, then a fixed debounce threshold in milliseconds can become inconsistent; this is why firmware sometimes uses a time-based (not scan-count-based) debounce. In simulation, you can model jitter by adding a small random or fixed offset to the scan period and showing how it affects event stability.

In real keyboards, debounce interacts with other features like mod-tap and tap dance. If a key takes too long to be recognized as pressed, a tap can be misclassified as a hold. This is why some designers tune debounce and tapping term together. This project isolates debounce in a controlled environment, so you can see its effects before those higher-level features are introduced in Project 5.

How this fits on projects

This project uses debounce to stabilize simulated key events. The same concept appears in Project 3 (handwired macro pad), Project 5 (tap-hold behavior), and Project 10 (firmware from scratch).

Definitions & key terms

  • Debounce window: The time a signal must remain stable before accepting a state change.
  • Scan rate: How often the firmware samples the matrix, typically in Hertz.
  • Chatter: Multiple unintended transitions caused by contact bounce.
  • Stable state: The last accepted logical state of a key.

Mental model diagram (ASCII)

Raw signal:  0 1 0 1 1 0 1 1 1 1
Time (ms):   0 1 2 3 4 5 6 7 8 9
Debounce=5ms => accept change only after 5ms stable
Logical:     0 0 0 0 0 0 0 1 1 1

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

  1. Sample raw key state each scan.
  2. If raw == stable, reset debounce counter.
  3. If raw != stable, increment counter.
  4. When counter reaches threshold, set stable = raw and emit event.

Invariant: Each key maintains independent counters. Failure modes include using a global debounce delay or tying debounce to scan counts without a stable scan period.

Minimal concrete example

if (raw != stable) {
    if (++counter >= debounce_ms) { stable = raw; counter = 0; }
} else {
    counter = 0;
}

Common misconceptions

  • “Debounce is just a delay”: A delay blocks everything; debounce filters per key.
  • “Higher debounce is always better”: It increases latency and can break tap behaviors.
  • “Scan rate doesn’t matter”: Debounce is tied to how often you sample the signal.

Check-your-understanding questions

  1. Why does a 1 kHz scan rate make debounce necessary?
  2. What happens if you set debounce to 1 ms on a bouncing switch?
  3. How would jitter in scan timing affect a count-based debounce?
  4. Why should debounce be per key instead of global?

Check-your-understanding answers

  1. At 1 kHz you sample every 1 ms, so you capture multiple bounce transitions unless filtered.
  2. The key may register multiple presses because the bounce lasts longer than 1 ms.
  3. A count-based debounce assumes constant timing; jitter changes the effective debounce duration.
  4. A global debounce would delay all keys because one key is bouncing.

Real-world applications

  • Keyboard firmware debounce in QMK, ZMK, and TMK.
  • Button handling in industrial control panels.

Where you’ll apply it

References

  • QMK documentation: debounce options.
  • “Making Embedded Systems” by Elecia White, Ch. 4-5.

Key insights

Debounce is time-domain filtering; the scan rate defines the sampling resolution of that filter.

Summary

Debounce converts noisy mechanical transitions into clean logical events. The choice of scan rate and debounce window is a latency vs stability trade-off that you can see clearly in the simulator.

Homework/Exercises to practice the concept

  1. Simulate a key that bounces for 7 ms and test debounce windows of 2, 5, and 10 ms.
  2. Change the scan rate from 250 Hz to 2 kHz and observe when a key becomes “stable”.

Solutions to the homework/exercises

  1. A 2 ms debounce will still show multiple presses; 5 ms will reduce them; 10 ms will feel sluggish but stable.
  2. Higher scan rates show more bounce transitions; the debounce threshold must be tuned to compensate.

3. Project Specification

3.1 What You Will Build

A deterministic CLI/TUI simulator that models a keyboard matrix scan loop. It must:

  • Represent a matrix of arbitrary size (rows/cols as CLI args).
  • Allow virtual key presses (interactive or scripted).
  • Simulate diode direction and show ghosting when diodes are absent.
  • Apply a configurable debounce window and show raw vs logical states.
  • Output a real-time view of the matrix and a log of detected key events.

3.2 Functional Requirements

  1. Matrix model: Represent rows/cols and pressed keys with explicit indices.
  2. Scan engine: Iterate through columns at a configurable scan rate.
  3. Ghost detection: Identify phantom keys caused by rectangles when no diodes are enabled.
  4. Debounce filter: Apply per-key debounce with a configurable window.
  5. Visualization: Show active column, raw state, debounced state, and ghosted keys.

3.3 Non-Functional Requirements

  • Performance: Must handle at least a 10x10 matrix at 1 kHz in real time.
  • Reliability: Deterministic output for a fixed input script and seed.
  • Usability: Clear CLI help and a stable log format.

3.4 Example Usage / Output

$ ./matrix_sim --rows 3 --cols 3 --scan-hz 1000 --debounce-ms 5 \
  --diodes col2row --script demo.json

[scan 000010] active_col=1 raw=R0C1 R1C1 logical=R0C1 R1C1
matrix raw:
  R0: 1 0 0
  R1: 0 1 0
  R2: 0 0 0
matrix debounced:
  R0: 1 0 0
  R1: 0 1 0
  R2: 0 0 0

[ghost] phantom R0C2 detected (rectangle)

3.5 Data Formats / Schemas / Protocols

Script file (JSON):

{
  "seed": 42,
  "events": [
    {"t_ms": 0, "press": "R0C0"},
    {"t_ms": 2, "press": "R0C1"},
    {"t_ms": 4, "press": "R1C0"},
    {"t_ms": 12, "release": "R0C1"}
  ]
}

3.6 Edge Cases

  • Pressing three keys in a rectangle (ghosting appears without diodes).
  • Pressing and releasing within the debounce window (no logical event).
  • Rows/cols set to 1 (degenerate matrix).
  • High scan rate with very low debounce (chatter).

3.7 Real World Outcome

The simulator runs deterministically and prints the matrix state each scan.

3.7.1 How to Run (Copy/Paste)

make
./matrix_sim --rows 3 --cols 3 --scan-hz 1000 --debounce-ms 5 \
  --diodes col2row --script demo.json

3.7.2 Golden Path Demo (Deterministic)

  • Seed fixed to 42 in demo.json.
  • Expected output includes a ghost event when diodes are disabled, and no ghost when --diodes col2row is enabled.

3.7.3 If CLI: exact terminal transcript

$ ./matrix_sim --rows 3 --cols 3 --scan-hz 1000 --debounce-ms 5 --diodes none --script demo.json
[scan 000010] active_col=1 logical=R0C1 R1C1
[ghost] phantom R0C2 detected (rectangle)
exit_code=0

$ ./matrix_sim --rows 3 --cols 3 --scan-hz 1000 --debounce-ms 5 --diodes col2row --script demo.json
[scan 000010] active_col=1 logical=R0C1 R1C1
[ghost] none
exit_code=0

$ ./matrix_sim --rows 0 --cols 3
error: rows must be >= 1
exit_code=2

4. Solution Architecture

4.1 High-Level Design

┌──────────────┐   ┌─────────────┐   ┌──────────────┐   ┌──────────────┐
│ Input Script │--▶│ Matrix Model│--▶│ Scan Engine  │--▶│ Debounce     │
└──────────────┘   └─────────────┘   └──────────────┘   └──────────────┘
                                         │
                                         v
                                  ┌──────────────┐
                                  │ Ghost Detector│
                                  └──────────────┘
                                         │
                                         v
                                  ┌──────────────┐
                                  │ CLI/TUI View │
                                  └──────────────┘

4.2 Key Components

Component Responsibility Key Decisions
Matrix Model Stores pressed keys and diode orientation Use explicit row/col indices
Scan Engine Produces raw matrix snapshots Fixed scan period + settle time
Debounce Filter Converts raw to logical state Time-based per-key counters
Ghost Detector Finds rectangles and phantom keys Rectangle-based detection
CLI/TUI Renderer Displays state and logs events Text-based for determinism

4.3 Data Structures (No Full Code)

typedef struct {
    bool pressed;
    uint16_t debounce_ms;
    uint16_t counter_ms;
    bool stable;
} key_state_t;

typedef struct {
    int rows, cols;
    key_state_t keys[MAX_R][MAX_C];
    bool diode_col2row;
} matrix_t;

4.4 Algorithm Overview

Key Algorithm: Scan + Debounce

  1. For each column: drive active, read rows.
  2. Update raw state in matrix.
  3. Apply debounce per key.
  4. Detect ghost rectangles if diodes disabled.

Complexity Analysis:

  • Time: O(R * C) per scan
  • Space: O(R * C)

5. Implementation Guide

5.1 Development Environment Setup

# macOS/Linux
cc --version
make

5.2 Project Structure

matrix-sim/
├── src/
│   ├── main.c
│   ├── matrix.c
│   ├── debounce.c
│   ├── ghost.c
│   └── render.c
├── include/
│   └── matrix.h
├── scripts/
│   └── demo.json
├── tests/
│   └── test_ghost.c
├── Makefile
└── README.md

5.3 The Core Question You’re Answering

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

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Matrix scanning and diode direction
    • Why is scanning needed instead of reading all columns at once?
    • How does diode orientation change the scan direction?
  2. Debounce windows
    • Why is per-key timing needed instead of a global delay?
    • How does scan rate affect debounce effectiveness?
  3. Array indexing and mapping
    • How do you map row/column to array indices consistently?

5.5 Questions to Guide Your Design

  1. How will you represent the raw vs debounced matrix state?
  2. How will you visualize ghosting without confusing normal presses?
  3. How will you ensure deterministic output with a fixed seed?

5.6 Thinking Exercise

Trace this by hand:

Press R0C0, R0C1, R1C0 with diodes disabled.
Which key becomes ghosted? What changes with diodes enabled?

5.7 The Interview Questions They’ll Ask

  1. What is ghosting and how do diodes prevent it?
  2. How does scan rate interact with debounce?
  3. Why is only one column active at a time?

5.8 Hints in Layers

Hint 1: Start with the matrix model Define a 2D array and a function to set key states.

Hint 2: Implement scan timing Use a fixed tick (1 ms) and increment a scan counter.

Hint 3: Add ghost detection If three corners of a rectangle are pressed, mark the fourth as ghosted.

Hint 4: Separate raw and logical Keep two matrices so you can show debounced changes.

5.9 Books That Will Help

Topic Book Chapter
Digital inputs “Making Embedded Systems” (White) Ch. 1-3
Logic design “Digital Design and Computer Architecture” Ch. 1-2
C arrays “C Programming: A Modern Approach” Ch. 8-10

5.10 Implementation Phases

Phase 1: Foundation (2-3 hours)

Goals: matrix model and basic scan loop

Tasks:

  1. Implement a matrix_t with row/col size.
  2. Implement a scan loop that reads active column.

Checkpoint: Raw matrix output updates as columns advance.

Phase 2: Core Functionality (3-4 hours)

Goals: debounce and ghost detection

Tasks:

  1. Implement per-key debounce counters.
  2. Implement rectangle-based ghost detection.

Checkpoint: Ghost events appear in scripted scenarios.

Phase 3: Polish & Edge Cases (1-2 hours)

Goals: CLI, determinism, test coverage

Tasks:

  1. Add JSON script input and fixed seed handling.
  2. Add CLI validation and error codes.

Checkpoint: Golden path and failure demos match specification.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Debounce model time-based vs count-based time-based stable under scan jitter
UI mode CLI only vs ncurses CLI only deterministic output
Ghost detection approach brute force vs adjacency brute force matrix sizes are small

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Validate ghost detection Rectangle detection
Integration Tests Scan + debounce pipeline Raw vs logical transitions
Edge Case Tests Validate CLI constraints rows=0, cols=0

6.2 Critical Test Cases

  1. Ghost rectangle: Press R0C0, R0C1, R1C0 with no diodes -> phantom R1C1.
  2. Debounce window: Bounce for 6 ms with debounce 5 ms -> only one press.
  3. Invalid args: rows=0 -> exit code 2.

6.3 Test Data

Script: demo.json (seed=42)
Expected ghost at t=4ms when diodes=none

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Floating inputs Random presses Model pull-ups in simulation
Wrong diode direction Keys never register Match scan direction to diodes
Global debounce delay All keys feel sluggish Use per-key counters

7.2 Debugging Strategies

  • Log raw vs debounced: print both matrices to see where events get filtered.
  • Use scripted inputs: deterministic JSON scripts isolate logic bugs.

7.3 Performance Traps

A naive renderer that redraws the full matrix every tick can consume most CPU. Batch updates or print on state changes only.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a “press schedule” that types a word over time.
  • Add a summary report of scan rate and event latency.

8.2 Intermediate Extensions

  • Implement multiple debounce algorithms and compare output.
  • Add a mode that simulates diode orientation mistakes.

8.3 Advanced Extensions

  • Simulate EMI noise and show false triggers without filtering.
  • Export logs for visualization in Python or CSV.

9. Real-World Connections

9.1 Industry Applications

  • Keyboard firmware development for custom keyboards.
  • Embedded control panels with multiplexed button grids.
  • QMK Firmware (matrix scanning implementation).
  • TMK Firmware (early keyboard matrix model).

9.3 Interview Relevance

  • Matrix scanning and debounce are common embedded interview topics.
  • Ghosting and diode direction show practical hardware awareness.

10. Resources

10.1 Essential Reading

  • “Digital Design and Computer Architecture” by Harris & Harris - Ch. 1-2.
  • “Making Embedded Systems” by Elecia White - Ch. 1-3.

10.2 Video Resources

  • QMK intro and matrix scanning talks (community videos).

10.3 Tools & Documentation

  • QMK Docs: matrix scanning and debounce.
  • ncurses documentation (optional TUI).

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain why scanning is required for a matrix.
  • I can describe ghosting and diode direction.
  • I can explain how debounce filters bouncing switches.

11.2 Implementation

  • The simulator accepts rows/cols and produces deterministic output.
  • Ghosting is demonstrated when diodes are disabled.
  • Debounce prevents duplicate events.

11.3 Growth

  • I can map this simulation to a real keyboard matrix.
  • I can explain scan timing trade-offs in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Deterministic scan loop with CLI output.
  • Ghosting detection demonstrated with a scripted input.
  • Per-key debounce implemented.

Full Completion:

  • All minimum criteria plus:
  • CLI validation with defined exit codes.
  • Unit tests for ghost detection.

Excellence (Going Above & Beyond):

  • TUI visualization with live key highlighting.
  • Comparison of multiple debounce algorithms with metrics.