QMK键盘固件自定义指南 - 打造你的专属键盘体验

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
};

通过这段代码,你可以在键盘映射中使用FOOBAR两个自定义键码。

编程键码行为

要控制键码的行为,你需要使用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
  }
}

⚙️ 键盘初始化流程

键盘初始化过程分为三个主要阶段:

  1. 键盘预初始化keyboard_pre_init_* - 在大多数功能初始化前执行,适合进行早期硬件设置
  2. 矩阵初始化matrix_init_* - 在固件启动过程中期执行
  3. 键盘后初始化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文件包含两个主要部分:

  1. 定义(包括自定义键码、图层名称等)
  2. 图层/键盘映射数据结构

定义部分示例:

#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_AKC_ENTER
  • 修饰键:如KC_LSHIFTKC_RALT
  • 图层切换:如MO()TG()TO()
  • 一键多用(Mod-Tap):如LCTL_T(KC_ESC)
  • 宏键:用户自定义的复杂功能

📝 总结

QMK提供了极其强大且灵活的键盘自定义能力,从简单的键位重映射到复杂的宏和功能。通过本文介绍的技术,你可以:

  1. 创建自定义键码并定义其行为
  2. 设置多层键盘映射实现不同功能
  3. 利用键盘初始化生命周期自定义键盘启动行为
  4. 使用矩阵扫描和内务管理函数添加特殊功能
  5. 实现节能休眠和唤醒功能
  6. 利用延迟执行实现定时任务

无论你是想要一个简单的QWERTY布局还是构建一个复杂的编程工作站,QMK都能满足你的需求。开始你的键盘自定义之旅吧!

🔗 相关资源

如果你对如何改进本文有任何建议,欢迎提交Issue!我们正在积极努力改进这些文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Despacito0o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值