西川和久の不定期コラム
「Raspberry Pi3 Model B」で遊んでみよう!Part2
~GPIO入門1。ポートのオン/オフとステータス取得編
2016年6月20日 06:00
前回のPart1は「Raspberry Pi3 Model B」の簡単な紹介だったが、今回Part2はいよいよRaspberry Pi特有のGPIOをコントロールする。そう言う筆者もここから先は未知の世界。いろいろ調べながらの執筆となった。まずは基本。ポートをオン/オフし、LEDをピカピカさせてみたい。
GPIOの仕様
GPIOとは“General Purpose Input/Output”の略であり、前回、「GPIO×26 3.3V/16mA、UART、I2C、SPI、I2S、PWM、5V、3.3V/50mAで構成」と書いたが具体的なピンアサインが分からないと何もできない。公式サイトによると、以下の図の通り。
これによると赤色の5Vが2つ、橙色の3.3Vが2つ、黒色のGNDが8つ。そしてGPIOが上に12、下に14配置されている。またGPIOのそれぞれは複数の機能を割り当て可能で、今回は一番単純な操作のみを行なう。さらにに複雑な操作はPart3か4で扱う予定だ。
なおGPIOはモデルによって26ピンと40ピンがあり、「Raspberry Pi3 Model B」は40ピン構成だ。互換性を保つため40ピン中1~26ピンまでは、26ピンモデルと同じ仕様になっている。
gpioコマンドを使ってコントロール
一般的にWindowsでもLinuxでもハードウェアをコントロールするには、デバイスドライバを用意しなければならない。とは言え、ここからプログラミングが必要となるとハードルが高過ぎるため、今回インストールした「RASPBIAN」には、予め複数方法が用意されている。その1つが「gpio」コマンドだ。このコマンド1つで、GPIOの設定やステータスを得ることができる。
手始めに、本当にソフトウェア的にGPIOをコントロールできるのか、公式サイトに見習って(笑)、LEDのオン/オフに挑戦する。回路は簡単!ピン12/GPIO18へLEDを接続、適当な抵抗を繋ぎ、ピン39/GNDへ落とす。これで準備は完了だ。
まず、“gpio readall”でGPIOのステータスを表示してみると以下のようになった。
$ gpio readall
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5V | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 1 | IN | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | IN | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
中央のPhysicalが実際のピンアサイン(BCMはGPIO番号、wPiはWiringPiと呼ばれるC言語のライブラリ用)なので、先の図を眺めつつ、何がどうなっているのか1つずつ追うのが理解の早道だろう。
なお、GPIOの初期値は全てMode INになっており、V=1の部分はプルアップ(電源へ抵抗で釣る)、V=0の部分はプルダウン(GNDへ抵抗で落とす)の処理が内部的に行なわれ、オープン(ハイ・インピーダンス)になっているポートはないようだ。各GPIOのプルアップ/ダウンはプログラマブルで変更できる。
gpioコマンドで特定のポートをコントロールするには、
1)ポート番号を指定してINかOUTを設定
2)ポート番号を指定してreadかwrite
の手順を踏む必要がある。またポート番号は“物理的なピン番号”か“GPIO番号”を指定可能だ。
$ gpio -1
※物理的なピン番号指定
$ gpio -g
※GPIO番号指定
まず、ポートを出力用に設定する。コマンドは以下の通り。
$ gpio -g mode 18 out
※GPIO18を出力に設定
$ gpio -1 mode 12 out
※12pinを出力に設定(上記と同じ意味となる)
$ gpio readall
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | OUT | GPIO. 1 | 1 | 18 |
※確かにpin12/GPIO18のモードがOUTになった。V=0
モードがOUTになったがV=0なのでLEDは消灯。LEDを点灯するにはV=1にする。コマンドは以下通り。
$ gpio -g write 18 1
※GPIO18を1に設定
$ gpio -1 write 12 1
※12pinを1に設定(上記と同じ意味となる)
$ gpio readall
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 1 | OUT | GPIO. 1 | 1 | 18 |
※確かにpin12/GPIO18のVが1になった。これでLEDは点灯する
LEDを消灯するには
$ gpio -g write 18 0
※GPIO18を0に設定
$ gpio -1 write 12 0
※12pinを0に設定(上記と同じ意味となる)
とすればよい。いかがだろうか? gpioコマンドを使えば、LEDのオン/オフ程度は非常に簡単に行えることがお分かり頂けたと思う。
応用例としてPHPを使って、一定間隔LEDが点灯/消灯を繰り返すプログラムを書いてみた(2秒ずつオン/オフを5回)。
<?php
system("gpio -g mode 18 out"); // GPIO18を出力モードへ
for ($i = 0; $i < 5; $i++) { // 5回繰り返し
system("gpio -g write 18 1"); // LED点灯
sleep(2); // 2秒そのまま
system("gpio -g write 18 0"); // LED消灯
sleep(2); // 2秒そのまま
}
?>
$ php led.php
※プログラムのファイル名がled.phpの場合、これで実行できる
逆にGPIOのステータスを読み込むには、該当ポートをプルアップにして、GNDとショートさせた方が回路的に楽なので、ポートを入力モードに切り替えてから、
$ gpio -g mode 18 in
※GPIO18を入力に設定
$ gpio -g mode 18 up
※プルダウンの時はgpio -g mode 18 down
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 1 | IN | GPIO. 1 | 1 | 18 |
※GPIO18がV=1となった。
として、GPIO18をプルアップにする。そしてそのままステータスを読み込むと、
$ gpio -g read 18
1
※GPIO18は1
スイッチか何かでGPIO18とGNDをショートすると(プルアップしているため、ショートすると電流が流れるものの、KΩ単位の抵抗で釣っているので微々たるもの。問題ない)、
$ gpio -g read 18
0
※GPIO18は0
となる。パラレル出力の簡単なセンサーであれば、同様の手法で値を取得可能だ。センサーからのデータさえ得られれば、後はプログラムでその内容を処理すればいい。
Pythonを使ってコントロール
「RASPBIAN」には“RPi.GPIO”と呼ばれるPythonのパッケージも用意されている。先にあげた“WiringPi”はC言語用でコンパイルが必要になるが、こちらはPHP同様、スクリプト言語なので即実行でき手軽だ。
またgpioコマンドはコマンドレベルの操作となり、PHPなどではsystem()を使わなければならず美しくないが、こちらはファンクションで操作でき、美しく書くことができる。
“RPi.GPIO”パッケージの詳細はこちらを参考にして欲しい。LEDをオン/オフする程度であれば、
setmode() モード設定
setup() セットアップ
output() アウトプット
cleanup() 後片付け
この4つのファンクションを使えばいい。具体的なコードは以下の通り(LEDを5秒点灯して消灯する)。
#!/usr/bin/python
# coding: utf-8(ソースコードはutf8)
# timeモジュールをインポート
import time
# RPi.GPIOモジュールをインポートして別名GPIOとして扱う
# RPi.GPIO.setmode(RPi.GPIO.BCM)などと書くのが面倒なため
import RPi.GPIO as GPIO
# GPIO番号で操作。物理的なpin番号で扱う場合は GPIO.setmode(GPIO.BOARD)となる
GPIO.setmode(GPIO.BCM)
# GPIO18を出力モードへ
GPIO.setup(18, GPIO.OUT)
# GPIO18を1へ。LED点灯
GPIO.output(18, 1)
# 5秒待つ
time.sleep(5)
# GPIO18を0へ。LED消灯
GPIO.output(18, 0)
# 後片付け
GPIO.cleanup()
これを例えばled.pyファイルに保存して、chmod +x led.pyで実行可能にした後、
$ ./led.py
で作動する。更に複数のGPIOへLEDを接続した場合、18と書いてダイレクトに指定している部分を配列とし、
bcm_list = [18,23,24]
GPIO.setup(bcm_list, GPIO.OUT)
と、このように書けば、配列の中で指定したポート全てが一度で設定可能だ。ポートを読み取るのは(別名GPIO、GPIO番号で操作の場合)、
# GPIO18を入力へ/プルアップ
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# GPIO18を入力へ/プルダウン(上記とどちらか)。
# もしくは , pull_up_down= 以降、必要なければ書かなくてもよい
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
# 画面にGPIO18のステータス0か1を表示
print GPIO.input(18)
となる。
いずれにしても先のPHPでsystem()を使うより、よりプログラムらしくなるのがお分かり頂けただろうか。
このほかGPIOの操作は、「ファイルシステムを使う方法」や、少し触れた「C言語のライブラリ“WiringPi”を使う方法」もあるので、興味のある人はインターネットで検索して欲しい。
余談になるが、(正確には違うが)1bitのポート単位で(簡単とは言え)プログラミングするのは何十年ぶりか!?の勢いだ(笑)。
筆者はもともとハードウェアからコンピュータを始めた世代で、一番初めに作ったのはオール手配線(笑)のMotorola MC6802を搭載したワンボードマイコン。当時高校生だったが、高価なNECの「TK-80」は買えずお年玉を握りしめてパーツを購入、自作した。
その後、仕事でMSXのゲームを作ったり、Z80のワンボードを設計して外部機器をコントロールするなど、直接ポートを叩くプログラムが主だった記憶がある。当時、開発環境はCP/M80。C言語を使ったROM化も割と早い時期から実戦に投入した(確かWhitesmith C)。今回久々にポート叩いて「そう言えば……」と思い出した次第だ。
Part2はGPIOを使う基本中の基本でポートをオン/オフし、LEDを点灯/消灯してみた。コントロール方法も複数あり、得意もしくは簡単なものから試すのがいいだろう。次回Part3は、もう少し実用的(?)なGPIOのコントロールを行ってみたい。