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言語で次々に送る」というものを発見した。

#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");
}
これは、6月19日にやっていた「シェルコマンドを並べたシェルスクリプトを実行」というやつを、C言語プログラムとしてまとめたもので、遠い遠い昔、1996年に作品 "Asian Edge" ()の作曲において、背景音響パートをライヴ生成するために、「MIDI制御を受けて"サウンドファイルを再生する"というシェルコマンドを多数、どんどん起動していく(最大、同時に20本以上のサウンドを多重生成)というプログラム」を、シリコングラフィクスのIndyワークステーション上で制作したのを思い出した。 ここ にある、 これこれ である。

そこでまず、 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に対応させてみた。

ピン番号     信号名     ソフト上の呼び名
11GPIO 0GPIO17
12GPIO 1GPIO18
13GPIO 2GPIO27
15GPIO 3GPIO22
16GPIO 4GPIO23
18GPIO 5GPIO24
22GPIO 6GPIO25
7GPIO 7GPIO4
8UART TxGPIO14
10UART RxGPIO15
3Ex_Out 0 / I2C SDAGPIO2
5Ex_Out 1 / I2C SDLGPIO3
19Ex_Out 2GPIO10
21Ex_Out 3GPIO9
23Ex_Out 4GPIO11
24Ex_Out 5GPIO8
26Ex_Out 6GPIO7

そして、「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();」が無く、「省略してもエラーにならない」などと書いてある(^_^;)。

#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();
}
その「init_gpio();」は出力用であればおよそ以下のような感じで、fdというのはファイルディスクリプタの事だろう。 かつてUnixをお勉強した時に、何度も出て来たやつである。 サンプルにあるエラー処理を残すか省略するかは悩ましいが、当面は残しておく事にした。 expでもvalでも「2」を書くのに、どうしてdirだけ「4」なのか、どこにも書いてない。
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);
    }
}
対応する「gpio_close();」はおよそ以下のような感じである。 実験としては、これを呼ばなくてもまた実行できるかどうかを確認してみよう。
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);
}
これを実行してみると、無事に「Ex_Out 6」である「GPIO7」が点滅したが、どうもその時間間隔が異常に長い。 今回はVNCでなくsshで実験しているが、sshのターミナル画面内のRaspberry Pi側でnanoを開けるので、わずかな変更はいちいち rcpしないでnanoで開いて修正してコンパイル→テストランが出来る。 いろいろ試して、以下のような事が判った。 要するに、fdで情報を取得してしまえば、Pythonドライバのようにリソースの管理をせずに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);
    }
}
ということで、とりあえず全15ビットのLEDを8ビットと7ビットのグループごとに交互に全点滅させる、という以下のCプログラムが完成して、無事に動いた。 やはり、GPIOのビン配置と、新しい信号名の対応がキチンとした事が本日最大の収穫だろう。
#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日記」トップに戻る