Project 9: Bluetooth Wireless Keyboard (ZMK)
Build a battery-powered BLE keyboard using ZMK/Zephyr with reliable pairing, low latency, and accurate battery reporting.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 5: Expert |
| Time Estimate | 6-12 weeks |
| Main Programming Language | C (ZMK/Zephyr) |
| Alternative Programming Languages | Rust (Embassy) |
| Coolness Level | Level 5: Pure Magic |
| Business Potential | Level 4: Wireless Premium |
| Prerequisites | QMK basics, BLE fundamentals |
| Key Topics | BLE HID, power management, battery reporting |
1. Learning Objectives
By completing this project, you will:
- Configure a ZMK keyboard for BLE HID (HOGP).
- Set connection parameters for low latency and good battery life.
- Implement battery measurement and reporting.
- Achieve a stable sleep/wake behavior with low idle current.
- Pair with at least two hosts and switch reliably.
2. All Theory Needed (Per-Concept Breakdown)
2.1 BLE HID (HOGP), Connection Parameters, and Pairing
Fundamentals
BLE HID devices use the HID over GATT Profile (HOGP). Instead of USB descriptors, BLE uses a report map in a GATT characteristic. The host connects and subscribes to notifications to receive key reports. Connection parameters (interval, latency, supervision timeout) determine both latency and power usage. Pairing and bonding store keys for secure reconnection. If these parameters are misconfigured, the keyboard will feel laggy or will disconnect unexpectedly.
Deep Dive into the Concept
HID over GATT is the BLE adaptation of the HID concept. A keyboard exposes a HID service with characteristics for the report map, input reports, output reports (LEDs), and protocol mode. The report map describes the report format just like a USB descriptor. When the host connects, it reads the report map and subscribes to notifications on the input report characteristic. Each keypress is sent as a notification. The host uses these notifications to generate key events.
Connection parameters shape the user experience. The connection interval is the time between connection events where data can be sent. A shorter interval (e.g., 7.5 ms) yields lower latency but higher power consumption because the radio wakes more often. A longer interval (e.g., 30-50 ms) conserves power but adds latency. Slave latency allows the device to skip connection events, saving power, but can increase latency if set too high. The supervision timeout determines how quickly a connection is considered lost; too short can cause disconnects in noisy environments, too long can make recovery slow.
Pairing and bonding are required for a reliable keyboard experience. Bonding stores the encryption keys so the keyboard can reconnect without re-pairing each time. ZMK supports multiple profiles so you can switch between hosts. The UX challenge is making profile switching predictable and providing feedback (e.g., LED or OLED). You should implement at least two profiles and ensure that switching is stable even after sleep.
BLE also has limitations: the radio environment can be noisy, and host BLE stacks vary. macOS, Windows, and Linux have different latency and bonding behavior. Testing on multiple hosts is essential. A keyboard that works on one OS may behave differently on another, especially with connection parameters. ZMK provides defaults that are conservative, but for a premium experience you will tune these values.
The report map itself must match the reports you send, just like USB. A mismatch results in missing or incorrect key events. ZMK provides standard report maps, but if you add media keys or extra features, you must ensure the report map includes them. This is why understanding report maps is still necessary even when using ZMK.
Additional BLE considerations: advertising interval affects how quickly the keyboard is discoverable and how much power it consumes while unpaired. A fast interval improves pairing UX but drains the battery quickly; after pairing, you can switch to a slower interval. Security mode matters too: Just Works is simpler but provides less protection; Passkey or LESC improves security but adds user friction. For multi-host support, store a clear profile index and expose a status indicator so users know which host is active. Document the pairing flow per OS because each host handles BLE pairing slightly differently.
Extra BLE detail: many hosts cache the HID report map and services. If you change the report map during development, you may need to remove the device from the OS Bluetooth list and re-pair to force a fresh read. Advertising data size is limited, so be selective about what you include. Consider adding a simple LED or OLED indicator for pairing mode to avoid user confusion. For profile switching, ensure the key combination is hard to hit accidentally.
How this fits on projects
This concept is central to Project 9 and parallels USB HID concepts from Project 2.
Definitions & key terms
- HOGP: HID over GATT Profile for BLE keyboards.
- Connection interval: Time between BLE connection events.
- Bonding: Storing encryption keys for reconnection.
- Report map: BLE equivalent of a HID descriptor.
Mental model diagram (ASCII)
Keyboard -> BLE GATT HID Service -> Host
Input Report -> notifications
How it works (step-by-step, with invariants and failure modes)
- Keyboard advertises as BLE HID device.
- Host connects and reads report map.
- Host subscribes to input report notifications.
- Keyboard sends key reports as notifications.
Invariant: report map must match report data. Failure modes include mis-tuned connection parameters causing lag or disconnects.
Minimal concrete example
Connection interval: 15 ms
Slave latency: 4
Supervision timeout: 5 s
Common misconceptions
- “Short interval always better”: It drains battery quickly.
- “Bonding is optional”: Without bonding, reconnection is painful.
- “BLE is like USB”: BLE is asynchronous and host-dependent.
Check-your-understanding questions
- What does the report map do in BLE HID?
- How does connection interval affect latency and power?
- Why is bonding important for a keyboard?
Check-your-understanding answers
- It describes the report format so the host can parse notifications.
- Shorter intervals reduce latency but increase power usage.
- It allows fast, secure reconnection without re-pairing.
Real-world applications
- Wireless keyboards, mice, and game controllers.
Where you’ll apply it
- In this project: §3.2 Functional Requirements and §5.10 Phase 1.
- Also used in: Project 11.
References
- “Bluetooth Low Energy: The Developer’s Handbook” Ch. 10-12.
- ZMK documentation on BLE HID.
Key insights
BLE HID is a contract between the report map and the connection parameters; both must be tuned for a good UX.
Summary
Understanding HOGP and connection parameters is the foundation of a responsive, reliable wireless keyboard.
Homework/Exercises to practice the concept
- List the default ZMK connection parameters and explain their trade-offs.
- Pair the keyboard with two hosts and switch between them.
Solutions to the homework/exercises
- Default parameters prioritize stability; shorter intervals reduce latency but cost power.
- Use ZMK profile switching and verify reconnection without re-pairing.
2.2 Power Management and Battery Budgeting
Fundamentals
Battery-powered keyboards must balance performance and power. Every scan, radio wake-up, and LED effect consumes energy. Power management involves reducing scan rate when idle, entering deep sleep states, and waking quickly on keypress. Battery budgeting is the calculation of expected battery life based on active and idle current. If you do not budget, you risk a keyboard that lasts only days instead of weeks.
Deep Dive into the Concept
Power consumption in a wireless keyboard has two main components: the microcontroller and the radio. The MCU scans the matrix and processes key events; the radio wakes up for BLE connection events. When idle, the best strategy is to reduce scanning frequency and enter a low-power sleep mode between scans. Some keyboards use interrupt-based wake on keypress, which allows the MCU to sleep deeply until a key is pressed. ZMK supports power saving by lowering the scan rate and enabling deep sleep in idle.
Battery budgeting starts with measurements. Suppose your keyboard draws 2 mA during active typing and 10 uA during sleep. If you type 2 hours per day and sleep 22 hours, your average current is (2 mA * 2/24) + (0.01 mA * 22/24) = ~0.18 mA. For a 200 mAh battery, this yields about 1111 hours (~46 days). This is the kind of calculation you must perform to validate your design. If you add RGB or OLED, the current draw increases dramatically, reducing battery life. Many wireless keyboards disable RGB by default for this reason.
Battery measurement and reporting require ADC calibration. The MCU’s ADC measures battery voltage, but the voltage depends on load and the battery’s discharge curve. A voltage divider is needed if the battery voltage exceeds ADC range. The firmware must map voltage to a percentage; this is approximate and should be calibrated against the battery’s datasheet. ZMK provides a battery service, but you must set the correct voltage thresholds. If these are wrong, the host may show inaccurate battery levels.
Power management also affects user experience. Aggressive sleep can cause missed keypresses or long wake times. You must ensure that wake-up latency is acceptable. A good target is waking within 20 ms. This often requires a compromise: allow shallow sleep during brief idle periods and deeper sleep after longer inactivity. ZMK provides configuration options for idle timeouts and deep sleep thresholds.
Testing is essential. Use a multimeter or power profiler to measure current draw during typing, idle, and sleep. Validate that the keyboard meets your battery life goals. Power issues are among the most common complaints in wireless keyboard products, so this project prepares you for real-world constraints.
Additional power management details: battery chemistry matters. A LiPo battery has a steep voltage drop near the end of discharge, so a simple linear percentage mapping can be misleading. Use a lookup table based on the battery’s discharge curve and calibrate with real measurements. If your design includes charging, follow proper safety guidelines and use a proven charger IC. Place the voltage divider close to the MCU ADC pin and ensure the divider does not consume too much current (use high-value resistors). For accurate measurements, sample after the radio is idle to avoid load-induced voltage sag.
Extra power insight: deep sleep wake behavior depends on your matrix wiring. To wake from deep sleep on any key, you need GPIO interrupts on row or column lines; this might require enabling internal pull-ups and configuring wake pins carefully. If you cannot wake on every key, choose a dedicated wake key and document it. Measure current with a proper power profiler if possible; multimeters can miss short spikes that affect battery life.
How this fits on projects
This concept is central to Project 9 and informs Project 11 when defining product requirements.
Definitions & key terms
- Idle current: Current draw when the device is inactive.
- Active current: Current draw during typing or radio activity.
- Deep sleep: Low-power mode with minimal MCU activity.
- Battery reporting: Sending battery level to the host.
Mental model diagram (ASCII)
Active (2 mA) -> Idle (0.1 mA) -> Deep sleep (0.01 mA)
Battery life depends on time spent in each state.
How it works (step-by-step, with invariants and failure modes)
- Measure active and idle current.
- Configure scan rate and sleep thresholds.
- Implement battery voltage measurement.
- Map voltage to percentage and report to host.
Invariant: low-power mode must not miss keypresses. Failure modes include waking too slowly or misreporting battery.
Minimal concrete example
Battery: 200 mAh
Active: 2 mA (2 h/day)
Sleep: 10 uA (22 h/day)
Average: ~0.18 mA -> ~46 days
Common misconceptions
- “BLE is always low power”: It depends on connection parameters and scan rate.
- “Battery % is precise”: It is a rough estimate without calibration.
- “RGB doesn’t matter”: It can dominate power draw.
Check-your-understanding questions
- Why does scan rate affect power usage?
- How do you compute battery life from current draw?
- Why is battery percentage only approximate?
Check-your-understanding answers
- Higher scan rate means the MCU wakes more often and consumes more power.
- Battery life (hours) = capacity (mAh) / average current (mA).
- Battery discharge curves are non-linear and load-dependent.
Real-world applications
- Wireless keyboards and mice.
- Any battery-powered embedded device.
Where you’ll apply it
- In this project: §3.3 Non-Functional Requirements and §5.10 Phase 2.
- Also used in: Project 11.
References
- “Bluetooth Low Energy: The Developer’s Handbook” Ch. 12.
- ZMK power management docs.
Key insights
Battery life is a design constraint, not an afterthought.
Summary
Wireless keyboards succeed when power management is treated as a core requirement. Measure, budget, and tune.
Homework/Exercises to practice the concept
- Measure idle current and compute expected battery life.
- Change connection interval and observe current draw differences.
Solutions to the homework/exercises
- Use the formula capacity/average current to estimate life.
- Longer intervals should reduce current at the cost of latency.
3. Project Specification
3.1 What You Will Build
A BLE keyboard using ZMK that:
- Pairs with at least two hosts.
- Reports battery level to the host.
- Sleeps when idle and wakes on keypress.
- Provides acceptable latency (< 15 ms).
3.2 Functional Requirements
- BLE HID: correct report map and input reports.
- Pairing: multi-profile bonding and switching.
- Battery reporting: voltage measured and reported.
- Sleep/wake: idle sleep and fast wake on keypress.
3.3 Non-Functional Requirements
- Latency: <= 15 ms typical typing latency.
- Battery life: at least 4 weeks on 200 mAh.
- Reliability: stable connection across 3-hour typing session.
3.4 Example Usage / Output
BLE Keyboard Status
Battery: 78%
Connection: Host 1
Latency: ~12 ms
3.5 Data Formats / Schemas / Protocols
- ZMK devicetree overlays and Kconfig settings.
3.6 Edge Cases
- Pairing fails because of old bond data.
- Battery percentage jumps (bad calibration).
- Device does not wake from deep sleep.
3.7 Real World Outcome
A wireless keyboard that behaves like a wired one, but with battery awareness.
3.7.1 How to Run (Copy/Paste)
west build -b nice_nano_v2
west flash
3.7.2 Golden Path Demo (Deterministic)
- Pair with Host 1 and Host 2.
- Switch profiles and reconnect within 5 seconds.
3.7.3 If CLI: exact terminal transcript
$ west build -b nice_nano_v2
[100%] Built target zephyr/zephyr.elf
exit_code=0
$ west flash
Flashing finished
exit_code=0
$ west flash
error: device not found
exit_code=3
4. Solution Architecture
4.1 High-Level Design
Matrix Scan -> ZMK Keymap -> BLE HID Service -> Host
Battery ADC -> Battery Service
Power Manager -> Sleep/Wake
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| BLE HID | Report map + notifications | Connection parameters |
| Power Manager | Sleep/wake behavior | Idle timeout values |
| Battery Service | Measure and report battery | Voltage thresholds |
4.3 Data Structures (No Full Code)
ZMK config: prj.conf, overlay.dts, keymap.keymap
4.4 Algorithm Overview
Key Algorithm: Sleep Policy
- If idle for X seconds -> reduce scan rate.
- If idle for Y minutes -> enter deep sleep.
- Wake on keypress interrupt.
Complexity Analysis:
- Time: O(R*C) per scan
- Space: O(1)
5. Implementation Guide
5.1 Development Environment Setup
west init -l config
west update
west build -b nice_nano_v2
5.2 Project Structure
zmk-config/
├── boards/shields/<keyboard>/*.dts
├── config/*.conf
└── keymap.keymap
5.3 The Core Question You’re Answering
“How do I deliver wired-like typing experience on a battery budget?”
5.4 Concepts You Must Understand First
- BLE HID and report maps.
- Connection parameter trade-offs.
- Power management and battery budgeting.
5.5 Questions to Guide Your Design
- What connection interval yields acceptable latency?
- How will you measure battery voltage accurately?
- When should the keyboard enter deep sleep?
5.6 Thinking Exercise
Compute battery life for 200 mAh with 2 mA active for 2 hours and 15 uA idle for 22 hours.
5.7 The Interview Questions They’ll Ask
- What is HID over GATT?
- How do connection parameters affect power?
- How do you handle reconnection across multiple hosts?
5.8 Hints in Layers
Hint 1: Start from a known ZMK board config
Use nice_nano as a base.
Hint 2: Enable battery reporting
Set CONFIG_ZMK_BATTERY.
Hint 3: Measure current draw Use a multimeter on idle and active states.
Hint 4: Tune connection interval
Adjust in prj.conf and re-test latency.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| BLE HID | “Bluetooth Low Energy: The Developer’s Handbook” | Ch. 10-14 |
| Zephyr basics | “Zephyr RTOS Embedded C Programming” | Ch. 1-3 |
| Embedded power | “Making Embedded Systems” | Ch. 4-5 |
5.10 Implementation Phases
Phase 1: BLE Bring-up (2-3 weeks)
Goals: BLE HID works with one host
Tasks:
- Configure report map and keymap.
- Pair with a host and verify typing.
Checkpoint: Host receives correct key events.
Phase 2: Power + Battery (2-3 weeks)
Goals: battery reporting and sleep
Tasks:
- Configure battery ADC and thresholds.
- Enable idle sleep and wake on keypress.
Checkpoint: Idle current < 20 uA.
Phase 3: Multi-Host (2-3 weeks)
Goals: profile switching
Tasks:
- Add profile switching keys.
- Test reconnection reliability.
Checkpoint: Switch between two hosts reliably.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Connection interval | 7.5-30 ms | 15 ms | balance latency/power |
| Sleep timeout | 1-10 min | 5 min | balance responsiveness |
| Battery report mapping | linear vs curve | curve-based | better accuracy |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| BLE Tests | Verify pairing and latency | typing test, ping timing |
| Power Tests | Measure current draw | multimeter measurement |
| Battery Tests | Validate report accuracy | voltage vs reported % |
6.2 Critical Test Cases
- Pair with two hosts and switch reliably.
- Idle current below 20 uA.
- Reported battery percentage within 10% of measured voltage curve.
6.3 Test Data
Battery: 200 mAh
Idle current target: <20 uA
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Bad connection params | Lag or disconnects | Tune interval and timeout |
| No deep sleep | Battery drains quickly | Verify sleep configuration |
| Incorrect battery scaling | Wrong % displayed | Calibrate voltage divider |
7.2 Debugging Strategies
- Use a BLE sniffer to inspect connection events.
- Log power states to confirm sleep transitions.
7.3 Performance Traps
RGB on wireless keyboards can cut battery life by 10x. Disable or heavily limit it.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a low-battery warning LED.
- Add a custom sleep animation.
8.2 Intermediate Extensions
- Implement dynamic connection parameter changes.
- Add per-profile OLED display if hardware supports.
8.3 Advanced Extensions
- Add BLE security improvements (LESC).
- Implement OTA firmware updates.
9. Real-World Connections
9.1 Industry Applications
- Wireless keyboards and mice.
- BLE HID devices for mobile devices.
9.2 Related Open Source Projects
- ZMK firmware.
- Zephyr BLE samples.
9.3 Interview Relevance
- Shows BLE protocol knowledge and power optimization skills.
10. Resources
10.1 Essential Reading
- “Bluetooth Low Energy: The Developer’s Handbook” Ch. 10-14.
- ZMK documentation on BLE and power.
10.2 Video Resources
- BLE HID tutorials and power profiling videos.
10.3 Tools & Documentation
west, Zephyr tools, multimeter, BLE sniffer.
10.4 Related Projects in This Series
- Project 2: HID fundamentals.
- Project 11: product scaling.
11. Self-Assessment Checklist
11.1 Understanding
- I can explain BLE connection parameters.
- I can compute battery life from current draw.
11.2 Implementation
- Keyboard pairs with at least two hosts.
- Battery reporting works and is reasonable.
11.3 Growth
- I can tune power vs latency trade-offs.
12. Submission / Completion Criteria
Minimum Viable Completion:
- BLE keyboard types reliably on one host.
- Idle current < 20 uA.
Full Completion:
- Multi-host pairing and profile switching.
- Battery reporting within 10% accuracy.
Excellence (Going Above & Beyond):
- OTA updates and dynamic connection parameter tuning.