{"id":6394,"date":"2026-06-22T12:37:46","date_gmt":"2026-06-22T11:37:46","guid":{"rendered":"https:\/\/pmortensen.eu\/world2\/?p=6394"},"modified":"2026-08-09T23:33:00","modified_gmt":"2026-08-09T22:33:00","slug":"compiling_and_flashing_firmware_for_the_keychron_v6_ultra_8k_incl_setting_up_a_local_zmk_environment_from_scratch","status":"publish","type":"post","link":"https:\/\/pmortensen.eu\/world2\/2026\/06\/22\/compiling_and_flashing_firmware_for_the_keychron_v6_ultra_8k_incl_setting_up_a_local_zmk_environment_from_scratch\/","title":{"rendered":"Compiling and flashing firmware for the Keychron V6 Ultra 8K, incl. setting up a local ZMK environment from scratch"},"content":{"rendered":"<p><!-- T H E   B E G I N N I N G . . .   Marker for editing in WordPress: qqqq --><\/p>\n<p><!--\n  Unsolved:\n\n    How is the microcontroller, e.g., RTL8762GTU or\n    RTL8762GKU, in file 'Kconfig.board', referenced\/\n    selected in the keyboard-specific part?\n--><\/p>\n<p>The short version: <a href=\"https:\/\/github.com\/zmkfirmware\/zmk\">ZMK<\/a> 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:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nwest build -p -b keychron -- -DSHIELD=keychron_v6_ultra_ansi\r\n<\/pre>\n<h2 id=\"Introduction\">Introduction<\/h2>\n<p>Keychron <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1tnm8pl\/comment\/onvaa7y\/\">released the source code<\/a> for <a href=\"https:\/\/www.keychron.com\/products\/keychron-v6-ultra-8k-wireless-custom-mechanical-keyboard\">V6&nbsp;Ultra&nbsp;8K<\/a> on 2026-05-23 in branch <a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/rtl8762g\/app\/boards\/shields\/keychron_v6_ultra_ansi\"><strong><em>&#8220;rtl8762g&#8221;<\/em><\/strong><\/a> in Keychron&#8217;s fork of ZMK (<a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/boards\/shields\/keychron\">in the same fork<\/a> as the source code <a href=\"https:\/\/pmortensen.eu\/world2\/2025\/03\/10\/customising-the-zmk-based-keychron-b6-pro-keyboard-without-the-via-clone\/\">for B6&nbsp;Pro<\/a>), but they didn&#8217;t provide <strong><em>any<\/em><\/strong> instructions on how to compile and flash the keyboard firmware.<\/p>\n<p>Locations:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/shields\/keychron_v6_ultra_ansi\"><em>\/app\/boards\/shields\/keychron_v6_ultra_ansi<\/em><\/a>. 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 <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/shields\/keychron_v6_ultra_ansi\/keychron_v6_ultra_ansi.conf#L6\"><em>\/app\/boards\/shields\/keychron_v6_ultra_ansi.conf<\/em><\/a>. (Though it is not 100%; for example, there is hardcoded conditional compilation in <em>\/app\/src<\/em>, e.g., the <strong><em>per-keyboard<\/em><\/strong> USB-side version in file <em>my_app_version.h<\/em> (see below).) For example, <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/shields\/keychron_v6_ultra_ansi\/Kconfig.shield\"><em>Kconfig.shield<\/em><\/a>. <!-- $HOME\/Keychron_ZMK_fork_rtl8762g_3\/app\/boards\/shields\/keychron_v6_ultra_ansi\/Kconfig.shield --> Another example is the overlay file, for example, <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/shields\/keychron_v6_ultra_ansi\/keychron_v6_ultra_ansi.overlay#L128\">keychron_v6_ultra_ansi.overlay<\/a> with the ridiculous default <a href=\"https:\/\/www.ganssle.com\/debouncing.htm\">key&nbsp;debounce<\/a> time of 50&nbsp;ms<\/li>\n<li><a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/arm\/keychron\"><em>\/app\/boards\/arm\/keychron<\/em><\/a>. In file <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/arm\/keychron\/Kconfig.board\"><em>Kconfig.board<\/em><\/a>, this reveals the microcontroller to be either RTL8762GTU or RTL8762GKU (there is a typo or copy-paste error in <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/arm\/keychron\/Kconfig.board#L2\">line 2<\/a>)<\/li>\n<li><a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/src\"><em>\/app\/src<\/em><\/a>. Despite the unspecific folder name, the meat of Keychron&#8217;s custom implementation (in C) is here. Example: <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/src\/dfu\/my_app_version.h\"><em>dfu\/my_app_version.h<\/em><\/a> with the USB side version. <!-- $HOME\/Keychron_ZMK_fork_rtl8762g_3\/app\/src --> <!-- $HOME\/Keychron_ZMK_fork_rtl8762g_3\/app\/src\/dfu\/my_app_version.h --> <\/li>\n<\/ul>\n<p><!-- $HOME\/Keychron_ZMK_fork_rtl8762g_3\/app\/module\/drivers\/kscan\/kscan_gpio_matrix.c --><\/p>\n<h2>Linear command lines to set up from scratch<\/h2>\n<p>Below are the (linear) command lines to set up the main ZMK project on a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Debian\">Debian<\/a>-like system (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Mint#LMDE\">LMDE<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Mint\">Linux Mint<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ubuntu_%28operating_system%29\">Ubuntu<\/a>, Debian itself, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lubuntu\">Lubuntu<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Kubuntu\">Kubuntu<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Xubuntu\">Xubuntu<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/CrunchBang_Linux#BunsenLabs\">BunsenLabs<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Lite\">Linux Lite<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Windows_Subsystem_for_Linux\">WSL<\/a>, etc.), and compile the keyboard firmware for the V6&nbsp;Ultra&nbsp;8K. It would be similar for other (Linux) systems and other keyboards in the V&nbsp;Ultra&nbsp;8K series and Q&nbsp;Ultra&nbsp;8K&nbsp;series.<\/p>\n<p>Note that the installation is for ZMK v0.3 (matching the effective ZMK version of Keychron&#8217;s fork), similar to the setup described in the blog post <a href=\"https:\/\/pmortensen.eu\/world2\/2025\/03\/10\/customising-the-zmk-based-keychron-b6-pro-keyboard-without-the-via-clone\/\">for&nbsp;B6&nbsp;Pro<\/a> (though at that time using the latest ZMK instructions did work; that was before the <a href=\"https:\/\/zmk.dev\/blog\/2025\/12\/09\/zephyr-4-1\">December 2025 breaking change<\/a> 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).<\/p>\n<p>Thus, the <a href=\"https:\/\/v0-3-branch.zmk.dev\/docs\/development\/local-toolchain\/setup\/native\">install instructions for v0.3<\/a> apply, <strong><em>not<\/em><\/strong> the latest ZMK instructions. Otherwise, the result would be a compile error:<\/p>\n<p>&#8220;<strong><em>ModuleNotFoundError: No module named &#8216;elftools&#8217;<\/em><\/strong>&#8221;<\/p>\n<p>Other notes:<\/p>\n<ul>\n<li>For these ZMK v0.3 instructions, <strong><em>.\/setup.sh<\/em><\/strong> 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&#8217;t be these prompts as the install instructions are somewhat different.<\/li>\n<li>It isn&#8217;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.<\/li>\n<\/ul>\n<p><strong><em>Warning<\/em><\/strong>: Note that with &#8220;&#8211;assume-yes&#8221; (&#8220;-y&#8221; = &#8220;&#8211;yes&#8221; = &#8220;&#8211;assume-yes&#8221;) for &#8216;sudo apt upgrade&#8217; and &#8216;apt install&#8217;, the upgrade and installation of the ZMK prerequisites becomes <strong><em>unconditional<\/em><\/strong> (so that pasting all the lines after &#8220;sudo ls > \/dev\/null&#8221; can become one single step). For a more cautious approach, remove the two &#8220;&#8211;assume-yes&#8221;s and upgrade and install the prerequisites as separate steps.<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\n# Based on the official ZMK install instructions\r\n# (for &quot;native&quot; building) for version v0.3:\r\n#\r\n#   &lt;https:\/\/v0-3-branch.zmk.dev\/docs\/development\/local-toolchain\/setup\/native&gt;\r\n#\r\n# Prime sudo. Though only required for\r\n# installing the dependencies, etc.\r\n# And install udev rules, but it may\r\n# have timed out by then...\r\n#\r\nsudo ls &gt; \/dev\/null\r\n\r\n# &lt;https:\/\/docs.zephyrproject.org\/3.5.0\/develop\/getting_started\/index.html#select-and-update-os&gt;\r\nsudo apt update\r\n\r\n# Note: This will upgrade packages which may be\r\n#       completely unrelated to the ZMK build,\r\n#       for example, package 'dotnet-sdk-10.0'\r\n#\r\nsudo apt-get --assume-yes upgrade\r\n\r\n# Install the required dependencies:\r\nsudo apt-get install\r\n  --assume-yes                                 \\\r\n  --no-install-recommends                      \\\r\n  git cmake ninja-build gperf                  \\\r\n  ccache dfu-util device-tree-compiler wget    \\\r\n  python3-dev python3-pip   python3-setuptools \\\r\n  python3-tk  python3-wheel                    \\\r\n  xz-utils file make                           \\\r\n  gcc gcc-multilib g++-multilib                \\\r\n  libsdl2-dev libmagic1\r\n\r\n\r\ncd $HOME\r\necho ; echo &quot;Start cloning: $(date +%FT%T_%N_ns)&quot; ; echo\r\ngit clone --branch rtl8762g https:\/\/github.com\/Keychron\/zmk.git  Keychron_ZMK_fork_rtl8762g_v0_3\r\necho ; echo &quot;End cloning:   $(date +%FT%T_%N_ns)&quot; ; echo\r\n\r\n\r\n# Create the virtual Python environment\r\npython3 -m venv $HOME\/.Keychron_Ultra_8K_ZMK_environment_v0_3\r\n\r\n# Enter the virtual Python environment\r\nsource $HOME\/.Keychron_Ultra_8K_ZMK_environment_v0_3\/bin\/activate\r\n\r\n\r\n# ######################################\r\n# Inside the virtual Python environment:\r\n\r\n\r\ncd $HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\r\ngit status   # Confirm Git branch &quot;rtl8762g&quot;\r\n\r\n\r\n# Install 'west'\r\n    cd $HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\r\n    pip install west\r\n\r\n\r\n# Initialise the application and update\r\n# to fetch modules, including Zephyr:\r\n    cd $HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\r\n    echo ; echo &quot;Start 'west init &amp; update': $(date +%FT%T_%N_ns)&quot; ; echo\r\n    sudo ls &gt; \/dev\/null # Refresh the 'sudo' priming.\r\n                        # Sample timing for this step:\r\n                        #   08 min 02 secs\r\n\r\n    west init -l app\/   # Current directory at the top of\r\n                        # the cloned repository is assumed\r\n    west update\r\n    echo ; echo &quot;End 'west init&amp; update':    $(date +%FT%T_%N_ns)&quot; ; echo\r\n\r\n\r\n# Export a Zephyr CMake package. This allows CMake to\r\n# automatically load boilerplate code required for\r\n# building Zephyr applications.\r\ncd $HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\r\nwest zephyr-export\r\n\r\n\r\n# Install the additional dependencies found\r\n# in Zephyr's requirements-base.txt\r\n    echo ; echo &quot;Start installing additional dependencies: $(date +%FT%T_%N_ns)&quot; ; echo\r\n    cd $HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\r\n    pip install -r zephyr\/scripts\/requirements-base.txt\r\n    echo ; echo &quot;End installing additional dependencies:   $(date +%FT%T_%N_ns)&quot; ; echo\r\n\r\n\r\n# It takes a lot of time!\r\n#\r\n# '\/zephyr' is critical...\r\n#\r\n# But on LMDE 6, it failed due to a\r\n# requirement of Python 3.12... It\r\n# apparently only has 3.11.2...\r\n#\r\n# LMDE 7: Python 3.13.5\r\n#\r\n# Starts by downloading the Zyphyr SDK v. 1.0.1\r\n# compressed tar ball (70 MB).\r\n#\r\n# And installs things like (downloading 45 - 145 MB\r\n# for each):\r\n#\r\n#   'arm-zephyr-eabi'    GNU toolchain.\r\n#   'riscv64-zephyr-elf' GNU toolchain.\r\n#\r\n# 'Install Zephyr SDK'. Points to Zephyr 3.5 instructions,\r\n# &lt;https:\/\/docs.zephyrproject.org\/3.5.0\/develop\/getting_started\/index.html#install-zephyr-sdk&gt;\r\n#\r\ncd $HOME\r\necho ; echo &quot;Start Zephyr SDK install: $(date +%FT%T_%N_ns)&quot; ; echo\r\nsudo ls &gt; \/dev\/null # Refresh the 'sudo' priming.\r\n                    # Sample timing for this step:\r\n                    #   08 min 31 secs\r\n\r\n# 1.2 GB\r\nwget https:\/\/github.com\/zephyrproject-rtos\/sdk-ng\/releases\/download\/v0.16.3\/zephyr-sdk-0.16.3_linux-x86_64.tar.xz\r\n\r\nwget -O - https:\/\/github.com\/zephyrproject-rtos\/sdk-ng\/releases\/download\/v0.16.3\/sha256.sum | shasum --check --ignore-missing\r\n\r\ntar xvf zephyr-sdk-0.16.3_linux-x86_64.tar.xz\r\n\r\n\r\ncd $HOME\/zephyr-sdk-0.16.3\r\n#\r\n# Note: Has several prompts, e.g.,\r\n#\r\n#   Install host tools [y\/n]?\r\n#\r\n.\/setup.sh\r\n\r\necho ; echo &quot;End Zephyr SDK install:   $(date +%FT%T_%N_ns)&quot; ; echo\r\n\r\n\r\n# Not yet\r\n#sudo cp $HOME\/zephyr-sdk-0.16.3\/sysroots\/x86_64-pokysdk-linux\/usr\/share\/openocd\/contrib\/60-openocd.rules \/etc\/udev\/rules.d\r\n#sudo udevadm control --reload\r\n\r\n\r\n# Exit the virtual Python environment\r\ndeactivate\r\n<\/pre>\n<p>The time is dominated by these two steps, taking about eight minutes each on my system:<\/p>\n<ul>\n<li><strong><em>west init &#038; update<\/em><\/strong><\/li>\n<li>Zephyr SDK install (several sub steps)<\/li>\n<\/ul>\n<p>The space is about 1.9 GB on disk, in about 49,000&nbsp;files and 11,000&nbsp;folders.<\/p>\n<p>The 1.9 GB happens to be about the same as <a href=\"https:\/\/docs.qmk.fm\/#\/faq_general?id=what-is-qmk\">QMK<\/a>. For both ZMK <a href=\"https:\/\/pmortensen.eu\/world2\/2026\/06\/29\/fully-automated-setup-of-multiple-qmk-projects-from-scratch-incl-forks-and-vial\/\">and QMK<\/a>, most of the space is due to the underlying <a href=\"https:\/\/en.wikipedia.org\/wiki\/Real-time_operating_system\">RTOS<\/a> (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Zephyr_(operating_system)\">Zephyr<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/ChibiOS\/RT\">ChibiOS\/RT<\/a> (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) &#8220;only&#8221; takes up about 15&nbsp;MB.<\/p>\n<p>The initial size for all of ZMK right after <a href=\"https:\/\/en.wikipedia.org\/wiki\/Git\">Git<\/a> cloning is &#8220;only&#8221; 40&nbsp;MB.<\/p>\n<h2 id=\"Compile\">Building<\/h2>\n<p>The magic command line for compiling the ZMK keyboard firmware for Keychron V8 Ultra 8K is:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\n# Enter the virtual Python environment\r\nsource $HOME\/.Keychron_Ultra_8K_ZMK_environment_v0_3\/bin\/activate\r\n\r\n# Crucial. Otherwise, the result may be\r\n#\r\n#   ERROR:\r\n#     source directory &quot;.&quot; does not contain a CMakeLists.txt;\r\n#     is this really what you want to build?\r\n#     (Use -s SOURCE_DIR to specify the\r\n#      application source directory)\r\n#\r\n#   FATAL ERROR: refusing to proceed without --force due to above error\r\n#\r\ncd $HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\/app\r\n\r\nwest build -p -b keychron -- -DSHIELD=keychron_v6_ultra_ansi\r\n\r\n# Exit the virtual Python environment\r\ndeactivate\r\n<\/pre>\n<p><!--\n  Nothing found (so the firmware file doesn't contain \"zmk\"):\n\n    find . | grep zmk | grep -v '\\.yml$' | grep -v '\\.yaml$' | grep -v '\\.h$' | grep -v '\\.h$' | grep -v '\\.md$' | grep -v '\\.jpg$' | wc\n\n  Nope:\n    find . | grep '\\.bin$' | grep -v '\/tests\/'\n    find . | grep '\\.uf2$'\n    find . | grep '\\.hex$'\n\n  Leads:\n\n    CONFIG_KEYCHRON_FWU_STRING_NAME:\n\n      clear ; find \"$HOME\/Keychron_ZMK_fork_rtl8762g_v0_3\/app\" -type f | grep -v '\\.bin$' | grep -v '\\.o$' | grep -v '\\.obj$' | grep -v '\\.a$' | grep -v '\\.elf$' | sort | tr \"\\\\n\" \"\\0\" | xargs -0 grep -n -i CONFIG_KEYCHRON_FWU_STRING_NAME | wc\n    \n--><\/p>\n<p>The -p (or <a href=\"https:\/\/pmortensen.eu\/world2\/2026\/06\/09\/hello-world-for-zmk-the-latest-version#Appendix_B_west_gory_details\">&#8211;pristine<\/a> (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 <a href=\"https:\/\/zmk.dev\/docs\/development\/local-toolchain\/build-flash#pristine-building\"><em>Pristine Building<\/em><\/a>).<\/p>\n<p>Result:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nMemory region         Used Size  Region Size  %age Used\r\n           FLASH:      303620 B       800 KB     37.06%\r\n             RAM:       81664 B       100 KB     79.75%\r\n           TRACE:       13713 B       512 KB      2.62%\r\n            ITCM:      101136 B       104 KB     94.97%\r\n        IDT_LIST:          0 GB         2 KB      0.00%\r\n\r\n    -rwxrwxr-x 1 embo embo 7565640 Jun 12 10:27 \/home\/embo\/Keychron_ZMK_fork_rtl8762g_v0_3\/app\/build\/zephyr\/zmk.elf\r\n<\/pre>\n<p>Here is <a href=\"https:\/\/pmortensen.eu\/temp2\/_Command_lines_for_setting_up_from_scratch_and_building_V6_Ultra_8K_ANSI_2026-06-12_v0.3_b.txt\">a transcript<\/a> of both the setup and building. It was tested on an <a href=\"https:\/\/blog.linuxmint.com\/?p=4924\">LMDE&nbsp;7<\/a> (Gigi) system, updated to the latest as of June 2026.<\/p>\n<h2 id=\"Flashing\">Flashing<\/h2>\n<p>The protocol <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1v5z2n5\/comment\/oznum9d\/\">was reverse engineered<\/a>&#8230; And <a href=\"https:\/\/github.com\/naaraxi\/zmk\/blob\/openrgb\/openrgb\/flash.py\">a stand-alone flasher<\/a> was developed (only known to be working on Linux, but it <em>may<\/em> be possible to adapt it for other platforms (it uses <a href=\"https:\/\/www.pjrc.com\/teensy\/rawhid.html\">raw HID<\/a>, which should be cross-platform)).<\/p>\n<p>Note: The following is for the &#8216;ISO&#8217; variant, whereas the building above is for the &#8216;ANSI&#8217; variant (because the source code for the &#8216;ISO&#8217; variant has not been released yet).<\/p>\n<p>Clone the repository with the flasher, etc.:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ngit clone https:\/\/github.com\/naaraxi\/zmk.git -b openrgb $HOME\/Keychron_ZMK_flasher\r\n<\/pre>\n<p>Change the hardcoded values in the flasher script:<\/p>\n<p>1. Change <a href=\"https:\/\/github.com\/naaraxi\/zmk\/blob\/openrgb\/openrgb\/flash.py#L32\">the keyboard identifier<\/a> from KCZKV68K <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1v5z2n5\/comment\/ozo05v0\/\">to KCZKV68I<\/a>:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ngeany $HOME\/Keychron_ZMK_flasher\/openrgb\/flash.py:33\r\n<\/pre>\n<p>2. And the same in <a href=\"https:\/\/github.com\/naaraxi\/zmk\/blob\/openrgb\/openrgb\/flash.py#L106\">the print statement<\/a> in function handshake():<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ngeany $HOME\/Keychron_ZMK_flasher\/openrgb\/flash.py:108\r\n<\/pre>\n<p>3. Change <a href=\"https:\/\/github.com\/naaraxi\/zmk\/blob\/openrgb\/openrgb\/flash.py#L57\">the USB product ID<\/a> from 0C60 to 0C61 in function find_dfu_hidraw():<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ngeany $HOME\/Keychron_ZMK_flasher\/openrgb\/flash.py:58\r\n<\/pre>\n<p><!-- Seems not to be required. But it is for QMK's \n     polling rate script, .\/util\/polling_rate.py \n\nInstall <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1i45nt3\/comment\/m7vd73d\/\">extra required<\/a> Python modules:\n\n[code language=\"text\" gutter=\"false\"]\nsudo apt install python3-usb\n[\/code]\n--><\/p>\n<p>Identify:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ncd $HOME\/Keychron_ZMK_flasher\r\nsudo .\/openrgb\/flash.py handshake\r\n<\/pre>\n<p>Output:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nDFU interface: \/dev\/hidraw29\r\n&gt;&gt; GET_MODEL_INFO (0x60)...\r\n   model = b'KCZKV68I'   fw = b'1.0.1-r'\r\n   dfu_version = 0x03 enc_mode = 0\r\n   framing\/identity VALID (matches KCZKV68I)\r\n<\/pre>\n<p>Partly TBD&#8230;<\/p>\n<p>It is definitely not the preferred choice, but there is also <a href=\"https:\/\/naaraxi.github.io\/zmk\/\">a browser-based method<\/a>. There is also a claim it is also <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1v5z2n5\/comment\/ozu1ajs\/\">possible to flash<\/a> using the <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1buwibx\/comment\/kxx3kt0\/\">Via clone<\/a>.<\/p>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/zmk.dev\/blog\/2025\/06\/20\/pinned-zmk#how-to-pin-your-zmk-version\"><em>How to pin your ZMK version<\/em><\/a><\/li>\n<\/ul>\n<p><!--\n  Interesting discoveries:\n\n  File 'keychron_v6_ultra_ansi.conf'\n  <https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/boards\/shields\/keychron_v6_ultra_ansi\/keychron_v6_ultra_ansi.conf>\n\n      CONFIG_KEYCHRON_FWU_STRING_NAME=\"KCZKV68K\"\n      CONFIG_RTK_DFU=y\n\n        Related to flashing?\n        \"FWU\" for firmware update?\n\n     CONFIG_ZMK_BATTERY_REPORTING=y\n     CONFIG_ZMK_BATTERY_REPORT_INTERVAL=30\n\n       The alias MY_FWU_STRING_NAME is used in functions fw_sc_upgrade_rsp_model_info() and fw_sc_upgrade_verify_crc():\n\n         <https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/src\/dfu\/tdfu.c#L267> \n\n     # PPT (2.4G Wireless)\n\n       Perhaps meaning \"PPT\" refers to\n       the 2.4 GHz connection? Also in:\n\n         File 'launcher.c'\n         <https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/src\/launcher\/launcher.c#L2184>\n\n         if (zmk_endpoints_selected().transport == ZMK_TRANSPORT_USB)\n         if (zmk_endpoints_selected().transport == ZMK_TRANSPORT_PPT)\n\n     # CONFIG_ZMK_HID_KEYBOARD_NKRO_EXTENDED_REPORT=y\n\n       NKRO disabled (it is outcommented)? Or just some\n       extended version of it?\n\n      CONFIG_ZMK_SLEEP=y\n      CONFIG_ZMK_IDLE_TIMEOUT=600000\n      CONFIG_ZMK_IDLE_SLEEP_TIMEOUT=7200000\n\n        10 minutes RGB sleep and 2 hours deep sleep.\n\n      CONFIG_I2C=y\n      CONFIG_SPI_RTL87X2G_DMA=y\n\n        Does it use an external chip for non-volatile\n        storage \/ EEPROM?\n\n      CONFIG_RTL87X2G_USE_P6_2_AS_GPIOB21=y\n\n        An I\/O pin for the knob\n\n      CONFIG_CKLED2001=y\n\n        Per RGB light. Was CKLED2001 the old name\n        in QMK for the Sonix SNLED27351? Or\n        something else?\n\n      CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE=3000\n      CONFIG_LAUNCHER_USER_SET_DEBOUNCE=y\n\n        3000 what? For 3 seconds? Some kind of timeout\n        for committing changes to non-volatile storage?\n\n      # Debug Logging\n\n        All the options are commented out.\n\n  File launcher.c\n  <https:\/\/github.com\/Keychron\/zmk\/blob\/rtl8762g\/app\/src\/launcher\/launcher.c#L57>\n\n      int zmk_24g_send_launcher_report(uint8_t *payload, uint8_t payload_len);\n\n        Left over from the experiment with configuration\n        using the Via clone.\n--><\/p>\n<p><!-- Internal refs:\n\n  Bash:\n\n    clear ; find \"$HOME\/temp2\" -type f | grep '\\.txt$' | grep -v 'YYY' | sort | tr \"\\\\n\" \"\\0\" | xargs -0 grep -n DSHIELD | grep 'keychron_v6_ultra_ansi'\n\n  2026-06-12:\n\n    <geany  \"$HOME\/temp2\/2026-06-12\/Command lines for setting up from scratch and building V6 Ultra 8K ANSI, 2026-06-12_v0.3_b.txt:288\">\n\n      Includes the transcript for setup and build\/compile.\n\n  <https:\/\/www.reddit.com\/r\/Keychron\/comments\/1tnm8pl\/comment\/or70x7c\/>\n\n  <https:\/\/www.reddit.com\/r\/Keychron\/comments\/1u4uurf\/comment\/orh2rmz\/>\n    Also references this blog post\n\n      <https:\/\/pmortensen.eu\/world2\/2026\/06\/22\/compiling_and_flashing_firmware_for_the_Keychron_V6_Ultra_8K_incl_setting_up_a_local_ZMK_environment_from_scratch\/>\n--><\/p>\n<p><!--\n  Code formatting template:\n    [code language=\"text\" gutter=\"false\"]\n    [\/code]\n--><\/p>\n<p><!-- T H E   E N D . . .   Marker for editing in WordPress: pppp --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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: Introduction Keychron released the source code for V6&nbsp;Ultra&nbsp;8K on 2026-05-23 &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"more-link\" href=\"https:\/\/pmortensen.eu\/world2\/2026\/06\/22\/compiling_and_flashing_firmware_for_the_keychron_v6_ultra_8k_incl_setting_up_a_local_zmk_environment_from_scratch\/\"> <span class=\"screen-reader-text\">Compiling and flashing firmware for the Keychron V6 Ultra 8K, incl. setting up a local ZMK environment from scratch<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[44,4,43,23,28,26,16,40],"tags":[],"_links":{"self":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/6394"}],"collection":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/comments?post=6394"}],"version-history":[{"count":73,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/6394\/revisions"}],"predecessor-version":[{"id":6855,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/6394\/revisions\/6855"}],"wp:attachment":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/media?parent=6394"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/categories?post=6394"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/tags?post=6394"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}