学习tmk代码的目标主要是理解自定义宏与函数的部分,但只是按键相关的方法,比如一键输入一串按键这种宏,对于与硬件比如led这种的就一窍不通了。
直奔主题,先看下tmk_keyboard_custom下的README.md,可以知道与具体键盘相关的代码在keyboard下面,咱们这次就只看gh60,但hhkb下面有一些可以学习的调用,可以结合着看。
首先看makefile,其中的
说明这几个文件是必需要的文件,而:
这个是在make的时候指定的,默认是keymap_poker.c, 也就是
make KEYMAP=xxxxx
这个命令里指定了配列相关的名字,但咱们先不从keymap_pocker.c开始看,而是从../hhkb/keymap_hasu.c这个代码结合从http://www.enjoyclick.org/tkg/# 从这里下载的代码来看(主要是想偷懒,借助自动生成的代码比较方便),因为这里的实现的方法多一些,可以从中学习。用来做示例的配列如下:
Layer 0: http://www.keyboard-layout-editor.com/#/gists/fa6c48d4975e6d64e361
Layer 1: http://www.keyboard-layout-editor.com/#/gists/652b6bb9da03a2781a59
Tmk生成代码时的配置为:
我们这次要关注的是 Fn 1和Fn2,下载下来的keymap.c是这样的,可见它的macro功能是还没有完善的:
// Generated by TKG at 2015-09-07 22:41:51
#include "keymap_common.h"
#define KEYMAP_TKG( \ K0A, K0B, K0C, K0D, K0E, K0F, K0G, K0H, K0I, K0J, K0K, K0L, K0M, K0N, \ K1A, K1B, K1C, K1D, K1E, K1F, K1G, K1H, K1I, K1J, K1K, K1L, K1M, K1N, \ K2A, K2B, K2C, K2D, K2E, K2F, K2G, K2H, K2I, K2J, K2K, K2L, K2N, \ K3A, K3C, K3D, K3E, K3F, K3G, K3H, K3I, K3J, K3K, K3L, K3N, \ K4A, K4B, K4C, K4F, K4J, K4K, K4L, K4M, K4N \ ) { \ { KC_##K0A, KC_##K0B, KC_##K0C, KC_##K0D, KC_##K0E, KC_##K0F, KC_##K0G, KC_##K0H, KC_##K0I, KC_##K0J, KC_##K0K, KC_##K0L, KC_##K0M, KC_##K0N }, \ { KC_##K1A, KC_##K1B, KC_##K1C, KC_##K1D, KC_##K1E, KC_##K1F, KC_##K1G, KC_##K1H, KC_##K1I, KC_##K1J, KC_##K1K, KC_##K1L, KC_##K1M, KC_##K1N }, \ { KC_##K2A, KC_##K2B, KC_##K2C, KC_##K2D, KC_##K2E, KC_##K2F, KC_##K2G, KC_##K2H, KC_##K2I, KC_##K2J, KC_##K2K, KC_##K2L, KC_NO, KC_##K2N }, \ { KC_##K3A, KC_NO, KC_##K3C, KC_##K3D, KC_##K3E, KC_##K3F, KC_##K3G, KC_##K3H, KC_##K3I, KC_##K3J, KC_##K3K, KC_##K3L, KC_NO, KC_##K3N }, \ { KC_##K4A, KC_##K4B, KC_##K4C, KC_NO, KC_NO, KC_##K4F, KC_NO, KC_NO, KC_NO, KC_##K4J, KC_##K4K, KC_##K4L, KC_##K4M, KC_##K4N } \ }
#ifdef KEYMAP_SECTION_ENABLE const uint8_t keymaps[][MATRIX_ROWS][MATRIX_COLS] __attribute__ ((section (".keymap.keymaps"))) = { #else const uint8_t keymaps[][MATRIX_ROWS][MATRIX_COLS] PROGMEM = { #endif [0] = KEYMAP_TKG( ESC, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSLS, \ TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC,BSPC, \ LCTL,A, S, D, F, G, H, J, K, L, SCLN,QUOT, ENT, \ LSFT, Z, X, C, V, B, N, M, COMM,DOT, SLSH, RSFT, \ APP, LALT,LGUI, SPC, GRV, LALT,LGUI,FN0, RCTL), [1] = KEYMAP_TKG( ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BSLS, \ CAPS,FN1, FN2, FN3, FN4, FN5, Y, U, I, O, P, LBRC,RBRC,BSPC, \ LCTL,END, S, D, F, G, LEFT,DOWN,UP, RGHT,SCLN,QUOT, ENT, \ LSFT, Z, DEL, C, V, B, N, M, COMM,DOT, SLSH, RSFT, \ APP, LALT,LGUI, SPC, GRV, LALT,LGUI,FN0, RCTL),
};
#ifdef KEYMAP_SECTION_ENABLE const uint16_t fn_actions[] __attribute__ ((section (".keymap.fn_actions"))) = { #else const uint16_t fn_actions[] PROGMEM = { #endif [0] = ACTION_LAYER_TOGGLE(1), [1] = ACTION_MACRO(), [2] = ACTION_MACRO(), [3] = ACTION_MACRO(), [4] = ACTION_MACRO(), [5] = ACTION_MACRO(), }; |
标红的部分就是我们在tmk页面上设置的那几个Fn键,其中0就是作为 layer 0 --> layer 1的开关,剩下的没有实现的,我们这次就是来实现它的。
把keymap.c改名为keymap_patrick.c(你喜欢的名字,但要以keymap_ 为前缀), 然后运行 make KEYMAP=patrick 看一下,是不是会报错,因为ACTION_MACRO这几个宏咱们还没有补充完成,看看下 keymap_hhkb.c里面是怎么写的:
enum macro_id { HELLO,
VOLUP, ALT_TAB, };
#ifdef KEYMAP_SECTION_ENABLE const uint16_t fn_actions[] __attribute__ ((section (".keymap.fn_actions"))) = { #else const uint16_t fn_actions[] PROGMEM = { #endif [0] = ACTION_DEFAULT_LAYER_SET(0), // Default layer(not used) [1] = ACTION_MACRO(HELLO), ....... [8] = ACTION_MACRO(ALT_TAB), // Application switching [9] = ACTION_MODS_KEY(MOD_LALT, KC_LEFT), [10] = ACTION_MODS_KEY(MOD_LALT, KC_RIGHT),
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) { switch (id) { case HELLO: return (record->event.pressed ? MACRO( I(0), T(H), T(E), T(L), T(L), W(255), T(O), END ) : MACRO_NONE ); case VOLUP: return (record->event.pressed ? MACRO( D(VOLU), U(VOLU), END ) : MACRO_NONE ); case ALT_TAB: return (record->event.pressed ? MACRO( D(LALT), D(TAB), END ) : MACRO( U(TAB), END )); } return MACRO_NONE; } void action_function(keyrecord_t *record, uint8_t id, uint8_t opt) { if (record->event.pressed) dprint("P"); else dprint("R"); dprintf("%d", record->tap.count); if (record->tap.interrupted) dprint("i"); dprint("\n");
switch (id) { case LSHIFT_LPAREN: ......... break; } } |
1.自己写不会,咱山寨一个还不可以吗,可以看到原创的有这几步:
2.定义一个enum.
3.增加 [1] = ACTION_MACRO(HELLO) 的赋值。
4.实现 action_function 函数,通过 id这个参数来做一下case选择,而这个id就是我们上面定义enum时的值。
5.实现case里面的实际操作,比如要输出 HELLO, 则要进行
6.MACRO( I(0), T(H), T(E), T(L), T(L), W(255), T(O), END ) :MACRO_NONE );
这个是什么意思呢?从文档里面可以看到 I为间隔, T为点击, W为等待,D为按下, U为抬起, END 标识结束, 整个语句的意思也就是:间隔0,输出HELL,等待255(不知道是啥单位), 再接着输出O,然后结束。
我们照写,最终得到这样的代码:
enum macro_id { HELLO,
ALT_TAB, };
#ifdef KEYMAP_SECTION_ENABLE const uint16_t fn_actions[] __attribute__ ((section (".keymap.fn_actions"))) = { #else const uint16_t fn_actions[] PROGMEM = { #endif [0] = ACTION_LAYER_TOGGLE(1), [1] = ACTION_MACRO(HELLO), [2] = ACTION_MACRO(ALT_TAB),//只定义这两个来试验一下,其它的fn大家可以补上 }; const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) { switch (id) { case ALT_TAB: return (record->event.pressed ? MACRO( D(LALT), D(TAB), END ) : MACRO( U(TAB), END )); case HELLO: return (record->event.pressed ? MACRO(I(0), T(H), T(E), T(L), T(L), W(255), T(O), END ) : MACRO_NONE ); } return MACRO_NONE; }
#ifdef KEYMAP_IN_EEPROM_ENABLE uint16_t keys_count(void) { return sizeof(keymaps) / sizeof(keymaps[0]) * MATRIX_ROWS * MATRIX_COLS; }
uint16_t fn_actions_count(void) { return sizeof(fn_actions) / sizeof(fn_actions[0]); } #endif //红色部分的代码是要我们自己补上去的,不然链接的时候会有些符号找不到 |
完整的代码(会有些不影响功能的变动)见附件。
然后我们再来 make KEYMAP=patrick的时候可以编译通过了,它会生成
,
而红圈的那个就是我们可以用来刷进去的固件了,但有个地方要注意下,Makefile里面的 KEYMAP_IN_EEPROM_ENABLE = yes 要注释掉,对嵌入式的不熟,只是不去掉刷固件的时候会报错,在网上找了下找到这个方法就可以成功了,有懂的同学可以告知下是什么原因。在win下可以把这个hex文件拖到
这里就可以进行刷了。win下刷似乎有问题,,mac/linux下都可以直接 make dfu
刷完成后可以试验下,先按fn0,把layer 切换到1,然后点击 Q(也就是fn1),和 W(Fn2)可以模拟 alt +tab 程序切换 、 输出 HELLO 这个字符串,成功实现了宏定义功能。
当然这里只说到了它的实现,至于原理或者整个调用流程,有时间再来写下吧。
这些都是我个人的理解,多有谬误,大家觉得有更便捷的方法或者看到哪里有错误麻烦指出,谢谢。
附:centos安装开发环境:
yum install avr-gcc avr-libc