aboutsummaryrefslogtreecommitdiff
path: root/docs/zh-cn/custom_quantum_functions.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/zh-cn/custom_quantum_functions.md')
-rw-r--r--docs/zh-cn/custom_quantum_functions.md490
1 files changed, 490 insertions, 0 deletions
diff --git a/docs/zh-cn/custom_quantum_functions.md b/docs/zh-cn/custom_quantum_functions.md
new file mode 100644
index 000000000..42ceba9ca
--- /dev/null
+++ b/docs/zh-cn/custom_quantum_functions.md
@@ -0,0 +1,490 @@
1# 如何定制你键盘的功能
2
3对于很多人来说客制化键盘可不只是向你的电脑发送你按了那个件这么简单。你肯定想实现比简单按键和宏更复杂的功能。QMK有能让你注入代码的钩子, 覆盖功能, 另外,还可以自定义键盘在不同情况下的行为。
4
5本页不假定任何特殊的QMK知识,但阅读[理解QMK](understanding_qmk.md)将会在更基础的层面帮你理解发生了什么。
6
7## A Word on Core vs 键盘 vs 布局
8
9我们把qmk组织成一个层次结构:
10
11* Core (`_quantum`)
12 * Keyboard/Revision (`_kb`)
13 * Keymap (`_user`)
14
15下面描述的每一个函数都可以在定义上加一个`_kb()`或 `_user()` 后缀。 建议在键盘/修订层使用`_kb()`后缀,在布局层使用`_user()`后缀。
16
17在键盘/修订层定义函数时,`_kb()`在执行任何代码前先调用`_user()`是必要的,不然布局层函数就不要被调用。
18<!-- 翻译问题:上面那句翻译的不太好-->
19# 自定义键码
20
21到目前为止,最常见的任务是更改现有键码的行为或创建新的键码。从代码角度来看这些操作都很相似。
22
23## 定义一个新键码
24
25创建键码第一步,先枚举出它全部,也就是给键码起个名字并分配唯一数值。QMK没有直接限制最大键码值大小,而是提供了一个`SAFE_RANGE`宏。你可以在枚举时用`SAFE_RANGE`来保证你取得了唯一的键码值。
26
27
28这有枚举两个键码的例子。把这块加到`keymap.c`的话你就在布局中能用`FOO`和`BAR`了。
29
30```c
31enum my_keycodes {
32 FOO = SAFE_RANGE,
33 BAR
34};
35```
36
37## 为键码的行为编程
38
39当你覆盖一个已存在按键的行为时,或将这个行为赋给新键时,你要用`process_record_kb()`和`process_record_user()`函数。这俩函数在键处理中真实键事件被处理前被QMK调用。如果这俩函数返回`true`,QMK将会用正常的方式处理键码。这样可以很方便的扩展键码的功能而不是替换它。如果函数返回`false` QMK会跳过正常键处理,然后发送键子抬起还是按下事件就由你决定了。
40
41当某个键按下或释放时这俩函数会被调用。
42
43### process_record_user()`函数示例实现
44
45这个例子做了两个事。自定义了一个叫做`FOO`的键码的行为,并补充了在按下回车时播放音符。
46
47```c
48bool process_record_user(uint16_t keycode, keyrecord_t *record) {
49 switch (keycode) {
50 case FOO:
51 if (record->event.pressed) {
52 // 按下时做些什么
53 } else {
54 // 释放时做些什么
55 }
56 return false; // 跳过此键的所有进一步处理
57 case KC_ENTER:
58 // 当按下回车时播放音符
59 if (record->event.pressed) {
60 PLAY_NOTE_ARRAY(tone_qwerty);
61 }
62 return true; // 让QMK触发回车按下/释放事件
63 default:
64 return true; // 正常处理其他键码
65 }
66}
67```
68
69### `process_record_*` 函数文档
70
71* 键盘/修订: `bool process_record_kb(uint16_t keycode, keyrecord_t *record)`
72* 布局: `bool process_record_user(uint16_t keycode, keyrecord_t *record)`
73
74`keycode(键码)`参数是在布局上定义的,比如`MO(1)`, `KC_L`, 等等。 你要用 `switch...case` 块来处理这些事件。
75
76`record`参数含有实际按键的信息:
77
78```c
79keyrecord_t record {
80 keyevent_t event {
81 keypos_t key {
82 uint8_t col
83 uint8_t row
84 }
85 bool pressed
86 uint16_t time
87 }
88}
89```
90
91# LED控制
92
93qmk提供了读取HID规范包含的5个LED的方法。:
94
95* `USB_LED_NUM_LOCK`
96* `USB_LED_CAPS_LOCK`
97* `USB_LED_SCROLL_LOCK`
98* `USB_LED_COMPOSE`
99* `USB_LED_KANA`
100
101这五个常量对应于主机LED状态的位置位。
102有两种方法可以获得主机LED状态:
103
104* 通过执行 `led_set_user()`
105* 通过调用 `host_keyboard_leds()`
106
107## `led_set_user()`
108
109当5个LED中任何一个的状态需要改变时,此函数将被调用。此函数通过参数输入LED参数。
110使用`IS_LED_ON(usb_led, led_name)`和`IS_LED_OFF(usb_led, led_name)`这两个宏来检查LED状态。
111
112!> `host_keyboard_leds()`可能会在`led_set_user()`被调用前返回新值。
113
114### `led_set_user()`函数示例实现
115
116```c
117void led_set_user(uint8_t usb_led) {
118 if (IS_LED_ON(usb_led, USB_LED_NUM_LOCK)) {
119 writePinLow(B0);
120 } else {
121 writePinHigh(B0);
122 }
123 if (IS_LED_ON(usb_led, USB_LED_CAPS_LOCK)) {
124 writePinLow(B1);
125 } else {
126 writePinHigh(B1);
127 }
128 if (IS_LED_ON(usb_led, USB_LED_SCROLL_LOCK)) {
129 writePinLow(B2);
130 } else {
131 writePinHigh(B2);
132 }
133 if (IS_LED_ON(usb_led, USB_LED_COMPOSE)) {
134 writePinLow(B3);
135 } else {
136 writePinHigh(B3);
137 }
138 if (IS_LED_ON(usb_led, USB_LED_KANA)) {
139 writePinLow(B4);
140 } else {
141 writePinHigh(B4);
142 }
143}
144```
145
146### `led_set_*`函数文档
147
148* 键盘/修订: `void led_set_kb(uint8_t usb_led)`
149* 布局: `void led_set_user(uint8_t usb_led)`
150
151## `host_keyboard_leds()`
152
153调用这个函数会返回最后收到的LED状态。这个函数在`led_set_*`之外读取LED状态时很有用,比如在[`matrix_scan_user()`](#矩阵扫描代码).
154为了便捷,你可以用`IS_HOST_LED_ON(led_name)`和`IS_HOST_LED_OFF(led_name)` 宏,而不直接调用和检查`host_keyboard_leds()`。
155
156## 设置物理LED状态
157
158一些键盘实现了为设置物理LED的状态提供了方便的方法。
159
160### Ergodox Boards
161
162Ergodox实现了提供`ergodox_right_led_1`/`2`/`3_on`/`off()`来让每个LED开或关, 也可以用 `ergodox_right_led_on`/`off(uint8_t led)` 按索引打开或关闭他们。
163
164此外,还可以使用`ergodox_led_all_set(uint8_t n)`指定所有LED的亮度级别;针对每个LED用`ergodox_right_led_1`/`2`/`3_set(uint8_t n)`;使用索引的话用`ergodox_right_led_set(uint8_t led, uint8_t n)`。
165
166Ergodox boards 同时定义了最低亮度级别`LED_BRIGHTNESS_LO`和最高亮度级别`LED_BRIGHTNESS_HI`(默认最高).
167
168# 键盘初始化代码
169
170键盘初始化过程有几个步骤。你是用那个函数取决于你想要做什么。
171
172有三个主要初始化函数,按调用顺序列出。
173
174* `keyboard_pre_init_*` - 会在大多数其他东西运行前运行。适用于哪些需要提前运行的硬件初始化。
175* `matrix_init_*` - 在固件启动过程中间被调用。此时硬件已初始化,功能尚未初始化。
176* `keyboard_post_init_*` - 在固件启动过程最后被调用。大多数情况下,你的“客制化”代码都可以放在这里。
177
178!> 对于大多数人来说`keyboard_post_init_user`是你想要调用的函数。例如, 此时你可以设置RGB灯发光。
179
180## 键盘预初始化代码
181
182这代码极早运行,甚至都在USB初始化前运行。
183
184在这之后不久矩阵就被初始化了。
185
186对于大多数用户来说,这用不到,因为它主要是用于面向硬件的初始化。
187
188但如果你有硬件初始化的话放在这里再好不过了(比如初始化LED引脚一类的).
189
190### `keyboard_pre_init_user()`函数示例实现
191
192本例中在键盘级别,设定 B0, B1, B2, B3, 和 B4 是LED引脚。
193
194```c
195void keyboard_pre_init_user(void) {
196 // 调用键盘预初始化代码
197
198 // 设置LED引脚为输出模式
199 setPinOutput(B0);
200 setPinOutput(B1);
201 setPinOutput(B2);
202 setPinOutput(B3);
203 setPinOutput(B4);
204}
205```
206
207### `keyboard_pre_init_*` 函数文档
208
209* 键盘/修订: `void keyboard_pre_init_kb(void)`
210* 布局: `void keyboard_pre_init_user(void)`
211
212## 矩阵初始化代码
213
214这将会在矩阵初始化时被调用,在某些硬件设置好后,但在一些功能被初始化前。
215
216这在你设置其他地方会用到的东西的时候会很有用,但与硬件无关,也不依赖于它的启动位置。
217
218
219### `matrix_init_*`函数文档
220
221* 键盘/修订: `void matrix_init_kb(void)`
222* 布局: `void matrix_init_user(void)`
223
224
225## 键盘后初始化代码
226
227这是键盘初始化过程中的最后一个任务。如果您想更改某些特性,这会很有用,因为此时应该对它们进行初始化。
228
229
230### `keyboard_post_init_user()`示例实现
231
232本示例在所有初始化完成后运行,配置RGB灯。
233
234```c
235void keyboard_post_init_user(void) {
236 // 调用后初始化代码
237 rgblight_enable_noeeprom(); // 使能Rgb,不保存设置
238 rgblight_sethsv_noeeprom(180, 255, 255); // 将颜色设置到蓝绿色(青色)不保存
239 rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING + 3); // 设置快速呼吸模式不保存
240}
241```
242
243### `keyboard_post_init_*` 函数文档
244
245* 键盘/修订: `void keyboard_post_init_kb(void)`
246* 布局: `void keyboard_post_init_user(void)`
247
248# 矩阵扫描代码
249
250可能的话你要用`process_record_*()`自定义键盘,以这种方式连接到事件中,以确保代码不会对键盘产生负面的性能影响。然而,在极少数情况下,有必要进行矩阵扫描。在这些函数中要特别注意代码的性能,因为它每秒至少被调用10次。
251
252### `matrix_scan_*`示例实现
253
254这个例子被故意省略了。在hook这样一个对性能及其敏感的区域之前,您应该足够了解qmk的内部结构,以便在没有示例的情况下编写。如果你需要帮助,请[建立一个issue](https://github.com/qmk/qmk_firmware/issues/new)或[在Discord上与我们交流](https://discord.gg/Uq7gcHh).
255
256### `matrix_scan_*` 函数文档
257
258* 键盘/修订: `void matrix_scan_kb(void)`
259* 布局: `void matrix_scan_user(void)`
260
261该函数在每次矩阵扫描时被调用,这基本与MCU处理能力上限相同。在这里写代码要谨慎,因为它会运行很多次。
262
263你会在自定义矩阵扫描代码时用到这个函数。这也可以用作自定义状态输出(比如LED灯或者屏幕)或者其他即便用户不输入你也想定期运行的功能。
264
265
266# 键盘 空闲/唤醒 代码
267
268如果键盘支持就可以通过停止一大票功能来达到"空闲"。RGB灯和背光就是很好的例子。这可以节约能耗,也可能让你键盘风味更佳。
269
270用两个函数控制: `suspend_power_down_*`和`suspend_wakeup_init_*`, 分别在系统板空闲和唤醒时调用。
271
272
273### suspend_power_down_user()和suspend_wakeup_init_user()示例实现
274
275
276```c
277void suspend_power_down_user(void) {
278 rgb_matrix_set_suspend_state(true);
279}
280
281void suspend_wakeup_init_user(void) {
282 rgb_matrix_set_suspend_state(false);
283}
284```
285
286### 键盘 挂起/唤醒 函数文档
287
288* 键盘/修订: `void suspend_power_down_kb(void)` 和`void suspend_wakeup_init_user(void)`
289* 布局: `void suspend_power_down_kb(void)` 和 `void suspend_wakeup_init_user(void)`
290
291# 层改变代码
292
293每当层改变这个就运行代码。这对于层指示或自定义层处理很有用。
294
295### `layer_state_set_*` 示例实现
296
297本例使用了Planck键盘示范了如何设置 [RGB背光灯](feature_rgblight.md)使之与层对应
298
299```c
300uint32_t layer_state_set_user(uint32_t state) {
301 switch (biton32(state)) {
302 case _RAISE:
303 rgblight_setrgb (0x00, 0x00, 0xFF);
304 break;
305 case _LOWER:
306 rgblight_setrgb (0xFF, 0x00, 0x00);
307 break;
308 case _PLOVER:
309 rgblight_setrgb (0x00, 0xFF, 0x00);
310 break;
311 case _ADJUST:
312 rgblight_setrgb (0x7A, 0x00, 0xFF);
313 break;
314 default: // for any other layers, or the default layer
315 rgblight_setrgb (0x00, 0xFF, 0xFF);
316 break;
317 }
318 return state;
319}
320```
321### `layer_state_set_*` 函数文档
322
323* 键盘/修订: `uint32_t layer_state_set_kb(uint32_t state)`
324* 布局: `uint32_t layer_state_set_user(uint32_t state)`
325
326
327该`状态`是活动层的bitmask, 详见[布局概述](keymap.md#布局的层状态)
328
329
330# 掉电保存配置 (EEPROM)
331
332这会让你的配置长期的保存在键盘中。这些配置保存在你主控的EEPROM里,掉电不会消失。 设置可以用`eeconfig_read_kb`和`eeconfig_read_user`读取,可以用`eeconfig_update_kb`和`eeconfig_update_user`写入。这对于您希望能够切换的功能很有用(比如切换RGB层指示。此外,你可以用`eeconfig_init_kb`和`eeconfig_init_user`来设置EEPROM默认值。
333
334最复杂的部分可能是,有很多方法可以通过EEPROM存储和访问数据,并且并没有用哪种方法是“政治正确”的。你每个功能只有一个双字(四字节)空间。
335
336记住EEPROM是有写入寿命的。尽管写入寿命很高,但是并不是只有设置写道EEPROM中。如果你写入频繁,你的MCU寿命将会变短。
337
338* 如果您不理解这个例子,那么您可能希望避免使用这个特性,因为它相当复杂。
339
340### 示例实现
341
342本例讲解了如何添加设置,并且读写。本里使用了用户布局。这是一个复杂的函数,有很多事情要做。实际上,它使用了很多上述函数来工作!
343
344
345在你的keymap.c文件中,将以下代码添加至顶部:
346```c
347typedef union {
348 uint32_t raw;
349 struct {
350 bool rgb_layer_change :1;
351 };
352} user_config_t;
353
354user_config_t user_config;
355```
356
357以上代码建立了一个结构体,该结构体可以存储设置并可用于写入EEPROM。如此这般将无需定义变量,因为在结构体中已然定义。要记住`bool` (布尔)值使用1位, `uint8_t`使用8位, `uint16_t`使用16位。你可以混合搭配使用,但是顺序记错可能会招致麻烦,因为那会改变写入写出的值。
358
359 `layer_state_set_*`函数中使用了`rgb_layer_change`,使用了`keyboard_post_init_user`和`process_record_user`来配置一切。
360
361首先要使用`keyboard_post_init_user,你要加入`eeconfig_read_user()`来填充你刚刚创建的结构体。然后您可以立即使用这个结构来控制您的布局中的功能。就像这样:
362```c
363void keyboard_post_init_user(void) {
364 // 调用布局级别的矩阵初始化
365
366 // 从EEPROM读用户配置
367 user_config.raw = eeconfig_read_user();
368
369 // 如使能,设置默认层
370 if (user_config.rgb_layer_change) {
371 rgblight_enable_noeeprom();
372 rgblight_sethsv_noeeprom_cyan();
373 rgblight_mode_noeeprom(1);
374 }
375}
376```
377以上函数会在读EEPROM配置后立即使用该设置来设置默认层RGB颜色。"raw"的值是从你上面基于"union"创建的结构体中转换来的。
378
379```c
380uint32_t layer_state_set_user(uint32_t state) {
381 switch (biton32(state)) {
382 case _RAISE:
383 if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_magenta(); rgblight_mode_noeeprom(1); }
384 break;
385 case _LOWER:
386 if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_red(); rgblight_mode_noeeprom(1); }
387 break;
388 case _PLOVER:
389 if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_green(); rgblight_mode_noeeprom(1); }
390 break;
391 case _ADJUST:
392 if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_white(); rgblight_mode_noeeprom(1); }
393 break;
394 default: // 针对其他层或默认层
395 if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_cyan(); rgblight_mode_noeeprom(1); }
396 break;
397 }
398 return state;
399}
400```
401这样仅在值使能时会改变RGB背光灯。现在配置这个值, 为`process_record_user`创建一个新键码叫做`RGB_LYR`。我们要确保,如果使用正常的RGB代码,使用上面的示例将其关闭,请将其设置为:
402```c
403
404bool process_record_user(uint16_t keycode, keyrecord_t *record) {
405 switch (keycode) {
406 case FOO:
407 if (record->event.pressed) {
408 // 按下时做点什么
409 } else {
410 // 释放时做点什么
411 }
412 return false; // 跳过此键的进一步处理
413 case KC_ENTER:
414 // 在按下回车时播放音符
415 if (record->event.pressed) {
416 PLAY_NOTE_ARRAY(tone_qwerty);
417 }
418 return true; // 让QMK产生回车按下/释放事件
419 case RGB_LYR: // 本句让underglow作为层指示,或正常使用。
420 if (record->event.pressed) {
421 user_config.rgb_layer_change ^= 1; // 切换状态
422 eeconfig_update_user(user_config.raw); // 向EEPROM写入新状态
423 if (user_config.rgb_layer_change) { // 如果层状态被使能
424 layer_state_set(layer_state); // 那么立刻更新层颜色
425 }
426 }
427 return false; break;
428 case RGB_MODE_FORWARD ... RGB_MODE_GRADIENT: // 对于所有的RGB代码 (see quantum_keycodes.h, L400 可以参考)
429 if (record->event.pressed) { //本句失能层指示,假设你改变了这个…你要把它禁用
430 if (user_config.rgb_layer_change) { // 仅当使能时
431 user_config.rgb_layer_change = false; // 失能,然后
432 eeconfig_update_user(user_config.raw); // 向EEPROM写入设置
433 }
434 }
435 return true; break;
436 default:
437 return true; // 按其他键正常
438 }
439}
440```
441最后你要加入`eeconfig_init_user`函数,所以当EEPROM重置时,可以指定默认值, 甚至自定义操作。想强制重置EEPROM,请用`EEP_RST`键码或[Bootmagic](feature_bootmagic.md)函数。比如,如果要在默认情况下设置RGB层指示,并保存默认值
442
443```c
444void eeconfig_init_user(void) { // EEPROM正被重置
445 user_config.raw = 0;
446 user_config.rgb_layer_change = true; // 我们想要默认使能
447 eeconfig_update_user(user_config.raw); // 向EEPROM写入默认值
448
449 // use the non noeeprom versions, 还要向EEPROM写入这些值
450 rgblight_enable(); // 默认使能RGB
451 rgblight_sethsv_cyan(); // 默认设置青色
452 rgblight_mode(1); // 默认设置长亮
453}
454```
455
456然后就完事了。RGB层指示会在你想让它工作时工作。这个设置会一直保存,即便你拔下键盘。如果你使用其他RGB代码,层指示将失能,现在它可以做你所想了。
457
458### 'EECONFIG' 函数文档
459
460* 键盘/修订: `void eeconfig_init_kb(void)`, `uint32_t eeconfig_read_kb(void)`和`void eeconfig_update_kb(uint32_t val)`
461* 布局: `void eeconfig_init_user(void)`, `uint32_t eeconfig_read_user(void)`和`void eeconfig_update_user(uint32_t val)`
462
463`val` 是你想写入EEPROM的值,`eeconfig_read_*`函数会从EEPROM返回一个32位(双字)的值。
464
465# 自定义击键-长按临界值(TAPPING_TERM)
466默认情况下,击键-长按临界值是全球统一的,并且不能通过键进行配置。对于大多数用户来说这很好。但是在有些情况下,对于`LT`键来说按键延时对双功能键的提升更大,可能是因为有些键比其他的键更容易按住。为了不给每个都自定义键码,本功能可以为每个键定义`TAPPING_TERM`。
467
468想使能这个功能的话, 要先在`config.h`加上`#define TAPPING_TERM_PER_KEY`。
469
470
471## `get_tapping_term`示例实现
472
473想要修改基于键码的`TAPPING TERM`,你要向`keymap.c`文件添加如下代码:
474
475```c
476uint16_t get_tapping_term(uint16_t keycode) {
477 switch (keycode) {
478 case SFT_T(KC_SPC):
479 return TAPPING_TERM + 1250;
480 case LT(1, KC_GRV):
481 return 130;
482 default:
483 return TAPPING_TERM;
484 }
485}
486```
487
488### `get_tapping_term` 函数文档
489
490不像这篇的其他功能,这个不需要quantum或者键盘级别的函数,只要用户级函数即可。