単一のスイッチ入力を処理するのに便利な簡単なデバウンスルーチンがありますか?
これは、OSのないシンプルなベアメタルシステムです。
プロセッサの速度が変動する可能性があるため、特定のカウントのループ構造は避けたいと思います。
単一のスイッチ入力を処理するのに便利な簡単なデバウンスルーチンがありますか?
これは、OSのないシンプルなベアメタルシステムです。
プロセッサの速度が変動する可能性があるため、特定のカウントのループ構造は避けたいと思います。
多くの場合、最も単純なソリューションが最適です。N ミリ秒ごと (スイッチによっては 10 ~ 50 の間) にスイッチの状態を読み取るだけで、常にうまくいくことがわかりました。
私は壊れていて複雑なデバウンス ルーチンを取り除き、単純なスロー ポーリングに置き換えました。結果は常に十分に良好でした。
これを実装するには、システムに単純な定期的なタイマー割り込みが必要です (RTOS がサポートされていないと仮定します) が、ベア メタルでのプログラミングに慣れている場合は、それを調整するのは難しくありません。
この単純なアプローチでは、状態の変化の検出に遅延が追加されることに注意してください。スイッチが新しい定常状態に到達するのに T ミリ秒かかり、X ミリ秒ごとにポーリングされる場合、プレスを検出するための最悪の場合の遅延は T+X ミリ秒です。ポーリング間隔 Xは、最悪の場合のバウンス時間 T より大きくなければなりません。
ここでこれについて多くを学ぶことができると思います: http://www.ganssle.com/debounce.pdf
可能であれば常にハードウェアでこれを行うのが最善の策ですが、ソフトウェアについてもいくつかの考えがあります。
TFA の簡単なコード例:
#define CHECK_MSEC 5 // Read hardware every 5 msec
#define PRESS_MSEC 10 // Stable time before registering pressed
#define RELEASE_MSEC 100 // Stable time before registering released
// This function reads the key state from the hardware.
extern bool_t RawKeyPressed();
// This holds the debounced state of the key.
bool_t DebouncedKeyPress = false;
// Service routine called every CHECK_MSEC to
// debounce both edges
void DebounceSwitch1(bool_t *Key_changed, bool_t *Key_pressed)
{
static uint8_t Count = RELEASE_MSEC / CHECK_MSEC;
bool_t RawState;
*Key_changed = false;
*Key_pressed = DebouncedKeyPress;
RawState = RawKeyPressed();
if (RawState == DebouncedKeyPress) {
// Set the timer which allows a change from current state.
if (DebouncedKeyPress) Count = RELEASE_MSEC / CHECK_MSEC;
else Count = PRESS_MSEC / CHECK_MSEC;
} else {
// Key has changed - wait for new state to become stable.
if (--Count == 0) {
// Timer expired - accept the change.
DebouncedKeyPress = RawState;
*Key_changed=true;
*Key_pressed=DebouncedKeyPress;
// And reset the timer.
if (DebouncedKeyPress) Count = RELEASE_MSEC / CHECK_MSEC;
else Count = PRESS_MSEC / CHECK_MSEC;
}
}
}
入力をデバウンスするために多数決方式を使用しました。単純な3ステートシフトレジスタタイプのデータ構造を設定し、各サンプルをシフトして、3つのうち最良の2つを「正しい」値として取得します。これは明らかに、ハードウェアを実際に読み取るために使用される方法に応じて、割り込みハンドラーまたはポーラーのいずれかの機能です。
ただし、最善のアドバイスは、フレンドリーなハードウェア設計者に値を「ラッチ」してもらい、到達したときにこの値をクリアできるようにすることです。
すべてのタイプのボタンで機能する単一の単純なソリューションはありません。ここで誰かが何を使うように言ったとしても、あなたのハードウェアで試してみて、それがどれだけうまく機能するかを確認する必要があります. そして、何が起こっているのかを本当に理解していることを確認するために、オシロスコープの信号を見てください。Rich B の pdf へのリンクは、開始するのに適した場所のようです。
デバウンスするには、特定のしきい値を下回るスイッチアップを無視する必要があります。スイッチアップ時にハードウェアタイマーを設定するか、定期的な割り込みを介して設定されたフラグを使用できます。
それを回避できる場合、ハードウェアでの最善の解決策は、スイッチに 2 つの異なる状態を持たせ、その間に状態を持たせないことです。つまり、各極がフリップフロップの R または S ラインに給電する SPDT スイッチを使用します。そのように配線すると、フリップフロップの出力はデバウンスされます。
私が通常行うことは、入力レジスタの幅に 3 つほどの変数を用意することです。通常は割り込みからのポーリングごとに、値を 1 つ上にシフトして、新しいサンプルに道を空けます。次に、サンプルの論理積を設定し、逆論理和をクリアすることによって、デバウンスされた変数を形成します。すなわち(テストされていない、記憶から)
input3 = input2;
input2 = input1;
input1 = (*PORTA);
debounced |= input1 & input2 & input3;
debounced &= (input1 | input2 | input3);
次に例を示します。
debounced には xxxx があります (「x」は「なんでも」)
input1 = 0110,
input2 = 1100,
input3 = 0100
上記の情報で、
ビット 2 を 1 に、ビット 0 を 0 に切り替えるだけで済みます。残りはまだ「バウンス」しています。
debounced |= (0100); //set only bit 2
debounced &= (1110); //clear only bit 0
その結果、デバウンス = x1x0 になります。
ハードウェア レベルでは、基本的なデバウンス ルーチンは、物理キー (またはスイッチ) の動作の次のセグメントを考慮に入れる必要があります。
キーが静かに座っている -> 指がキーに触れて押し下げ始める -> キーがトラベルの底に到達し、指がそこに留まる -> 指がキーを放し始め、バネがキーを押し上げる -> 指がキーを離し、キーが静止するまで少し振動する
これらのすべての段階では、2 つの金属片をこすり、こすり、ぶつかり合わせて、ミリ秒単位で電圧を 0 から最大値まで上下に揺らします。そのため、すべての段階で電気ノイズが発生します。
(1) 湿度、振動、温度変化などの環境問題により、キー接点の電圧変化が原因で、キーに触れていないときにノイズが発生します。
(2) キーを押したときに発生するノイズ
(3) キーを押したときのノイズ
(4) 離鍵時のノイズ
(5) 離鍵後の振動音
キーが人によって押されていることを基本的に推測するアルゴリズムは次のとおりです。
「押されている可能性がある」、「確実に押されている」、「確実に押されていない」、「押されていない可能性がある」などのキーの状態を読み取ります (実際には確実ではありません)。
キーが「おそらく」押されている間 (ハードウェアを扱う場合、これはあるしきい値より大きい電圧サンプルです)、「絶対に」押されていない (しきい値電圧より低い) までループします (これは初期化であり、ノイズが発生するのを待ちます)。静止、「可能性がある」および「絶対にない」の定義は、特定のアプリケーションに依存します)
キーが「絶対に」押されていない間、キーが「押される可能性がある」までループする
キーが「押されている可能性がある」場合、キーの状態のループとサンプリングを開始し、キーが「押されている可能性がある」時間を追跡します-キーが「押されていない可能性がある」または「確実に押されていない」一定時間前の状態で、手順を再開します - 選択した特定の時間 (ミリ秒数) で (通常はさまざまな値を試して)、サンプル値がノイズによるものではなくなったと判断しますが、ノイズによる可能性が非常に高いと判断しますキーが実際に人間の指で押されているために発生し、値「押された」を返します
while(keyvalue = maybepressed){
//loop - wait for transition to notpressed
sample keyvalue here;
maybe require it to be "notpressed" a number of times before you assume
it's really notpressed;
}
while(keyvalue = notpressed){
//loop - wait for transition to maybepressed
sample keyvalue
again, maybe require a "maybepressed" value a number of times before you
transition
}
while(keyvalue=maybepressed){
presstime+=1;
if presstime>required_presstime return pressed_affirmative
}
}
return pressed_negative
ganssle.com のアルゴリズムにはバグがある可能性があります。次の行の印象があります
static uint8_t Count = RELEASE_MSEC / CHECK_MSEC;
読むべき
static uint8_t Count = PRESS_MSEC / CHECK_MSEC;
最初のプレスを正しくデバウンスするため。
全体の概念は、Jack Ganssle によってよく説明されています。元の質問への回答として投稿された彼のソリューションは非常に優れていますが、その一部がどのように機能するかがはっきりしていません。
スイッチのバウンスに対処するには、主に次の 3 つの方法があります。 - ポーリングの使用 - 割り込みの使用 - 割り込みとプーリングの組み合わせ。
私は主に低電力または低電力になりがちな組み込みシステムを扱っているため、統合するという Keith の回答は私にとって非常に合理的です。
機械的に安定した 1 つの位置を持つ SPST プッシュ ボタン タイプのスイッチを使用する場合は、割り込みとプーリングの組み合わせを使用して機能するソリューションをお勧めします。
このように: GPIO 入力割り込みを使用して、最初のエッジ (立ち下がりまたは立ち上がり、非作動スイッチ状態の反対方向) を検出します。GPIO 入力下 ISR 検出に関するフラグを設定します。
時間を測定するための別の割り込み (つまり、汎用タイマーまたは SysTick) を使用して、ミリ秒をカウントします。SysTick のインクリメントごと (1 ミリ秒):
IF buttonFlag が true の場合、関数を呼び出して、プッシュ ボタンの状態をポーリングします (ポーリング)。
これを N 回連続して SysTick をインクリメントしてから、フラグをクリアします。
ボタンの状態をポーリングするときは、M の連続した読み取り値が同じ、Z を超える平均、状態の場合のカウント、最後の X の読み取り値が同じなど、ボタンの状態を決定するロジックを使用します。
このアプローチは、N SysTick のインクリメント後にボタンのポーリングが行われないため、割り込み時の応答性と消費電力の削減の恩恵を受けると思います。さまざまな割り込みの間に複雑な割り込み変更がないため、プログラム コードはかなり単純で読みやすいはずです。
ボタンを「離す」必要があるか、長押しを検出する必要があるか、ボタンを離したときにアクションが必要かなどを考慮してください。ボタンを離したときのボタン アクションは好きではありませんが、そのように機能するソリューションもあります。