Compiling and flashing firmware for the Keychron V6 Ultra 8K, incl. setting up a local ZMK environment from scratch
The short version: ZMK must be set up for v0.3, not the latest version (as of 2026-06-22), otherwise the result is a compile error. The command line for compiling is:
west build -p -b keychron -- -DSHIELD=keychron_v6_ultra_ansi
Introduction
Keychron released the source code for V6 Ultra 8K on 2026-05-23 in branch “rtl8762g” in Keychron’s fork of ZMK (in the same fork as the source code for B6 Pro), but they didn’t provide any instructions on how to compile and flash the keyboard firmware.
Locations:
- /app/boards/shields/keychron_v6_ultra_ansi. Most of the keyboard model-specific part (in this case, V8 Ultra 8K) is here, mostly definitions and data, not actual C code. For example, the USB identity is in file /app/boards/shields/keychron_v6_ultra_ansi.conf. (Though it is not 100%; for example, there is hardcoded conditional compilation in /app/src, e.g., the per-keyboard USB-side version in file my_app_version.h (see below).) For example, Kconfig.shield. Another example is the overlay file, for example, keychron_v6_ultra_ansi.overlay with the ridiculous default key debounce time of 50 ms
- /app/boards/arm/keychron. In file Kconfig.board, this reveals the microcontroller to be either RTL8762GTU or RTL8762GKU (there is a typo or copy-paste error in line 2)
- /app/src. Despite the unspecific folder name, the meat of Keychron’s custom implementation (in C) is here. Example: dfu/my_app_version.h with the USB side version.
Linear command lines to set up from scratch
Below are the (linear) command lines to set up the main ZMK project on a Debian-like system (LMDE, Linux Mint, Ubuntu, Debian itself, Lubuntu, Kubuntu, Xubuntu, BunsenLabs, Linux Lite, WSL, etc.), and compile the keyboard firmware for the V6 Ultra 8K. It would be similar for other (Linux) systems and other keyboards in the V Ultra 8K series and Q Ultra 8K series.
Note that the installation is for ZMK v0.3 (matching the effective ZMK version of Keychron’s fork), similar to the setup described in the blog post for B6 Pro (though at that time using the latest ZMK instructions did work; that was before the December 2025 breaking change in ZMK. The instructions in that blog post still works (as they are essentially a copy of the instructions at the time), but not the instructions it references).
Thus, the install instructions for v0.3 apply, not the latest ZMK instructions. Otherwise, the result would be a compile error:
“ModuleNotFoundError: No module named ‘elftools’”
Other notes:
- For these ZMK v0.3 instructions, ./setup.sh has several prompts, so if pasting on the command line, for the first paste, ./setup.sh must be the last one, before the rest, after ./setup.sh has completed. If it was the latest version of ZMK, there wouldn’t be these prompts as the install instructions are somewhat different.
- It isn’t clear if a ZMK v0.3 and ZMK 2026-06 installation can coexist or not on the same system (even if they use different virtual Python environments), for example, if they somehow share something that may require a particular version, like the Zyphyr SDK.
Warning: Note that with “–assume-yes” (“-y” = “–yes” = “–assume-yes”) for ‘sudo apt upgrade’ and ‘apt install’, the upgrade and installation of the ZMK prerequisites becomes unconditional (so that pasting all the lines after “sudo ls > /dev/null” can become one single step). For a more cautious approach, remove the two “–assume-yes”s and upgrade and install the prerequisites as separate steps.
# Based on the official ZMK install instructions
# (for "native" building) for version v0.3:
#
# <https://v0-3-branch.zmk.dev/docs/development/local-toolchain/setup/native>
#
# Prime sudo. Though only required for
# installing the dependencies, etc.
# And install udev rules, but it may
# have timed out by then...
#
sudo ls > /dev/null
# <https://docs.zephyrproject.org/3.5.0/develop/getting_started/index.html#select-and-update-os>
sudo apt update
# Note: This will upgrade packages which may be
# completely unrelated to the ZMK build,
# for example, package 'dotnet-sdk-10.0'
#
sudo apt-get --assume-yes upgrade
# Install the required dependencies:
sudo apt-get install
--assume-yes \
--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
cd $HOME
echo ; echo "Start cloning: $(date +%FT%T_%N_ns)" ; echo
git clone --branch rtl8762g https://github.com/Keychron/zmk.git Keychron_ZMK_fork_rtl8762g_v0_3
echo ; echo "End cloning: $(date +%FT%T_%N_ns)" ; echo
# Create the virtual Python environment
python3 -m venv $HOME/.Keychron_Ultra_8K_ZMK_environment_v0_3
# Enter the virtual Python environment
source $HOME/.Keychron_Ultra_8K_ZMK_environment_v0_3/bin/activate
# ######################################
# Inside the virtual Python environment:
cd $HOME/Keychron_ZMK_fork_rtl8762g_v0_3
git status # Confirm Git branch "rtl8762g"
# Install 'west'
cd $HOME/Keychron_ZMK_fork_rtl8762g_v0_3
pip install west
# Initialise the application and update
# to fetch modules, including Zephyr:
cd $HOME/Keychron_ZMK_fork_rtl8762g_v0_3
echo ; echo "Start 'west init & update': $(date +%FT%T_%N_ns)" ; echo
sudo ls > /dev/null # Refresh the 'sudo' priming.
# Sample timing for this step:
# 08 min 02 secs
west init -l app/ # Current directory at the top of
# the cloned repository is assumed
west update
echo ; echo "End 'west init& update': $(date +%FT%T_%N_ns)" ; echo
# Export a Zephyr CMake package. This allows CMake to
# automatically load boilerplate code required for
# building Zephyr applications.
cd $HOME/Keychron_ZMK_fork_rtl8762g_v0_3
west zephyr-export
# Install the additional dependencies found
# in Zephyr's requirements-base.txt
echo ; echo "Start installing additional dependencies: $(date +%FT%T_%N_ns)" ; echo
cd $HOME/Keychron_ZMK_fork_rtl8762g_v0_3
pip install -r zephyr/scripts/requirements-base.txt
echo ; echo "End installing additional dependencies: $(date +%FT%T_%N_ns)" ; echo
# It takes a lot of time!
#
# '/zephyr' is critical...
#
# But on LMDE 6, it failed due to a
# requirement of Python 3.12... It
# apparently only has 3.11.2...
#
# LMDE 7: Python 3.13.5
#
# Starts by downloading the Zyphyr SDK v. 1.0.1
# compressed tar ball (70 MB).
#
# And installs things like (downloading 45 - 145 MB
# for each):
#
# 'arm-zephyr-eabi' GNU toolchain.
# 'riscv64-zephyr-elf' GNU toolchain.
#
# 'Install Zephyr SDK'. Points to Zephyr 3.5 instructions,
# <https://docs.zephyrproject.org/3.5.0/develop/getting_started/index.html#install-zephyr-sdk>
#
cd $HOME
echo ; echo "Start Zephyr SDK install: $(date +%FT%T_%N_ns)" ; echo
sudo ls > /dev/null # Refresh the 'sudo' priming.
# Sample timing for this step:
# 08 min 31 secs
# 1.2 GB
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
tar xvf zephyr-sdk-0.16.3_linux-x86_64.tar.xz
cd $HOME/zephyr-sdk-0.16.3
#
# Note: Has several prompts, e.g.,
#
# Install host tools [y/n]?
#
./setup.sh
echo ; echo "End Zephyr SDK install: $(date +%FT%T_%N_ns)" ; echo
# Not yet
#sudo cp $HOME/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
# Exit the virtual Python environment
deactivate
The time is dominated by these two steps, taking about eight minutes each on my system:
- west init & update
- Zephyr SDK install (several sub steps)
The space is about 1.9 GB on disk, in about 49,000 files and 11,000 folders.
The 1.9 GB happens to be about the same as QMK. For both ZMK and QMK, most of the space is due to the underlying RTOS (Zephyr and ChibiOS/RT (for ARM microcontrollers), respectively) and their (source code) dependencies (libraries), not the keyboard project themselves. Even the 2,000 supported keyboards in QMK (separate from the core of QMK) “only” takes up about 15 MB.
The initial size for all of ZMK right after Git cloning is “only” 40 MB.
Building
The magic command line for compiling the ZMK keyboard firmware for Keychron V8 Ultra 8K is:
# Enter the virtual Python environment source $HOME/.Keychron_Ultra_8K_ZMK_environment_v0_3/bin/activate # Crucial. Otherwise, the result may be # # ERROR: # source directory "." does not contain a CMakeLists.txt; # is this really what you want to build? # (Use -s SOURCE_DIR to specify the # application source directory) # # FATAL ERROR: refusing to proceed without --force due to above error # cd $HOME/Keychron_ZMK_fork_rtl8762g_v0_3/app west build -p -b keychron -- -DSHIELD=keychron_v6_ultra_ansi # Exit the virtual Python environment deactivate
The -p (or –pristine (two ASCII dashes, not as rendered here)) is for making sure it works if some other ZMK keyboard was previously compiled. It can often be left out for repeated building, for faster building of the keyboard firmware. See also Pristine Building).
Result:
Memory region Used Size Region Size %age Used
FLASH: 303620 B 800 KB 37.06%
RAM: 81664 B 100 KB 79.75%
TRACE: 13713 B 512 KB 2.62%
ITCM: 101136 B 104 KB 94.97%
IDT_LIST: 0 GB 2 KB 0.00%
-rwxrwxr-x 1 embo embo 7565640 Jun 12 10:27 /home/embo/Keychron_ZMK_fork_rtl8762g_v0_3/app/build/zephyr/zmk.elf
Here is a transcript of both the setup and building. It was tested on an LMDE 7 (Gigi) system, updated to the latest as of June 2026.
Flashing
The protocol was reverse engineered… And a stand-alone flasher was developed (only known to be working on Linux, but it may be possible to adapt it for other platforms (it uses raw HID, which should be cross-platform)).
Note: The following is for the ‘ISO’ variant, whereas the building above is for the ‘ANSI’ variant (because the source code for the ‘ISO’ variant has not been released yet).
Clone the repository with the flasher, etc.:
git clone https://github.com/naaraxi/zmk.git -b openrgb $HOME/Keychron_ZMK_flasher
Change the hardcoded values in the flasher script:
1. Change the keyboard identifier from KCZKV68K to KCZKV68I:
geany $HOME/Keychron_ZMK_flasher/openrgb/flash.py:33
2. And the same in the print statement in function handshake():
geany $HOME/Keychron_ZMK_flasher/openrgb/flash.py:108
3. Change the USB product ID from 0C60 to 0C61 in function find_dfu_hidraw():
geany $HOME/Keychron_ZMK_flasher/openrgb/flash.py:58
Identify:
cd $HOME/Keychron_ZMK_flasher sudo ./openrgb/flash.py handshake
Output:
DFU interface: /dev/hidraw29 >> GET_MODEL_INFO (0x60)... model = b'KCZKV68I' fw = b'1.0.1-r' dfu_version = 0x03 enc_mode = 0 framing/identity VALID (matches KCZKV68I)
Partly TBD…
It is definitely not the preferred choice, but there is also a browser-based method. There is also a claim it is also possible to flash using the Via clone.
Leave a Reply