qmk vial 対応

以前にsu120を使ってつくったコントコーラー
ちょっと変更しようとしたら、qmkが進化してて、compileが通らない。
今回作りたいのは、読書用のコントローラー。
PGUP/PGDOWN や、MOUSE WHEELの操作をロータリーエンコーダで行いたい。
色々トライした結果、やりたいことが以下のように絞れた
- GUIでkeymapの(再)設定がしたい
- RotaryEncoderが使いたい
- OLEDを自由に使いたい
- Mouse Key が使いたい
- LAYER で、独自SAFE_RANGE が使いたい
(QMKキーボードを複数台使う場合、通常のMO(0)などを利用すると、別キーボード影響がでてしまう。)
これを全部かなえるためには、VIAではなく、 VIAL対応
が必要だとわかった。
AIに頼んで、まとめてもらった
qmk, VIA, VIAL 比較
QMK / VIA / VIAL の比較表
機能・特徴 | QMK | VIA | VIAL |
---|---|---|---|
キー配列の設定 | コードで定義 | UIでリアルタイムに変更可 | UIでリアルタイムに変更可 |
カスタムキーコード | |||
OLED表示 | |||
ロータリーエンコーダー | |||
keymap変更 Web UI | |||
専用アプリ | |||
EEPROMの設定保存 | |||
セキュリティロック(鍵機能) | |||
独自キーUI表示(名前付きキー) | |||
複数レイヤーサポート | |||
初心者向け | △ やや知識が必要 | ||
開発者向け自由度 | △ 制限あり |
ざっくりまとめ
- QMK:すべての土台。最も自由だけどコード編集が必要。
- VIA:初心者向け。UIでレイヤーやキー変更ができて簡単。
- VIAL:VIAのパワーアップ版。カスタムキーやセキュリティも扱える上級者向けUIツール。
QMKからvialへの移行作業
ざっくりとは、この解説がとてもよかった。
必要な構成は、以下の通り。
vial-qmk/keyboards/su120/
.
├── keyboard.json
└── keymaps
└── vial
├── config.h
├── keymap.c
├── rules.mk
└── vial.json
作成すべき.jsonは keyboard.json と vial.json。
以降に2つのjsonファイルの内容を記述する。
vial.json の作成
レイアウトファイルを作成
vial GUI用に、どんなキー配置かを情報化する。
以下のサイトで、キー配置を作成し、 Top Legend
に0 origin でキーの配置を 0,0
という形式で記述しておく。
今回は、1行x4列のキーボードなので、こんな感じ。
レイアウトのJSON形式は、 Raw data
TABで確認できる。
が、ここで特殊な対応。
今回のキーボードは、1 ROW なので、配列の要素が浅い。
正しい値は、以下の通り。
[["0,0","0.1","0.2","0.3"]]
vial.json の作成
以上のキー配列データをGUIに渡すための情報、vial.json の 項目 keymap
に設定する。
{
"lighting": "none",
"matrix": {
"rows": 1,
"cols": 4
},
"layouts": {
"keymap": [
["0,0","0,1","0,2","0,3"]
]
}
}
keyboard.json の作成
qmk の config.h に記述するような内容を以下の形式で記述する。
AIに変換してもらった。。
{
"keyboard_name": "SU120",
"manufacturer": "e3w2q",
"maintainer": "e3w2q",
"url": "",
"usb": {
"vid": "0x1209",
"pid": "0x4649"
},
"processor": "atmega32u4",
"bootloader": "caterina",
"matrix_pins": {
"rows": ["F6" ],
"cols": [ "D1", "D0", "D4", "C6" ]
},
"diodes": {
"direction": "COL2ROW"
},
"layouts": {
"LAYOUT": {
"layout": [
{ "matrix": [0, 0], "x": 0, "y": 0 }, { "matrix": [0, 1], "x": 1, "y": 0 },
{ "matrix": [0, 2], "x": 2, "y": 0 }, { "matrix": [0, 3], "x": 3, "y": 0 }
]
}
}
}
souce codeの移行
config.h
VIAL_KEYBOARD_UID は、ユーニックなるように生成
python3 util/vial_generate_keyboard_uid.py
output をconfig.h に反映して、こんな感じ。
#pragma once
#define DEVICE_VER 0x0001
#define VIAL_KEYBOARD_UID {0xDC, 0xB9, 0x6E, 0xD8, 0x30, 0xFC, 0x43, 0xE5}
#define VIAL_UNLOCK_COMBO_ROWS {0, 0}
#define VIAL_UNLOCK_COMBO_COLS {0, 1}
/* Rotary encoder */
#define ENCODERS_PAD_A {F5, B5}
#define ENCODERS_PAD_B {F4, B4}
#define ENCODER_RESOLUTION 4
// エンコーダーのデバウンス設定を強化
#define DEBOUNCE 10 // デバウンス時間を増やす(デフォルトは5)
#define ENCODER_RESOLUTIONS {4, 4} // 分解能を下げて誤検知を減らす
#define ENCODER_RESOLUTIONS_RIGHT {4, 4}
// 方向検出の安定化
#define ENCODER_DIRECTION_FLIP // 方向が逆の場合はこの行をコメントアウト
#define ENCODER_DEBOUNCE 8 // エンコーダー専用のデバウンス値を設定
// スキャン頻度を調整
#define TAP_CODE_DELAY 20 // 遅延を少し増やして安定性を向上
/* COL2ROW, ROW2COL*/
#define DIODE_DIRECTION COL2ROW
rules.mk
このファイルが一番大事
今回、OLEDとMOUSE KEYとロータリーエンコーダを全部利用しようとしたら、compile後のbinaryが大きすぎて、Pro Micro 入りきらない。
どうやってサイズダウンしようかとAIに相談すると、使わない高度な機能を全部無効化しろと。
結果、なんとか収まったので、AIありがとう。
# VIAL対応
VIA_ENABLE = yes
VIAL_ENABLE = yes
# ロータリーエンコーダのカスタマイズ用
ENCODER_ENABLE = yes
ENCODER_MAP_ENABLE = yes
# OLED
OLED_ENABLE = yes
# マウス操作
MOUSEKEY_ENABLE = yes
# binaryのsize縮小用にdebugや使わない機能を除外
CONSOLE_ENABLE = no
COMMAND_ENABLE = no
SPACE_CADET_ENABLE = no
GRAVE_ESC_ENABLE = no
MAGIC_ENABLE = no
KEY_OVERRIDE_ENABLE = no
TAP_DANCE_ENABLE = no
COMBO_ENABLE = no
# バイナリ最適化オプションを有効化
LTO_ENABLE = yes
keymap.c
#include QMK_KEYBOARD_H | |
// カスタムキーコードを定義 | |
enum custom_keycodes { | |
SAFE_MO1 = SAFE_RANGE, | |
SAFE_MO2, | |
// 必要に応じて追加 | |
}; | |
// キーマップの定義 | |
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |
[0] = LAYOUT( | |
KC_BTN1, KC_LEFT_SHIFT, KC_LEFT_CTRL,SAFE_MO1 | |
), | |
[1] = LAYOUT( | |
KC_SPC, KC_UP, KC_DOWN, XXXXXXX | |
) | |
}; | |
#if defined(ENCODER_MAP_ENABLE) | |
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = { | |
[0] = { ENCODER_CCW_CW(KC_WH_U, KC_WH_D), ENCODER_CCW_CW(KC_1, KC_2) }, | |
// Encoder 1 Encoder 2(None) | |
[1] = { ENCODER_CCW_CW(KC_PAGE_DOWN, KC_PAGE_UP), ENCODER_CCW_CW(KC_1, KC_2) } | |
// Encoder 1 Encoder 2(None) | |
}; | |
#endif | |
void keyboard_post_init_user(void) { | |
// デバッグ機能を無効化してファームウェアサイズを削減 | |
debug_enable=false; | |
//debug_matrix=false; | |
//debug_keyboard=false; | |
//debug_mouse=false; | |
} | |
// QMKロゴデータ(32x32px) | |
static const char PROGMEM qmk_logo[] = { | |
// 'r', 32x32px | |
0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0x70, 0x38, 0x38, 0x1c, 0x9c, 0x8c, 0xce, 0xce, 0x0e, | |
0x0e, 0xce, 0xce, 0x8c, 0x9c, 0x1c, 0x38, 0x38, 0x70, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0xe0, 0xfc, 0xff, 0x07, 0x03, 0x60, 0x78, 0x7e, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x07, 0x00, | |
0x00, 0x07, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0x7e, 0x78, 0x60, 0x03, 0x0f, 0xff, 0xfc, 0xe0, 0x00, | |
0x00, 0x0f, 0x7f, 0xfe, 0xe0, 0x80, 0x0e, 0x3c, 0x7c, 0xfe, 0xfc, 0xf0, 0xe0, 0xc0, 0xc0, 0x00, | |
0x00, 0xc0, 0xc0, 0xe0, 0xf0, 0xfc, 0xfe, 0x7c, 0x3c, 0x0e, 0x80, 0xe0, 0xff, 0x7f, 0x0f, 0x00, | |
0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1c, 0x1c, 0x38, 0x31, 0x73, 0x73, 0x63, 0x67, 0x60, | |
0x60, 0x67, 0x63, 0x73, 0x73, 0x31, 0x38, 0x1c, 0x1c, 0x0f, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00 | |
}; | |
oled_rotation_t oled_init_user(oled_rotation_t rotation) { | |
return OLED_ROTATION_180; | |
} | |
// レイヤー状態を追跡する変数 | |
static uint8_t saved_layer = 0; | |
static bool needs_update = false; // 追加: 更新フラグ | |
void oled_display(void) | |
{ | |
// エンコーダモード表示 | |
// logo表示 | |
uint8_t bytes_per_page = sizeof(qmk_logo)/4; | |
for (uint8_t page = 0; page < 4; page++) { | |
oled_set_cursor(0, page); | |
oled_write_raw_P(qmk_logo + (bytes_per_page*page), bytes_per_page); | |
} | |
// QMKロゴ表示 | |
oled_set_cursor(7, 0); | |
switch (get_highest_layer(layer_state)) { | |
case 0: | |
oled_write_P(PSTR("Enc: WHEEL "), false); | |
break; | |
case 1: | |
oled_write_P(PSTR("Enc: PG UP/DN"), false); | |
break; | |
} | |
// キー配置表示 | |
oled_set_cursor(7, 3); | |
switch (get_highest_layer(layer_state)) { | |
case 0: | |
oled_write_P(PSTR("BTN1 SHFT CTRL"), false); | |
break; | |
case 1: | |
oled_write_P(PSTR("SPC ^ v "), false); | |
break; | |
} | |
} | |
bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |
switch (keycode) { | |
case SAFE_MO1: | |
if (record->event.pressed) { | |
// キーを押した時:現在のレイヤーを保存して新しいレイヤーに切り替え | |
saved_layer = get_highest_layer(layer_state); | |
layer_on(1); // レイヤー1をオン | |
needs_update = true; // 更新フラグをセット | |
oled_display(); | |
} else { | |
// キーを離した時:保存していたレイヤーに戻す | |
layer_off(1); // レイヤー1をオフ | |
needs_update = true; // 更新フラグをセット | |
layer_on(saved_layer); | |
oled_display(); | |
} | |
return false; // 他のキーボードにキーコードを送信しない | |
case SAFE_MO2: | |
if (record->event.pressed) { | |
saved_layer = get_highest_layer(layer_state); | |
layer_on(2); | |
} else { | |
layer_off(2); | |
layer_on(saved_layer); | |
} | |
return false; | |
} | |
return true; // 他のキーは通常通り処理 | |
} | |
bool oled_task_user(void) { | |
if (needs_update) { // フラグがセットされている場合のみ更新 | |
oled_display(); | |
needs_update = false; // フラグをリセット | |
} | |
return true; // trueを返して通常の更新も許可 | |
} | |
// エンコーダーの回転処理を改善するカスタム関数 | |
#ifndef ENCODER_MAP_ENABLE | |
// ENCODER_MAP_ENABLEが無効な場合のみこの関数が使用される | |
// 前回の回転方向を記録する変数 | |
static uint8_t prev_encoder_direction[NUM_ENCODERS] = {0}; | |
// 連続した同じ方向の回転をカウントする変数 | |
static uint8_t same_direction_count[NUM_ENCODERS] = {0}; | |
bool encoder_update_user(uint8_t index, bool clockwise) { | |
// エンコーダーの状態変化を検出したらOLED更新フラグをセット | |
needs_update = true; | |
// 現在のレイヤーに基づいて処理 | |
uint8_t layer = get_highest_layer(layer_state); | |
// 方向の一貫性をチェック - 誤検知防止 | |
uint8_t current_direction = clockwise ? 1 : 2; | |
// 前回と同じ方向なら、カウントを増やす | |
if (prev_encoder_direction[index] == current_direction) { | |
same_direction_count[index]++; | |
} else { | |
// 方向が変わった場合、カウントをリセット | |
same_direction_count[index] = 0; | |
// 方向が変わったばかりの場合は、誤検知の可能性があるため無視する | |
// 少なくとも1回は同じ方向に回転した後でないと方向転換と見なさない | |
if (prev_encoder_direction[index] != 0) { | |
prev_encoder_direction[index] = current_direction; | |
return false; | |
} | |
} | |
// 方向を記録 | |
prev_encoder_direction[index] = current_direction; | |
if (index == 0) { // 最初のエンコーダー | |
switch (layer) { | |
case 0: | |
// レイヤー0: マウスホイール - 通常速度 | |
if (clockwise) { | |
tap_code(KC_WH_D); | |
} else { | |
tap_code(KC_WH_U); | |
} | |
break; | |
case 1: | |
// レイヤー1: ページスクロール | |
if (clockwise) { | |
tap_code(KC_PAGE_DOWN); | |
} else { | |
tap_code(KC_PAGE_UP); | |
} | |
break; | |
default: | |
if (clockwise) { | |
tap_code(KC_RIGHT); | |
} else { | |
tap_code(KC_LEFT); | |
} | |
break; | |
} | |
} else if (index == 1) { // 2番目のエンコーダー(存在する場合) | |
if (clockwise) { | |
tap_code(KC_2); | |
} else { | |
tap_code(KC_1); | |
} | |
} | |
return false; // QMKのデフォルト処理をスキップ | |
} | |
#else | |
// ENCODER_MAP_ENABLEが有効な場合のエンコーダー処理 | |
// 前回の回転方向を記録する変数 | |
static uint8_t prev_encoder_direction[NUM_ENCODERS] = {0}; | |
// 連続した同じ方向の回転をカウントする変数 | |
static uint8_t same_direction_count[NUM_ENCODERS] = {0}; | |
// エンコーダーマップの後処理を行う関数 | |
void post_encoder_event(uint8_t index, bool clockwise) { | |
// エンコーダーの状態変化を検出したらOLED更新フラグをセット | |
needs_update = true; | |
} | |
bool encoder_update_user(uint8_t index, bool clockwise) { | |
// 方向の一貫性をチェック - 誤検知防止 | |
uint8_t current_direction = clockwise ? 1 : 2; | |
// 前回と同じ方向なら、カウントを増やす | |
if (prev_encoder_direction[index] == current_direction) { | |
same_direction_count[index]++; | |
} else { | |
// 方向が変わった場合、カウントをリセット | |
same_direction_count[index] = 0; | |
// 方向が変わったばかりの場合は、誤検知の可能性があるため無視する | |
if (prev_encoder_direction[index] != 0) { | |
prev_encoder_direction[index] = current_direction; | |
return false; | |
} | |
} | |
// 方向を記録 | |
prev_encoder_direction[index] = current_direction; | |
// エンコーダーマップの処理を実行 | |
if (!encoder_update_kb(index, clockwise)) { | |
return false; | |
} | |
// 追加の処理を実行 | |
post_encoder_event(index, clockwise); | |
return true; | |
} | |
#endif | |
compile
git colne した vial-qmk配下で、qmkと同様にcompile
qmk compile -kb su120 -km vial
今回のディレクトリ構成。keyboardは、 su120
。keymap は、 vial
とした。
vial-qmk/keyboards/su120/keymaps/vial
ところで、AIに聞くと、以下のコマンドで、vialだよーという情報が出るらしいが、出ない。
でも動いたからよし。
qmk info -kb su120 -km vial
---
☒ su120: VIAL_ENABLE in rules.mk is no longer a valid option and should be removed
su120: DEVICE_VER in config.h is deprecated in favor of `usb.device_version` in info.json and will be removed at a later date
☒ su120: VIAL_KEYBOARD_UID in config.h is no longer a valid option and should be removed
☒ su120: VIAL_UNLOCK_COMBO_COLS in config.h is no longer a valid option and should be removed
☒ su120: VIAL_UNLOCK_COMBO_ROWS in config.h is no longer a valid option and should be removed
Keyboard Name: SU120
Manufacturer: e3w2q
Website:
Maintainer: e3w2q
Layouts: LAYOUT
Processor: atmega32u4
Bootloader: caterina
ファームウェアの書き込み
qmk flash -kb su120 -km vial
以下のメッセージがでたら、Pro Microのリセットスイッチを押す。
Flashing for bootloader: caterina
Waiting for USB serial port - reset your controller now (Ctrl+C to cancel)....
remap!
あとは、vialのWeb UIか、専用アプリでremapするだけ!
Chromeで、以下のサイトにアクセスする。
USBの接続メニューは、 su120
を選択。
keymapの設定画面は、まず、上部の4個のキーのどれかをクリックし、その後、下部のキーコードサンプルをクリックすると設定される。
ただし、一番右の 0x7e40
は、 SAFE_RANGE
で指定したLAYER指定なので、触ってはいけない