5.8 · ZMK

03 May 2026

Por:
Anton
Sección:
Módulo 5 · Teclados mecánicos
Lectura:
6 min
Infografía: 5.8 · ZMK

ZMK: firmware wireless. Devicetree, GitHub Actions, ZMK Studio, keymap-editor. Errores comunes (prefijo &kp, puntos y comas)

QMK y Vial son el mundo del cable. Cuando quieres un teclado inalámbrico por Bluetooth, con batería y bajo consumo, el firmware es ZMK; resuelve justo el inalámbrico. Está pensado desde cero para BLE (Bluetooth Low Energy) y para microcontroladores como el nRF52840 (la placa nice!nano, la Seeed XIAO BLE), que llevan radio Bluetooth y gestión de batería integradas. ZMK no se configura en C: usa ficheros devicetree, una sintaxis distinta con sus propias trampas. En este artículo entiendes su flujo de trabajo, escribes un .keymap real y aprendes a evitar los errores que pillan a todo el mundo el primer día.

Conviene avisar de un cambio de terreno respecto a QMK. Allí el keymap era código C que compilabas en tu propia máquina. ZMK introduce dos cosas nuevas a la vez: la compilación ocurre en un servidor remoto (GitHub Actions, lo veremos abajo) y el keymap se escribe en devicetree, no en C. Son dos saltos independientes; este artículo los presenta por separado y, antes del ejemplo, define la sintaxis de devicetree que vas a leer.

Por qué ZMK para inalámbrico

QMK soporta cable y poco más; su soporte de BLE es marginal. ZMK nació para resolver justo eso:

  • Bluetooth Low Energy de serie: empareja con el ordenador o el móvil sin dongle, y gestiona varios perfiles BT para cambiar de dispositivo.
  • Bajo consumo real: está optimizado para que una batería pequeña dure semanas, no horas.
  • Split inalámbrico: las dos mitades de un Corne pueden hablar entre sí por BLE, sin cable TRRS entre ellas (solo USB o batería para alimentar cada lado).

Si tu cliente quiere un Corne sin cables que conectar, ZMK + nice!nano es el estándar.

El flujo de trabajo: GitHub Actions

Aquí ZMK hace algo muy distinto a QMK. No compilas en tu máquina: usas tu propio repositorio de configuración (zmk-config) en GitHub, y GitHub Actions compila el firmware en la nube cada vez que haces push. GitHub Actions es el sistema de automatización de GitHub: ejecuta tareas (en este caso, compilar el firmware) en sus propios servidores cuando ocurre un evento en el repositorio, como subir un cambio. El flujo:

  1. Creas tu repo zmk-config a partir de la plantilla oficial.
  2. Editas tu .keymap y tu .conf.
  3. Haces commit y push.
  4. GitHub Actions compila automáticamente y te deja los ficheros listos para flashear como artifacts (los ficheros resultantes de una ejecución, descargables desde la página del repositorio).

El resultado de la compilación son ficheros .uf2, uno por cada lado del split (corne_left.uf2, corne_right.uf2). Para flashear el nice!nano: conéctalo por USB, pulsa dos veces el botón de reset; el chip arranca en modo bootloader y aparece una unidad de almacenamiento USB llamada NICENANO; arrastras el .uf2 a esa unidad y se reinicia ya con el firmware. Flasheas cada mitad con su .uf2 correspondiente.

La estructura de un zmk-config

Tu repositorio contiene básicamente:

zmk-config/
├── config/
│   ├── corne.keymap   # las capas y bindings (devicetree)
│   ├── corne.conf     # opciones (BT, deep sleep, etc.)
│   └── west.yml        # de dónde sale ZMK
└── build.yaml          # qué shields/boards compilar

El .conf es donde activas cosas como el ahorro de energía:

CONFIG_ZMK_SLEEP=y
CONFIG_ZMK_IDLE_SLEEP_TIMEOUT=900000

La sintaxis devicetree, antes del ejemplo

Devicetree es un lenguaje para describir hardware y su configuración. No es C: no tiene funciones ni variables al uso, sino una jerarquía de nodos entre llaves. Para leer el keymap basta con cuatro reglas de sintaxis:

  • Un nodo es un bloque con nombre seguido de llaves: default_layer { ... };. Dentro van sus propiedades.
  • Una propiedad se asigna con = y termina siempre en punto y coma: propiedad = valor;. El punto y coma no es opcional.
  • Los bindings (las teclas de una capa) se escriben como una lista entre < y >: bindings = < ... >;. El bloque entero cierra con >;.
  • El símbolo & delante de un nombre es una referencia a un comportamiento (un behavior) ya definido. &kp no es texto: apunta al comportamiento “pulsar tecla”. Sin el &, devicetree no sabe que estás invocando un comportamiento.

Los comportamientos que aparecen en el ejemplo son tres:

  • &kp (key press): envía una tecla. &kp A envía la “a”.
  • &mo (momentary layer): activa una capa mientras mantienes la tecla. &mo 1 activa la capa 1. Es el equivalente al MO() de QMK.
  • &trans (transparent): tecla transparente, deja pasar lo que haya en la capa de debajo.

Con esas reglas, el fichero siguiente se lee sin sorpresas.

Un .keymap real (devicetree)

El keymap es devicetree, no C. Mira un ejemplo de tres capas para el Corne:

#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/bt.h>

/ {
    keymap {
        compatible = "zmk,keymap";

        default_layer {
            bindings = <
&kp TAB   &kp Q &kp W &kp E    &kp R &kp T   &kp Y &kp U  &kp I     &kp O   &kp P    &kp BSPC
&kp LCTRL &kp A &kp S &kp D    &kp F &kp G   &kp H &kp J  &kp K     &kp L   &kp SEMI &kp SQT
&kp LSHFT &kp Z &kp X &kp C    &kp V &kp B   &kp N &kp M  &kp COMMA &kp DOT &kp FSLH &kp ESC
            &kp LGUI &mo 1 &kp SPACE   &kp RET &mo 2 &kp RALT
            >;
        };

        lower_layer {
            bindings = <
&kp TAB   &kp N1 &kp N2 &kp N3 &kp N4 &kp N5   &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp BSPC
&kp LCTRL &trans &trans &trans &trans &trans   &trans &trans &trans &trans &trans &trans
&kp LSHFT &trans &trans &trans &trans &trans   &trans &trans &trans &trans &trans &trans
            &kp LGUI &trans &kp SPACE   &kp RET &mo 2 &kp RALT
            >;
        };

        raise_layer {
            bindings = <
&kp TAB   &kp F1 &kp F2 &kp F3 &kp F4 &kp F5   &kp F6   &kp F7   &kp F8 &kp F9   &kp F10 &kp BSPC
&kp LCTRL &trans &trans &trans &trans &trans   &kp LEFT &kp DOWN &kp UP &kp RIGHT &trans &trans
&kp LSHFT &trans &trans &trans &trans &trans   &trans   &trans   &trans &trans   &trans  &trans
            &kp LGUI &mo 1 &kp SPACE   &kp RET &trans &kp RALT
            >;
        };
    };
};

Cosas a entender:

  • &kp es el behavior “key press”. &kp A envía la “a”. El & indica que referencias un comportamiento.
  • &mo 1 activa la capa 1 mientras la mantienes (equivale al MO() de QMK).
  • &trans es transparente (deja pasar la capa de abajo).
  • Las capas se numeran por orden: 0 es la primera definida, 1 la segunda, etc.

Para Bluetooth añades bindings como &bt BT_SEL 0 (selecciona el primer perfil) o &bt BT_CLR (borra emparejamientos), normalmente en una capa de ajustes.

Los errores que pillan a todo el mundo

Como devicetree no es C, hay tres errores clásicos que generan fallos de compilación crípticos:

  • Olvidar el prefijo &. Es &kp A, no kp A. Sin el &, el compilador no reconoce el comportamiento y la build falla.
  • El punto y coma final. Cada bloque bindings = < ... >; termina en >;. Olvidar el ; rompe toda la compilación con un error que no apunta claramente al sitio.
  • Nombres de tecla. En ZMK los números son N1N0, la comilla es SQT, la barra FSLH. No asumas los nombres de QMK; consulta la lista de keycodes de ZMK.

Cuando una build de GitHub Actions falla, abre el log del job: ZMK muestra el devicetree procesado y el error de compilación. Casi siempre es un & o un ; que falta.

ZMK Studio: editar sin recompilar

ZMK también tiene su respuesta al “no quiero recompilar para cada cambio”: ZMK Studio. Es una aplicación (de escritorio y web) que permite cambiar las capas del keymap en tiempo real sobre un teclado ya flasheado, sin volver a pasar por GitHub Actions. Para usarla, tu keymap tiene que incluir un binding &studio_unlock que desbloquea la edición (igual que el unlock de Vial, por seguridad física). Para el aprendizaje inicial también existen editores visuales como keymap-editor, que te dibujan el teclado y generan el devicetree por ti, útil para no pelearte con la sintaxis al principio.

Recomendación práctica

Para un Corne inalámbrico, monta nice!nano (nRF52840), usa un zmk-config propio en GitHub y deja que Actions compile. Empieza con un keymap sencillo, verifica que cada mitad empareja por BT y añade complejidad poco a poco. Si quieres que el cliente ajuste capas sin tocar GitHub, habilita ZMK Studio. Y memoriza la regla de oro de la sintaxis: siempre & delante del behavior, siempre ; al cerrar el bloque. Esos dos detalles te ahorrarán el 90% de los fallos de compilación.

Del blog al libro Este post forma parte del temario de Construir teclados split. El libro completo incluye las dos rutas de ensamblaje (v3 y v4) completas y los keymaps del repo complementario.

Ver el libro

En construcción

Estamos preparando algo. Vuelve pronto.

Newsletter gratis

Novedades y montajes.

Directo a tu correo.

Sin spam.

Sin anuncios.

Al suscribirte aceptas recibir correos del taller. Puedes darte de baja cuando quieras.

Síguenos