QMK键盘固件自定义指南 - 打造你的专属键盘体验
🚀 前言
在机械键盘的世界里,QMK固件让你的键盘不再只是简单的输入设备,而是可以按照你的意愿定制的强大工具。本文将深入浅出地介绍如何自定义QMK键盘的行为,从基础概念到高级应用,带你玩转QMK!
📚 QMK的分层架构
QMK采用分层架构设计,从上到下依次为:
- 核心层(_quantum):提供基础功能
- 社区模块层(_<module>):提供扩展功能
- 社区模块 -> 键盘/修订版(_<module>_kb)
- 社区模块 -> 键盘映射(_<module>_user)
- 键盘/修订版层(_kb):特定键盘的功能
- 键盘映射层(_user):用户自定义配置
💡 小贴士:在键盘/修订版级别定义函数时,必须在适当位置调用
_user()
,否则键盘映射级别的函数将永远不会被执行。
🔑 自定义键码
定义新键码
创建自定义键码的第一步是枚举它们。QMK提供了SAFE_RANGE
宏来确保你的自定义键码获得唯一的编号。
enum my_keycodes {
FOO = SAFE_RANGE,
BAR
};
通过这段代码,你可以在键盘映射中使用FOO
和BAR
两个自定义键码。
编程键码行为
要控制键码的行为,你需要使用process_record_kb()
和process_record_user()
函数。这些函数在按键处理过程中被调用:
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case FOO:
if (record->event.pressed) {
// 按下时执行的代码
} else {
// 释放时执行的代码
}
return false; // 跳过此键的后续处理
case KC_ENTER:
// 按下回车键时播放音效
if (record->event.pressed) {
PLAY_SONG(tone_qwerty);
}
return true; // 让QMK继续处理回车键的按下/释放事件
default:
return true; // 正常处理所有其他键码
}
}
record
参数包含按键事件的详细信息:
keyrecord_t record {
keyevent_t event {
keypos_t key {
uint8_t col
uint8_t row
}
bool pressed
uint16_t time
}
}
⚙️ 键盘初始化流程
键盘初始化过程分为三个主要阶段:
- 键盘预初始化:
keyboard_pre_init_*
- 在大多数功能初始化前执行,适合进行早期硬件设置 - 矩阵初始化:
matrix_init_*
- 在固件启动过程中期执行 - 键盘后初始化:
keyboard_post_init_*
- 在固件启动过程结束时执行,适合放置"自定义"代码
⚠️ 注意:对于大多数用户,
keyboard_post_init_user
是你想要实现的函数。例如,这是设置RGB底光的理想位置。
键盘预初始化示例
void keyboard_pre_init_user(void) {
// 调用键盘预初始化代码
// 设置LED引脚为输出模式
gpio_set_pin_output(B0);
gpio_set_pin_output(B1);
gpio_set_pin_output(B2);
gpio_set_pin_output(B3);
gpio_set_pin_output(B4);
}
键盘后初始化示例
void keyboard_post_init_user(void) {
// 调用后初始化代码
rgblight_enable_noeeprom(); // 启用RGB灯光但不保存设置
rgblight_sethsv_noeeprom(180, 255, 255); // 设置青色但不保存
rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING + 3); // 设置快速呼吸模式但不保存
}
🔄 矩阵扫描与内务管理
矩阵扫描
矩阵扫描函数在每次按键矩阵扫描时被调用,频率非常高。需要谨慎编写这部分代码以避免影响键盘性能。
void matrix_scan_user(void) {
// 这里的代码将以MCU能处理的最高频率运行,请谨慎添加代码
}
键盘内务管理
内务管理函数在所有QMK处理结束后、开始下一次迭代前调用:
void housekeeping_task_user(void) {
// 此时所有按键处理、层状态更新、USB报告发送、LED更新等已完成
}
下面是一个使用housekeeping_task_user
实现RGB灯光超时关闭的示例:
#define RGBLIGHT_SLEEP // 在keymap.c中启用rgblight_suspend()和rgblight_wakeup()
#define RGBLIGHT_TIMEOUT 900000 // RGB超时时间,900K毫秒即15分钟
static uint32_t key_timer; // 最后键盘活动的计时器
static void refresh_rgb(void); // 刷新活动计时器和RGB
static void check_rgb_timeout(void); // 检查RGB是否超时
bool is_rgb_timeout = false; // 存储RGB是否已超时
void refresh_rgb(void) {
key_timer = timer_read32(); // 存储最后刷新时间
if (is_rgb_timeout)
{
is_rgb_timeout = false;
rgblight_wakeup();
}
}
void check_rgb_timeout(void) {
if (!is_rgb_timeout && timer_elapsed32(key_timer) > RGBLIGHT_TIMEOUT)
{
rgblight_suspend();
is_rgb_timeout = true;
}
}
/* 在QMK内置的后处理函数中调用上述函数 */
void housekeeping_task_user(void) {
#ifdef RGBLIGHT_TIMEOUT
check_rgb_timeout();
#endif
}
/* 每次按键后检查是否有活动 */
void post_process_record_user(uint16_t keycode, keyrecord_t *record) {
#ifdef RGBLIGHT_TIMEOUT
if (record->event.pressed)
refresh_rgb();
#endif
}
/* 每次旋钮更新后检查是否有活动 */
void post_encoder_update_user(uint8_t index, bool clockwise) {
#ifdef RGBLIGHT_TIMEOUT
refresh_rgb();
#endif
}
💤 键盘休眠与唤醒
如果你的键盘支持,可以通过以下函数控制键盘休眠状态:
void suspend_power_down_user(void) {
// 键盘休眠时运行的代码,会被多次调用
}
void suspend_wakeup_init_user(void) {
// 键盘唤醒时运行的代码
}
🔌 键盘关机与重启
当固件重置时(软重置或跳转到引导加载程序),会调用shutdown_*
函数:
bool shutdown_kb(bool jump_to_bootloader) {
if (!shutdown_user(jump_to_bootloader)) {
return false;
}
if (jump_to_bootloader) {
// 准备跳转到引导加载程序,设置红色
rgb_matrix_set_color_all(RGB_RED);
} else {
// 软重置,关闭LED
rgb_matrix_set_color_all(RGB_OFF);
}
// 强制刷新缓冲区
rgb_matrix_update_pwm_buffers();
return true;
}
⏱️ 延迟执行
QMK提供了延迟执行功能,可以在指定时间后执行回调。要启用此功能,在rules.mk
中添加:
DEFERRED_EXEC_ENABLE = yes
延迟执行回调函数示例:
uint32_t my_callback(uint32_t trigger_time, void *cb_arg) {
/* 执行一些操作 */
bool repeat = my_deferred_functionality();
return repeat ? 500 : 0; // 如果需要重复,返回500毫秒延迟,否则返回0
}
注册延迟执行:
deferred_token my_token = defer_exec(1500, my_callback, NULL);
📊 Keymap概述
键盘映射和图层
QMK中的键盘映射保存在const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS]
数组中,最多可以定义32个图层(0-31),较高的图层具有优先权。
Keymap: 32 Layers Layer: action code matrix
----------------- ---------------------
stack of layers array_of_action_code[row][column]
____________ precedence _______________________
/ / | high / ESC / F1 / F2 / F3 ....
31 /___________// | /-----/-----/-----/-----
30 /___________// | / TAB / Q / W / E ....
29 /___________/ | /-----/-----/-----/-----
: _:_:_:_:_:__ | : /LCtrl/ A / S / D ....
: / : : : : : / | : / : : : :
2 /___________// | 2 `--------------------------
1 /___________// | 1 `--------------------------
0 /___________/ V low 0 `--------------------------
键盘映射层状态由两个32位参数决定:
default_layer_state
- 表示基本键盘映射层(0-31)layer_state
- 在其位中保存每个图层的开/关状态
图层优先级和透明度
较高层在图层堆栈中具有更高优先级。固件从最高活动层向下工作以查找键码。一旦找到非透明键码,就停止搜索。
如果在高层使用透明键码(KC_TRNS
、_______
或KC_TRANSPARENT
),则会使用下层对应位置的键码。
Keymap.c文件解析
一个典型的keymap.c
文件包含两个主要部分:
- 定义(包括自定义键码、图层名称等)
- 图层/键盘映射数据结构
定义部分示例:
#include QMK_KEYBOARD_H
// 有用的定义
#define GRAVE_MODS (MOD_BIT(KC_LSFT)|MOD_BIT(KC_RSFT)|MOD_BIT(KC_LGUI)|MOD_BIT(KC_RGUI)|MOD_BIT(KC_LALT)|MOD_BIT(KC_RALT))
// 每个图层都有一个名称以提高可读性
enum layer_names {
_BL, // 基础层
_FL, // 功能层
_CL, // 控制层
};
图层定义示例(基础层):
[_BL] = LAYOUT(
F(0), KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_GRV, KC_BSPC, KC_PGUP,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN,
KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NUHS, KC_ENT,
KC_LSFT, KC_NUBS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_INT1, KC_RSFT, KC_UP,
KC_LCTL, KC_LGUI, KC_LALT, KC_INT5, KC_SPC,KC_SPC, KC_INT4, KC_RALT, KC_RCTL, MO(_FL), KC_LEFT, KC_DOWN, KC_RGHT
),
功能层示例:
[_FL] = LAYOUT(
KC_GRV, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, KC_DEL, BL_STEP,
_______, _______, _______,_______,_______,_______,_______,_______,KC_PSCR,KC_SCRL, KC_PAUS, _______, _______, _______, _______,
_______, _______, MO(_CL),_______,_______,_______,_______,_______,_______,_______, _______, _______, _______, _______,
_______, _______, _______,_______,_______,_______,_______,_______,_______,_______, _______, _______, _______, _______, KC_PGUP,
_______, _______, _______, _______, _______,_______, _______, _______, _______, MO(_FL), KC_HOME, KC_PGDN, KC_END
),
🔍 拓展知识
1. 社区模块
社区模块是QMK的扩展功能,允许第三方实现代码供他人导入。要添加社区模块到你的构建中,在keymap.json
中添加:
{
"modules": [
"qmk/hello_world"
]
}
2. EEPROM持久配置
QMK可以使用EEPROM存储长期保持的配置,如默认图层、RGB灯效设置等。这使得设置可以在断电后保持。
3. Bootloader驱动安装
在Windows上刷写键盘固件时,有时需要为bootloader安装特殊驱动程序。推荐使用Zadig工具安装正确的驱动。
4. QMK键码类型
QMK支持多种类型的键码:
- 基本键码:如
KC_A
、KC_ENTER
等 - 修饰键:如
KC_LSHIFT
、KC_RALT
等 - 图层切换:如
MO()
、TG()
、TO()
等 - 一键多用(Mod-Tap):如
LCTL_T(KC_ESC)
等 - 宏键:用户自定义的复杂功能
📝 总结
QMK提供了极其强大且灵活的键盘自定义能力,从简单的键位重映射到复杂的宏和功能。通过本文介绍的技术,你可以:
- 创建自定义键码并定义其行为
- 设置多层键盘映射实现不同功能
- 利用键盘初始化生命周期自定义键盘启动行为
- 使用矩阵扫描和内务管理函数添加特殊功能
- 实现节能休眠和唤醒功能
- 利用延迟执行实现定时任务
无论你是想要一个简单的QWERTY布局还是构建一个复杂的编程工作站,QMK都能满足你的需求。开始你的键盘自定义之旅吧!
🔗 相关资源
如果你对如何改进本文有任何建议,欢迎提交Issue!我们正在积极努力改进这些文档。