自由課題

学んだり、考えたり、試したりしたこと。

Jetson NanoでFPVラジコンを作る(9)

前回までの記事

前回記事を書いたあとステアリングモーターと駆動モーターを制御するライブラリを書いた。

github.com

RCCar::steer(float)RCCar::go(float)を使ってこんな感じで制御できる。
両関数とも引数の値域は0.0〜1.0である。

int main(int argc, const char *argv[])
{
    RCCar car;

    //swing the steering
    for(float s = 0.f; s <= 3.f; s+=0.05f) {
        car.steer(std::sin(2*M_PI*s));
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }

    //stop and go
    for(float s = 0.f; s <= 2.f; s+=0.01f) {
        auto speed = 0.2/*max speed cap*/ * ((-std::cos(2*M_PI*s)+1)/2);
        car.go(speed);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    return 0;
}

今回はゲームパッドの入力を受け取るためのライブラリを書く。
現在使用しているゲームパッドは以下。

GitHub第七回で使用したevtestソースコードがあった*1ので、

github.com

以下のLinux Kernelのドキュメントを参照しながらコードを読んだ。

www.kernel.org

do_capture()という関数がボタンなどの入力を受け付ける処理のメイン部分となっている。

ざっくり要約すると、例えば/dev/input/event0などのデバイスファイルを

    if ((fd = open(filename, O_RDONLY)) < 0) {
        //エラー処理
    }

した後、

    struct input_event ev[64];
    int i, rd;
    fd_set rdfs;

    FD_ZERO(&rdfs);
    FD_SET(fd, &rdfs);

    while (!stop) {
        select(fd + 1, &rdfs, NULL, NULL, NULL);

        //...

        rd = read(fd, ev, sizeof(ev));

        for (i = 0; i < rd / sizeof(struct input_event); i++) {
            //struct input_eventの処理
        }        
    }

のような感じでイベント(struct input_event)を受け取る。

ここをみると、struct input_eventはごくシンプルな構造体になっている。

struct input_event {
        struct timeval time;
        unsigned short type;
        unsigned short code;
        unsigned int value;
};

timeは入力時刻で今回はあまり気にしないとして、イベント種別を表すtypeはここ、具体的なイベント名を表すcodeと入力値を表すvalueはtype毎に意味が決められており、ここに説明がある。
これもざっくり要約してみる。

ON/OFFの二値をとるキーやボタンはtypeがEV_KEYとなり、この場合codeは例えばKEY_A(キーボードのAというキーの場合)のようになる。valueはキー押し下げ時に1、キーのリリース時に0が設定される。

アナログコントローラーやトリガーのような多値をとるセンサーの場合はtypeがEV_ABS*2となり、この場合も具体的なセンサー名がcodeに設定される。valueはセンサー毎に値域(0〜1023や0〜65535など)が決まっている*3

では、ことゲームパッドに関してどのボタンが使えるのかというのがここに書いてある。 図が分かりやすかったので以下に引用する。

          ____________________________              __
         / [__ZL__]          [__ZR__] \               |
        / [__ TL __]        [__ TR __] \              | Front Triggers
     __/________________________________\__         __|
    /                                  _   \          |
   /      /\           __             (N)   \         |
  /       ||      __  |MO|  __     _       _ \        | Main Pad
 |    <===DP===> |SE|      |ST|   (W) -|- (E) |       |
  \       ||    ___          ___       _     /        |
  /\      \/   /   \        /   \     (S)   /\      __|
 /  \________ | LS  | ____ |  RS | ________/  \       |
|         /  \ \___/ /    \ \___/ /  \         |      | Control Sticks
|        /    \_____/      \_____/    \        |    __|
|       /                              \       |
 \_____/                                \_____/

     |________|______|    |______|___________|
       D-Pad    Left       Right   Action Pad
               Stick       Stick

                 |_____________|
                    Menu Pad

アナログスティックのcodeは左側はABS_X(横軸)、ABS_Y(縦軸)、右側はABS_RX(横軸)、ABS_RY(縦軸)である。

また、トリガーについてもドキュメントには記載があるが、今使っているゲームパッドだとevtestの結果を見る限りドキュメントとは違い、codeは左側、右側それぞれABS_ZABS_RZとなっている。

上記を踏まえ、簡単なC++のライブラリを作りここにpushした。

使い方はこんな感じ。
下記を実行すると、ゲームパッドを認識したとき、ABS_XABS_RZを操作したとき、ゲームパッドを認識しなくなったときにメッセージが出力される。

#include "lunchjet_user_input_device.h"

#include <iostream>
#include <linux/input.h>
#include <linux/input-event-codes.h>

class Listener : public UserInputDevice::EventListener {
    void on_connect() {
        std::cout << "connect" << std::endl;
    }

    void on_receive(const struct input_event &event) override {
        std::cout << "event type:" << event.type << " code:" << event.code << " value:" << event.value << std::endl;
    }

    void on_close() {
        std::cout << "close" << std::endl;
    }
};


int main(int argc, const char *argv[])
{
    //シグナル周りの設定

    UserInputDevice device("/dev/input/event3", stop_user_input_device_thread);
    Listener listener;

    device.add_listener({{EV_ABS, ABS_X}, {EV_ABS, ABS_RZ}}, listener);
    if(device.listen() == -1){
        std::cout << "listen() failed" << std::endl;
        exit(1);
    }

    pause();
    std::cout << "exit main()" << std::endl;

    return 0;
}

これでラジコンの制御とゲームパッドの入力がそれぞれ実装できたので、次回はいよいよ両者を結合して動作確認をする。

(追記)次回 : Jetson NanoでFPVラジコンを作る(10) - 自由課題

前回までの記事

Jetson NanoでFPVラジコンを作る(1) - 自由課題
Jetson NanoでFPVラジコンを作る(2) - 自由課題
Jetson NanoでFPVラジコンを作る(3) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(4) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(5) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(6) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(7) - 自由課題
Jetson NanoでFPVラジコンを作る(8) - 自由課題

*1:どうもミラーリポジトリらしい

*2:センサーによってはEV_RELが送出されるらしい

*3:おそらくものによって能力が違い、値域はioctl()で取得可能