離島プログラマの雑記

島根県の離島、隠岐・西ノ島に移住して子育て中のフリープログラマです。

Raspberry Pi で GPIO

ちょっと工作で Raspberry Pi でモータを制御する必要があったので、GPIO(General Purpose Input/Output、汎用入出力)制御について調べたりしました。そもそもGPIOとはなんぞや、という話はここでは書きません。

前提条件

この記事では以下の環境で開発しています。

  • Raspberry Pi model B を使用
  • OS は Raspbian がインストール済み
  • 言語はPythonを使用(使用経験なし^^;)
  • ライブラリはRPi.GPIO(0.5.11)を使用

ライブラリの公式wiki( raspberry-gpio-python / Wiki / Home )にサンプルコードなどがあります。この記事では主にここの内容を参考に書いています。ちなみにRuby版もあるようです。

用意するもの

セットアップ

GPIOライブラリのインストール

Python の GPIO制御用ライブラリ(RPi.GPIO)をインストールします。 ただし、Raspbian にはデフォルトでインストール済みです。

$ sudo apt-get install python-rpi.gpio
スクリプトファイルの作成

好きなエディタで適当な .py 拡張子のファイルを作成します。

$ nano gpio_test.py
ライブラリのインポート

作成したファイルにGPIOライブラリのインポート文を記述します。

import RPi.GPIO as GPIO
チャンネルの指定方法

RPi.GPIO では、P1から始まるボード上の物理的なピン番号、もしくはチップから見たGPIOピンの番号(GPIO12など)のいずれかをチャンネルとして抽象化して扱います。チャンネルの指定方法は以下から選択できますが、いずれかのモード指定は必須です。

  • GPIO.BOARD

    P1から順番にボードに並んでいる順でピン番号をチャンネルとして扱います。 物理的に見たままのピン配置なので、ジャンパワイヤでブレッドボードにつなぐ場合はこちらがわかりやいです。 ただし、Raspberry Pi のモデルによってピンに割り当てられた機能は異なるため、プログラムの互換性はなくなります。

  • GPIO.BCM

    チップ依存のGPIOピン名称をチャンネルとして扱います。Raspberry Pi のモデルやリビジョンによって物理ピンの場所が異なります。 GPIOピン全体をブレッドボードに引き出すアダプタを使用する場合は、大抵チップ依存のGPIOピン名称が書かれているのでこちらが良さそうです。

例えばGPIO.BCMに設定する場合は以下のように指定します。

GPIO.setmode(GPIO.BCM)

どちらのモードに設定されているかは以下の関数で取得できます。

mode = GPIO.getmode()
チャンネルのモード設定

使用するチャンネルはモードを設定する必要があります。 以下のように指定します。

GPIO.setup([チャンネル], [GPIO.IN または GPIO.OUT])

例えば、チャンネル18を出力モードに指定する場合は以下の通りです。

GPIO.setup(18, GPIO.OUT)

複数チャンネルをまとめて設定することもできます。チャンネル11/12を出力モードに指定する場合は以下のように書きます。

chan_list = [11,12] 
GPIO.setup(chan_list, GPIO.OUT)

GPIO出力

Raspberry Pi のGPIO出力はデジタル出力とアナログ出力(PWM)が可能です。 ただしアナログ出力ができるピンはどのモデルでも固定でGPIO18ピン(番号体系がGPIO.BCMの場合)のみです。 なお、ピンの最大出力電流は50mAなので、これを超えるような負荷は接続できません。 また、入出力電圧は3.3Vなので、3.3Vを超える電圧を入力すると壊れる可能性があります。

デジタル出力

デジタル出力ではピンの電圧をHIGH(3.3V)、またはLOW(0V)に設定できます。 以下のように指定します。

GPIO.output([チャンネル], [GPIO.LOW または GPIO.HIGH])

例えば、チャンネル18をHIGHに指定する場合は以下のように書きます。

GPIO.output(18, GPIO.HIGH)

複数チャンネルをまとめて設定することもできます。 チャンネル11/12をHIGHに指定する場合は以下のように書きます。

chan_list = [11,12]
GPIO.output(chan_list, GPIO.HIGH) 

チャンネル11をHIGH、12をLOWに指定する場合は以下のように書きます。

chan_list = [11,12]
GPIO.output(chan_list, (GPIO.HIGH, GPIO.LOW)) 
アナログ出力

アナログ出力では周波数とデューティ比を指定して、モータ制御などに使うPWM制御ができます。

まずピンに対して周波数を設定してpwmオブジェクトを取得します。

pwm = GPIO.PWM([チャンネル], [周波数(Hz)])

次にpwmオブジェクトに対してデューティ比を指定して出力を開始します。

pwm.start([デューティ比])

例えば、ピン18に周波数1KHz、デューティ比50%でPWM出力する場合は以下のように書きます。

pwm = GPIO.PWM(18, 1000)
pwm.start(50)

途中で周波数を変更する場合は以下の関数を使用します。

pwm.ChangeFrequency([周波数(Hz)])

途中でデューティ比を変更する場合は以下の関数を使用します。

pwm.ChangeDutyCycle([デューティ比])

PWM出力を停止する場合は以下の関数を実行します。

pwm.stop()

スクリプト終了時にはちゃんと停止しておきましょう。

GPIO入力

ポーリング

GPIOからの入力をポーリングで読み取る場合は以下の関数を使用します。

GPIO.input(ピン番号)

戻り値にはGPIO.HIGH(または1、True)かGPIO.LOW(または0、False)が返ります。

例えば、ピン22の読み出しで処理を分岐する場合は以下のように書きます。

if GPIO.input(22):
    # ピン22がHIGHの場合
else:
    # ピン22がLOWの場合

また、GPIO.input() に pull_up_down パラメータを指定してやることで、プルアップ抵抗(GPIO.PUD_UP)またはプルダウン抵抗(GPIO.PUD_DOWN)を有効にできます。 プルアップ抵抗またはプルダウン抵抗が有効な場合、回路がつながっていない状態でもGPIO.HIGHまたはGPIO.LOWが読み出されます。 例えば、チャンネル22をプルダウンに設定する場合は以下のように初期化します。

GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)

なお、Raspbian ではデフォルトでピンごとにプルアップまたはプルダウンされた状態に設定されてるようです。 各ピンの機能や初期状態については RPi BCM2835 GPIOs - eLinux.org に細かくまとめられています。

割り込みとエッジ検出

エッジとは、電気信号が LOW から HIGH (立ち上がりエッジ) または HIGH から LOW (立ち下がりエッジ) の状態に変化する瞬間のことを指します。例えばプッシュボタンなどが押されたことを検知したい場合などに、状態変化をイベントとして通知する仕組みが用意されています。

エッジ検出待ち

wait_for_edge() を呼び出すと、立ち上がりエッジまたは立ち下がりエッジが検出されるまで待ち状態に入ります。 単なるポーリングでのエッジ検出待ちよりもCPU資源を消費しない点が有利です。

GPIO.wait_for_edge([チャンネル], [検出したいエッジ])

検出したいエッジの指定は以下の3種類です。

  • GPIO.RISING (立ち上がりエッジ)

  • GPIO.FALLING (立ち下がりエッジ)

  • GPIO.BOTH (立ち上がりまたは立ち下がりエッジ)

例えば、チャンネル18の立ち上がりエッジ検出待ちをする場合は以下のように書きます。

GPIO.wait_for_edge(18, GPIO.RISING)
エッジ検出イベントの取得

event_detected() を呼び出すと、前回の event_detected() 呼び出し以降のエッジ検出イベントの発生状態を取得できます。今現在のチャンネルの状態にかかわらず、エッジ検出イベントの発生を後から確認できるので、ゲームなどのメインループ中でボタンが押されていたことを知りたい場合などに便利です。使用する前にあらかじめ add_event_detect() でチャンネルにイベント通知登録が必要です。

イベントの通知登録

GPIO.add_event_detect([チャンネル], [検出したいエッジ])

イベント発生状態の取得

if GPIO.event_detected([チャンネル]):
    #エッジ検出イベント発生
else:
    #エッジ検出イベント未発生

例えば、チャンネル18の立ち上がりエッジ検出イベントが発生していたかどうかを知りたい場合は、以下のように書きます。

GPIO.add_event_detect(18, GPIO.RISING)
# 何らかの処理
if GPIO.event_detected(18)
    #エッジ検出イベント発生
else:
    #エッジ検出イベント未発生
コールバック関数

add_event_detect() にcallbackパラメータを指定するとコールバック関数を登録できます。エッジ検出イベントが発生すると、登録したコールバック関数が別のスレッドで実行されます。add_event_detect() を同じチャンネルに対して複数回呼び出すことで、複数のコールバック関数を登録することも可能ですが、コールバック関数を呼び出すスレッドは単一のため、各コールバック関数は同時ではなく順次呼び出されます。

GPIO.add_event_detect([チャンネル], [検出したいエッジ], callback=[コールバック関数名])  
スイッチのチャタリング

エッジ検出を行う場合、チャタリングの発生により1回のボタンプッシュで複数回のイベントが発生する場合があります。チャタリングの影響を回避するには以下の方法をとります。

  1. スイッチとの間に 0.1μFのコンデンサを追加
  2. ソフトウェアによるチャタリングの除去
  3. A/Bを両方ともやる

ソフトウェアでチャタリングを除去する場合は、add_event_detect() でコールバック関数を登録する際に bouncetime パラメータを指定します。これにより、エッジ検出イベント発生直後からバウンス時間に指定された時間に発生した他のエッジ検出イベントが無視されるため、イベントが複数回上がるのを避けることができます。ただし、バウンス時間が短いと十分にチャタリングを回避できず、長くとりすぎると連続でボタンをプッシュした場合の反応が悪くなるので、用途に応じて適切な値に調節してあげる必要があります。

GPIO.add_event_detect([チャンネル], [検出したいエッジ], callback=[コールバック関数名], bouncetime=[バウンス時間(ミリ秒)])
イベント検知の解除

イベント検知を解除したい場合は remove_event_detect() を使用します。

GPIO.remove_event_detect([チャンネル])

終了処理

スクリプト終了時の後始末はGPIO.cleanup()で行います。これを忘れると再度スクリプト実行したときに失敗します。

GPIO.cleanup()

GPIO.setup() の対になる関数なのでパラメータにチャンネル(またはチャンネルのリストかタプル)を指定すると、指定したチャンネルのみ終了できます。

GPIO.cleanup(22)