QMK 固件中 BINEPAD BNR1 旋钮的移植实战:双版本构建、编码器映射与 Bootloader 进入方式
2026/9/17 4:46:30 网站建设 项目流程

QMK 固件中 BINEPAD BNR1 旋钮的移植实战:双版本构建、编码器映射与 Bootloader 进入方式

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

BINEPAD BNR1 是一款集"旋转、按压、边按边转"于一体的多功能 USB 旋钮外设,在 QMK 固件 中以binepad/bnr1键盘目录提供完整的移植支持,覆盖 ATMega32u4(V1)和 STM32F103(V2)两个硬件版本。本文基于该移植目录的 readme、键盘配置与默认 keymap 源码,完整讲清 BNR1 在 QMK 中的构建方法、端口配置细节、旋钮"按压 + 旋转"双重输入的实现方式(含编码器事件队列原理),以及三种进入 bootloader 烧录固件的途径,读完即可独立完成 BNR1 固件的编译与烧录。

一、设备定位:一个旋钮承载三种输入

BNR1 的核心形态是一枚多功能旋钮(multifunction knob),其输入能力分为三类:

  1. 旋转(rotate):旋钮本体为旋转编码器,通过两相正交信号产生方向事件,QMK 将其映射为方向敏感的键码(如音量加减、鼠标滚轮);
  2. 按压(press):旋钮中央是一块物理按键,在 QMK 中体现为 1x1 键盘矩阵的一个键位,可使用全部普通键码与高级键码(如LTMO);
  3. 边按边转(rotate while pressed):按压状态会切换图层,旋转在该图层下被重新映射——这正是默认 keymap 中"按住旋钮时旋转变为鼠标滚轮"的实现基础。

该外设由 Binepad 维护,硬件分为两代,对应仓库中两个不同的 target 目录:

硬件版本MCUTarget 路径说明
BNR1 / BNR1 R2(V1)ATMega32u4keyboards/binepad/bnr1/v1/初代硬件,Atmel DFU bootloader
BNR1 V2STM32F103keyboards/binepad/bnr1/v2/第二代硬件,stm32duino bootloader

两代硬件在 QMK 中的配置独立存放(各自的keyboard.json),但共用同一份默认 keymap,见 keymaps/default。

二、构建固件:两条 make 命令

在配好 QMK 构建环境(make 工具链、交叉编译器)之后,BNR1 的固件构建命令由版本决定:

# V1 硬件(ATMega32u4) make binepad/bnr1/v1:default # V2 硬件(STM32F103) make binepad/bnr1/v2:default

其中binepad/bnr1/v1是 target(对应keyboards/binepad/bnr1/v1/目录下的硬件配置),default是 keymap 名(对应 keymaps/default)。首次接触 QMK 的开发者可参考仓库文档 getting_started_make_guide.md 了解 make 工作流,以及 newbs.md 的完整新手指南。

三、固件能力:info.json 声明的功能集

键盘级别的公共元数据在 info.json 中声明,可直接读出该外设启用/禁用的 QMK 特性:

{ "manufacturer": "Binepad", "keyboard_name": "BNR1", "maintainer": "Binpad", "features": { "bootmagic": true, "extrakey": true, "mousekey": true, "nkro": false, "encoder": true }, "usb": { "vid": "0x4249" }, "community_layouts": ["ortho_1x1"], "layouts": { "LAYOUT_ortho_1x1": { "layout": [ {"matrix": [0, 0], "x": 0, "y": 0, "w": 2, "h": 2} ] } } }

逐项解读:

  • encoder: true:启用 QMK 编码器支持,这是"旋转"能力的来源;
  • mousekey: true:启用鼠标键支持。默认 keymap 的第二图层把旋转映射为MS_WHLD/MS_WHLU(鼠标滚轮下/上),依赖该特性;
  • extrakey: true:启用扩展键(KC_MUTE等多媒体键);
  • bootmagic: true:启用 Bootmagic——即"按住某个键插入 USB 即进入 bootloader",与下文 Bootloader 章节对应;
  • nkro: false:显式关闭 NKRO。BNR1 只有 1 个矩阵键位,全键无冲无意义,关闭可节省空间;
  • usb.vid: 0x4249:厂商 ID 为0x4249(对应 ASCII "BI"),两代硬件共用,各自在keyboard.json中定义不同的 PID;
  • layouts:布局为 1x1 正交矩阵(LAYOUT_ortho_1x1),矩阵位置[0,0]占据 2u×2u 的旋钮面积。

四、硬件端口配置:两代 keyboard.json 逐项对照

每个 target 的硬件差异全部集中在各自的keyboard.json中,这是 QMK 的"数据驱动配置"方式——不写 C 代码即可描述引脚映射。

V1(ATMega32u4)

keyboards/binepad/bnr1/v1/keyboard.json:

{ "bootloader": "atmel-dfu", "processor": "atmega32u4", "diode_direction": "COL2ROW", "usb": { "pid": "0x4231", "device_version": "1.0.0" }, "build": { "lto": true }, "matrix_pins": { "cols": ["B0"], "rows": ["E6"] }, "encoder": { "enabled": true, "rotary": [ {"pin_a": "D6", "pin_b": "D7"} ] } }

要点:

  • bootloader 为atmel-dfu:烧录走 DFU 协议,与 readme 中三种进入 bootloader 的方式配套;
  • 矩阵按键引脚:单列B0、单行E6,即旋钮按压键接在 PB0(列输入)与 PE6(行驱动)之间,diode_directionCOL2ROW(列接低侧二极管、行驱动),符合 QMK 标准的行/列扫描模型;
  • 编码器两相引脚pin_a: D6pin_b: D7,QMK 编码器驱动据此做正交解码判断顺时针/逆时针;
  • build.lto: true:启用链接期优化(LTO),对 AVR 这类空间紧张的 MCU 有助于压缩固件体积。

V2(STM32F103)

keyboards/binepad/bnr1/v2/keyboard.json:

{ "bootloader": "stm32duino", "processor": "STM32F103", "diode_direction": "COL2ROW", "usb": { "pid": "0x4241", "device_version": "2.0.0" }, "matrix_pins": { "cols": ["A15"], "rows": ["A8"] }, "encoder": { "enabled": true, "rotary": [ {"pin_a": "B3", "pin_b": "B4"} ] } }

与 V1 的差异:bootloader 换为stm32duino,USB PID 由0x4231改为0x4241,设备版本升至2.0.0;矩阵键位改接A15/A8,编码器改接B3/B4。注意两代硬件仅 PID 不同(V10x4231/ V20x4241),主机侧可通过该值区分具体硬件代数。

五、默认 keymap:按压切层 + 编码器双图层映射

默认 keymap 完整继承了"旋转、按压、边按边转"三合一的设计,源码见 keymap.c:

enum { _L0, _L1 } keyboard_layers; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [_L0] = LAYOUT_ortho_1x1( LT(_L1, KC_MUTE) ), [_L1] = LAYOUT_ortho_1x1( _______ ) }; #if defined(ENCODER_MAP_ENABLE) const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { [_L0] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, [_L1] = { ENCODER_CCW_CW(MS_WHLD, MS_WHLU) } }; #endif

行为拆解:

  • L0 层LT(_L1, KC_MUTE)使旋钮按压键兼具两种功能——短按发送静音键KC_MUTE按住不放则切换到 L1 层(LT 即 Layer Tap,QMK 标准高级键码);
  • L1 层:矩阵键位为透明键_______(按压本身在 L1 不再产生动作,L1 完全由"按住"维持);
  • encoder_map:为每层各定义一组方向键码,ENCODER_CCW_CW(ccw, cw)宏把两个参数分别绑定到逆时针/顺时针方向。L0 下逆时针/顺时针对应KC_VOLD/KC_VOLU(音量减/加);L1 下对应MS_WHLD/MS_WHLU(鼠标滚轮下/上)。由此"边按边转"即鼠标滚轮的效果在默认 keymap 中天然成立。
  • encoder_map的声明依赖编译开关:它包裹在#if defined(ENCODER_MAP_ENABLE)中,而该开关正是由 rules.mk 打开的:
ENCODER_MAP_ENABLE = yes

ENCODER_MAP_ENABLE打开后,编码器旋转不再调用普通的encoder_update_kb(),而是被转换为一对"按下/抬起"动作事件(详见下节)。

六、原理印证:QMK 编码器事件队列与动作执行

理解encoder_map为何能实现"方向敏感键码",需要看 QMK 的编码器核心实现 quantum/encoder.c 与 quantum/encoder.h。

(1)事件入队:编码器硬件层(如 drivers/encoder/encoder_quadrature.c 的正交解码驱动)负责读引脚、判方向,判出"索引 + 方向"后调用encoder_queue_event(index, clockwise)写入环形队列。队列结构在 quantum/encoder.h 中定义:

typedef struct encoder_event_t { uint8_t index : 7; uint8_t clockwise : 1; } encoder_event_t; typedef struct encoder_events_t { uint8_t enqueued; uint8_t dequeued; uint8_t head; uint8_t tail; encoder_event_t queue[MAX_QUEUED_ENCODER_EVENTS]; } encoder_events_t;

BNR1 只有 1 个编码器(NUM_ENCODERS为 1),队列深度默认至少为MAX(4, NUM_ENCODERS_MAX_PER_SIDE + 1),足以缓冲快速旋转产生的连续事件。

(2)动作执行:主循环中encoder_task()驱动事件消费,quantum/encoder.c 的encoder_handle_queue()展示了两种模式的分叉:

  • ENCODER_MAP_ENABLE开启(BNR1 默认 keymap 正是此模式):每个旋转事件被展开为"按下 + 抬起"两次action_exec()调用,且方向由MAKE_ENCODER_CW_EVENT/MAKE_ENCODER_CCW_EVENT区分:
action_exec(clockwise ? MAKE_ENCODER_CW_EVENT(index, true) : MAKE_ENCODER_CCW_EVENT(index, true)); #if ENCODER_MAP_KEY_DELAY > 0 wait_ms(ENCODER_MAP_KEY_DELAY); #endif action_exec(clockwise ? MAKE_ENCODER_CW_EVENT(index, false) : MAKE_ENCODER_CCW_EVENT(index, false));

即旋转一格 = 一次完整的按键 down/up 序列,方向决定走哪一列encoder_map键码——这正是 L0 层KC_VOLD/KC_VOLU与 L1 层MS_WHLD/MS_WHLU的生效路径。注释中特别提到ENCODER_MAP_KEY_DELAY的延迟是为了兼容 Windows 对按键事件的时序要求(默认取TAP_CODE_DELAY,见 quantum/encoder.c);

  • 未开启时:退化为调用encoder_update_kb(index, clockwise)回调,由键盘/用户代码自行处理方向。

此外,should_process_encoder()在默认实现中返回is_keyboard_master(),为分体键盘预留了从机侧不处理编码器事件的钩子;BNR1 是单 MCU 设备,该判断恒为真,从源码结构看这一抽象对外设类键盘无额外负担。

(3)方向宏的约定ENCODER_CCW_CW(ccw, cw)在 quantum/encoder.h 中展开为{(cw), (ccw)}——注意宏内部做了参数交换,因此写法是"逆时针参数在前、顺时针参数在后",而存储顺序与NUM_DIRECTIONS(固定为 2)的方向索引对齐。

七、进入 Bootloader 的三种方式

编译完成后需要烧录。BNR1 提供三种进入 bootloader 的途径(见 readme):

  1. Bootmagic reset(推荐):按住旋钮(按压键)的同时插入 USB 线缆。此方式依赖info.json中启用的bootmagic特性,无需记忆任何键码;
  2. 物理复位键:轻按 PCB 底面的 RESET 按钮;
  3. 键码触发:在 keymap 中为旋钮映射QK_BOOT(别名RESET)键码后直接按下。

进入 bootloader 后,V1 的 Atmel DFU 与 V2 的 stm32duino bootloader 均以 HID DFU 形式呈现,使用 QMK 构建后提示的make ...:flash或对应 DFU 烧录流程即可完成写入。日常使用 Bootmagic 即可,无需拆机寻找复位键。

八、小结与延伸阅读

BNR1 在 QMK 中的移植是一个典型的"极小矩阵 + 编码器"外设范例:1 个矩阵键位承载全部按键语义,LT切层实现"边按边转",ENCODER_MAP_ENABLE+encoder_map实现方向敏感的逐层映射,而两代硬件的差异完全由keyboard.json数据驱动地表达。相关可继续深入的文件:

  • keyboards/binepad/bnr1/readme.md:设备 readme 原文(构建命令与 Bootloader 方式)
  • keyboards/binepad/bnr1/info.json:特性开关、VID 与布局定义
  • keyboards/binepad/bnr1/v1/keyboard.json / keyboards/binepad/bnr1/v2/keyboard.json:两代硬件引脚配置
  • keyboards/binepad/bnr1/keymaps/default/keymap.c:默认 keymap 与编码器映射
  • quantum/encoder.c / quantum/encoder.h:编码器事件队列与动作执行核心
  • docs/feature_layers.md、docs/quantum_keycodes.md:图层机制与高级键码(LTRESET)参考

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询