Files
qmk_firmware/CLAUDE.md
T
rootiest b4a76db6f3 docs: expand CLAUDE.md with K17 Max, branch names, and EEPROM notes
- Rename dev branch references from q5_dev to dev/q5; add dev/k17 for
  K17 Max work
- Add K17 Max build/flash commands using the correct rgb subvariant path
  (keychron/k17_max/ansi_encoder/rgb) to distinguish from the white LED
  variant
- Add EEPROM Layout Notes section documenting the Q5 Max address map,
  VIA_EEPROM_MAGIC_ADDR pinning rules, and EECONFIG_KB_DATA_SIZE #undef
  requirement
- Add Keychron RGB section capturing lessons from the persistence work:
  when to call eeconfig_init_custom_rgb(), version stamp placement,
  kc_rgb_save() mode persistence, retail_demo_enable 0xFF bug, and
  extern array requirements
- Add DIP Switch section explaining the frame overlay approach and why
  direct rgb_matrix_mode() calls must be avoided
2026-04-13 12:57:14 -04:00

6.0 KiB
Raw Blame History

CLAUDE.md - QMK Development (Keychron Q5 Max / K17 Max)

Guidelines and commands for the customized Keychron firmware project based on the wireless_playground fork.

Project Scope

  • Origin: git.rootiest.dev/rootiest/qmk_firmware
  • Upstream: github.com/Keychron/qmk_firmware (branch: wireless_playground)
  • Primary Keyboard: Keychron Q5 Max (ANSI Encoder)
  • Secondary Keyboard: Keychron K17 Max (occasionally)
  • Development Branches:
    • dev/q5 — Q5 Max work-in-progress, merges to main
    • dev/k17 — K17 Max work-in-progress, merges to main
  • Feature Goals: Tap-Dance, expanded layers, advanced Chording, Unicode support, and Auto-correct.

Build and Flash Commands

All commands must be run from the root of the repository inside the project Python virtual environment. The system qmk is not used — activate the venv first:

source .venv/bin/activate

Every qmk command below assumes the venv is active (or prefix each with source .venv/bin/activate &&).

Compilation

# Build the Q5 Max ANSI Encoder firmware
qmk compile -kb keychron/q5_max/ansi_encoder -km via

# Build the K17 Max firmware (rgb variant; separate 'white' LED variant exists)
qmk compile -kb keychron/k17_max/ansi_encoder/rgb -km via

Flashing

# Flash the Q5 Max (requires the board to be in bootloader mode)
qmk flash -kb keychron/q5_max/ansi_encoder -km via

# Flash the K17 Max (requires the board to be in bootloader mode)
qmk flash -kb keychron/k17_max/ansi_encoder/rgb -km via

Environment Setup

# Ensure the submodules are up to date (critical for the wireless_playground branch)
git submodule update --init --recursive

# Set the default keyboard/keymap
qmk setup
qmk config user.keyboard=keychron/q5_max/ansi_encoder
qmk config user.keymap=via

Code Style and Patterns

  • Keymap Structure: Keep the keymap.c organized by layers. Use descriptive defines for layer names (e.g., _BASE, _FN, _CHORD).
  • Feature Modules: For advanced features like Chording or Tap-Dance, prefer creating separate headers/source files in the keymap folder to keep keymap.c readable.
  • Firmware Size: Monitor the compiled .bin size, as wireless features and large feature sets (like Auto-correct) can quickly fill up flash memory.
  • Documentation: Comment any complex chording logic or non-standard Tap-Dance implementations to ensure maintainability.

Development Workflow

  1. Verify the current branch is dev/q5 (Q5 Max) or dev/k17 (K17 Max).
  2. Implement features in the relevant keymap directory:
    • Q5 Max: keyboards/keychron/q5_max/ansi_encoder/keymaps/via/
    • K17 Max: keyboards/keychron/k17_max/ansi_encoder/rgb/keymaps/via/
  3. Test compilation locally before committing.
  4. Ensure rules.mk has the necessary flags enabled (e.g., TAP_DANCE_ENABLE = yes, UNICODE_ENABLE = yes).

Git Conventions

  • Use conventional commits (feat:, fix:, docs:, chore:, etc.) scoped to the keyboard where relevant (e.g. feat(q5_max):, fix(k17_max):).
  • Do not include Co-Authored-By: Claude trailers in commit messages.

Chained / Stacked PRs

When merging a chain of PRs (e.g. A → main, B → A, C → B), always delete the branch after each merge. Gitea (and GitHub) will automatically retarget any open PRs pointing at the deleted branch to the branch it was merged into. This keeps the chain collapsing cleanly into main without manual retargeting or cleanup PRs.

EEPROM Layout Notes

The Q5 Max uses wear-leveling EEPROM (STM32F401). Key layout facts:

  • EECONFIG_RGB_MATRIX is at bytes 2431; byte 0 packs mode[7:2] | enable[1:0].
  • Keychron custom RGB data (effect list, regions, per-key colours, retail demo flag) lives in EECONFIG_KB_DATABLOCK immediately after EECONFIG_BASE_SIZE (37 bytes).
  • VIA_EEPROM_MAGIC_ADDR is pinned to 544 in ansi_encoder/config.h. Do not lower this value — it must stay above EECONFIG_BASE_SIZE + EECONFIG_KB_DATA_SIZE. If Keychron EEPROM grows, raise 544 accordingly and clear EEPROM on the board.
  • EECONFIG_KB_DATA_SIZE is computed in eeconfig_kb.h and requires an #undef before the #define to suppress QMK's default-zero value.

Keychron RGB (KEYCHRON_RGB_ENABLE)

Enabled via KEYCHRON_RGB_ENABLE = yes in rules.mk. Key behavioural notes:

  • eeconfig_init_custom_rgb() loads Keychron RGB state from EEPROM into RAM. It must be called in keyboard_post_init_kb() and in wireless_enter_connected_kb(); without it the arrays are zero-initialised and Launcher settings are lost on every boot or transport change.
  • eeconfig_reset_custom_rgb() writes defaults to EEPROM and stamps the version. The version stamp (eeprom_update_dword(EECONFIG_KEYBOARD, ...)) belongs here only — not in the load path.
  • kc_rgb_save() must call eeconfig_update_rgb_matrix() to persist the QMK RGB mode alongside the Keychron custom data; otherwise rgb_matrix_init() (triggered on every transport change by REINIT_LED_DRIVER = 1) reloads the compile-time default RGB_MATRIX_TYPING_HEATMAP.
  • retail_demo_enable is a single byte. A bug in the original Keychron code used eeprom_read_block instead of eeprom_update_block in eeconfig_reset_custom_rgb(), leaving 0xFF on freshly-flashed boards. retail_demo_task() treats any non-zero value as "demo active" and forces CUSTOM_MIXED_RGB every scan. The load path now clamps > 1 to 0 and re-writes the byte as a one-time recovery.
  • default_per_key_led[] and default_region[] must be defined in board-specific code (e.g. ansi_encoder.c) — keychron_rgb.c declares them extern.

DIP Switch (Win/Mac)

The Win-side dip switch uses a frame overlay rather than calling rgb_matrix_mode(). A dip_win_active flag is set on switch change; rgb_matrix_indicators_advanced_user() paints all LEDs white each frame when the flag is set. This avoids writing to EEPROM and preserves the Launcher-configured effect, which would otherwise be overwritten by the direct mode call.