QMK 中 3W6HS 分体键盘的构建、烧录与双 MCU 矩阵扫描机制详解
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
3W6HS 是一款基于 RP2040、支持热插拔(hotswap)的低配列(low profile)分体(split)键盘,每侧 5 列 × 4 行共 36 键,由 weteor 的 3W6 改版而来,维护者为 beekeeb。本文以仓库中 3W6HS 键盘文档 为主体,结合 config.h、matrix.c、rules.mk 与默认键位 keymap.c,讲解该键盘在 QMK 固件中的完整构建与烧录流程、RP2040 bootloader 进入方法,以及左板原生 GPIO + 右板 TCA9555 I2C 扩展芯片的混合矩阵扫描实现。读完本文,你可以独立完成make beekeeb/3w6hs:default的编译烧录,并理解分体键盘中一侧通过 I2C 扩展 GPIO 的接线与扫描原理。
硬件规格与仓库布局
按 readme.md 的官方描述,3W6HS 的核心属性如下:
| 项目 | 说明 |
|---|---|
| 主控 | RP2040(bootloader 为 RP2040 板载 USB-Bootloader) |
| 形态 | 低配列、分体、左右各 5×4 矩阵加底部拇指键,共 36 键 |
| 热插拔 | 支持(hotswap) |
| 维护者 | beekeeb(由 weteor 的 3W6 改版) |
键盘目录下的关键文件分工明确:
- keyboard.json:描述处理器(
RP2040)、bootloader(rp2040)、USB 描述符(VID0xBEEB、PID0x0003)、启用bootmagic、mousekey、extrakey特性,并定义布局LAYOUT_split_3x5_3的键位坐标; - config.h:定义矩阵尺寸 8 行 × 10 列及两侧引脚分配;
- matrix.c:自定义矩阵扫描逻辑(I2C + TCA9555);
- rules.mk:声明
CUSTOM_MATRIX = lite、加入matrix.c并启用 I2C 驱动; - halconf.h / mcuconf.h:ChibiOS 层配置,启用
HAL_USE_I2C并将 RP2040 的 I2C 外设锁定为 I2C0。
构建与烧录
文档给出的两条标准命令(在配置好 QMK 构建环境之后执行):
# 编译默认键位 make beekeeb/3w6hs:default # 编译并烧录 make beekeeb/3w6hs:default:flash构建环境配置与 make 用法可参考仓库内的 构建工具入门、make 指南 与 新手指南。default指使用 keymaps/default/keymap.c 中预置的六层键位;若自行编写键位,将其置于keyboards/beekeeb/3w6hs/keymaps/<name>/keymap.c后执行make beekeeb/3w6hs:<name>即可。
编译时 rules.mk 的三个指令决定了固件形态:
CUSTOM_MATRIX = lite # 使用自定义 matrix_scan_custom() 而非通用扫描器 SRC += matrix.c # 加入键盘目录下的自定义矩阵实现 I2C_DRIVER_REQUIRED = yes # 强制启用 I2C 硬件驱动(右板 TCA9555 依赖)其中CUSTOM_MATRIX = lite的含义是:QMK 不再使用内置的matrix.c通用扫描,而是调用本键盘提供的matrix_init_custom()与matrix_scan_custom()(见后文)。I2C_DRIVER_REQUIRED = yes则保证即便键位层面没有显式依赖 I2C,I2C 外设驱动也一定会被编入固件——这对右半区扫描是硬性前提。
进入 RP2040 Bootloader
readme.md 给出的 bootloader 进入步骤:
- 将键盘从电脑上断开;
- 找到 BOOT 按键——位于键盘左侧面板的顶部;
- 按住 BOOT 键不放,重新将键盘连接回电脑。
此时 RP2040 会以板载 USB-Bootloader(mass storage)模式枚举,即可通过make beekeeb/3w6hs:default:flash或手动拷贝.uf2文件完成烧录。由于keyboard.json中 bootloader 字段为rp2040,QMK 构建系统会自动生成配套的.uf2固件(将固件镜像包装进 UF2 容器)。另外该键盘启用了bootmagic特性:若将 USB 连接至特定键位(默认是左板 Q 键所在的矩阵点,可编译时通过BOOTMAGIC_DIODE_DIRECTION调整),固件会在启动时直接进入 bootloader,方便快速重刷。
矩阵结构与引脚分配
从 config.h 可以确认整个键盘是一个逻辑上的 8×10 矩阵,物理上被拆成两半:
#define MATRIX_ROWS 8 #define MATRIX_COLS 10 #define MATRIX_ROWS_PER_SIDE (MATRIX_ROWS / 2) // 4 #define MATRIX_COLS_PER_SIDE (MATRIX_COLS / 2) // 5 #define MATRIX_ROW_PINS_L { GP7, GP8, GP9, GP10 } #define MATRIX_COL_PINS_L { GP11, GP12, GP13, GP14, GP15 } #define MATRIX_ROW_PINS_R { P10, P11, P12, P05 } #define MATRIX_COL_PINS_R { P06, P13, P14, P01, P00 } #define I2C_DRIVER I2CD0 #define I2C1_SDA_PIN GP0 #define I2C1_SCL_PIN GP1左板 4 行 5 列全部由 RP2040 的 GPIO 直接驱动;右板的 4 行 5 列虽然也在config.h中声明了引脚宏(MATRIX_ROW_PINS_R/MATRIX_COL_PINS_R),但从 matrix.c 的实现看,右板实际是经由一颗TCA9555 I2C GPIO 扩展芯片扫描的:I2C 数据线挂在 RP2040 的GP0(SDA)与GP1(SCL),通过I2CD0(即 I2C0 外设)通信,这一选择由 mcuconf.h 中的RP_I2C_USE_I2C0 TRUE与 halconf.h 中的HAL_USE_I2C TRUE配合保证。这种"单 MCU + I2C 扩展"的接线方式正是该键盘分体方案中无需为右板再放一颗主控、又能保持热插拔与低配列结构的原因(右板行选择与列读取均通过 TCA9555 的寄存器完成)。
TCA9555 初始化与寄存器操作
matrix.c 顶部定义了 TCA9555 的 I2C 地址与关键寄存器:
// TCA9555 的地址引脚全部接地:0b0100000 << 1 => 0x20 #define I2C_ADDR (0b0100000 << 1) #define IODIRA 0x06 // I/O 方向寄存器 #define IODIRB 0x07 #define IREGP0 0x00 // GPIO 输入端口寄存器 #define IREGP1 0x01 #define OREGP0 0x02 // 输出端口寄存器(写操作) #define OREGP1 0x03init_tca9555()的执行流程:
- 若 I2C 子系统尚未初始化,调用 QMK 通用驱动
i2c_init()(引脚即config.h中的 GP0/GP1)并延时I2C_TIMEOUT等待上电稳定; - 写方向寄存器
IODIRA:Port A 全设为输入(读列线),Port B 低 4 位设为输出(驱动行选择)、高 4 位保持输入; - 将
i2c_write_register()的返回值存入全局tca9555_status,作为后续扫描的"右板健康"标志。
i2c_write_register()/i2c_read_register()声明于 drivers/i2c_master.h,是 QMK 提供的 I2C 寄存器读写抽象,屏蔽了底层时序细节。
矩阵扫描:左右并行与故障自愈
matrix_scan_custom()是每次扫描循环的核心,其行为有三个值得关注的点:
1. TCA9555 断连时的自动重试。若上一次 I2C 通信出错(tca9555_status非零),扫描函数会让 8 位计数器tca9555_reset_loop自增,利用其溢出回卷的特性,大约每 255 次扫描(源码注释:略高于每秒一次)重新执行一次init_tca9555()。这意味着右板热插拔或 I2C 总线短暂紊乱后,固件无需重启即可自动恢复,日志会通过dprint输出 "right side attached" / "right side not responding"。
2. 左右行同步选择。每轮循环对MATRIX_ROWS_PER_SIDE(4)对行同时做select_row(i)与select_row(i + 4),再各调用一次store_matrix_row()读回左右两侧状态。左板行选择是本地 GPIO 拉低;右板行选择则是向 TCA9555 的OREGP1写一个"仅目标行位为 0、其余位为 1"的字节。源码注释还指出:由于选行本身要经过一次 I2C 写操作,耗时已远超传统 AVR 方案所需的 30 µs 列稳定延时,因此无需额外延时。
3. 右板列数据的位重排。read_cols()对行号>= MATRIX_ROWS_PER_SIDE的读取流程是:读 TCA9555 的IREGP0→ 按位取反(键按下时列线被拉低)→ 将 Port A 的物理引脚位重排为矩阵列序:
data |= (port0 & 0x01) << 4; data |= (port0 & 0x02) << 2; data |= (port0 & 0x04); data |= (port0 & 0x08) >> 2; data |= (port0 & 0x10) >> 4;注释说明右板列线在 TCA9555 上并非按顺序映射(col 5 对应 GPIO04、col 6 对应 GPIO03 等,从高到低递减),因此需要这段位重排才能与keyboard.json中声明的[row, col]矩阵坐标一致。任一步 I2C 读取失败时该侧行返回 0(视为无键按下),且不再污染全局状态以外的逻辑。
左板路径则完全走 RP2040 原生 GPIO:init_cols()将 5 个列脚设为带内部上拉的输入(gpio_set_pin_input_high+gpio_write_pin_high),read_cols()中按键按下时列脚被行线拉低,pin_state ? 0 : (1 << col_index)将低电平映射为矩阵位 1,即keyboard.json中"diode_direction": "COL2ROW"所描述的"列接输入、行驱动"极性。
默认键位:六层设计与 Tap-Mod
默认键位 定义了 6 个层,充分利用了 36 键的拇指键区:
enum layers { _ALPHA_QWERTY, _ALPHA_COLEMAK, _SYM, _NAV, _NUM, _CFG };_ALPHA_QWERTY/_ALPHA_COLEMAK:两套字母层,拇指区六个键为LCTL_T(KC_ESC)、LT(_NUM, KC_SPC)、LT(_NAV, KC_TAB)、LT(_SYM, KC_BSPC)、KC_ENT、LALT_T(KC_DEL)——同时使用 Tap-Mod(_T,轻敲/修饰二合一)与LT(按下时切层、松开还原)两类技巧;_SYM:符号层,MO(_CFG)直达配置层;_NAV:音量、媒体键与光标方向键(Colemak 层中 Colemak 用户习惯 Enter 在左、Esc 在右,故 Colemak 层拇指区将 Enter/Esc 对调);_NUM:F1–F12 与数字小键盘(KC_P*系列);_CFG:配置层,提供DF(_ALPHA_QWERTY)/DF(_ALPHA_COLEMAK)两个"默认层"按键——按下即持久切换默认字母布局,重刷前也保留在 EEPROM 中。
布局宏LAYOUT_split_3x5_3由 keyboard.json 自动生成:前 5 个参数为左板第一行(矩阵 [0][0..4]),随后 5 个为右板第一行([4][0..4]),中间行同理,最后 6 个参数为底部拇指区(矩阵 [3][2..4] 与 [7][0..2])。该坐标映射保证键位表中的键与物理位置一一对应,社区布局文件(community_layouts: ["split_3x5_3"])亦可复用同一宏。
小结
3W6HS 在 QMK 中的实现要点可以归纳为:RP2040 主控 + RP2040 bootloader(按住左板顶部 BOOT 键上电进入);make beekeeb/3w6hs:default[:flash]完成编译烧录;8×10 逻辑矩阵拆分为左板 4×5 原生 GPIO 扫描 + 右板 4×5 经 GP0/GP1 上的 I2C0 总线驱动 TCA9555 扫描;CUSTOM_MATRIX = lite将扫描权交给 matrix.c 中具备 I2C 故障自愈能力的自定义实现;默认键位则以 Tap-Mod/LT/MO/DF组合覆盖 QWERTY、Colemak、符号、导航、数字与配置六大层。这一实现是 QMK 分体键盘中"单 MCU + I2C 扩展"接线的典型参考案例。
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考