1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

備忘録 ラズパイ5 ROS2 勉強再開 ③ GPIO その2 libgpiod 「入力」callback関数←思った通りには動かなかった

Posted at

 前回、ラズパイのGPIOの出力を、Cのライブラリlibgpiodを利用してLチカのプログラムを記述しました。
 今回は、入力です。

callback関数の変遷?

 callbackという呼び方は、検索すると今は、「関数ポインタや関数オブジェクトを使って、関数の実行を他の関数に委譲する手法」だそうです。わからん!
 特徴の一つが「非同期処理」なんだそうです。いつI/Oが変化するかわからない処理を記述するので、そいう言葉で呼ぶのは理屈が通っているのでしょう。

 10年前、Arduinoやラズパイでは、「割り込み関数の記述のこと」でした。
もっと前は、callback関数という用語は存在せず、割り込み関数と呼ばれていました。マイコンでは、CPUに割り込み用のピンが複数実装されていて、そこの信号がLowもしくはHighになると、特定な番地にジャンプしました。その番地に何らかの処理を書いたり、領域が狭いので、別のアドレスへジャンプして処理プログラムを記述しました。
 その後、ソフト割り込みというのが実装されました。割り込みテーブルを定義できるので、複数の割り込みの記述が格段に書きやすくなりました。
しかし、割り込みの処理の途中で別の割り込みが入るなどして、4レベルぐらいの多重割り込みの処理の中を飛び回っていると、そう、デバッグがほとんどできません。なんて時代が大昔にありました。

 この時代でも、I/Oに信号が入ることを知るには、ポーリングと割り込みの2種類がありました。
では、ポーリングでプログラムを書いていきます。

ポーリング

 プッシュ・スイッチをGPIO16(36番ピン)と+3.3V(17番ピン)につなぎました。チャタリング対策はしていません。

ex403.cpp
#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
#include <iostream>

int value;

int main() {
    struct gpiod_chip *gchip;
    struct gpiod_line *glinein, *glineout;
    // GPIOデバイスを開く
    gchip = gpiod_chip_open("/dev/gpiochip4");
    // GPIOのピンのハンドラを取得
    glineout = gpiod_chip_get_line(gchip, 21);
    glinein  = gpiod_chip_get_line(gchip, 16);
    // モード設定
    gpiod_line_request_output(glineout, "LED ON/OFF", 0);
    gpiod_line_request_input(glinein, "LED ON/OFF");

    for (int  i=0; i < 10; i++){
        value = gpiod_line_get_value(glinein);
        std::cout << "SW0 " << value << "\n";
        if (value == 1){
            // std::cout << " SW " << value << "\n";
            gpiod_line_set_value(glineout, 1);  // GPIO21の値を1に
        }else{
            gpiod_line_set_value(glineout, 0);  // GPIO21の値を0に
        }
        std::cout << i << "\n";
        sleep(1);
    }

    gpiod_chip_close(gchip);  // GPIOデバイスを閉じる
    return 0;
}

 動作させます。SWを押しているとLEDが点灯し、SWをoffするとLEDは消えます。

value = gpiod_line_get_value(glinein);

 GPIO16の状態を取得している部分ですが、前回、gpioinfoコマンドを動かしたとき、

$ gpioinfo gpiochip4
gpiochip4 - 54 lines:
        line  16:     "GPIO16"       unused   input  active-high

 active-highと記述されていました。意味は、デフォルトはプルダウンされていて、ピンがHighになると、信号が入ったと解釈するものと思われます。

High level API

 このlibgpiod 1.6.3には、High level APIという項目があります。

ex201.png

 そして、中を見ると、callbackにかかわるtypedefが記述されています。

ex202.png

 検索して、callback関数の書き方というのを探すと、解説には、

// コールバック関数の型を定義
typedef int(* gpiod_ctxless_event_handle_cb) (int, unsigned int, const struct timespec *, void *);

を記述するようにとあります。
 うまいことに、

// コールバックとして登録される関数 Callback function
int myEvent(int x, unsigned int c, const struct timespec * v, void * b){
    std::cout << " in callback function \n";
    return 0;
}

も記述できます。
なので、
 このHigh level APIを利用して、callback関数の記述すればいいように見えます。
 まー、1週間ほどいろいろ試しているのですが、気になることがあります。それは、新しいバージョンlibgpiod 2.1.2は、検索すると見つかります。
 
https://libgpiod.readthedocs.io/en/latest/modules.html

 それには、callbackにかかわるHigh level APIが消えてしまっているのです。代わりに、割り込み記述で使われる関数らしいのが見つかりますが、そう、どのOSで走るかもわからないので、使っている事例が見つかりません。じっくり検索するとLinaro が見つかります。Linuxが動くボード・コンピュータという認識?です。ラズパイは含まれない?ようです。

High level APIを使う

 gpiod_ctxless_get_value

を使ってみます。Read current value from a single GPIO line.という機能の関数です。

ex405.cpp
#include <iostream>
#include <gpiod.h>
#include <unistd.h>

int rc;

int main(){
    std::cout << "  start \n";

    struct gpiod_chip *gchip;
    struct gpiod_line *glinein, *glineout;
    // GPIOデバイスを開く
    gchip = gpiod_chip_open("/dev/gpiochip4");
    glineout = gpiod_chip_get_line(gchip, 16);
    const char * consu = gpiod_line_consumer(glineout);
    std::cout << &consu << "\n";

    const char * pBank = "gpiochip4";  // device name of gpiochip
    const char * c     = "io-event";   // name of consumer
    int in_line        = 16;           // offset for gpio
    for (int i = 0; i < 10; i++){
        rc = gpiod_ctxless_get_value(pBank, in_line, 0, c);
        std::cout << "sw is " <<rc << "\n";
        sleep(1);
    }

    gpiod_chip_close(gchip);
    std::cout << "\n";

    return 0;
}

 実行します。

$ g++ ex405.cpp -lgpiod
$ ./a.out
  start
0xffffd0eb56b0
sw is 0
sw is 0
sw is 0
sw is 1
sw is 0
sw is 1
sw is 1
sw is 0
sw is 1
sw is 0

 ポーリングのプログラムのときと同様に、プッシュ・スイッチをGPIO16(36番ピン)と+3.3V(17番ピン)につないでいます。チャタリング対策はしていません。10回のループの間、SWを押したら1に、押さないと0が表示されます。

callback

 C++のcallback関数の書き方は次のようになるという解説をみても、よくわかりません。
 試行錯誤しました。

ex404.cpp
#include <iostream>
#include <gpiod.h>
#include <unistd.h>

// コールバック関数の型を定義
typedef int(* gpiod_ctxless_event_handle_cb) (int, unsigned int, const struct timespec *, void *);

// コールバックとして登録される関数 Callback function
int myEvent(int x, unsigned int c, const struct timespec * v, void * b){
    std::cout << " in callback function  x=" << x << " c=" << c << " v=" << &v << " b=" << &b << "\n";
    return 0;
}

int main(){
    std::cout << "  start \n";

    struct gpiod_chip *gchip;
    struct gpiod_line *glinein, *glineout;
    // GPIOデバイスを開く
    gchip = gpiod_chip_open("/dev/gpiochip4");
    glineout = gpiod_chip_get_line(gchip, 16);
    const char * consu = gpiod_line_consumer(glineout);

    std::cout << &consu << "\n";

    // in my main function
    gpiod_ctxless_event_handle_cb p = &myEvent; //setup callback
    const char * pBank = "gpiochip4";  // name of gpiochip
    const char * c     = "io-event";   // name of consumer
    int in_line        = 16;           // offset for gpio
    struct timespec time;
        time.tv_nsec=1000000;
        time.tv_sec=1;
//    int flags      =0;
    int rc = gpiod_ctxless_event_monitor(pBank, GPIOD_LINE_EVENT_RISING_EDGE,
             in_line, 1, c, &time, NULL, p, NULL);

/*
const char *    device,
int     event_type,  // GPIOD_CTXLESS_EVENT_CB_RISING_EDGE, GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE
unsigned int    offset,
bool    active_low,
const char *    consumer,
const struct timespec *         timeout,
gpiod_ctxless_event_poll_cb     poll_cb,
gpiod_ctxless_event_handle_cb   event_cb, // Callback function to call for each line event.
void *  data
*/

    std::cout << rc << "\n";

    std::cout << " set done \n";
    sleep(1);
    std::cout << " 1 \n";
    sleep(1);
    std::cout << " 2 \n";
    sleep(1);
    std::cout << " 3 \n";
    sleep(1);
    std::cout << " 4 \n";
    sleep(1);


    // main in kara loop
    for (int i = 0; i < 10; i++){
        std::cout << (int)i << " ";
        sleep(1);
    }

    gpiod_chip_close(gchip);
    std::cout << "\n";

    return 0;
}

 実行します。startの表示の後、SWを押します。

$ g++ ex404.cpp -lgpiod
yoshi@yoshi:~/book$ ./a.out
  start
0xffffd24abc68
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=1 c=0 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=1 c=0 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8
 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8

 CTRL-Cで抜けます。timeoutを1秒に設定しているので、SWを押さないとこの割込み関数は、

in callback function  x=1 c=0 v=0xffffd24ab700 b=0xffffd24ab6f8

を表示し続けます。
SWを押すと、

 in callback function  x=2 c=16 v=0xffffd24ab700 b=0xffffd24ab6f8

を表示します。チャタリングがあるので、まとまって複数行表示されています。
 mainのプログラムは存続しません。
 しっかり、SWのONを非同期で取得できているのはいいのですが、mainに戻ってきません。名称の通り、ずっとGPIOをモニタし続けます。
 これは、利用目的には適していない動作です。

 このHigh level API関数、名前の通り、ほかの関数より上位にある?という解釈をすれば、最初の、

   // GPIOデバイスを開く
   gchip = gpiod_chip_open("/dev/gpiochip4");
   glineout = gpiod_chip_get_line(gchip, 16);

とか不要ではないかと、消してしまいました。

ex406.cpp
#include <iostream>
#include <gpiod.h>
#include <unistd.h>

// コールバック関数の型を定義
typedef int(* gpiod_ctxless_event_handle_cb) (int, unsigned int, const struct timespec *, void *);

// コールバックとして登録される関数 Callback function
int myEvent(int x, unsigned int c, const struct timespec * v, void * b){
    std::cout << " in callback function  x=" << x << " c=" << c << " v=" << &v << " b=" << &b << "\n";
    return 0;
}

int main(){
    std::cout << "  start \n";

    // in my main function
    gpiod_ctxless_event_handle_cb p = &myEvent; //setup callback
    const char * pBank = "gpiochip4";  // name of gpiochip
    const char * c     = "io-event";   // name of consumer
    int in_line        = 16;           // offset for gpio
    struct timespec time;
        time.tv_nsec=1000000;
        time.tv_sec=1;
    int rc = gpiod_ctxless_event_monitor(pBank, GPIOD_LINE_EVENT_RISING_EDGE,
             in_line, 1, c, &time, NULL, p, NULL);

    std::cout << rc << "\n";

    std::cout << " set done \n";
    sleep(1);
    std::cout << " 1 \n";
    sleep(1);
    std::cout << " 2 \n";
    sleep(1);
    std::cout << " 3 \n";
    sleep(1);
    std::cout << " 4 \n";
    sleep(1);


    // main in kara loop
    for (int i = 0; i < 10; i++){
        std::cout << (int)i << " ";
        sleep(1);
    }

    std::cout << "\n";

    return 0;
}

 実行結果は同じように動きました。
 ここまでで、callbackの解説にある、「関数ポインタや関数オブジェクトを使って」というのを実装していません。なぜって、どの解説を読んでも、意味がわからないからです。
 それを実装すると、上記のプログラムを非同期にできるのか? できないのかもわかりません。

蛇足

 うまく動かないときは、一度プログラムを動かすと、

         line  16:     "GPIO16"   "io-event"   input  active-high [used]

になります。

$ gpioset gpiochip4 16=1
gpioset: error setting the GPIO line values: Device or resource busy

 使用中だということで変更がききません。
なので、プログラム中で、

gpiod_line_release(glineout);

しても、初期化されないようです。
 初期状態に戻すには、rebootをかけます。でも、それは、実際には利用できないので、どうにかしたいです。

もろもろ そもそも

  ROS2を利用して搬送車を作ろうという目論見で、C++の勉強をしていたりします。
 この手の自動機には、緊急停止ボタンがあります。
 モータはオリエンタルモーターの製品をCANopen言語で動かしたいと思っています。同社のモータは、modbusでは、この緊急用スイッチに対応していますが、CANopenでは、ディジタル入力がサポートされていません。たとえば、EPOS4では、ディジタル入出力があって、緊急停止ボタンを実装できるようになっています。CANopenでサポートされていたはずです。
で、ラズパイをコントローラで使っているので、緊急停止ボタンは、ラズパイのほうで何とかしようとしてGPIOを割り込みで処理したかったのですが、すんなりいかないものですねー。

プログラムを進める

callback関数内を、SWが押されたら、押されたことを表示するに変更しました。

ex407.cpp
#include <iostream>
#include <gpiod.h>
#include <unistd.h>
bool intFlag = false;

// コールバック関数の型を定義
typedef int(* gpiod_ctxless_event_handle_cb) (int, unsigned int, const struct timespec *, void *);

// コールバックとして登録される関数 Callback function
int myEvent(int x, unsigned int c, const struct timespec * v, void * b){
//    std::cout << " in callback function  x=" << x << " c=" << c << " v=" << &v << " b=" << &b << "\n";
    intFlag =  false;
    if (c == 16) {
      intFlag = true;
      std::cout << " intFlag(GPIO16 done)=" << intFlag << "\n";
    }
    return 0;
}

int main(){
    std::cout << "  start \n";

    // in my main function
    gpiod_ctxless_event_handle_cb p = &myEvent; //setup callback
    const char * pBank = "gpiochip4";  // name of gpiochip
    const char * c     = "io-event";   // name of consumer
    int in_line        = 16;           // offset for gpio
    struct timespec time;
        time.tv_nsec=1000000;
        time.tv_sec=1;
    int rc = gpiod_ctxless_event_monitor(pBank, GPIOD_LINE_EVENT_RISING_EDGE,
             in_line, 1, c, &time, NULL, p, NULL);

    std::cout << rc << "\n";

    return 0;
}

 SWが押されたら、シャットダウンするつもりなので、このmonitorの実行を止めるようにするにはどうしたらいいのでしょうか?

gpiod_ctxless_event_monitor_multiple()のところに、 Both callbacks can stop the loop at any point.と書かれています。
 試しに、SWが押されたとき、returnで-1を戻してみました。

ex408.cpp
#include <iostream>
#include <gpiod.h>
#include <unistd.h>

bool intFlag = false;

// コールバック関数の型を定義
typedef int(* gpiod_ctxless_event_handle_cb) (int, unsigned int, const struct timespec *, void *);

// コールバックとして登録される関数 Callback function
int myEvent(int x, unsigned int c, const struct timespec * v, void * b){
//    std::cout << " in callback function  x=" << x << " c=" << c << " v=" << &v << " b=" << &b << "\n";
    intFlag =  false;
    if (c == 16) {
      intFlag = true;
      std::cout << " intFlag(GPIO16 done)=" << intFlag << "\n";
      return -1;
    }
    return 0;
}

int main(){
    std::cout << "  start \n";

    // in my main function
    gpiod_ctxless_event_handle_cb p = &myEvent; //setup callback
    const char * pBank = "gpiochip4";  // name of gpiochip
    const char * c     = "io-event";   // name of consumer
    int in_line        = 16;           // offset for gpio
    struct timespec time;
        time.tv_nsec=0;
        time.tv_sec=1;
    int rc = gpiod_ctxless_event_monitor(pBank, GPIOD_LINE_EVENT_RISING_EDGE,
             in_line, 1, c, &time, NULL, p, NULL);

    std::cout << rc << "\n";

    std::cout <<  " --- \n ";
    sleep(1);
    std::cout <<  " 1\n ";
    sleep(1);
    std::cout <<  " 2\n ";
    sleep(1);
    std::cout <<  " 3\n ";
    sleep(1);
    std::cout <<  " 4\n ";
    sleep(1);

    return 0;
}

 実行しました。

$ g++ ex408.cpp -lgpiod
$ ./a.out
start
intFlag(GPIO16 done)=1
-1
---
1
2
3
4

 一度-1でcallback関数 myEvent(int x, unsigned int c, const struct timespec * v, void * b)を抜けた後は、割り込みは発生しなくなりました。
 抜けた後も、gpiod_ctxless_event_monitor()は動作を続けてほしかったのですが。

以上、GPIO入力の割り込み処理は実現できなかったので、ポーリングで処理をすることにします。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?