QMK 固件 Tri Layer 功能详解:TL_LOWR/TL_UPPR 键码实现与三层配置实践

发布时间:2026/9/14 8:14:14
QMK 固件 Tri Layer 功能详解:TL_LOWR/TL_UPPR 键码实现与三层配置实践 QMK 固件 Tri Layer 功能详解TL_LOWR/TL_UPPR 键码实现与三层配置实践【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware本文以 QMK 固件的 Tri Layers 功能文档 为主体系统讲解 OLKB 风格的 Tri Layer三层键码机制如何启用该功能、TL_LOWR/TL_UPPR键码的行为规则、三个层号的默认配置与修改方式并结合 quantum/tri_layer.c、quantum/process_keycode/process_tri_layer.c 与 quantum/action_layer.c 的源码剖析 按下两个键激活第三层 这一行为在固件内部是如何被判定和执行的。读完后你不仅能正确配置和使用该功能还能理解其位运算判定逻辑以及为何层设置不具备掉电保持性。功能概述Tri Layer 是什么QMK 的 Tri Layer 功能支持 OLKBOLeary 键盘风格的 Tri Layer 键码。其工作方式类似MOmomentary瞬时层切换功能键按住某个键时打开某一层松开后关闭。不同的是Tri Layer 提供了两个功能键——Lower下层键和 Upper上层键当两个键同时被按住时会额外激活第三个 Adjust调整层。典型应用场景在调整层中放置灯光、音效、EEPROM 写入等低频但重要的管理功能用户无需专门记忆一个独立的 Adjust 层切换键而是通过 Lower Upper 同时按下 这一直觉化的组合触达。启用方式在键盘的rules.mk中添加以下配置即可启用TRI_LAYER_ENABLE yes该选项是 QMK 通用特性开关之一由构建系统 builddefs/common_features.mk 统一处理并在 builddefs/show_options.mk 中登记为可展示的特性选项。启用后相关键码与处理逻辑才会在编译时进入固件源码中大量使用#ifdef TRI_LAYER_ENABLE进行条件编译例如 quantum/action_tapping.c 中对 tap-hold 计时逻辑的特判。层号取值规则需要明确两点来自原文档upper、lower、adjust 这三个名称没有任何特殊技术含义仅仅是用于标识和澄清行为的命名层的处理顺序是从数值最大到最小但这些层号不要求连续。例如 lower 为 1、upper 为 5、adjust 为 8 是完全合法的配置。关于层栈layer stack本身的完整工作机制瞬时层、叠加关系、层优先级等可进一步参考 Keymap Overview 的 Keymap and Layers 章节。键码定义TL_LOWR 与 TL_UPPRTri Layer 功能提供两个键码键码别名行为说明QK_TRI_LAYER_LOWERTL_LOWR瞬时打开 lower 层若此时 upper 层也已打开则同时打开 adjust 层QK_TRI_LAYER_UPPERTL_UPPR瞬时打开 upper 层若此时 lower 层也已打开则同时打开 adjust 层两个键码互为镜像无论先按哪个、后按哪个只要两个键同时处于按下状态adjust 层就会被激活。从源码看键码数值在 quantum/keycodes.h 中定义QK_TRI_LAYER_LOWER 0x7C77, QK_TRI_LAYER_UPPER 0x7C78,别名TL_LOWR、TL_UPPR在同一文件的别名段quantum/keycodes.h中直接映射到上述常量因此在 keymap 中直接使用TL_LOWR即可。同时键码字符串模块 quantum/keycode_string.c 已注册了这两个键码的显示名用于 OLED/LCD 等界面的键码回显。源码行为剖析process_tri_layer这两个键码的实际处理逻辑位于 quantum/process_keycode/process_tri_layer.c其核心流程非常清晰case QK_TRI_LAYER_LOWER: if (record-event.pressed) { layer_on(get_tri_layer_lower_layer()); // 按下打开 lower 层 update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer()); // 重新判定 adjust 层 } else { layer_off(get_tri_layer_lower_layer()); // 松开关闭 lower 层 update_tri_layer(...); // 重新判定 adjust 层 } return false;QK_TRI_LAYER_UPPER的分支与之对称。这里有两点值得注意按下和松开都会调用update_tri_layer()。也就是说松开任意一个键时固件会重新检查 lower/upper 是否仍同时处于激活状态若不再满足条件adjust 层会随之关闭。这保证了组合状态变化的两个方向都能正确联动层号来自运行时可查询的变量get_tri_layer_*_layer()而非编译期常量这正是后面 运行时修改层号 功能的基础。adjust 层的判定逻辑一次位运算真正决定 adjust 层是否开启 的是 quantum/action_layer.c 中的update_tri_layer_state()layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3) { layer_state_t mask12 ((layer_state_t)1 layer1) | ((layer_state_t)1 layer2); layer_state_t mask3 (layer_state_t)1 layer3; return (state mask12) mask12 ? (state | mask3) : (state ~mask3); }其语义可以直白地翻译为把整个层状态state一个位图每一位代表一个层与 lower 位 | upper 位 的掩码做与运算若结果恰好等于掩码本身即两个位全部为 1说明 lower 和 upper 层都激活就把 adjust 层的位置 1否则把 adjust 层的位置 0。随后update_tri_layer()将新状态写回全局层状态并触发层状态更新。从源码结构看这种实现有两个工程上的好处无状态判定判定完全基于当前层状态位图不需要额外的标志位记录 谁按下了谁因此天然支持两个键的任意先后顺序、甚至与 OS 层、TO 层等其他层机制叠加使用非破坏性函数只对 adjust 层对应的位做置位/清零不改动位图中的其他层用户其他手动开启的层不受影响。配置项自定义三个层号三个层号均有编译期默认值可在键盘的config.h中通过宏覆盖配置名默认值说明TRI_LAYER_LOWER_LAYER1设置 lower 层的默认层号TRI_LAYER_UPPER_LAYER2设置 upper 层的默认层号TRI_LAYER_ADJUST_LAYER3设置 adjust 层的默认层号例如若希望把 Adjust 层设置为第 5 层在config.h中加入#define TRI_LAYER_ADJUST_LAYER 5这些默认值在源码中的落点是 quantum/tri_layer.h#ifndef TRI_LAYER_LOWER_LAYER # define TRI_LAYER_LOWER_LAYER 1 #endif #ifndef TRI_LAYER_UPPER_LAYER # define TRI_LAYER_UPPER_LAYER 2 #endif #ifndef TRI_LAYER_ADJUST_LAYER # define TRI_LAYER_ADJUST_LAYER 3 #endif即config.h中定义了对应宏就使用用户值否则回退到 1/2/3。这三个宏随后被 quantum/tri_layer.c 用作三个静态运行时变量的初值static uint8_t tri_layer_lower_layer TRI_LAYER_LOWER_LAYER; static uint8_t tri_layer_upper_layer TRI_LAYER_UPPER_LAYER; static uint8_t tri_layer_adjust_layer TRI_LAYER_ADJUST_LAYER;这解释了配置模型的分层设计config.h宏只决定出厂默认值运行时实际生效的值由下方这组 setter/getter 管理。运行时函数接口动态切换层号Tri Layer 模块对外暴露一组 set/get 函数声明见 quantum/tri_layer.h实现在 quantum/tri_layer.c函数名功能set_tri_layer_lower_layer(layer)运行时修改 lower 层号*set_tri_layer_upper_layer(layer)运行时修改 upper 层号*set_tri_layer_adjust_layer(layer)运行时修改 adjust 层号*set_tri_layer_layers(lower, upper, adjust)一次性设置三个层号*get_tri_layer_lower_layer()读取当前 lower 层号get_tri_layer_upper_layer()读取当前 upper 层号get_tri_layer_adjust_layer()读取当前 adjust 层号注意这些运行时设置不持久化——断电或控制器重启后会恢复为config.h中定义的默认值。这一点与源码实现严格对应三个层号保存在static uint8_t变量中quantum/tri_layer.c并未写入 EEPROM因此复位后自然回到宏初值。一个典型的实战用法是在 keymap 中预留几组自定义键配合set_tri_layer_layers()让不同工作模式如游戏模式、编程模式使用不同的 lower/upper/adjust 层组合而TL_LOWR/TL_UPPR两个物理键的位置和行为保持不变。由于process_tri_layer()每次都实时读取 getter 的返回值切换层号后下一次按键即刻生效无需重新编译。与 Tap-Hold 等机制的交互细节Tri Layer 键虽然是按住生效的 hold 型行为但它并不占用 QMK tap-hold 状态机。从源码结构看在 flow tap流式敲击判定路径中固件显式忽略了 Tri Layer 键的释放事件quantum/action_tapping.c#ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys. case QK_TRI_LAYER_LOWER: case QK_TRI_LAYER_UPPER: #endif // TRI_LAYER_ENABLE return;这意味着在开启 flow tap / 推测式 hold 判定的键盘上Tri Layer 键的按下/释放不会被当作 上一个敲击 来影响相邻 tap-hold 键的判定窗口。同理quantum/process_keycode/process_repeat_key.c 将两个 Tri Layer 键码标记为 hold 型键码使其与 Caps Word、repeat 键等依赖键码分类的模块正确协同。如果你在编写或审计 keymap 时遇到 Tri Layer 键与 MT 键相邻出现行为异常 一类的问题这些分类宏MODIFIER_KEYCODE_RANGE、QK_MOMENTARY ...等 switch 分支是排查的第一站。此外quantum/process_keycode/process_caps_word.c 也把QK_TRI_LAYER_LOWER ... QK_TRI_LAYER_UPPER区间纳入了其键码类型判断说明该键码区间被整体视为 非字符类 功能键——这与0x7C77/0x7C78这两个键码值紧邻分配、区间连续的设计意图一致。测试验证仓库为 Tri Layer 功能提供了专门的单元测试目录 tests/tri_layer/test_tri_layer.cpp通过模拟按键序列验证层状态转换。在 QMK 中这类功能测试通常以 quantum/keycodes.h 中定义的键码驱动虚拟矩阵事件断言layer_state的变化是验证 按下 Lower 后 Adjust 未激活、再按下 Upper 后 Adjust 激活、松开任一后 Adjust 关闭 这类时序逻辑的可靠手段。如果你修改了与层状态相关的代码如update_tri_layer_state建议运行该测试回归验证确保位运算判定未被破坏。小结启用rules.mk中加TRI_LAYER_ENABLE yes使用keymap 中放置TL_LOWRQK_TRI_LAYER_LOWER0x7C77与TL_UPPRQK_TRI_LAYER_UPPER0x7C78双键同按即激活 adjust 层配置config.h中用TRI_LAYER_LOWER_LAYER默认 1、TRI_LAYER_UPPER_LAYER默认 2、TRI_LAYER_ADJUST_LAYER默认 3设定默认层号层号无需连续扩展set_tri_layer_*系列函数支持运行时改层号但不掉电保持原理process_tri_layer()在按下/松开时开关对应层并调用update_tri_layer()后者用一次位图掩码运算判定 lower、upper 是否同时激活从而置位或清除 adjust 层——整个机制无额外状态、可与其他层机制自由叠加。【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考