You are currently viewing チャタリングをソフト的に解消しよう

チャタリングをソフト的に解消しよう

メカニカルキーボードにはチャタリングが付き物と言われます。kkonnnakkkannjiなどと入力されてしまうあれです。その原因はスイッチの不良や劣化にあるので、問題のスイッチ自体を交換すれば修理可能ではあります。けれども、自作キーボードたるもの、せっかくファームウェアをいじり放題なのですから、出来ることならソフト的な解決を図ろう!というお話です。

ちょっと長めなので結論だけ聞きたい人は以下目次の3をクリックしてください。

Table of Contents

チャタリングの原因

QMKのデバウンスについて調べる人はあまりいないかもしれません。というのも、状態の良好なスイッチを使っているキーボードであればデフォルトのデバウンス処理で何ら問題が出ないからです。

筆者はこれまで2桁台のキーボードを組み立てて来ましたが、チャタリング/バウンスに悩まされることは稀です。しかし稀ながら何台かは確実にあります。特定のスイッチで、しかも使用している環境によるものではないかと考えられます。つまり、スイッチ接点の劣化が起こる条件があるわけです。スイッチ接点は銅製です。

以前、某びあっこ氏が某Discordで紹介していた論文によると、室温であっても、比較的高温もしくは/および高湿の場合は銅表面に酸化皮膜が成長すると言います。しかもその温度は40や50℃といった程度だそうですから、夏場の陽当たり良好な窓辺などに設置していれば簡単に達してしまいそうです。筆者の作業部屋環境もまさしくそれに当たり、夏になると朝日をもろに受けて午前7時には灼熱となります(寝室でもリビングでもないのでその時間帯はエアコンを切っているのです)。たとえ現状ではこの種の悩みがない方であっても、そうした高温多湿な環境にメカニカルキーボードを放置することは推奨されない、ということは知っておいた方がいいかもしれません。

筆者同様にそうした場所でキーボードを使わざるを得ない、あるいは、車移動が多く、車内に放置される機会があるといったユーザーにとって、チャタリング/バウンスは解決しなければならない切実な問題でしょう。

なお、JIS(※)によるチャタリングの定義は「衝撃、振動などの外的影響に起因して生じる間欠的でランダムな開接点の閉動作及び/又は閉接点の開動作」とありますので、JISに従うのであれば本稿で扱うのはチャタリングと呼ぶべきではないことになります。ではなんと呼ぶかと言うと、コンタクトバウンスになります。JISによるコンタクトバウンスの定義は「スイッチ機構に起因する、接点の移動後に生じる可能性のある間欠的でランダムな閉接点の開動作又は開接点の閉動作」です。

一般にキー入力がダブったり入力されないとチャタってるなどと言いますので、本稿ではチャタリングもバウンスも同義のように使いますが、念のため。

 

※規格番号 JISC5445 規格名称 電子機器用スイッチ-第1部:通則 最新改正年月日 2021/05/20

 

QMKのデフォルトのデバウンス

銅接点に対して過酷な環境に設置してあるとしても、充分なデバウンス処理が施されさえすれば問題が表面化しないのではないか?という疑問はもっともです。QMKにもデバウンス処理は組み込まれており、デフォルトでオンになっています。ところが、バウンスに悩まされるのであれば、このデバウンス処理が効いていないことになります。効いてないというのは語弊がありますね。通常のバウンスに対するデバウンスは有効です。効かないのであればそれはもはや正常なバウンスではなく接点不良による異常なバウンスなのです。その接点不良をソフト的にどうにかしたいわけです。だって組みあがったキーボードをばらして1個1個スイッチを分解するの面倒じゃないですか?

話を戻しましょう。キー入力がチャタリングを起こした際、QMKを使っているユーザーならDEBOUNCEの定数を増やしたことがあるのではないでしょうか? DEBOUNCEはデフォルト(無指定)の場合は5です。単位はmsとなり、この場合は5msを意味します。

ところでこのDEBOUNCE時間は何の時間でしょう?

幸いQMKのドキュメントはよく整備されていますので、まずはドキュメントを確認します。QMKにおけるデバウンスの基本はこちらに書かれています。

https://docs.qmk.fm/#/ja/feature_debounce_type

バウンスとは、スイッチを叩いて接点同士が接触する瞬間からの一定時間にそれら接点が跳ねることによって接触がオンオフを繰り返すことですから、デバウンスではその望ましくないオンオフを取り除く処理をしなければなりません。

デフォルトのデバウンス処理はsym_defer_gと呼ばれるものです。これは、キーマトリクスに変化があった時点からDEBOUNCEで設定されている時間の間、変化が一切なければ入力されるというアルゴリズムになります。

実際の入力を想像してみましょう。

キーが押されると接点が閉じるやいなやバウンスが発生し、瞬間的にオンオフが繰り返されますが、DEBOUNCE時間に満たなければ変化は無視されます。オンにしろオフにしろ信号が変化するごとにDEBOUNCE時間のタイマーをスタートさせ、バウンスが起こるごとにタイマーはリセットされるため、バウンスしている間は入力は無効なわけです。そして、接点の跳ね返りが落ち着いてバウンスがなくなるとmatrix.cで受け取るデータに変化がなくなり、やがてDEBOUNCE時間が経過し、めでたく入力が発生します。キーリリースの際も同じことが行われます。

この方式はたいていの場合うまく動作します。ところが、冒頭のようにおかしくなったスイッチではバウンスが除去しきれずに問題となってきます。具体的な症状としては、文字がkkonnnakkannjjjjiのように二重三重になってしまうというものです。なお、まったく入力されない場合、たいていは数十回ほどオンオフを繰り返しているうちに反応する(チャタリングする)ようになりますが、まったく入力が起こらない場合は接点不良が致命的かもしれません。その際は取り外して接点リーフに接点復活剤をかけるか、それが面倒な場合はスイッチの隙間から接点復活剤をシューっとするという荒業もあります(ただしプレートやケースなどにまで被害が及んでも当方は関知しませんのであしからず)。

チャタリングが確認された場合、多くの人はまずDEBOUNCEを調整してみることになるでしょう。デフォルトの倍となる10ms、あるいは20msにしてみます。軽度なバウンス症状の場合はこれで解決するかもしれません。しかし、重度の場合は解決しません。それではとさらにBOUNCEの数値を50ms、100msと増やしていくと、今度は別の問題が生じてきます。

sym_defer_gのコードを見てみましょう。

https://github.com/qmk/qmk_firmware/blob/master/quantum/debounce/sym_defer_g.c  

				
					/*
Copyright 2017 Alex Ong<the.onga@gmail.com>
Copyright 2021 Simon Arlott
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
Basic global debounce algorithm. Used in 99% of keyboards at time of implementation
When no state changes have occured for DEBOUNCE milliseconds, we push the state.
*/
#include "matrix.h"
#include "timer.h"
#include "quantum.h"
#ifndef DEBOUNCE
#    define DEBOUNCE 5
#endif

#if DEBOUNCE > 0
static bool         debouncing = false;
static fast_timer_t debouncing_time;

void debounce_init(uint8_t num_rows) {}

void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
    if (changed) {
        debouncing      = true;
        debouncing_time = timer_read_fast();
    }

    if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) {
        for (int i = 0; i < num_rows; i++) {
            cooked[i] = raw[i];
        }
        debouncing = false;
    }
}

bool debounce_active(void) { return debouncing; }

void debounce_free(void) {}
#else  // no debouncing.
#    include "none.c"
#endif
				
			

とても単純な方式です。マトリクスが変化してから一定時間変化がなければその時点(一定時間経過後の)のマトリクスデータを受け入れます。

いま、koと打ったとしましょう。
このときのデバウンス処理を含む正常な入力の流れはこうです。

(人)kのキープレス→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオン→(人)kのキーリリース→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオフ

(人)oのキープレス→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオン→(人)oのキーリリース→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオフ

チャタリングが発生するときは上記のようにはならないわけです。たとえばkkのようにダブって入力されてしまう際は次のような流れになります。

(人)kのキープレス→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオン→(人)kのキーを押したままなのにバウンスによって接点が離れる→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオフ→(人)まだ押したままなのでバウンスが戻って接点が短絡する→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオン→(人)kのキーリリース→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりキーオフ

実際にチャタリングが起こった際の入力状況を以下に示します。ここではAキーがチャタリングによって1ストロークに対して3回入力されてしまっています。1回目のキーオンは32ms、2回目が15ms、見切れていますが3回目は32msです。

ちなみに、キースイッチやユーザーのタイピング、あるいは選択したデバウンスタイプにもよるところが大きいものの、正常なキー入力では50ms程度となるように思います。

次に、チャタリング解消のためにDEBOUNCE時間を増やしていくと、どうなるでしょう?
キープレスしている時間がDEBOUNCE時間よりも短くなると、そもそもキーオンが起こりません。おそらく50msを超えたあたりから使い物にならなくなります。次のようにkのリリース前にoのプレスが先行しても同じです。

(人)kのキープレス→DEBOUNCEタイマースタート→(人)oのキープレス→DEBOUNCEタイマースタート→(人)kのキーリリース→DEBOUNCEタイマースタート→(人)oのキーリリース→キーオンが発生しない    

また、キープレスの時間がDEBOUNCE時間よりも長く、正常に入力出来るとしても、DEBOUNCE時間を過ぎるまではキーオン処理はなされないため、PCなど接続したホストにおける実際の入力までにDEBOUNCE時間の分だけタイムラグが発生します。これはとてもストレスです。

それに、次のようにキーの取りこぼしも起こり得ます。

(人)kのキープレス→DEBOUNCEタイマースタート→(人)oのキープレス→DEBOUNCEタイマースタート→(人)kのキーリリース→DEBOUNCEタイマースタート→(oのキーは押されたまま)DEBOUNCE時間経過によりoのキーのみキーオン→(人)oのキーリリース→DEBOUNCEタイマースタート→DEBOUNCE時間経過によりoのキーオフ

この場合はkを取りこぼし、oのみホストに入力されることになります。

デフォルトのアルゴリズムの問題は、タイマーが全キーに対して1つしかなく、しかも耐ノイズ性のためキープレス/リリース発生から一定時間が経つまで変化が確定されない点です。個人的には、通常の機械スイッチを前提とした場合にはスイッチの入り切り以外にバウンスやノイズが発生することはまず稀であり、一般的なキーボードはそれほどインピーダンスが高い回路でもないので外来ノイズを危惧する必要性は薄いのではないかと思います(ノイズが問題になるハード実装もあり得ますので、こうしたアルゴリズムが用意されていることに異論はありませんし、そもそもスイッチ接点に問題がなければこのアルゴリズムは問題なく機能します)。

タイマーをキーボード全体で共有することについてはかなり厳しい印象があります。これは、一度にプレス/リリース(キーが押された/離された瞬間からDEBOUNCE時間を経てキーオン/オフが確定されるまでのプロセス)が起こるキーが1つに限られているという前提に立つものであって、DEBOUNCE時間が数十msに達するようになると弊害が大きくなってきます。    

おすすめのデバウンスアルゴリズム

QMKでは上記デフォルトのアルゴリズム以外にも複数のアルゴリズムが用意され、ユーザーはrules.mkにより好みのものを選択することが出来るようになっています。現時点のラインナップの中で筆者がおすすめするのはsym_eager_pkというアルゴリズムです。これは、キープレス/リリースが起こった瞬間にキーオン/オフを発行し、その後DEBOUNCE時間を過ぎるまでをマスクします。しかも、タイマーがキーごとに用意されるため、タイマーを共有する場合に問題となる取りこぼしが起こりません。この方式であれば、たとえばDEBOUNCEを50ms以上に設定したとしても、タイムラグが発生することもないのです。よって、接点不良によりバウンスが長時間(10ms以上)に渡って生じるようになったスイッチだとしても、正常に、そしてストレスなしに入力することが可能になるのです。

このデバウンスタイプを使用するには、まずrules.mkに以下の行を加えます。

				
					DEBOUNCE_TYPE = sym_eager_pk

				
			

また、config.hに下記の指定をしてください。ここでは50としていますが、もっと長くしても大丈夫です。人類は同じキーを1秒間にそう何度も叩けません。

				
					#define DEBOUNCE 50
				
			

優れたアルゴリズムであるのにデフォルトではないのはなぜかというと、それは多大なリソースを食うからにほかなりません。各キーごとにタイマーを確保しているため、たとえば60キーのキーボードなら符号なし8bit整数、サイズ60の配列を必要とします。コードもより凝ったものになりますので、プログラムメモリもそれなりに消費します。使用するMCUがAVRの場合は、無視できない場合もあるでしょう。とはいえ、リソース面で許せるならば試してみることをおすすめします。とくにバウンスが酷くで困っている方は、多くの場合、スイッチ交換なしで解消することが出来ますのでぜひ!

この原稿はバウンスがひどかったALETH42に、sym_eager_pkとDEBOUNCE 50を設定したファームで快適に書きました。

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn