Customising the ZMK-based Keychron B6 Pro keyboard without the Via clone
This was written as it happened (though not necessarily in the same order) when I was new to ZMK, on day 1 of getting the Keychron B6 Pro.
Some pros:
- Independence of a web service.
- More permanent configuration of the keyboard (not requiring reloading from a saved configuration after every reset to factory defaults (the reloading also requires the web service))
Installation of ZMK
I initially considered using the standard Docker image for ZMK, but the instructions for installation of Docker itself seemed way too confusing; it wasn’t at all clear how to actually perform the installation (if they could just show a concrete linear example for some popular platform, say, Ubuntu). It is an example of overgeneralised installation instructions (way too abstract).
Instead, I decided to try to follow the official ZMK instructions for a native installation without Docker.
Initially, the installation failed on Ubuntu 20.04 (yes, I know), as expected. Instead, a LMDE 6 system was used.
These are the magic command lines (it is a linear thing (unlike the official instructions which makes “subroutine calls” to the Zephyr instructions (confusing)), and it should be possible to use them blindly, one by one, to get a ZMK environment for Keychron up and running):
# <https://zmk.dev/docs/development/local-toolchain/setup/native>
git clone https://github.com/zmkfirmware/zmk.git
cd zmk
# <https://docs.zephyrproject.org/3.5.0/develop/getting_started/index.html#select-and-update-os>
sudo apt update
sudo apt upgrade
# Install the required dependencies:
sudo apt install --no-install-recommends git cmake ninja-build gperf \
ccache dfu-util device-tree-compiler wget \
python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \
make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1
# Install a virtual environment (in this case 'venv'),
# 'pip', and Git.
# 'venv': <https://docs.python.org/3/library/venv.html>
#
# Notes:
#
# 1. A "virtual environment" in this context is
# a sandboxed Python installation. That is,
# isolated from the system Python
# installation (which the operating
# system heavily relies on; thus it
# should not be messed with)
#
# 2. 'venv' is part of Python's standard
# library (since version 3.2)
sudo apt install python3-venv python3-pip git
# Create a new virtual environment
python3 -m venv ~/.ZMK_environment
source ~/.ZMK_environment/bin/activate
# Inside the virtual environment:
#
# Install west
pip install west
# Initialize the application and update to
# fetch modules, including Zephyr:
west init -l app/
west update
# Export a Zephyr CMake package. This allows CMake to automatically
# load boilerplate code required for building Zephyr applications.
west zephyr-export
# Install the additional dependencies found
# in Zephyr's requirements-base.txt
pip install -r zephyr/scripts/requirements-base.txt
# <https://docs.zephyrproject.org/3.5.0/develop/getting_started/index.html#install-zephyr-sdk>:
#
# Install Zephyr SDK
#
# The Zephyr Software Development Kit (SDK) contains toolchains for
# each of Zephyr's supported architectures, which include
# a compiler, assembler, linker and other programs
# required to build Zephyr applications.
#
#
# Download and verify the Zephyr SDK bundle
#
# 1.2 GB... (download size. Compressed)
#
cd ~
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.3/zephyr-sdk-0.16.3_linux-x86_64.tar.xz
wget -O - https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.3/sha256.sum | shasum --check --ignore-missing
# Extract the Zephyr SDK bundle archive:
tar xvf zephyr-sdk-0.16.3_linux-x86_64.tar.xz
# Run the Zephyr SDK bundle setup script:
cd zephyr-sdk-0.16.3
./setup.sh
#Not yet...
# Install udev rules, which allow you to flash most Zephyr
# boards as a regular user:
sudo cp ~/zephyr-sdk-0.16.3/sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d
sudo udevadm control --reload
# <https://zmk.dev/docs/development/local-toolchain/build-flash>
#
# Building and flashing
cd ~/zmk/app
# Build firmware for a sample keyboard
#
# </home/embo/zmk/app/boards/arm/planck/planck_rev6.yaml>
# Has:
#
# identifier: planck_rev6
#
# </home/embo/zmk/app/boards/arm/planck/planck_rev6.keymap>
# </home/embo/zmk/app/boards/arm/planck/planck_rev6.zmk.yml>
# </home/embo/zmk/app/boards/arm/planck/planck_rev6.dtsv
#
# A build folder:
#
# </home/embo/zmk/app/build/zephyr/boards/boards/arm/planck_rev6>
#
#
cd ~/zmk/app
west build -b planck_rev6
find ~/zmk/app | grep elf | xargs ls -l
# Keychron B6 Pro
#
# Note: The Git branch will be "main" after the clone...
# Keychron's Git branch is "keychron_bpro"
#
# And we should clone Keychron's fork,
# not the main one...
#
# <https://github.com/Keychron/zmk/tree/keychron_bpro/app/boards/shields/keychron/b6>
#
# <https://github.com/Keychron/zmk/blob/keychron_bpro/app/boards/shields/keychron/b6/uk/Kconfig.shield>
# Has: 'keychron_b6_uk'?
#
# Clone the source code repository
#
cd ~
git clone https://github.com/Keychron/zmk.git zmk_KeychronFork_forReal
# The action is in Git branch "keychron_bpro"
cd ~/zmk_KeychronFork_forReal
git switch keychron_bpro
# Or in a single step:
#
# cd ~
# git clone -b keychron_bpro https://github.com/Keychron/zmk.git zmk_KeychronFork_forReal
# Initialize the application and update to
# fetch modules, including Zephyr.
#
# This will take some time...
#
cd ~/zmk_KeychronFork_forReal
west init -l app/
west update # Primary approx. 300 MB. 145496 "objects".
# And several Git submodules, e.g.
# 'modules/lib/canopennode'
# 'modules/hal/stm32'
#
# Nearly 2000 lines in the screen output.
# Export a Zephyr CMake package. This allows CMake to automatically
# load boilerplate code required for building Zephyr applications.
west zephyr-export
# Keyboard Planck in Keychron's fork of ZMK
cd ~/zmk_KeychronFork_forReal/app
west build -b planck_rev6
# Failed:
#
# _power.h: No such file or directory
# 18 | #include <hal/nrf_power.h>
# | ^~~~~~~~~~~~~~~~~
# --pristine due to the previous build of keyboard Planck
cd ~/zmk_KeychronFork_forReal/app
west build --pristine -b keychron -p -- -DSHIELD=keychron_b6_uk
OK, this was probably predicated on Git already being installed, so the Git clone step should probably be moved down somewhat.
The blocking build error
FAILED: zephyr/CMakeFiles/zephyr.dir/home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.c.obj
ccache /home/embo/zephyr-sdk-0.16.3/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc -DKERNEL -DNRF52840_XXAA -D_FORTIFY_SOURCE=2 -D__PROGRAM_START -D__ZEPHYR__=1 -I/home/embo/zmk_KeychronFork_forReal/zephyr/kernel/include -I/home/embo/zmk_KeychronFork_forReal/zephyr/arch/arm/include -I/home/embo/zmk_KeychronFork_forReal/zephyr/include -I/home/embo/zmk_KeychronFork_forReal/app/build/zephyr/include/generated -I/home/embo/zmk_KeychronFork_forReal/zephyr/soc/arm/nordic_nrf/nrf52 -I/home/embo/zmk_KeychronFork_forReal/zephyr/soc/arm/nordic_nrf/common/. -I/home/embo/zmk_KeychronFork_forReal/zephyr/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/nrfx_glue -I/home/embo/zmk_KeychronFork_forReal/zephyr/subsys/bluetooth -I/home/embo/zmk_KeychronFork_forReal/zephyr/subsys/usb/device -I/home/embo/zmk_KeychronFork_forReal/zephyr/subsys/settings/include -I/home/embo/zmk_KeychronFork_forReal/modules/hal/cmsis/CMSIS/Core/Include -I/home/embo/zmk_KeychronFork_forReal/modules/hal/nordic/nrfx -I/home/embo/zmk_KeychronFork_forReal/modules/hal/nordic/nrfx/drivers/include -I/home/embo/zmk_KeychronFork_forReal/modules/hal/nordic/nrfx/mdk -I/home/embo/zmk_KeychronFork_forReal/zephyr/modules/hal_nordic/nrfx/. -I/home/embo/zmk_KeychronFork_forReal/modules/crypto/tinycrypt/lib/include -I/home/embo/zmk_KeychronFork_forReal/app/module/include -I/home/embo/zmk_KeychronFork_forReal/app/module/drivers/sensor/battery/. -isystem /home/embo/zmk_KeychronFork_forReal/zephyr/lib/libc/minimal/include -isystem /home/embo/zephyr-sdk-0.16.3/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/include -isystem /home/embo/zephyr-sdk-0.16.3/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/include-fixed -fno-strict-aliasing -Os -imacros /home/embo/zmk_KeychronFork_forReal/app/build/zephyr/include/generated/autoconf.h -ffreestanding -fno-common -g -gdwarf-4 -fdiagnostics-color=always -mcpu=cortex-m4 -mthumb -mabi=aapcs -mfp16-format=ieee --sysroot=/home/embo/zephyr-sdk-0.16.3/arm-zephyr-eabi/arm-zephyr-eabi -imacros /home/embo/zmk_KeychronFork_forReal/zephyr/include/zephyr/toolchain/zephyr_stdint.h -Wall -Wformat -Wformat-security -Wno-format-zero-length -Wno-main -Wno-pointer-sign -Wpointer-arith -Wexpansion-to-defined -Wno-unused-but-set-variable -Werror=implicit-int -fno-pic -fno-pie -fno-asynchronous-unwind-tables -fno-reorder-functions --param=min-pagesize=0 -fno-defer-pop -fmacro-prefix-map=/home/embo/zmk_KeychronFork_forReal/app=CMAKE_SOURCE_DIR -fmacro-prefix-map=/home/embo/zmk_KeychronFork_forReal/zephyr=ZEPHYR_BASE -fmacro-prefix-map=/home/embo/zmk_KeychronFork_forReal=WEST_TOPDIR -ffunction-sections -fdata-sections -std=c99 -nostdinc -Wfatal-errors -MD -MT zephyr/CMakeFiles/zephyr.dir/home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.c.obj -MF zephyr/CMakeFiles/zephyr.dir/home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.c.obj.d -o zephyr/CMakeFiles/zephyr.dir/home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.c.obj -c /home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.c
In file included from /home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.c:1:
/home/embo/zmk_KeychronFork_forReal/app/src/dfu/tdfu_prv.h:3:10: fatal error: ..\version.h: No such file or directory
3 | #include "..\version.h"//<app_version.h>
| ^~~~~~~~~~~~~~
compilation terminated.
And I got the same error, using the final step for actually building for B6 Pro (using the instruction found here):
source ~/.ZMK_environment/bin/activate cd ~/zmk_KeychronFork_forReal/app west build --pristine -b keychron -p -- -DSHIELD=keychron_b6_uk deactivate
Simple keymappings, including modifier keys
Edit the keymap file, /app/boards/shields/keychron/b6/uk/keychron_b6_uk.keymap, for example,
geany '$HOME/zmk_KeychronFork_forReal/app/boards/shields/keychron/b6/uk/keychron_b6_uk.keymap:233'
Fn + J + Z doesn’t work for the self-compiled firmware
Unlike Keychron’s official firmware, holding down Fn + J + Z doesn’t work for the self-compiled firmware (nothing happens). This is a similar situation to the QMK-based Keychron keyboards where it doesn’t work either using the official source code.
In the source code, by inspection, the time lock is expressed in file keychron_b6_uk.keymap:
combos1
{
compatible = "zmk,combos";
combo_jz
{
timeout-ms = <1000>;
layers = <1 3>;
key-positions = <69 81>; // Fn + J + Z
bindings = <&long_press_recover 0 0>;
};
For some of the magic numbers, I added this (an extension to a custom Perl script to reformat the messy code, to automatically generate a symbolic representation of the keynumbers):
enum key_numbers
{
k_J = 69
k_Z = 81
k_Fn = 102
};
“long_press_recover” is in the same file:
long_press_recover:l1
{
compatible = "zmk,behavior-hold-tap";
label = "l1";
#binding-cells = <2>;
flavor = "tap-preferred";
tapping-term-ms = <3000>; // Delay amount in milliseconds
bindings = <&recover>, <&none>;
};
Drawing an analogy from the similarly non-working holding Fn + “-“ for 3 seconds to enter bootloader mode, using “&recover” in a regular keymapping may be a workaround.
Appendix A: Key numbers
The source code file for the key map contains a lot of magic numbers, for example key numbers in the keymap. Here is the list of them for the B6 Pro, ISO variant:
| Key number | Key | Notes |
|---|---|---|
| 0 | ESC | |
| 1 | F1 | |
| 2 | F2 | |
| 3 | F3 | |
| 4 | F4 | |
| 5 | F5 | |
| 6 | F6 | |
| 7 | F7 | |
| 8 | F8 | |
| 9 | F9 | |
| 10 | F10 | |
| 11 | F11 | |
| 12 | F12 | |
| 13 | F13 | |
| 14 | PSCRN | |
| 15 | SLCK | |
| 16 | PAUS | |
| 17 | C_AL_CALC | |
| 18 | &uc LG(TAB) | |
| 19 | &uc LG(LC(LEFT)) | |
| 20 | &uc LG(LC(RIGHT)) | |
| 21 | GRAVE | |
| 22 | N1 | |
| 23 | N2 | |
| 24 | N3 | |
| 25 | N4 | |
| 26 | N5 | |
| 27 | N6 | |
| 28 | N7 | |
| 29 | N8 | |
| 30 | N9 | |
| 31 | N0 | |
| 32 | MINUS | |
| 33 | EQUAL | |
| 34 | BSPC | |
| 35 | INS | |
| 36 | HOME | |
| 37 | PG_UP | |
| 38 | KP_NLCK | |
| 39 | KP_SLASH | |
| 40 | KP_ASTERISK | |
| 41 | KP_MINUS | |
| 42 | TAB | |
| 43 | Q | |
| 44 | W | |
| 45 | E | |
| 46 | R | |
| 47 | T | |
| 48 | Y | |
| 49 | U | |
| 50 | I | |
| 51 | O | |
| 52 | P | |
| 53 | LBKT | |
| 54 | RBKT | |
| 55 | DEL | |
| 56 | END | |
| 57 | PG_DN | |
| 58 | KP_N7 | |
| 59 | KP_N8 | |
| 60 | KP_N9 | |
| 61 | KP_PLUS | |
| 62 | CLCK | |
| 63 | A | |
| 64 | S | |
| 65 | D | |
| 66 | F | |
| 67 | G | |
| 68 | H | |
| 69 | J | |
| 70 | K | |
| 71 | L | |
| 72 | SEMI | |
| 73 | SQT | |
| 74 | NUHS | |
| 75 | RET | |
| 76 | KP_N4 | |
| 77 | KP_N5 | |
| 78 | KP_N6 | |
| 79 | LSHFT | |
| 80 | NUBS | |
| 81 | Z | |
| 82 | X | |
| 83 | C | |
| 84 | V | |
| 85 | B | |
| 86 | N | |
| 87 | M | |
| 88 | COMMA | |
| 89 | DOT | |
| 90 | FSLH | |
| 91 | RSHFT | |
| 92 | UP | |
| 93 | KP_N1 | |
| 94 | KP_N2 | |
| 95 | KP_N3 | |
| 96 | LCTRL | |
| 97 | LGUI | |
| 98 | LALT | |
| 99 | SPACE | |
| 100 | RALT | |
| 101 | RGUI | |
| 102 | &mo 3 | |
| 103 | RCTRL | |
| 104 | LEFT | |
| 105 | DOWN | |
| 106 | RIGHT | |
| 107 | KP_N0 | |
| 108 | KP_DOT | |
| 109 | KP_ENTER | |
| 110 | &none | |
| 111 | &out OUT_BLE | |
| 112 | &out OUT_24G | |
| 113 | &out OUT_CHG | |
| 114 | &out OUT_CHGD |
References
- ZMK keycodes. For example, RWIN for right Windows key and K_APP for the context menu (the corresponding keycode in QMK is KC_APP; they are probably both aliases)
- ZMK ‘behaviors’. Includes an explanation for, for example, “&kp” (key press), “&none”, “&trans” (transparent), “&mo” (momentary layer; e.g., the Fn key), “&out” (output selection), “&to” (to layer), “&tog” (toggle layer), “&sl” (sticky layer), “<” (layer tap), “&sk” (sticky key), “&mt” (modifier tap), and “&gresc” (grave escape). But not “&uc”…
- ZMK cheat sheet. But it doesn’t have an explanation for “&uc” either… Is it Keychron-specific? An alias of “user_custom” (whatever that is)?
- Building a reset firmware [sic]. Does it only apply for split keyboards?
- USB logging (in ZMK)
- Troubleshooting Bluetooth connection issues in Windows
- Enable NKRO in wireless mode (ZMK), using CONFIG_ZMK_HID_REPORT_TYPE_NKRO
- Keychron B6 Pro keymap (ISO)
- Comparison of pipx to other tools (Python)
Leave a Reply