ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ZMK 多层键盘映射实战:从 Devicetree 语法到三层 Keymap 完整解析

2026/10/6 2:01:11 拓冰建站 浏览量
ZMK 多层键盘映射实战:从 Devicetree 语法到三层 Keymap 完整解析 固件嵌入式智能硬件蓝牙【免费下载链接】zmkZMK Firmware Repository项目地址https://gitcode.com/gh_mirrors/zm/zmk点击查看免费下载本篇技术指南以 ZMK Firmware 官方文档中的三层 Keymap 示例为骨架逐步拆解 ZMK 的键位映射Keymap声明式配置体系从keyboard.keymap文件的 Devicetree 结构与compatible zmk,keymap节点的要求到层Layer的编号与优先级机制、行为绑定Behavior Binding的引用与参数传递并结合仓库源码app/src/keymap.c与真实键盘配置corne.keymap验证其底层实现。读完本文你将能够独立读懂并编写一个包含多层、层切换与修饰键组合的完整 ZMK 键位映射文件。一、ZMK 的声明式 Keymap 哲学ZMK 采用一种**声明式declarative**的键位映射方式用户通过 Devicetree设备树语法在一个keyboard.keymap文件中描述按键位置与行为Behavior的对应关系而不是编写命令式处理代码。这份声明式配置同时定义了按键映射、所使用的行为以及某些功能的配置。这种.keymap文件有几个关键特性每个键盘定义完整的板子或 shield都自带一份默认 keymap因此 ZMK 无需任何修改即可产出可用的库存固件用户完成 user setup 后库存的.keymap会被复制到用户配置目录供按需 customize这些 keymap 还可以通过 ZMK Studio 在运行时通过 USB 或 BLE动态修改。本文将以官方文档中那个三层 6 键 Macropad示例为线索逐行讲解其含义然后扩展到真实键盘配置。二、核心示例一个三层 Keymap 全解读官方文档 docs/docs/keymap-example.md 给出了一个非常直观的三层映射示例它是后续所有讲解的出发点。完整代码如下keymap { compatible zmk,keymap; default_layer { // Layer 0 display-name Base; // ---------------------------------------------- // | Z | M | K | // | mo 1 | LEFT SHIFT | mo 2 | bindings kp Z kp M kp K mo 1 kp LSHIFT mo 2 ; }; abc { // Layer 1 display-name ABC; // ---------------------------------------------- // | A | B | C | // | trans | trans | trans | bindings kp A kp B kp C trans trans trans ; }; xyz { // Layer 2 display-name XYZ; // ---------------------------------------------- // | X | Y | Z | // | LEFT CTRL | LEFT ALT | trans | bindings kp X kp Y kp Z kp LCTRL kp LALT trans ; }; };这个例子描绘的是一块2 行 × 3 列、共 6 个按键位置的 Macropad它在keymap节点下定义了三个层。注意注释中的 ASCII 图与bindings列表是一一对应的——每行注释对应bindings中的一行绑定这正是 ZMK keymap 里常见的注释即可视化布局写法让键位排布一目了然。2.1 层 0default_layer默认层默认层是整个映射的基石节点名取为default_layer编号固定为0default_layer { // Layer 0 display-name Base; bindings kp Z kp M kp K mo 1 kp LSHIFT mo 2 ; };kp Z、kp M、kp K三个普通按键按下时向主机发送对应字母键码kp LSHIFT左下角是左 Shift 修饰键mo 1、mo 2分别是**瞬间层Momentary Layer**绑定按住时临时启用第 1 层 / 第 2 层。也就是说这个 6 键设备在没有按任何层切换键时表现为Z / M / K Shift 层切换的布局一旦按住mo 1或mo 2所在键位便会临时进入对应层。2.2 层 1abc瞬间层的效果abc { // Layer 1 display-name ABC; bindings kp A kp B kp C trans trans trans ; };层 1 把三个主键位改成了 A / B / C而下方三个键位全部是trans。transtransparent透明是一个极其重要的行为它不消费事件而是把按键事件透传给下一个编号更高的活跃层。关于这一点本文第四部分会结合源码详细展开。在这里它的作用就是保持默认层对应键位的原有行为不变——如果默认层底部中间是 LSHIFT那么按住mo 1时底部中间依然是 LSHIFT。2.3 层 2xyz修饰键组合层xyz { // Layer 2 display-name XYZ; bindings kp X kp Y kp Z kp LCTRL kp LALT trans ; };层 2 把主键位变成 X / Y / Z把左侧两个键位变成kp LCTRL左 Ctrl与kp LALT左 Alt右下角仍是trans透传。这种某层用于输出功能键/修饰键的用法在实际键盘例如数字层、符号层中非常常见。2.4 从示例中提炼的三个要点层节点顺序决定层编号第一个子节点是层 0第二个是层 1依此类推见 config/keymap.md 的说明bindings列表顺序必须与键盘扫描配置kscan中键位的定义顺序一致见 kscan 配置多少个键位就有多少个绑定项display-name是可选属性用于 ZMK Studio 与显示屏上的层状态小部件显示层名称例如这里的Base、ABC、XYZ。三、Keymap 文件的结构骨架上面那段keymap { ... };并不是一份完整的.keymap文件。要让它真正可编译还需要包裹它的外层结构。综合 keymaps/index.mdx 与仓库内真实文件如 corne.keymap一份完整 keymap 的标准骨架如下#include behaviors.dtsi #include dt-bindings/zmk/keys.h #include dt-bindings/zmk/bt.h // 仅当用到蓝牙相关行为时 / { keymap { compatible zmk,keymap; default_layer { // Layer 0 display-name Base; bindings /* 每个键位一个绑定顺序与 kscan 键位顺序一致 */ ; // sensor-bindings ... ; // 可选编码器等传感器绑定 }; // 更多层... }; };3.1 头部 Includes行为与键码从哪来.keymap文件在交给 Zephyr 使用前会先经过C 预处理因此顶部可以使用标准的#include与#define。绝大多数 keymap 至少包含两行#include behaviors.dtsi #include dt-bindings/zmk/keys.hbehaviors.dtsi仓库路径 app/dts/behaviors.dtsi为所有内置行为定义了节点。它内部又 include 了key_press.dtsi、mod_tap.dtsi、layer_tap.dtsi、momentary_layer.dtsi、to_layer.dtsi、toggle_layer.dtsi、sticky_key.dtsi、macros.dtsi等 30 个左右的行为定义文件。正是这些 include 让kp、mo、to、tog这类以锚点anchor命名的引用成为可能dt-bindings/zmk/keys.h仓库路径 app/include/dt-bindings/zmk/keys.h提供所有键码的#define例如字母A、数字N1、媒体键C_PLAY以及修饰键LSHIFT、LCTRL、LALT等。这也是为什么示例中能直接写kp LSHIFT而不是某个晦涩的数字——LSHIFT只是原始 HID 键码的助记宏。3.2 根节点与 Keymap 节点所有剩余节点都嵌套在 Devicetree 根节点/ { ... };内。根节点之下是 keymap 节点/ { keymap { compatible zmk,keymap; // Layer nodes go here! }; };节点名字本身不重要叫keymap、keymap0均可但必须有compatible zmk,keymap属性否则 ZMK 无法识别它。这一点在源码中有硬性校验如果编译时找不到兼容zmk,keymap的节点app/src/keymap.c 第 32-36 行会直接抛出编译错误#if !DT_NODE_EXISTS(DT_DRV_INST(0)) #error Keymap node not found, check a keymap is available and is has compatible zmk,keymap set #endif3.3 层节点的属性来自官方 Schemazmk,keymap节点本身没有任何属性它只要求每个层作为其子节点存在且默认层必须是第一个子节点层 0。每个层节点的属性定义见仓库的绑定 Schema app/dts/bindings/zmk,keymap.yaml汇总如下属性类型必填说明display-namestring否在 ZMK Studio 与显示屏上显示的层名称对应源码中LAYER_NAME(node)宏见 app/src/keymap.cbindingsphandle-array是行为绑定列表每个键位一项顺序必须与键盘扫描配置kscan中键位定义顺序一致sensor-bindingsphandle-array否传感器目前主要是编码器行为绑定列表顺序与传感器定义顺序一致此外Schema 中还保留了一个已废弃的label属性提示改用display-name兼容历史配置。bindings中每一项都是一个phandle 引用 最多两个参数的组合正是下一节要讲的行为绑定。四、行为Behavior与行为绑定示例中每个符号的原理ZMK 的核心抽象是行为Behavior可以把行为绑定到某个键位、传感器编码器或层上当该绑定对应的事件发生时键按下/释放、编码器旋转等行为执行相应动作。例如最基础的 key press 行为键位按下时向主机发送某个键码释放时通知主机键码已释放。4.1 绑定的语法引用 参数行为绑定的一般形式是behavior_name [param1 [param2]]后是行为节点的锚点名称参数是可选的、最多两个用于微调该行为在这个特定键位上的表现kp A引用kpkey press行为参数A是 HID 键码宏mo 1引用momomentary layer行为参数1是层号mt LSHIFT D引用mtmod-tap行为两个参数分别是长按时使用的修饰键与短按时发送的键码。4.2 示例中涉及的行为与源码定义下面把示例中用到的行为逐个对照仓库源码均在 app/dts/behaviors/ 下Key Presskp——key_press.dtsikp: key_press { compatible zmk,behavior-key-press; #binding-cells 1; display-name Key Press; };#binding-cells 1表示该行为接受 1 个绑定参数即键码。更详细的键码分类与用法见 key-press 文档 与 键码列表。Momentary Layermo——momentary_layer.dtsimo: momentary_layer { compatible zmk,behavior-momentary-layer; #binding-cells 1; display-name Momentary Layer; };按住时启用指定层释放时立即禁用该层详见 层行为文档。Transparenttrans——transparent.dtsi透传行为不消费事件将其继续传给下一个更高编号的活跃层。这是覆盖式层设计的基础。Mod-Tapmt——mod_tap.dtsimt: mod_tap { compatible zmk,behavior-hold-tap; #binding-cells 2; flavor hold-preferred; tapping-term-ms 200; bindings kp, kp; display-name Mod-Tap; };#binding-cells 2说明它接受两个参数修饰键 键码。它本质上是 hold-tap 行为的特例长按输出修饰键短按输出普通键码默认tapping-term-ms 200、flavor hold-preferred。示例中虽然没有直接出现mt但kp LSHIFT/kp LCTRL/kp LALT与mo的组合正是许多键位图中同一键位既可作修饰键又可作层切换方案的替代实现思路。4.3 层行为的完整家族示例用到了mo与trans而实际键盘还会大量使用以下层行为定义见 layers 文档行为引用参数作用Momentary Layermo层号按住时临时启用该层释放即禁用Layer-Taplt层号 键码长按启用层短按输出键码见 hold-tapTo Layerto层号启用指定层同时禁用除默认层外的所有层Toggle Layertog层号切换层的启用/禁用状态to与tog在启用层时会锁定该层防止被非to/tog行为意外停用tog还支持通过自定义toggle-mode on或off的行为只开不关或只关不开。这几个行为的源码定义分别在 to_layer.dtsi带locking;属性与 toggle_layer.dtsi带locking;属性。五、层机制与优先级为什么示例里mo 1会盖住默认层理解了行为绑定语法后需要理解 ZMK 的层Layer运行机制才能预测多层示例在实际按下时的行为。官方文档 keymaps/index.mdx 的 Layers 一节说明了三条核心规则且都能在源码 app/src/keymap.c 中找到对应实现5.1 规则一默认层永远活跃层由自然数编号基础层固定为 0默认层始终处于启用状态。在 app/src/keymap.c 的set_layer_state中有明确保护// Default layer should *always* remain active if (layer_id _zmk_keymap_layer_default !state) { return 0; }即使显式尝试停用默认层也会被拒绝。5.2 规则二键位事件总是交给编号最高的活跃层当一个键位被按下/释放时事件会被发送到当前活跃且编号最高的层中该键位绑定的行为。对应实现是zmk_keymap_highest_layer_active()app/src/keymap.c——它从最大层号开始向下扫描返回第一个活跃层zmk_keymap_layer_index_t zmk_keymap_highest_layer_active(void) { for (int layer_idx ZMK_KEYMAP_LAYERS_LEN - 1; layer_idx LAYER_ID_TO_INDEX(_zmk_keymap_layer_default); layer_idx--) { ... if (zmk_keymap_layer_active(layer_id)) { return LAYER_ID_TO_INDEX(layer_id); } } return LAYER_ID_TO_INDEX(zmk_keymap_layer_default()); }事件分发的完整逻辑在zmk_keymap_position_state_changed()app/src/keymap.c从高层向低层遍历调用行为后根据返回值决定是否继续透传ret 0继续到下一层ret 0停止。这正是trans得以工作的机制——透明行为返回继续处理让事件下沉到更低的活跃层。5.3 规则三激活顺序不影响优先级层的优先级只由定义顺序编号决定与激活的先后顺序无关。即使层 1 在层 2 之后被激活层 2 的优先级依然更高。官方文档特别提示如果要用tog切换多个基础布局层如 QWERTY / Colemak应把它们放在层 0、层 1 这两个最低编号以便临时层在其上叠加工作。相关层状态管理set_layer_state、zmk_keymap_layer_activate/deactivate/toggle/to集中在 app/src/keymap.c。5.4 用规则重看三层示例默认状态下只有层 0 活跃按下Z位置 → 命中层 0 的kp Z主机收到 Z按住mo 1键位时层 1 被临时激活此时按Z位置 → 层 1 编号更高且活跃命中kp A主机收到 A按底部中间位置 → 层 1 是trans透传到层 0 的kp LSHIFT仍输出 Shift释放mo 1后层 1 立即禁用一切回到层 0 行为若同时按住mo 1与mo 2层 0、1、2 同时活跃键位事件将优先命中层 2编号最高层 2 中的trans位置再依次透传到层 1、层 0。六、把示例升级为完整可用配置6.1 用#define命名层告别魔法数字多层键盘上同一个层号可能被多处引用直接写数字既难读又难维护。官方推荐在文件头部用 C 预处理器宏给层命名详见 layers 文档#define DEFAULT 0 #define ABC 1 #define XYZ 2然后示例中的绑定就可以写成mo ABC // 等价于 mo 1 mo XYZ // 等价于 mo 26.2 补全头部与根节点后的完整文件综合前文所有要素把官方三层示例补全为一份可放入用户配置目录repo/config/keyboard.keymap的完整文件#include behaviors.dtsi #include dt-bindings/zmk/keys.h #define DEFAULT 0 #define ABC 1 #define XYZ 2 / { keymap { compatible zmk,keymap; default_layer { // Layer 0 display-name Base; // ---------------------------------------------- // | Z | M | K | // | mo 1 | LEFT SHIFT | mo 2 | bindings kp Z kp M kp K mo ABC kp LSHIFT mo XYZ ; }; abc { // Layer 1 display-name ABC; // ---------------------------------------------- // | A | B | C | // | trans | trans | trans | bindings kp A kp B kp C trans trans trans ; }; xyz { // Layer 2 display-name XYZ; // ---------------------------------------------- // | X | Y | Z | // | LEFT CTRL | LEFT ALT | trans | bindings kp X kp Y kp Z kp LCTRL kp LALT trans ; }; }; };6.3 真实键盘上的多层写法上面的 Macropad 示例规模很小但同样的结构在真实键盘上原样放大。以官方 Corne shield 的默认映射 app/boards/shields/corne/corne.keymap 为例其默认层同样是bindings ... ;逐键列出每行对应一排键位注释里画出了 ASCII 键位图并且通过mo 1、mo 2在拇指区挂载 Lower / Raise 两个临时层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 层mo 1触达里则大量使用trans透传默认层键位、用kp N1...kp N0输出数字、用bt BT_SEL 0等切换蓝牙配置档位见 app/boards/shields/corne/corne.keymap。这与三层示例中临时层 透传 功能键的写法完全一致只是键位数与绑定类型更多。类似的完整示例还可参考 kyria.keymap 与 lily58.keymap。七、进阶给层绑定传感器编码器示例中的层只使用了bindings属性如果键盘带有旋转编码器等传感器每个层还可以增加可选的sensor-bindings属性按传感器定义顺序列出行为绑定。对应 Schema 见 zmk,keymap.yamlsensor-bindings: type: phandle-array, required: false运行时由zmk_keymap_sensor_event()处理app/src/keymap.c编码器类型说明见 encoders 文档。八、小结与调试建议本文以官方三层 Keymap 示例为主线覆盖了 ZMK 键位映射从语法到原理的完整链路文件结构#include头部 →/ { }根节点 →keymap节点必须compatible zmk,keymap→ 按编号排列的层节点层节点属性bindings必填顺序对应键位、sensor-bindings可选、display-name可选绑定语法行为 参数1 参数2#binding-cells决定参数个数层机制默认层永远活跃、键位事件优先交给编号最高的活跃层、激活顺序不影响优先级trans负责事件透传下沉源码验证层状态管理、最高层查找与事件分发均可在 app/src/keymap.c 中逐一对应Schema 定义见 zmk,keymap.yaml。调试与继续学习建议阅读官方 Keymaps Behaviors 总览、层行为文档 与 Keymap 配置页 获取更完整的行为清单与属性说明直接浏览仓库 app/boards/shields/ 下各键盘的.keymap文件是最快的学习路径若修改键位后固件行为与预期不符优先检查bindings数量是否与键位数一致、层编号是否被意外改动、trans是否正确地让事件下沉到预期层。赞分享固件嵌入式智能硬件蓝牙【免费下载链接】zmkZMK Firmware Repository项目地址https://gitcode.com/gh_mirrors/zm/zmk点击查看免费下载相关推荐ZMK Devicetree 全面指南从声明式树结构到键位映射的底层原理ZMK Devicetree 全面指南从声明式树结构到键位映射的底层原理 本文是 ZMK 固件中 Devicetree 机制的入门与进阶指南面向 ZMK 用固件嵌入式智能硬件蓝牙ZMK 固件 Keymap 配置完全指南zmk,keymap 节点、层绑定与 Keymap Sensors 详解ZMK 固件 Keymap 配置完全指南zmk,keymap 节点、层绑定与 Keymap Sensors 详解 本篇技术指南以 ZMK 官方配置文档中的 K固件嵌入式智能硬件蓝牙SerenityOS 键盘布局管理实战keymap 命令详解与键盘映射体系剖析SerenityOS 键盘布局管理实战keymap 命令详解与键盘映射体系剖析 导读 键盘布局Keyboard Layout是操作系统输入子系统中最贴近日操作系统内核驱动上一篇Lepton 开发者指南性能优化技巧下一篇3snake源码解析从main.c到追踪器的完整代码实现分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考