qmk vial 対応

最終更新日

以前にsu120を使ってつくったコントコーラー

Netflix専用キーボードにOLEDを付ける

Netflix専用キーボードにOLEDを付ける

2021.09.25

ちょっと変更しようとしたら、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でリアルタイムに変更可
カスタムキーコード ✅ 対応(自由) ❌ 非対応(SAFE_RANGE不可) ✅ 対応(SAFE_RANGE使用可)
OLED表示 ✅ 対応 ✅ ただし表示内容は固定 ✅ 同上
ロータリーエンコーダー ✅ 対応 ✅ QMK側で制御 ✅ 同上
keymap変更 Web UI ❌ なし ✅ usevia.app ✅ vial.rocks
専用アプリ ❌ 不要(QMK Toolboxなど) ✅ VIAアプリ(軽量) ✅ VIALアプリ(高機能)
EEPROMの設定保存 ✅ ✅ ✅ より細かく制御可
セキュリティロック(鍵機能) ❌ ❌ ✅ キーボードの保護機能あり
独自キーUI表示(名前付きキー) ❌ ❌ ✅ 対応
複数レイヤーサポート ✅ ✅(最大4レイヤー) ✅(制限なし)
初心者向け ❌(やや上級者向け) ✅ とても扱いやすい △ やや知識が必要
開発者向け自由度 ✅ 最大限の自由 △ 制限あり ✅ ほぼ自由

✨ ざっくりまとめ

  • 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
view raw Keymap.c hosted with ❤ by GitHub

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で、以下のサイトにアクセスする。

https://vial.rocks

USBの接続メニューは、 su120 を選択。

keymapの設定画面は、まず、上部の4個のキーのどれかをクリックし、その後、下部のキーコードサンプルをクリックすると設定される。

ただし、一番右の 0x7e40 は、 SAFE_RANGE で指定したLAYER指定なので、触ってはいけない

シェアする