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

　前回、ラズパイの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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/b91abd77-7113-046b-6f3e-eb04d32e66f8.png)

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

 ![ex202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/89990c8b-7c7f-b6a0-9d56-b5508dde2cb3.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入力の割り込み処理は実現できなかったので、ポーリングで処理をすることにします。