ダイジェスト⑦
磁気スイッチ触り
photonに購入したり頂いたりしたphylina,kom,jadegaming,tiheをつけて打鍵比較してみました。

・トップアウト
KOMは大きな音。トップアウトでポスポス鳴る。押さずにステムを揺らすとカシャカシャ鳴る
次いでjadegamingもステムを揺らすとカシャつき、phylinaと続きtiheが良い感じ
komとjadegamingは濁り、phylinaは高音より、tiheは低音より
・ボトムアウト
ターンと強く押すとkomは濁った音に加えてトップアウトと同様パスっと音が出る。
jadegamingはパチッと鋭い音が鳴り、phylinaモニタ傾向でやや高めの音
カチカチ普通に押すとtiheとphylinaはカッカッと小気味よくなり、こちらはtiheの方が高い
komは全体的にカシャカシャしている
jadegamingじゃ強く押すとパチッとなるが普通に押すと低い地味で耳触りの良い音が出る
・ストローク
jadegamingは擦れ感が強い。その点tiheとphylinaはスムーズで優秀
komはばねがすげーカシャカシャする。ばねが強いため安定感はないもののスムーズ
jadegamingはブレ感が気になる。
このブレ感やスムーズさは所謂ラピトリ精度とかとはまた別の話です。
・返り
komは押し込みが軽く帰りが早い
jadegamingは過重変動少なく重め感で普通のリニアな感じ
tiheは普通の軽めのリニア
phylinaはなんか、
tiheが総じて良く感じたがいい音と静音性はまた別の話ではある。
以前はkomやjade系は全体的に打鍵感や打鍵音が絶賛されていた印象だがよくわからなかった。
phylinaも絶賛されていた印象で買ってみたがタイピング体験としてもゲーム体験としても正直よくはな気がした。でも安いのでいいと思う。
なんかでっかいステムのスイッチが多く出ているけれど、でっかいステム族自体がでっかいステム族のスイッチで分類できる気がして、tiheやastrolinkのような普通のタイプの方が好きかもしれない
astrolinkもtiheと似たような感じだったがトップアウトがちょっとうるさい。
精度がというのが大きな問題なのかもしれないが1umで動作するキーボードは持っていないのでわからない
継続的に色々出てるから気が向いたらまた新しそうなの買ってみる
ファームウェア作るぞ
さて、ハードはいったん置いておいてキーボードとして動かすためのファームウェアにとりかかろうと思います。
とりかかって大分たったのですが中々備忘録るやる気が出ずずるずると来ている
結果としては、すべてをqmkに対応させたファームウェアで製作しようという方針になっています。そもそもでいうとゲーム用左手デバイスであればキーマップなどゲーム側でいじればよいのでありラピトリ設定のみいじれればいいのですからarduinoでそのまま作ってしまえばよいのであります。
そうせずqmkでやろうというのは、自作磁気キーボードが普及すればいいなと思うためです。そのためにはゲームデバイスに限らずタイピングも行えるキーボードが必要で、タイピングを行うということはキーマップのまともな設定ソフトが必要で、それはqmkしかありません。
なぜ自作磁気キーボードが流行らないのかというと誰も求めていないからというのが私の考えです。だってそうですよね、私自身立体形状筐体に限定して価値があるとして製作しているわけですから。つまりプログラムだの組み込みだのに一切経験も知識もない私には難しいのですが、自キで色々活動されている方からすれば自作磁気キーボードを作るのは緒ちょいのちょいなのでしょうけれど、無意味だからやっている人が少ない、というわけです。
何もわからないのでハードに引き続き無料ai君頼りで進めます。
さてではどのような構成でqmkに対応させようとするべきかというと、自作磁気キーボードをやってみようと思えるほど、簡単に準備ができることが必要です。そのためにはホールセンサやマルチプレクサ等のメカニカルにはなじみのない部品こそ使いますが安価で馴染みのあるrp2040を使用することです。rp2040zeroとかだな。もちろんゲーマーの方からすればrp2040でできあがるものにはいろいろな面で性能には足らないでしょうがそれはまた別畑の話でそのはるか先です。
ゲーム用ならば設定など不要と言いましたが、タイピングも行う60%サイズなどの磁気キーボードを想定したとき、やはりソフトウェアはvial-qmkに対応していなければ使う気が起きません。私のように自作キーボードに取り組む一方ゲーミングデバイスにこだわりがあったりゲームを遊ぶという人は多くなくとも少なくもないと思うのです。
そういう人、それこそ標準的な配列以外を好んでいる方はそこそこのラピトリ最高の設定ソフトor最高のラピトリそこそこの設定ソフトであれば間髪入れず前者を選ぶのではないかと思っています。
というわけで誰でも簡単に自作磁気キーボードが作れるようにrp2040でqmkに対応させます。qmkのファイルも極力configをいじるだけで済ませられるようにします。その後、ラピトリ等の設定を行える別途ソフトウェアを作ります。という展望となります。
qmkでADC入力
今までqmkではただkeyboardjsonを書くだけのことしかしてきていないですが、ホールセンサでのアナログ出力から判定を行う必要があるのでscanをカスタムする必要があります。
初めてのカスタムマトリクスです。そしてカスタムマトリクスの中でADC入力を受け取るように設定する必要があります。
何がだるいってもはや写真など一枚も出てこずしばらく永遠に文字とアルファベットの羅列だけの内容になるのが書くのも見るのもキツイだろなんだこれ
まずこの前のテスト基板どんな内容なのというところですが⑥から雑図を持ってきます。

rp2040ボードのADCピンに2つのMUXが接続され、MUXにはホールセンサが複数接続されています。
またMUXにはrp2040ボードのデジタル出力ピンがつながっています。
マルチプレクサはrp2040からのデジタル出力により接続されたホールセンサから一つを選択あるいはオフにし、選択されたホールセンサからの電圧出力をrp2040のADC入力に通します。
つまり使用しているADCピンは一つですがmuxにより今回だと8つのホールセンサ出力を受け取るように拡張されています。
デジタル出力を制御することで電圧値を読みたいホールセンサを選択し、ADCで電圧を読むだけです。読んだ電圧値から判定を行いキーをオンオフするわけです。
とりあえずラピトリどうこうでなくカスタムマトリクスの実装とADC入力の確認を行いたいので、まずは読みとったADC値をある閾値でオンオフ判定を行うようなものを作りたいです。
カスタムマトリクス
カスタムマトリクスというのはただ指定したピンでcol2rowだとかrow2colだとかdirectpinsじゃない方式でマトリクススキャンしたい時に自分で実装するというやつですね。二乗マトリクスとかなんとかそういうキー数いっぱい増やすやつで馴染みが深い気がしますが私は使ったことがありません。
ドキュメント
まずはqmkドキュメントのCustom Matrix項をgoogle翻訳して読んでます。https://docs.qmk.fm/custom_matrix
QMK は、デフォルトのマトリックススキャン ルーチンを独自のコードで補足または置き換えるメカニズムを提供します。
この機能を使用する理由は次のとおりです。
キーボードのスイッチとMCUピンの間に追加のハードウェア
I/Oマルチプレクサ
このスイッチ以外のハードウェアを追加というのが今回ですね。
カスタムマトリックスの実装には通常、追加のソースファイルのコンパイルが必要です。一貫性を保つため、このファイルは という名前にすることをお勧めしますmatrix.c。
キーボード ディレクトリに新しいファイルを追加します。
keyboards/matrix.c
新しいファイルのコンパイルを設定するには、以下を追加しますrules.mk:
SRC += matrix.c
keyboard.jsonと同じディレクトリにmatrix.cというファイルを作成しその中に新規マトリクススキャンの中身を書きます。
それを使用したファームウェアを作成するには一緒にコンパイルされるようにする必要があり、rule.mk内にSRC += matrix.cと書く必要があるようです。
lite
様々なスキャン関数のデフォルト実装を提供し、カスタムマトリックスを実装する際の定型コードを削減します。設定するには、以下を に追加します
Full Replacement
スキャンルーチンをより細かく制御する必要がある場合は、完全なスキャンルーチンを実装することもできます。設定するには、rules.mkに以下を追加します。
カスタムマトリクスにはliteとfull replacementの2種類があるようです。
両者で記述する関数が異なり、例えばfullでは”matrix_scan“ですがliteでは”matrix_scan_custom”となっています。fullの方が自由度が高そうでliteは一部だけ書き換えるような感じですが、調べてみます。
qmkmsysでgrep -r “matrix_scan_custom”コマンドを実行します。該当するファイルを検索してくれるコマンドで色々調べるのによく使いまいしたので覚えました。
quantum\matrix_common.cにヒット 見てみます。
__attribute__((weak)) uint8_t matrix_scan(void) {
bool changed = matrix_scan_custom(raw_matrix);
#ifdef SPLIT_KEYBOARD
changed = debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed) | matrix_post_scan();
#else
changed = debounce(raw_matrix, matrix, ROWS_PER_HAND, changed);
matrix_scan_kb();
#endif
return changed;
}
matrix_scan関数の中でmatrix_scan_customが呼び出されており、その後デバウンス処理らしきものが行われています。
fullはこのmatrix_scan全体を書き換え、liteはマトリクススキャン部分だけを書き換え、その後自動でデバウンス処理が行われる感じでしょうか。今回はアナログ入力でデバウンス処理は不要あるいは実装するとしても別の形なので、fullでよさそうです。
attribute((weak))というのは他に同じものが定義していたら上書きする、のようなものらしい。今回のようにmatrix.cを実装する際はそちらを優先する感じでしょうか。
Full Replacement設定するには、rules.mkに以下を追加します。
CUSTOM_MATRIX = yesmatrix.c
キーボード フォルダー内のファイル
に次の関数を実装します。
matrix_row_t matrix_get_row(uint8_t row)
void matrix_print(void)
void matrix_init(void)
uint8_t matrix_scan(void)
rule.mkにCUSTOM_MATRIX = yesを書くことでfull replacementのカスタムマトリクスが有効になります。
またmatrix.c内にはこのような関数を実装すればよいみたい。matrix_printはデバッグような感じなので置いておきます。基本的にはすでにあるデフォルトで読み込まれるものを見れば理解でき得るはずなので、参照しながら関数を一つ一つ見ていきます。マトリクススキャン部は基本的には下記ファイルにありそう
quantum\matrix.h
matrix.c
matrix_common.c
スキャン以外にも、qmkの機能的な部分はquantumフォルダ内のファイルを探すと情報がみつかりそうです。
matrix_get_row
matrix配列のrow番目を取得する関数のようです。
inline matrix_row_t matrix_get_row(uint8_t row) {
matrix_common.c
// Matrix mask lets you disable switches in the returned matrix data. For example, if you have a switch blocker installed and the switch is always pressed.
ifdef MATRIX_MASKED
return matrix[row] & matrix_mask[row];
else
return matrix[row];
endif
matrix[]はというと、行数個の要素を持った配列で、デバウンス後のキー状態が格納されています。現在のキーボードとしての各キー入力状態といったところでしょうか
先ほど見たmatrix_scanの中でdebounce関数の中にraw_matrix[]が引数となっていますから、きっとdebounce関数で処理されてmatrix[]になっているのでしょう。
このキー状態配列はmatrix.c内で定義されているのでカスタムマトリクスでも同様に定義する必要がありそうです。
/* matrix state(1:on, 0:off) */
matrix.c
extern matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values
extern matrix_row_t matrix[MATRIX_ROWS]; // debounced values
matrix_get_row[]やmatrix[]の型であるmatrix_row_tはmatrix.hにありました。列数に応じてサイズが変更されていますがこれは単に容量削減でしょうか。
matrix_row_t型はcolに関係のある型と分かります。これはつまりマトリクスのある行のキー状態を表しているようです。
if (MATRIX_COLS <= 8)
matrix.h
typedef uint8_t matrix_row_t;
elif (MATRIX_COLS <= 16)
typedef uint16_t matrix_row_t;
elif (MATRIX_COLS <= 32)
typedef uint32_t matrix_row_t;
else
error “MATRIX_COLS: invalid value”
endif
例えば4行5列のマトリクスを想定したとき、0がオフ、1がオンでこのような状態の時、matrix配列のrow番目にはrow行目の各colの状態がビット列で格納されている感じでしょうか。1次元配列にビット列を格納することで2次元マトリクスのデジタル状態がわかりますね。
col4 | col3 | col2 | col1 | col0 | ||
row0 | 0 | 1 | 0 | 0 | 0 | matrix[0]=01000 |
row1 | 0 | 0 | 1 | 1 | 0 | matrix[1]=00110 |
row2 | 0 | 0 | 0 | 0 | 0 | matrix[2]=00000 |
row3 | 0 | 0 | 0 | 0 | 1 | matrix[3]=00001 |
話は戻ってmatrix_get_row関数は指定した行のキー状態を渡していることがわかりました。知りませんが、スキャン後のqmkとしてのキーボード入力処理のところでこれを呼び出してキー状態を把握しているのでしょうかね。
matrix mask?走らないので行わなくてよさそうですしキー状態を渡させるだけなので、ここは流用でよさそうです。
matrix_init
スキャンを開始する前にキーボード起動後諸々の初期化を行います。matrix_common.cにひとつ、ありますがこちらはカスタムマトリクスliteで使用するものなので無視します。
matrix.cの中にもmatrix_initがあります。これが通常使用されるものでしょう。
キー状態の初期化及び、ピンの初期化を行っています。
matrix_init_pins関数でピンの初期化を行っています。matrix.c内でを追うと、スキャン方式でif分岐しています。確かにcol2rowとrow2colとdirectpinsとではピンの使い方が異なりますね。分岐するのもあってinitの外で定義されているのでしょうか。
内容としては使用するピンをinput/output high状態にしています。directpinsは常にinput highにしてスキャンしますし、ダイオードのマトリクスでは任意の行or列をoutput high→lowにして対するinput highした列or行を呼んでlowになっているかどうかで判定するので、その通りですね。
void matrix_init(void) {
matrix.c
ifdef SPLIT_KEYBOARD
~ //分割用、割愛
endif
// ピンを初期化
matrix_init_pins();
//マトリクスを初期化、配列内全部0にしてすべてオフに
memset(matrix, 0, sizeof(matrix));
memset(raw_matrix, 0, sizeof(raw_matrix));
//デバウンス初期化
debounce_init(ROWS_PER_HAND);
//ユーザー定義用
matrix_init_kb();
}
カスタムマトリクスする際は入出力ピンの状態と、スキャンする際に使用する配列等に初期状態、すべてオフなどを実装しておけばよさそうですね。
磁気キーボードの場合はキャリブレーションやラピトリ設定値等のプロファイルを読み込んでおくのもここでしょうか。
matrix_scan
こちらもmatrix.c内にあるものを確認します。
col2rowとrow2colとでif文分岐しています。これはmatrix[row]配列という形でrowごとにまとまりとしてキー状態を保存しているため向きが異なると順序が逆になるためです。
col2row,directpins・・・行ごとに走査するためmatirx[row]を1桁ずつ埋めていけばよい
row2col・・・列ごとに操作するため、桁ごとにmatrix[row]を選択し埋めていく
matrix.c内のmatrix_read~関数を追うと結果としてcurr_matrixに現在のキー状態が格納されています。その後前回のキー状態raw_matrixと比較、変更があったらraw_matrixに反映しデバウンス処理。
デバウンス処理した結果から帰り値としてのchanged判定とおおもとのmatrix配列更新を行っています。
uint8_t matrix_scan(void) {
amtrix.c
//一時データを初期化
matrix_row_t curr_matrix[MATRIX_ROWS] = {0};
//directpins, col2rowの時
if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW)
// matrix_read_cols_on_rowで行ごとにスキャン
for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) {
matrix_read_cols_on_row(curr_matrix, current_row);
}
//row2colの時
elif (DIODE_DIRECTION == ROW2COL)
// matrix_read_rows_on_colでcolごとにスキャン
matrix_row_t row_shifter = MATRIX_ROW_SHIFTER;
for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++, row_shifter <<= 1) {
matrix_read_rows_on_col(curr_matrix, current_col, row_shifter);
}
endif
//raw(前)とcurr(今)を比較しchangedを判定
bool changed = memcmp(raw_matrix, curr_matrix, sizeof(curr_matrix)) != 0;
//状態変更があったらraw matrixを更新
if (changed) memcpy(raw_matrix, curr_matrix, sizeof(curr_matrix));
//デバウンス処理しmatrixに反映, changedもデバウンス込みで更新ありか判定
changed = debounce(raw_matrix, matrix, ROWS_PER_HAND, changed);
matrix_scan_kb();
//キー状態変更通知
return (uint8_t)changed;
}
磁気はデバウンス処理が必要ないので使用するmatrix[]配列は2で済みそうです。キー状態の変更があった場合changedで返すこと、matrix[]をみてその後処理されるので最新のキー状態を反映させること。がやらなければならないことです。
また、これらのスキャンの中で使用していたgpioの関数は何をしているのでしょうか。次はそこから追ってみます。
qmk進捗
とりあえず今回で中身はないですがカスタムマトリクスが少し書けました。
rule.mk
rule.mk:
CUSTOM_MATRIX = yes
SRC += matrix.c
matrix.c
matrix.c:
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "util.h"
#include "matrix.h"
//#include "debounce.h"
#include "atomic_util.h"
//キー状態
extern matrix_row_t raw_matrix[MATRIX_ROWS];//scanで更新
extern matrix_row_t matrix[MATRIX_ROWS];//matrix_get_row受け渡し
//初期化
void matrix_init(void) {
//スキャンで使用するピンや配列の初期化
//キャリブレーションやラピトリ設定の読み込み
}
//マトリクススキャン
uint8_t matrix_scan(void) {
//スキャンしmatrix[], changedを更新する
//変更通知
return changed;
}
//キー状態受け渡し
matrix_row_t matrix_get_row(uint8_t row) {
return matrix[row];
}
//デバッグ用
void matrix_print(void) {
}
おわり
カスタムマトリクスが何なのかどうすりゃいいのかよくわからないなりにだらだらやっていますが終わる気配がない。しばらくqmkとかchibiosのコードを何やら追う内容になりそう。
現時点でqmkのラピトリ実装と動作確認まではできているので、ちょこちょこダイジェスト進めて追いつきたい。
現時点の考えですがキャリブレーションはソフトウェアの必要ないと思っていて、キャリブレーションモードの切り替わるトグルスイッチを設けてスイッチポチポチするだけでいいじゃんと思います。そのほか、速度精度度、ap,rp,ラピトリ値、スイッチストロークを設定する必要があるのでやはりソフトウェア自体は必要なのですが、それ自体も正直一度好きな値に設定したら正味変え無くね?と思うのです。
ゲームで使用しないキーは速度重視かつaprpに余裕を持ち、ゲームで使用する部分のみ精度重視かつデッドゾーン0.1mmラピトリ0.05mmとかで十分と思うのだ。ライトゲーマーなので、、
するとqmkmsysでmakeしてぶっこむことができる人は別途設定ソフトなくても困らないなと思ったり。
ここら辺は割とすいすい行きそうなので、一番の課題はやはりスキャンの高速化だと思います。私はマウスカーソルみたいなアナログ信号でなく0,1のデジタル入力なのだから8kとか100kスキャンとか別にという人ですが、早いに越したことはないですからね。
コメント