twitter icon   twitter icon   rss icon

Linux.com Japan

Home Linux Jp チュートリアル BeagleBone Blackシリーズ: Linux GPIOを通して割り込みを取得する

BeagleBone Blackシリーズ: Linux GPIOを通して割り込みを取得する

原文は、こちらです。

  BeagleBone Black は、512Mb の RAM、2Gb のオンボード・フラッシュ メモリを搭載した 1GHz クロックの小さな ARM マシンです。組み込み用途に使える 2 つのヘッダー ピンのソケットもついています。価格が Arduino の約 2 倍の $45 と聞いて尻込みするかもしれません。確かに 32Kb のストレージと数 Kb の SRAM を持つ Arduino で充分な実験もあると思います。しかし、他のハードウェアと接続して 100Mb の RAM を使用する用途には、BeagleBone Black が適しています。

bbb-trackball
以前の文献では、Arduino と BeagleBone Black の SPI を使ったチップ アクセスの方式の違いを検証しました。今回は、BeagleBone Blackで外部機器から割り込みを受け取る方法について調べます。

BeagleBone Black の各ヘッダー ピンは、GPIO として使用できます。各ピンの出力電圧をハイ・ローに設定したり、各ピンの入力電圧がハイ・ローのいずれであるかを調べたりできます。通常、ハイは 3.3V で、ローは接地 (0V) です。Linux の GPIO サポートには、各ピンの電圧がローからハイに変化したとき、または、その反対にハイからローへ変化したときに割り込みを発生させるオプションがあります。

ピンをアクセス可能状態に設定

以前書いたように、BeagleBone Blackに用意されているヘッダー ピンへのアクセスは、Linux カーネルの GPIO インタフェースを使います。

 下記のコードは、Linux カーネルを使って、ピン 42 (GPIO 7) をアクセス可能にして、ピンの現在の電圧を読み取ります。まず、export ファイルに GPIO ピン番号を echo すると、GPIO_7 をファイル システムにマップできます。以下のコードでは、ピン 42 から読み出すために、export ファイルを使い、新しい gpio7 というリンクを作っています。

root@bbb:/sys/class/gpio# echo 7 > /sys/class/gpio/export
root@bbb:/sys/class/gpio# ls -lh
total 0
--w------- 1 root root 4.0K Jun  1  10:54 export
lrwxrwxrwx 1 root root    0  Jun  1 10:54 gpio7 ->  ../../devices/virtual/gpio/gpio7
lrwxrwxrwx 1 root root    0  Jan  1   2000 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
lrwxrwxrwx 1 root root    0  Jan  1   2000 gpiochip32 -> ../../devices/virtual/gpio/gpiochip32
lrwxrwxrwx 1 root root    0  Jan  1   2000 gpiochip64 -> ../../devices/virtual/gpio/gpiochip64
lrwxrwxrwx 1 root root    0  Jan  1   2000 gpiochip96 -> ../../devices/virtual/gpio/gpiochip96
--w------- 1 root root 4.0K Jan   1  2000 unexport
root@bbb:/sys/class/gpio# cd gpio7
root@bbb:/sys/class/gpio/gpio7# cat value
0

割り込みの生成

 割り込みを生成する簡単な方法として、プッシュ ボタンを利用します。ボタンの一方の端子を P9 ヘッダーの 3.3V 出力ピンの 1 つにつなぎ、もう一方の端子と pin42 (GPIO_7) を抵抗を通して接続します。ボタンが期待とおりに動作しているかを確認するために、Value ファイルを読み込み、0 であることを確認します。次に、ボタンを押下している間は Value が 1 になり、ボタンを離すと 0 になることを確認します。これで、ボタンを押下したときと離したときに割り込みを受け取ることができます。

root@bbb:/sys/class/gpio/gpio7# cat value
0
root@bbb:/sys/class/gpio/gpio7# cat value
1
root@bbb:/sys/class/gpio/gpio7# cat value
0

 ハードウェアがサポートしていれば、エッジの立ち上がり、立ち下りのどちらか (または両方) での割り込みを各ピンごとに指定できます。下記にコードを示します。Linux カーネルがこの操作をサポートしていたとしても、ハードウェアがサポートしていなければ設定できません。BeagleBone Blackについては、こちらを参照してください。

root@bbb:~# echo both > /sys/class/gpio/gpio7/edge

監視機能セットアップ

 gpio7 ディレクトリの Value ファイルを監視する方法を考えてみましょう。ボタンの押下・解放に応じて、ファイルの内容が変化します。しかし、ボタンが押されたかどうかを見るためにファイルを常時監視していることはできません。最初は inotify を使うことを考えましたが、このシステムコールは、Value ファイルの値の更新をイベントとして割り込みで伝えるように設定できません。そこで、C++ で監視プログラムを書いてみました。poll() や select() 関数のかわりに glib2 ライブラリを使い、コードを簡単にしました。glib2 ライブラリは UI なしで使用できます。glib2 ライブラリを使うことで、コードが簡単になるとともに、グラフィカルなアプリケーションがハードウェアから入力を受け取るためのコードを簡単に書けます。

 main() 関数を下記に示します。最初に gpio7/value をオープンします。glib2 チャネルを生成して、gpio7/value ファイルの状態変化が優先的なイベントとなり、onButtonEvent 関数 (callback) をトリガーできるようにします。この時点で、onButtonEvent は即座に呼び出されます。なぜなら gpio/value ファイルに使えるデータが存在しているからです。gpio/value ファイルを読み出して、最初の呼び出しを止めることができます。

int main( int argc, char** argv )
{
   GMainLoop* loop =  g_main_loop_new( 0, 0 );
   int fd = open(  "/sys/class/gpio/gpio7/value", O_RDONLY | O_NONBLOCK );
   GIOChannel* channel =  g_io_channel_unix_new( fd );
   GIOCondition cond =  GIOCondition( G_IO_PRI );
   guint id = g_io_add_watch(  channel, cond, onButtonEvent, 0 );
   
   g_main_loop_run( loop );
}

割り込み処理ループの修正

下記に「割り込み処理」を示します。引用符を使ったのは、これが通常の C 関数としてコーディングされていて、マイクロ コントローラーの割り込み機構に特有な他の機能に依存しないからです。onButtonEvent 関数がするべきことは、glib2 チャネルをトリガーした変更が二重割り込みをしないようにすることです。さもなければ、onButtonEvent 関数が再び呼び出されてしまいます。何もしないと、さらに呼び出しが続き、無限ループになってしまいます。onButtonEvent 関数が呼び出されるとコンソールにメッセージが表示されるので、この割り込み処理ループにすぐに気付いて修正することができます。

glib2 チャネルのファイル (value ファイル) が、onButtonEvent 関数呼び出しのもととなっています。イベントをクリアし、イベントが処理済みであることを glib2 チャネルに認識させる方法の 1 つは、ファイルをもう一度読むことです。この時点で、読み出し位置は、ファイル オフセットの一番後ろにセットされているので、先頭に戻す必要があります。そして、あらためて、ボタンが押されたか、離されたかを読み出します。onButtonEvent 関数が 1 を戻すということは、glib2 が callback を削除しないことを意味します。

static gboolean
onButtonEvent( GIOChannel *channel,
               GIOCondition  condition,
               gpointer user_data  )
{
   cerr <<  "onButtonEvent" << endl;
   GError *error = 0;
   gsize bytes_read = 0;
   g_io_channel_seek_position(  channel, 0, G_SEEK_SET, 0 );
   GIOStatus rc =  g_io_channel_read_chars( channel,
                                            buf, buf_sz - 1,
                                            &bytes_read,
                                            &error );
   cerr << "rc:"  << rc << "  data:"  << buf << endl;
   
   // thank you, call again!
   return 1;
}

 

 ビルドには、下記に示すような簡単な Makefile を使います。実行中に、ボタンを 1 回押し (data:1)、離しました (data:0)。このコードは gpio7/value ファイルをまだ読んでいないため、すぐに読み出されます。

$ cat Makefile
watch-button-glib: watch-button-glib.cpp Makefile
        g++ watch-button-glib.cpp  -o watch-button-glib ` pkg-config glib-2.0   --cflags --libs`
$ make
$ ./watch-button-glib
onButtonEvent
rc:1  data:0
onButtonEvent
rc:1  data:1
onButtonEvent
rc:1  data:0

他のハードウェアへの応用

 同じテクニックを他の高機能なハードウェアにも応用できます。たとえば、Blackberry Trackballer Breakout は、ホール効果センサーを 4 つ持ち、ボールが回転する間の磁場変化を検出します。各センサーがトリガーをかけるたびに、ボールの動き (上下左右) に応じて割り込みを送信します。BeagleBone Black で 4 つの GPIO ポートを監視し、割り込み発生ごとに xaxis と yaxis の値を調整します。ボールの動きに応じ、xaxis と yaxis を調整して表示するように修正した onButtonEvent 関数を下記に示します。

enum 
{
    DIRECTION_UP = 1,
    DIRECTION_DOWN,
    DIRECTION_LEFT,
    DIRECTION_RIGHT 
};
int xaxis = 0;
int yaxis = 0;
static gboolean
onButtonEvent( GIOChannel *channel,
               GIOCondition condition,
               gpointer user_data )
{
    cerr << "onButtonEvent" << endl;
    int direction = (int)user_data;
    GError *error = 0;
    gsize bytes_read = 0;
    
    g_io_channel_seek_position( channel, 0, G_SEEK_SET, 0 );
    GIOStatus rc = g_io_channel_read_chars( channel,
                                            buf, buf_sz - 1,
                                            &bytes_read,
                                            &error );
    switch( (int)user_data )
    {
        case DIRECTION_UP:
            yaxis++;
            break;
        case DIRECTION_DOWN:
            yaxis--;
            break;
        case DIRECTION_LEFT:
            xaxis--;
            break;
        case DIRECTION_RIGHT:
            xaxis++;
            break;
    }
    cerr << " direction:" << direction << " x:" << xaxis << " y:" << yaxis << endl;
    
    // thank you, call again!
    return 1;
}

 トラックボールからの入力として、GPIO ピン 70 から 73 を使いました。前記のプログラムと同じようにピンを監視します。onButtonEvent コールバックで使用される user_data ポインタは、DIRECTION enum の 1 つの値をとります。

パーミッションの設定

 GPIO ピンのデフォルトのパーミッションについては、たとえば /sys/class/gpio/gpio72 ディレクトリは誰でも読み出せますが、root だけが書き込み権限を持ちます。つまり、他の人に書き込みを許可するためには、ssh を使って root で BeagleBone Black にログインする必要があります。/sys/class/gpio/export ファイルのパーミッションを一般ユーザーに書き込み可能にしても、ピンをアクセス可能状態にしたとき、生成された gpioNN ディレクトリとその下のファイルは、root からのみ書き込み可能です。これは、udev のルールで解決できると思います。

 外部チップからの割り込み受信は、Linux の GPIO を使って簡単にできます。Linux で使える言語やライブラリは多数あり、gpio/value ファイルの監視や割り込み処理の実装を多彩なものにします。

Linux Foundationメンバーシップ

30人のカーネル開発者

人気コンテンツ


Linux Foundationについて

Linux Foundation はLinux の普及,保護,標準化を進めるためにオープンソース コミュニティに資源とサービスを提供しています

 

The Linux Foundation Japan

サイトマップ

問い合わせ先

サイトに関するお問い合わせはこちらまで

Linux Foundation Japan

Linux Foundation

Linux Training

提案、要望

Linux.com JAPANでは広く皆様の提案、要望、投稿を受け付ける予定です。

乞うご期待!