Pythonでデバイスを制御しよう

第2回:プッシュボタンを扱う(2) 誤動作の原因となるチャタリングを防止する

プッシュボタンを扱う(1) Pythonでプッシュボタンを扱うには

 

「プッシュボタンを扱う」のパート2では、タクトスイッチなどのボタンを扱う際に誤動作の原因となり得るチャタリングについて解説します。
ラズパイなどのマイコンボードに人間の意思を伝えるためにプッシュボタンは欠かせません。プッシュボタンは、指で押せばラズパイに信号を伝えられるシンプルな部品であり、今さら説明の必要もないでしょう。ところがこのボタン、プログラムから見れば厄介な存在で、注意しなければ誤動作の原因となってしまいます。
この原因の一つが「チャタリング」と呼ばれる現象です。安定した動作が求められる電子機器ではチャタリング対策は必須です。
今回は「チャタリングとは何か?」と基本的なことから、ソフトウェアで行える対策について解説します
本記事ではプッシュボタンにタクトスイッチを使用します。タクトスイッチは小型で入手性も良く、基板やブレッドボードに直接実装できるので電子工作にとどまらず電子機器で広く使用される電子部品です。

control-device-with-python-vol2-01-01

 

目次

  1. タクトスイッチの構造とチャタリング
    1. 1.1. タクトスイッチとは?
    2. 1.2. チャタリングとは?
    3. 1.3. チャタリングが発生した時の入力信号の波形
  2. プログラムへのチャタリングの影響を確認
    1. 2.1. 本プログラムの構成
    2. 2.2. チャタリングを確認するPythonのプログラム p2-1.py
    3. 2.3. プログラムの説明
    4. 2.4. プログラムの実行と検証
  3. ソフトウェアでできるチャタリングの防止策
  4. まとめ

 

タクトスイッチの構造とチャタリング

まずはプッシュボタンの代表と言えるタクトスイッチの構造について説明します。

タクトスイッチとは?

ラズパイなどのマイコンボードに人間の意思を伝えるためにプッシュボタンは欠かせません。中でもタクトスイッチは小型で入手性も良く、基板やブレッドボードに直接実装できるので電子工作にとどまらず電子機器で広く使用される電子部品です。
本記事ではプッシュボタンの例としてタクトスイッチを使用しますが、多くはほかのタイプのプッシュボタンにも当てはまる内容です。タクトスイッチは「タクタイルスイッチ」と呼ばれる場合もありますが、本記事では「タクトスイッチ」に統一します。

タクトスイッチは以下のような外観・構造を持つプッシュボタンです。

control-device-with-python-vol2-02_01

タクトスイッチの外観・分解写真

 

上から見ると、正方形の本体の中央に丸い突起が見られます。この丸い突起を押す/離すことで接点が閉じ/開き(回路がON/OFF)ボタンとして機能します。丸い突起は「プランジャ」と呼ばれます。
プランジャを囲むように四隅に黒い小さな突起があります。これは銀色の鉄板(カバー)を本体に固定するための樹脂でボタンの動作には関係ありません。タクトスイッチによっては別の方法でカバーを本体に固定します。
このような形状のタクトスイッチは四隅の小さな突起を削り落とすことで分解できます(分解すると元には戻せません)。
本体の大部分を占める四角い黒色の樹脂は「ベース」と呼ばれます。カバープランジャを取り除くとベースの中には丸い金属の板が入っています。この板は中央が盛り上がった板バネ(反転バネ)となっていて、プランジャを押し込む力によって変形、下に凸となります。
下に凸となった板バネベースに取り付けられた接点(両端と中央の間)を閉じることで、ベースから出る端子間に電流が流れます。

control-device-with-python-vol2-02_02

タクトスイッチのプランジャの働き

 

本体のサイズが5×5ミリ前後のタクトスイッチが普及しています。

一般的なタクトスイッチの端子は4本です。これは基板に取り付けた際に本体を安定させるためであり、2本ずつ(2本×2本)内部で接続されています。この構造は回路図上のタクトスイッチを表すシンボルにも表示されています。

control-device-with-python-vol2-02_03

タクトスイッチの回路図

 

通常のプッシュボタンの回路図は上図の右のように2個の接点(端子)と、接点間を橋渡しするバーで構成されます。
一方のタクトスイッチの回路図では上図の中央のように2個の接点に4個の端子がつながります。これによると2個の端子間はボタンの状態に関係なく接続されていることがわかります。
この回路図は正式なものではありません。表記が異なる場合もあるので注意してください。

以上が一般的なタクトスイッチの外観と構造です。本体のサイズを含めプランジャの形状や内部構造、端子の違いなど、さまざまなバリエーションがあります。また端子間の電気的な接続が異なる場合もあるので、設計時は製品のデータシートを確認してください。

チャタリングとは?

タクトスイッチに限らず、ほとんどのプッシュボタンは接点を閉じたり開いたりするために内部でバネを使用しています。
こうした機械バネは想定した形状にならない「たわみ」や、静止時から動作を始める時/動作時から静止する時に振動する「バウンス」と呼ばれる特性を持っています。
そのため接点にバネを使用するプッシュボタンは、接点が接する瞬間、または接点が離れる瞬間に理想的な動きにならず、何度か接点が接する/離れる動作を微小時間内で繰り替えしています。
この一瞬のうちに数回起こるON/OFFがチャタリングです。
プログラムの実行速度はこの機械的な振動より早いため、プログラムはチャタリングさえもボタンが押された/離されたと検出してしまいます。

control-device-with-python-vol2-02_04

タクトスイッチの接点の様子

 

チャタリングが発生した時の入力信号の波形

ラズパイに接続したタクトスイッチ(プッシュボタン)を押す/離す 時のGPIOピンに加わる理想的な電圧は次の図の通りです。

control-device-with-python-vol2-02_05

理想的な波形

 

左はプッシュボタンをプルアップして使用した波形、右はプルダウンした時の波形です。
通常、プッシュボタンを
GPIOピン ≫ プッシュボタン ≫ グランド」
と接続する際はプルアップして、また
「電源 ≫ プッシュボタン ≫ GPIOピン
と接続する際はプルダウンして使用します。
プログラムが読み取る信号はプルアップ/プルダウンによってHIGH/LOWが逆転します。
理想としてはプッシュボタンを押す/離す瞬間に「HIGHからLOW」「LOWからHIGH」に一瞬で切り替わる波形が望まれます。

一方、チャタリングを考慮すると、スイッチは次のような波形になります。

control-device-with-python-vol2-02_06

チャタリングの波形

 

左はプッシュボタンをプルアップして使用した波形、右はプルダウンした時の波形です。
このように、プッシュボタンを押す/離す瞬間に「HIGHからLOW」「LOWからHIGH」と数回信号が切り替わります。
プログラム内で
「信号がHIGHからLOWに切り替わったら処理を起動する」
とコーディングしていると、チャタリングによって数回発生する信号の「HIGHからLOW」の変化のたびに処理が起動してしまいます。

プログラムを誤動作させる要因にはチャタリングだけでなくノイズの影響もあります。ノイズは「想定していない信号」全般を指す言葉なので「チャタリングノイズの一種」とする考えもありますが、一般的にある程度原因がはっきりとしているチャタリングは別に扱います。
ノイズは「周囲に高電圧を扱う機器が多い」「微弱な信号をセンシングする」といった状況であれば考慮が必要ですが通常の電子工作ではあまり問題になりません。そのため本記事ではチャタリングに絞って説明を進めます。

 

プログラムへのチャタリングの影響を確認

それでは、実際にPythonのプログラムを作成してチャタリングがプログラムに与える影響を確認します。

本プログラムの構成

以下がチャタリングを確認するPythonのプログラムの構成です。

control-device-with-python-vol2-02_07

システム構成

 

このプログラムの動作にはプッシュボタン1個とライブラリ「RPi.GPIO」が必要です。パート1を参考に配線とインストールを行ってください。

このプログラムはイベント駆動の手法を使っています。プログラムの詳細はパート4の『イベント駆動でプッシュボタンに反応する』で解説します。
ここでは

  • ボタンが押されたら信号がHIGHからLOWに切り替わる
  • 信号がHIGHからLOWに切り替わったら関数「button_pressed()」が呼び出される
  • button_pressed()関数では「日付時刻(マイクロ秒単位)+メッセージ」を画面に表示する

この3点が読み取れれば問題ありません。

チャタリングを確認するPythonのプログラム p2-1.py

以下に示すのは、チャタリングがどのようにプログラムに影響を与えるかを確認するPythonのプログラムです。テキストエディタで以下のプログラムを入力してp2-1.pyの名前で保存してください。

#!/usr/bin/env python

import sys
import time
import datetime

import RPi.GPIO as GPIO


# ボタンは"GPIO5"に接続
BUTTON = 5


# 主処理
def main():
    
    try:
        # 操作対象のピンは「GPIOn」の"n"を指定する
        GPIO.setmode(GPIO.BCM)
        # BUTTONがつながるGPIOピンの動作は「入力」「プルアップあり」
        GPIO.setup(BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        
        # 立ち下がり(GPIO.FALLING)を検出する(プルアップなので通常時1/押下時0)
        GPIO.add_event_detect(BUTTON, GPIO.FALLING, bouncetime=1)
        # イベント発生時のコールバック関数を登録
        GPIO.add_event_callback(BUTTON, button_pressed)
        
        # 無限ループ
        while True:
            # 主処理は何もしない
            time.sleep(1)
        
    # キーボード割り込みを捕捉
    except KeyboardInterrupt:
        print("例外'KeyboardInterrupt'を捕捉")
    
    print("処理を終了します")
    
    # GPIOの設定をリセット
    GPIO.cleanup()
    
    return 0


# ボタンAが押された時に呼び出されるコールバック関数
# gpio_no: イベントの原因となったGPIOピンの番号
def button_pressed(gpio_no):
    
    # メッセージを表示
    print_message("ボタンが押されました")


# ターミナル上に「日付 時刻.マイクロ秒: メッセージ」を表示する関数
# message: 表示する「メッセージ」
def print_message(message):
    
    # 現在の日付時刻を取得して「年-月-日 時:分:秒.マイクロ秒」にフォーマット
    now = datetime.datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S.%f")
    
    # 引数で送られたメッセージを表示
    print("{}: {}".format(timestamp, message))


if __name__ == "__main__":
    sys.exit(main())

プログラムの説明

このプログラムはイベント駆動の手法を使っています。プログラムの詳細はパート4「イベント駆動でプッシュボタンに反応する(制御手法)」で解説します。ここで説明しなかった部分についてはプログラム内のコメントを参考に大まかな処理の流れを把握してください。

プログラムが起動すると

  1. GPIOピンの番号はGPIOnのnを指定する
  2. ボタンがつながるGPIO5の動作は「入力」「プルアップあり」
  3. GPIO5の信号がHIGHからLOWに切り替わったら(立ち下がり)反応する
  4. 立ち下がりを検出したら関数「print_message()」を呼び出す
  5. 無限ループ

と処理が進み、以降は立ち下がりの検出でprint_message()関数が呼び出されるのを待機します。

関数「print_message()」で実際に画面上にメッセージを表示します。メッセージは引数として受け取り、出力時、メッセージの前にタイムスタンプを付加します。
付加されるタイムスタンプは
「「年-月-日 時:分:秒.マイクロ秒」」
のフォーマットです。(100,000マイクロ秒 = 100ミリ秒 = 0.1秒)

プログラムの実行と検証

プログラムを保存したディレクトリに移動したら以下のコマンドでプログラムを起動してください。

./p2-1.py

プログラムの実行中に、ラズパイに接続したタクトスイッチをゆっくりと何度か押してください。

プログラムを終了するにはキーボードの「Ctrl+c」(「Ctrl」キーを押しながら「c」キー)を押してください。ラズパイのOSのベースであるLinuxでは「Ctrl+c」はプログラムを終了させるためのショートカットキーのような働きをします。

以下のようなメッセージがターミナル上に表示されるはずです。

2021-02-23 15:49:35.041597: ボタンが押されました
2021-02-23 15:49:36.293192: ボタンが押されました
2021-02-23 15:49:37.304258: ボタンが押されました
2021-02-23 15:49:38.230100: ボタンが押されました
2021-02-23 15:49:39.228648: ボタンが押されました
2021-02-23 15:49:40.497885: ボタンが押されました
2021-02-23 15:49:40.827800: ボタンが押されました
^C例外'KeyboardInterrupt'を捕捉
処理を終了します

今回のプログラムの想定する動作は
「ボタンを1回押すごとに1行メッセージが表示される」
です。
チャタリングはボタンを押す際と離す際の両方で発生する可能性があり

a. ボタンを1回押すとメッセージが2行以上表示される
b. ボタンを離すとメッセージが表示される

場合はチャタリングの影響を受けています。

control-device-with-python-vol2-02_08

チャタリングのプログラムへの影響

 

筆者の環境では、ボタンを離す際にメッセージが表示される事例が多くありました。
上記「a.」「b.」の誤動作が見られない場合も油断は禁物です。最近ではタクトスイッチの性能が向上しチャタリングは減少したと言われています。しかし接点の動作に機械バネを使用している限りチャタリングをゼロにすることは難しいでしょう。

以降はソフトウェアで行えるチャタリング対策を紹介します。

 

ソフトウェアでできるチャタリングの防止策

チャタリング対策はハードウェア、ソフトウェアの両方で実施する方法があります。本記事ではソフトウェアでのチャタリングの防止策を紹介します。

ソフトウェアで対処する場合は「タイマー」を使用するのが一般的です。

control-device-with-python-vol2-02_09

タイマーで立ち上がり/立ち下がりの検出を無視する

 

立ち上がり/立ち下がりを検出した後にタイマーなどで一定の時間「検出を停止」します。通常、チャタリングしやすい期間は1ミリ秒から数10ミリ秒と言われています。そこでタイマーなどを使って100ミリ秒(0.1秒)程度、立ち上がり/立ち下がりを検出しないようにするとチャタリングによる誤動作を防げます。

タイマーを使って自分で「100ミリ秒程度、立ち上がり/立ち下がりを検出しない」コーディングもできますが、本記事で使用するPythonのライブラリ「RPi.GPIO」には、こうした処理が組み込まれています。

ライブラリ「RPi.GPIO」が持つ機能を使ってチャタリングを防止するのは簡単です。先ほどのソースコードを以下のように修正してください。

# 立ち下がり(GPIO.FALLING)を検出する(プルアップなので通常時1/押下時0)
GPIO.add_event_detect(BUTTON, GPIO.FALLING, bouncetime=100)

先ほどのプログラムでは
「bouncetime=1」
としたところを
「bouncetime=100」
と変更します。
bouncetimeが「立ち上がり/立ち下がりを検出しない時間」であり、100は「100ミリ秒(0.1秒)」を意味します。
プログラムについてはパート4の『イベント駆動でプッシュボタンに反応する』でも解説します。

以上のようにライブラリ「RPi.GPIO」を使用すれば簡単にソフトウェアでチャタリングを防止できます。
ただし注意点があります。この方法で対処できるのはボタンを押した時のチャタリングであり、ボタンを離した時のチャタリングを無視するにはもう一工夫必要です。
パート3の『状態遷移を使った制御』では、想定するボタンの動き(状態)をプログラムで監視することでボタンを押した時/離した時のチャタリングを防ぎます。

 

まとめ

タクトスイッチをはじめとするプッシュボタンの多くは、接点の開閉に機械バネを使用しています。このようなスイッチはバネのたわみやバウンスなどにより、押す/離す際に不要なON/OFFを繰り返すチャタリングを起こし、プログラムを誤動作させます。
GPIOピンの制御に使用するRPi.GPIOライブラリにはチャタリング対策に使用できる機能があらかじめ組み込まれていますが、「チャタリングとは何か?」といった基本的なことをマスターしていないと、その機能も有効に活用できません。
より安定したプログラムの動作が必要な場合は、RPi.GPIOライブラリの持つ機能では不十分な場合もあります。次回のパート3ではポーリング制御状態遷移の手法を使ってこの問題に取り組みます。

 

今回の連載の流れ

プッシュボタンを扱う(1) Pythonでプッシュボタンを扱うには
プッシュボタンを扱う(2) 誤動作の原因となるチャタリングを防止する(今回)
プッシュボタンを扱う(3) ポーリング制御でプッシュボタンに反応する
プッシュボタンを扱う(4) イベント駆動でプッシュボタンに反応する
プッシュボタンを扱う(5) プログラムをストップさせる例外を捕捉して処理する
プッシュボタンを扱う(6) 「raise Exception」で積極的に例外を利用する

エレクトロニクスやメカトロニクスを愛するみなさんに、深く愛されるサイトを目指してDevice Plusを運営中。

https://deviceplus.jp

RoboMaster 2019 参戦ファーストステップガイド