これはNetBSD Advent Calendar 2018の16日めの記事です。
Raspberry PiにはIOピンヘッダがあり気軽に電子工作を楽しめるという面があります。また、登場以来、そういった電子工作を紹介する書籍や記事もたくさんあります。IOTという言葉の流行とともに語られることも多いようです。
しかしながら当然のように、サンプルプログラム等は基本的にRaspberryPiの標準環境、つまりLinux用です。
せっかくRaspberry PiでNetBSDが動くのだから、これらをNetBSDから制御してみようと思います。
NetBSDではRaspberry PIのGOIOは以下のように認識されています。
bcmgpio0 at obio0: GPIO [0...31] gpio0 at bcmgpio0: 32 pins bcmgpio1 at obio0: GPIO [32...53] gpio1 at bcmgpio1: 22 pins
つまり/dev/gpio0を使えばよさそうです。
まずはGPIOに接続されているLEDを光らせます。RPIでGPIOを使う方法は以下のような操作となります。
これをNetBSDで行うにはgpioctl(8)コマンドを使用します。
# gpioctl /dev/gpio0 5 set out
# gpioctl gpio0 5 on
これでLEDが光るはず…って、動きませんね。最初のsetで失敗してしまいます。
# gpioctl gpio0 5 set out gpioctl: GPIOSET: Operation not permittedしかも、gpioctlで制御可能なピン数を調べると、0と言われてしまいます。
# gpioctl gpio0 /dev/gpio0: 0 pins
これはカーネルのセキュリティレベルが0以下でしかgpioのモード制御が許されないことになっているためです。 NetBSDのRaspberryPI用デフォルトカーネルでは、起動直後はセキュリティレベルが0で通常起動すると1になります。一度レベルが上がると、下げることはできません。つまり通常起動した後では0にすることはできません。このままではGPIOのモード設定を変更することができません。gpioctl(8)のman pageにも記載があります。
じゃぁどうすればいいかというと、選択肢は2つ。
開発中などブレッドボード等で頻繁に接続変更や追加等を行うような場合はINSECUREカーネルを使うといいかなと思います。既にデバイスの接続が固定されているのであれば起動直後に初期化を済ませばよさそうです。
ここではカーネルの入れ替えも面倒なので起動時にモード設定を行うようにします。/etc/gpio.confというテキストファイルにgpioctlのコマンドを並べます(詳細はgpio.conf(5)のman page参照)。また、起動時の設定を有効にすることを/etc/rc.confに記述します。
gpio0 5 set out gpio0 6 set out gpio0 22 set in gpio0 23 set in
: gpio=YES
設定が終わったらシステムを再起動します。前述のように一旦上がってしまったセキュリティレベルは下げることができないためです。
設定と再起動が終わったら、ピンの状態を確認してみます。
# gpioctl gpio0 /dev/gpio0: 4 pins
4つのピンを設定したため、4と返ってきました。
それではgpioctlでLEDを光らせてみます。
# gpioctl gpio0 5 on pin 5: state 0 -> 1
これでLED1(青色)が光りました。onをoffに変えると消灯し、toggleにすると光ってれば消え、消えて入れば光ります。pinを6にすればLED2(白色)が対象になります。
ここではrootで実行しましたが、_gpioグループに所属している一般ユーザであればそのまま実行できます。
% id uid=1000(oshima) gid=100(users) groups=100(users),0(wheel),5(operator),29(_gpio) % gpioctl gpio0 5 toggle pin 5: state 1 -> 0
ただし、GPIOの入出力モードを設定するgpioctl setコマンドは特権ユーザ(root)でなければ実行できません。これはセキュアレベルが0であったり、INSECUREカーネルであっても同じです(ドライバレベルで非特権ユーザでの実行が禁止されています)。
この節全体2018.12.20に追記
次に入力としてタクトスイッチの状態を参照してみます。まずはそのまま読み込んでみます。
$ gpioctl gpio0 22 pin 22: state 0スイッチSW1を押しながら実行してみます。
$ gpioctl gpio0 22 pin 22: state 0
あれ、相変わらずstateは0のままですよ?
回路図を見直すと、SW1,2とも、GPIOのピンとGNDの間にそのまま繋がっていました。つまりSWを押すとGNDと電位が同じ=0Vになることを期待しているわけで押してない場合には電圧が高い状態(3.3V側)になる回路でなければなりません。RPIのGPIOは内部でプルアップするかプルダウンするか選べるようになってて、GPIO22/23の初期状態はプルダウン(=GND側に抵抗を付けて接続)になっているため、これを変更する必要があります。
ということで、/etc/gpio.confに以下を追加します。
gpio0 22 pu gpio0 23 pu
設定して再起動し、再度gpioctlで状態を見てみます。
$ gpioctl gpio0 22 pin 22: state 1
次にSW1を押しつづけながら実行します。ちょっと操作的に辛いのでshell loopで回しておきます(止めるのはCntl-C)。
$ while true; do gpioctl gpio0 22; sleep 1; done pin 22: state 1 pin 22: state 1 pin 22: state 0 pin 22: state 1 ^C $
ボタンを押しているときだけ0になることが確認できました。これでSWの状態を検出すること=GPIOからの入力ができました。
gpioctlコマンドを使ってGPIOのピン毎にON/OFFという最低限の動作ができましたが、色々な処理を行うプログラムに組み込むにはコマンドだけでは不便です。そこでプログラムインターフェースを使ってみます。gpio(4)のman pageによると、以下のようなIOCTLが存在するようです。
CMD | 説明 |
---|---|
GPIOINFO | 使用可能なピン数を報告する |
GPIOREAD | ピンの状態(ON/OFF)を取得する |
GPIOWRITE | ピンに状態(ON/OFF)を設定する |
GPIOTOGGLE | ピンの状態を反転させる |
GPIOSET | ピンの入出力モードを設定する |
GPIOUNSET | ピンの入出力モードを解除する |
GPIOATTACH | GPIOのピンに各種ドライバを割り当てる |
基本的にはgpioctl(8)のコマンドがそのままioctlのコマンドになっています。このうちGPIOSET/GPIOUNSETはgpioctl setコマンドと同じく、rootでなければ実行できません。
なおGPIOATTACHは、gpioの先に他のドライバをattachするものでちょっと特殊なもののようです。よくわかってないのでここでは割愛します。
ということでプログラムからはioctl GPIOREAD/GPIOWRITEを使ってGPIOを制御できるようです。 例えば次のようなコードでLEDを光らせることができます。最初にLEDをONにして5秒間そのまま、その後5秒間点滅し消灯して終了します。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/gpio.h> #include <sys/ioctl.h> int main() { int fd; /* ファイルディスクリプタ*/ struct gpio_req req; /* ioctlに渡すパラメータ */ /* deviceのopen */ if ((fd = open("/dev/gpio0", O_RDWR))==-1) { perror("open"); exit(1); } req.gp_name[0] = 0; /* ピン名称による指定はしない */ req.gp_pin = 5; /* ピン 5 */ req.gp_value = GPIO_PIN_HIGH; /* ONにする(実際の値は1) */ /* ioctlの実行 */ if ((ioctl(fd, GPIOWRITE, &req)) == -1) { perror("ioctl"); exit(1); } sleep(5); /* 5秒待ち */ /* だいたい5秒間点滅 */ for ( int i = 0; i< 50; i++ ) { usleep(100000); /* 100ms待ち */ req.gp_value = i%2; /* 交互に */ if ((ioctl(fd, GPIOWRITE, &req)) == -1) { perror("ioctl"); exit(1); } } /* 消す */ req.gp_value = GPIO_PIN_LOW; /* OFFにする(実際の値は0) */; if ((ioctl(fd, GPIOWRITE, &req)) == -1) { perror("ioctl"); exit(1); } }
一応思ったように動きました。
明日はI2C接続のキャラクタディスプレイをNetBSDから使ってみます。