QMK Sweet16 轴体测试器 Keymap 实战:基于 switches 数组与位域宏实现按轴报名
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
本篇文章以 QMK Firmware 仓库中keyboards/1upkeyboards/sweet16/keymaps/switchtester这个官方示例 keymap 为主体,完整拆解它是如何把一块 4×4 的 Sweet16 小键盘垫变成“轴体测试器”的:按下任意按键,固件就把该位置安装的机械轴名称(如Cherry MX Blue、Gateron Red)以文本形式输出到电脑。读完本文,你将掌握 QMK 中process_record_user按键拦截、二维数组按矩阵位置索引、位域结构体定义轴体属性、名称拼接与send_string输出等完整实现链路,并可以直接照搬到自己的键帽/轴体测试项目中。
一、这个 Keymap 是什么:把 Sweet16 变成轴体测试器
Sweet16是由 1up Keyboards 出品的 4×4 正交网格小键盘垫(macropad),其厂商与维护者信息、USB VID 以及LAYOUT_ortho_4x4、LAYOUT_numpad_4x4两种布局定义都可以在 keyboards/1upkeyboards/sweet16/info.json 中查到;键盘整体说明见 keyboards/1upkeyboards/sweet16/readme.md。
switchtester是官方提供的其中一个 keymap,它的用途正如其名:每个按键位对应一枚真实焊接在 PCB 上的机械轴体,按下后固件会把该轴的型号名称直接打字输出,相当于一个即插即用的“轴体标签打印机”,适合用来给批量收到的轴体做分类标记或手测手感。
与同键盘的默认 keymap(keymaps/default/keymap.c,数字小键盘功能)不同,switchtester 的按键矩阵本身不响应任何常规按键功能,而是全部交给自定义逻辑处理。
二、整体架构:三个文件的分工
该 keymap 目录(keyboards/1upkeyboards/sweet16/keymaps/switchtester/)下共有 4 个核心文件,职责非常清晰:
| 文件 | 职责 |
|---|---|
keymap.c | 定义 4×4 空键位、声明switches轴体摆放表、实现process_record_user按键处理 |
switches.h | 定义struct mechswitch位域结构体、品牌/颜色/变体枚举常量,以及全部轴体宏 |
switches.c | 提供品牌/颜色/变体名称映射表与switch_name()名称拼接函数 |
rules.mk | 该 keymap 的构建配置,并通过SRC += switches.c把自定义源文件编入固件 |
这种“数据(宏定义)+ 逻辑(名称映射)+ 键位(keymap)”分层方式,是把这个测试器扩展到任意新轴体时最容易维护的结构。
三、键位与轴体摆放表:用 4×4 数组一一对应矩阵位置
3.1 空键位定义
keymap.c 中的键位映射全部使用KC_NO,表示这些键位不执行任何标准按键行为:
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { LAYOUT_ortho_4x4( KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO ) };MATRIX_ROWS/MATRIX_COLS由键盘定义给出(此处为 4×4,与LAYOUT_ortho_4x4的 16 个矩阵坐标一一对应,见 info.json 中"matrix": [row, col]的定义顺序)。
3.2 轴体摆放表switches
轴体与矩阵位置的对应关系保存在一个与键位矩阵同样形状的二维数组里,行/列索引即按键的行/列:
struct mechswitch switches[MATRIX_ROWS][MATRIX_COLS] = { {CHERRY_MX_BLUE, CHERRY_MX_RED, CHERRY_MX_BLACK, CHERRY_MX_BROWN}, {GATERON_BLUE, GATERON_RED, GATERON_BLACK, GATERON_BROWN}, {KAILH_BLUE, KAILH_RED, KAILH_BLACK, KAILH_BROWN}, {HAKO_CLEAR, HAKO_TRUE, HAKO_VIOLET, HAKO_ROYAL_TRUE} };也就是说:第一行焊接的是 4 颗 Cherry MX(蓝/红/黑/茶),第二行是 4 颗 Gateron,第三行是 4 颗 Kailh,第四行是 4 颗 Hako。要测试别的轴,只需要把对应位置的宏替换掉即可,无需改动任何处理逻辑。
四、按键处理核心:process_record_user 与矩阵坐标读取
处理逻辑同样在 keymap.c 中,它是整个测试器的“心脏”:
bool process_record_user(uint16_t keycode, keyrecord_t *record) { uint8_t col = record->event.key.col; uint8_t row = record->event.key.row; if (record->event.pressed) { char name[MAX_SWITCH_NAME_LENGTH]; switch_name(switches[row][col], name); send_string(name); SEND_STRING("\n"); } return false; }关键点逐条说明:
- 取矩阵坐标:
record->event.key.row与record->event.key.col是本次事件对应的矩阵行列号,switches[row][col]就能直接取到该位置安装的轴体。 - 仅在按下时响应:通过
record->event.pressed判断,只在下压沿输出一次,避免按住时重复触发。 - 名称拼接:
switch_name()把struct mechswitch解析为可读文本,写入name缓冲区。 - 文本输出:
send_string(name)将名称以 HID 键盘文本形式逐字打出,随后SEND_STRING("\n")补一个回车换行,形成“每按一次、输出一行轴名”的效果。 - 返回
false:吞掉本次按键事件,阻止其继续走常规键码处理(键位本身也是KC_NO,双保险确保不会产生意外输入)。
send_string/SEND_STRING是 QMK 提供的最常用的字符串发送宏,适用于这种需要“打字”场景;本 keymap 在rules.mk中开启了EXTRAKEY_ENABLE等特性,send_string属于核心功能、默认可用。
五、数据模型:用位域结构体描述一颗轴
5.1struct mechswitch位域设计
switches.h 用一个紧凑的位域结构体描述轴体三要素——品牌、变体、颜色:
struct mechswitch { unsigned int brand: 4; // 品牌,最多 16 种 unsigned int variant: 4; // 变体,最多 16 种 unsigned int color: 5; // 颜色,最多 32 种 };总共只用 13 bit 就能唯一标识一颗轴,说明这个 keymap 把“描述数据”压缩到了极致;每个字段用宏常量枚举:
- 品牌(brand):
BRAND_KAILH(1)、BRAND_KAILH_LOW(2)、BRAND_GATERON(3)、BRAND_CHERRY_MX(4)、BRAND_CHERRY_ML(5)、BRAND_OUTEMU(6)、BRAND_GREETECH(7)、BRAND_VARMILO(8)、BRAND_MOD(9)、BRAND_HAKO(10)。 - 颜色(color):从
COLOR_NO(0) 到COLOR_SH(30),共 31 个,覆盖White/Black/Blue/Red/Brown/Green/Clear/Silver/Nature White/Grey/Jade/Navy/Burnt Orange/Pale Blue/Dark Yellow/Gold/Chocolate White/Burgundy/Purple/Light Green/True/Berry/Plum/Sage/Violet/L/M/H/SH等实际轴色命名。 - 变体(variant):
VARIANT_NO(0)、VARIANT_BOX、VARIANT_BOX_THICK、VARIANT_BOX_HEAVY、VARIANT_SILENT、VARIANT_TACTILE、VARIANT_LINEAR、VARIANT_SPEED、VARIANT_SPEED_HEAVY、VARIANT_SPEED_CLICK_THICK、VARIANT_PRO、VARIANT_PRO_HEAVY、VARIANT_ROYAL、VARIANT_CLICK_THICK等,用来表达“BOX”“Silent”“Speed”“Pro”“Royal”这类系列名。
5.2 轴体宏:复合常量的定义方式
在枚举之上,switches.h 用“宏 → 结构体初始化器”的方式定义了大量现成轴体,例如:
#define CHERRY_MX_BLUE {BRAND_CHERRY_MX, VARIANT_NO, COLOR_BLUE} #define CHERRY_MX_SILENT_BLACK {BRAND_CHERRY_MX, VARIANT_SILENT, COLOR_BLACK} #define GATERON_SILENT_CLEAR {BRAND_GATERON, VARIANT_SILENT, COLOR_CLEAR} #define KAILH_BOX_WHITE {BRAND_KAILH, VARIANT_BOX, COLOR_WHITE} #define KAILH_BOX_THICK_JADE {BRAND_KAILH, VARIANT_BOX_THICK, COLOR_JADE} #define KAILH_SPEED_GOLD {BRAND_KAILH, VARIANT_SPEED, COLOR_GOLD} #define KAILH_PRO_BURGUNDY {BRAND_KAILH, VARIANT_PRO, COLOR_BURGUNDY} #define HAKO_ROYAL_TRUE {BRAND_HAKO, VARIANT_ROYAL, COLOR_TRUE} #define MOD_L_TACTILE {BRAND_MOD, VARIANT_TACTILE, COLOR_L}这份清单覆盖了该 keymap 内置可用的全部轴型,包括 Cherry MX 全色系与 Silent/Tactile/Linear 变体、Cherry ML、Gateron 常规与 Silent 系列、Greetech、Outemu、Kailh 常规/BOX/BOX Thick/BOX Heavy/Speed/Speed Heavy/Speed Thick Click/Pro/Pro Heavy/Low Profile Choc 系列、Hako 与 Hako Royal、以及 MOD 系列——新增轴体时,只需按同样格式追加一条宏即可,例如:
#define YOUR_SWITCH {BRAND_KAILH, VARIANT_BOX, COLOR_RED}随后把该宏填入switches数组即可。
六、名称渲染:switches.c 的映射表与拼接逻辑
6.1 三张静态名称映射表
switches.c 顶部定义了与枚举一一对应的三张字符串表:
BRAND_NAMES[]:"Kailh"、"Kailh Low Profile Choc"、"Gateron"、"Cherry MX"、"Cherry ML"、"Outemu"、"Greetech"、"Varmilo"、"MOD"、"Hako"。COLOR_NAMES[]:空字符串(对应COLOR_NO)加 30 个颜色名。VARIANT_NAMES[]:空字符串(对应VARIANT_NO)加"BOX"、"BOX Thick"、"BOX Heavy"、"Silent"、"Tactile"、"Linear"、"Speed"、"Speed Heavy"、"Speed Thick Click"、"Pro"、"Pro Heavy"、"Royal"、"Thick Click"、"Heavy"。
三个取值函数直接用枚举值做下标取字符串:
const char *brand_name(struct mechswitch ms) { return BRAND_NAMES[ms.brand - 1]; } const char *variant_name(struct mechswitch ms) { return VARIANT_NAMES[ms.variant]; } const char *color_name(struct mechswitch ms) { return COLOR_NAMES[ms.color]; }注意品牌表下标做了- 1偏移(品牌枚举从 1 开始),而变体与颜色下标直接使用枚举值(从 0 开始)。
6.2switch_name():拼出完整轴名
void switch_name(struct mechswitch ms, char *buf) { const char *v_name = variant_name(ms); const char *c_name = color_name(ms); snprintf(buf, MAX_SWITCH_NAME_LENGTH, "%s", brand_name(ms)); strncat(buf, " ", MAX_SWITCH_NAME_LENGTH - strlen(buf)); if (strlen(v_name) > 0) { strncat(buf, v_name, MAX_SWITCH_NAME_LENGTH - strlen(buf)); strncat(buf, " ", MAX_SWITCH_NAME_LENGTH - strlen(buf)); } if (strlen(c_name) > 0) { strncat(buf, c_name, MAX_SWITCH_NAME_LENGTH - strlen(buf)); } }拼接规则为:品牌 [变体] [颜色],变体/颜色为空字符串时自动跳过并省略多余空格,因此:
CHERRY_MX_BLUE→Cherry MX BlueKAILH_BOX_THICK_NAVY→Kailh BOX Thick NavyGATERON_SILENT_RED→Gateron Silent RedHAKO_ROYAL_TRUE→Hako Royal True
缓冲区长度由switches.h中的MAX_SWITCH_NAME_LENGTH(256)限定,所有strncat都带剩余长度上限,避免溢出。同文件还附带了一个辅助函数bitfieldtoi(),把三个位域拼成一个整数(brand << 9 | variant << 5 | color),可以推断这是为调试/序列化场景预留的工具函数。
七、构建配置:rules.mk 怎么把自定义代码编进去
keymaps/switchtester/rules.mk 中的关键一行是:
SRC += switches.c它把 keymap 目录下的switches.c加入本次构建的源文件列表,从而让keymap.c中调用的switch_name()等函数能被链接进固件。其余选项依次为:
BOOTMAGIC_ENABLE = no MOUSEKEY_ENABLE = no EXTRAKEY_ENABLE = yes # 媒体键与系统控制 CONSOLE_ENABLE = yes # 调试用控制台输出 COMMAND_ENABLE = yes # 调试与配置命令 NKRO_ENABLE = no RGBLIGHT_ENABLE = no其中EXTRAKEY_ENABLE提供媒体/系统键支持,CONSOLE_ENABLE与COMMAND_ENABLE为调试打开通道,RGBLIGHT_ENABLE在该 keymap 中被关闭(测试器不需要灯效)。
八、编译、烧录与使用
在 QMK 环境中按标准工作流编译烧录该 keymap:
qmk compile -kb 1upkeyboards/sweet16 -km switchtester qmk flash -kb 1upkeyboards/sweet16 -km switchtester(-kb指定键盘,-km指定 keymap 名称;也可用传统方式make 1upkeyboards/sweet16:switchtester。)
使用流程:
- 将待测轴体按
switches数组的摆放焊入/装入 Sweet16 的对应键位; - 打开任意文本编辑器或终端;
- 按下某个键,电脑即收到该键位轴体的完整名称文本并自动回车换行;
- 逐个按压即可快速得到一份“哪个键位装了什么轴”的输出清单,用于核对或记录。
九、如何扩展成你自己的轴体测试器
把这个示例改造成自用测试器只需三步:
- 加宏:在 switches.h 中按
#define 轴名 {BRAND_*, VARIANT_*, COLOR_*}格式新增你手里的轴(若品牌/颜色不在现有枚举里,先在枚举区追加常量,再在 switches.c 的BRAND_NAMES/COLOR_NAMES表里补对应字符串——下标必须与枚举顺序严格一致); - 填数组:把新宏按矩阵行列填进
keymap.c的switches[MATRIX_ROWS][MATRIX_COLS],与物理焊接位置一一对应; - 编译烧录:重新
qmk compile/qmk flash即可。
同理,该模式也适用于其他任意正交网格 macropad(把LAYOUT_ortho_4x4换成自己的布局、矩阵尺寸换成自己的行列数),是 QMK 中“自定义数据 + 矩阵坐标索引 + 文本输出”这一类实用 keymap 的典型范本。
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考