Raspberry Pi 日記 (part2)
長嶋 洋一
2013年6月24日(月)
また新しい週の始まり、月曜日である。 昨日の午後は、おいちゃん作曲に集中して、合間に 学生の自主制作展 を見たりした。 朝6時に研究室に出て来ると、昨日レンダリングを走らせていたムービーが出来ていて、 こんなページ を作ったり、おいちゃんから「完成版OK」(^_^)のメイルが届いて、無事にあとは7月末のおいちゃん映像作品の完成を待つこととなった。
CQのRaspberry Pi本の gpio_dev.c であるが、実は ダウンロードページ からゲットしたサンプルは上のような内容(一部不要なものをカット)であり、「gpio_dev_c_test.c」よりもシンプルな「gpio_dev_c_test.c」というのがあった。 そしてさらに、「gpio_sh_c」というディレクトリに、以下のような「シェルコマンドをC言語で次々に送る」というものを発見した。
これは、6月19日にやっていた「シェルコマンドを並べたシェルスクリプトを実行」というやつを、C言語プログラムとしてまとめたもので、遠い遠い昔、1996年に作品 "Asian Edge" (★★★★)の作曲において、背景音響パートをライヴ生成するために、「MIDI制御を受けて"サウンドファイルを再生する"というシェルコマンドを多数、どんどん起動していく(最大、同時に20本以上のサウンドを多重生成)というプログラム」を、シリコングラフィクスのIndyワークステーション上で制作したのを思い出した。 ここ にある、 これ と これ である。#include <stdio.h> int main(int argc, char *argv[]) { int i; system("echo \"7\" > /sys/class/gpio/export"); system("echo \"out\" > /sys/class/gpio/gpio7/direction"); for (i=0; i<1000; i++) { system("echo \"1\" > /sys/class/gpio/gpio7/value"); system("echo \"0\" > /sys/class/gpio/gpio7/value"); } system("echo \"7\" > /sys/class/gpio/unexport"); }そこでまず、 Raspberry PiのGPIO解説ページ なども参考にして(ビン番号のバグも自己修正(^_^;))、 このプログラム をRaspberry Piにrcpで送り、コンパイルしてsudoで実行して、ほぼLEDが点灯することを確認した。 ただし「ほぼ」というところが謎で(^_^;)、「Ex_Out 0」から「Ex_Out 4」までの5ビットはOKだが、肝心の「GPIO 0」から「GPIO 7」までの8ビットのうち、「GPIO 2」と「GPIO 7」が無反応であった。 これはLEDの向きなのか断線なのか、それともポート設定に問題があるのか、以前に敢えて軽くスルーしていたが(^_^;)、きちんと確認する必要がありそうだ。 ここで2限の講義「音楽情報科学」の時間となったので、続きは午後からである。
そして午後になり、 この解説ページ を改めて眺めてみると、なんと上のように、ピン配置と信号名の対応表に、2つの異なったバージョンがある、という事実が判明した。 こんな事は、これまでどこにも書かれていなかった(^_^;)。 そしてまさに、ウンともスンとも言わなかった13ピンこそ、この信号名がRev1からRev2で変更されている場所だったのである。 また、以前の実験で「Ex_In 0」「Ex_In 1」と名付けた、pull-upされている2ピンのLEDが点灯しなかったのも、信号名が変更されていたためと思われた。 そこでさっそく、以前に書いていたピン信号対応表を、拡張出力も7ビットに戻して、以下のように新しいRev2に対応させてみた。
ピン番号 信号名 ソフト上の呼び名 11 GPIO 0 GPIO17 12 GPIO 1 GPIO18 13 GPIO 2 GPIO27 15 GPIO 3 GPIO22 16 GPIO 4 GPIO23 18 GPIO 5 GPIO24 22 GPIO 6 GPIO25 7 GPIO 7 GPIO4 8 UART Tx GPIO14 10 UART Rx GPIO15 3 Ex_Out 0 / I2C SDA GPIO2 5 Ex_Out 1 / I2C SDL GPIO3 19 Ex_Out 2 GPIO10 21 Ex_Out 3 GPIO9 23 Ex_Out 4 GPIO11 24 Ex_Out 5 GPIO8 26 Ex_Out 6 GPIO7 そして、「GPIO 7」が点灯しなかったのはdirectionの行が抜けていたというバグ(^_^;)も発見して、新たに作った C言語でシェルコマンドを実行するプログラム を転送して、以下のYouTubeのように見事に、汎用ポート8ビット、拡張ポート7ビットが全て快調に点灯すること、さらに「sleep 1」だけでなく「sleep 0.1」というように、sleep時間は実数で定義できることも確認した。 こうなれば、もうPythonでGPIOを制御する必要もなくなってくる(^_^)。
YouTube 動作を確認しているシェルコマンドを、単にC言語プログラムのシステムコールで並べただけなので、これが動くこと自体はそれほど嬉しい事もないのだが(^_^;)、GPIOコネクタの信号名の定義が新しいバージョンになっていた、という事実が判明して、これで不明確なところが無くなった、というのは大きな進歩である。 これで安心して、「C言語でGPIOデバイスドライバを叩く」という次のステップに進める。 CQのRaspberry Pi本の gpio_dev.c をベースに、今回実験している8+7=15ビットのLEDを表示するプログラムを作りたい。 こごてはまず、「Ex_Out 6」である「GPIO7」の点滅を目指そう。 プログラムの概形は以下であり、「init_gpio();」でexportとdirection設定、「gpio_close();」でunexportである。 ただし、「gpio_dev.c」よりシンプルな「gpio_dev_c_test.c」では、上記の「gpio_close();」が無く、「省略してもエラーにならない」などと書いてある(^_^;)。
その「init_gpio();」は出力用であればおよそ以下のような感じで、fdというのはファイルディスクリプタの事だろう。 かつてUnixをお勉強した時に、何度も出て来たやつである。 サンプルにあるエラー処理を残すか省略するかは悩ましいが、当面は残しておく事にした。 expでもvalでも「2」を書くのに、どうしてdirだけ「4」なのか、どこにも書いてない。#include <stdio.h> #include <stdlib.h> #include <fcntl.h> int fd_val; char s[64]; int main() { int i; init_gpio(); for (i=0; i<10; i++) { write(fd_val,"1",2); sleep(0.5); write(fd_val,"0",2); sleep(0.5); } close(fd_val); gpio_close(); }対応する「gpio_close();」はおよそ以下のような感じである。 実験としては、これを呼ばなくてもまた実行できるかどうかを確認してみよう。void init_gpio() { int fd_exp, fd_dir; fd_exp = open("/sys/class/gpio/export", O_WRONLY); if (fd_exp < 0) { printf("GPIO export open error\n"); exit(1); } write(fd_exp, "7", 2); close(fd_exp); sprintf(s,"/sys/class/gpio/gpio%d/direction",7); fd_dir = open(s, O_RDWR); if (fd_dir < 0) { printf("GPIO %d direction open error\n",7); exit(1); } write(fd_dir, "out", 4); close(fd_dir); sprintf(s,"/sys/class/gpio/gpio%d/value",7); fd_val = open(s, O_RDWR); if (fd_val < 0) { printf("GPIO %d value open error\n",7); exit(1); } }これを実行してみると、無事に「Ex_Out 6」である「GPIO7」が点滅したが、どうもその時間間隔が異常に長い。 今回はVNCでなくsshで実験しているが、sshのターミナル画面内のRaspberry Pi側でnanoを開けるので、わずかな変更はいちいち rcpしないでnanoで開いて修正してコンパイル→テストランが出来る。 いろいろ試して、以下のような事が判った。void gpio_close() { int fd_exp; fd_exp = open("/sys/class/gpio/unexport", O_WRONLY); if (fd_exp < 0) { printf("GPIO unexport open error\n"); exit(1); } write(fd_exp, "7", 2); }要するに、fdで情報を取得してしまえば、Pythonドライバのようにリソースの管理をせずにGPIOポートがアクセス出来てしまうわけである。 ということで、エラー処理も省略して、さらに上記の「無くても実行できる」ものを全て省略(^_^;)すると、上のプログラムはなんと、以下のようになり、これでも何度でも実行できる事を確認した。
- 「sleep(1);」だとおよそ1秒で、これが最速
- 「sleep(0.5);」だと、およそ2秒になる
- 「sleep(0.1);」でも、およそ2秒になる
- 「sleep(0.01);」でも、およそ2秒になる
- 「sleep(0);」だとゼロで何も待たない
- 最後に「gpio_close();」を呼ばなくても、新たに実行させるとちゃんと動く(^_^;)
- main()の中の最後の「close(fd_val);」を消しても、新たに実行させるとちゃんと動く(^_^;)
- dirで書き込む値を「4」でなく他と同じ「2」にしても走った
- それぞれのfdのclose(fd_???);」を消しても、新たに実行させるとちゃんと動く
- exportの信号名(番号)は、「"7"」でなく数字の「7」でも動く
- valueに書き込む信号レベルは「"0"」や「"1"」でないと駄目で、「0」や「1」では動かない
ということで、とりあえず全15ビットのLEDを8ビットと7ビットのグループごとに交互に全点滅させる、という以下のCプログラムが完成して、無事に動いた。 やはり、GPIOのビン配置と、新しい信号名の対応がキチンとした事が本日最大の収穫だろう。#include <stdio.h> #include <stdlib.h> #include <fcntl.h> int fd; char s[64]; int main() { int i; fd = open("/sys/class/gpio/export", O_WRONLY); write(fd, 7, 2); sprintf(s,"/sys/class/gpio/gpio%d/direction",7); fd = open(s, O_RDWR); write(fd, "out", 2); sprintf(s,"/sys/class/gpio/gpio%d/value",7); fd = open(s, O_RDWR); for (i=0; i<10; i++) { write(fd,"0",2); sleep(1); write(fd,"1",2); sleep(1); } }#include <stdio.h> #include <stdlib.h> #include <fcntl.h> int main() { int fd[15]; int assign[15] = {17,18,27,22,23,24,25,4,2,3,10,9,11,8,7}; char s[64]; int i, j, ff; for (i=0; i<15; i++) { ff = open("/sys/class/gpio/export", O_WRONLY); write(ff, assign[i], 2); sprintf(s,"/sys/class/gpio/gpio%d/direction", assign[i]); ff = open(s, O_RDWR); write(ff, "out", 2); sprintf(s,"/sys/class/gpio/gpio%d/value", assign[i]); fd[i] = open(s, O_RDWR); } for (j=0; j<4; j++) { for (i=0; i<8; i++) { write(fd[i],"0",2); } for (i=8; i<15; i++) { write(fd[i],"1",2); } sleep(1); for (i=0; i<8; i++) { write(fd[i],"1",2); } for (i=8; i<15; i++) { write(fd[i],"0",2); } sleep(1); } }YouTube
合わせて、上のようにGPIOポートのビン信号マップも最新に改訂した。 これでちょうど5限となり、アカペラの新入生・補習特訓の時間となった。 まずまず進んだというところである。(^_^)
「Raspberry Pi日記」トップに戻る