ハードウェアと連携するソフトウェアを作った


やったこと

ハードウェア入出力を行う簡易的なアプリを作った。

  • 入力用スイッチ3つ
    • 押下中は緑四角を表示
    • どれか1つでも押されているとLEDを発光

要求を定義し、実装方法を考えた。

要求1. ハードウェアの変化(入力)を素早く検出する
「他処理を実行中のためボタンの入力検出ができなかった」といったことがないように、ハードウェアの変化検出を別スレッドとして常時動作させるようにした。

要求2. イベント駆動で処理する
スイッチを押して画面を変化させる場合、スイッチを押したというイベントと、画面を変化させるという処理の対応付けが必要になる。イベントなどの状態変化に応じた処理を行う場合、デザインパターンのObserverが有効である。今回はObserverの実装の1つであるQtのシグナル/スロットを利用した。なお、Qtではイベントのことをシグナルと表現している。

開発環境

JetsonでのQt開発開発環境の構築はこちらを参照のこと。

  • ホスト(Ubuntu)
  • ターゲット(Jetson TX1)
    • タクトスイッチ x 3個
    • GUI画面
    • LED

回路図

絵はラズパイだがJetsonのGPIOポートとして見ること。割当表はこれ

実装例

ソースコード全体はGithubを参照。
以下のソースコードは説明のために簡略化している。

スイッチの状態監視

Switchクラスでスイッチの状態監視およびイベント通知を行う。
QThreadを継承させることで、スレッド処理ができるようになる。
run()内でスイッチの状態を監視し、状態が変化した場合switch_eventシグナルを発生させる。

class Switch : public QThread, public Gpio {
    Q_OBJECT
    ...
protected:
    void run();
signals:
    void switch_event(Switch::SwitchEvent event);
};

Switchオブジェクトのコンストラクタにて、start()を実行する。
start()を実行すると、run()が別スレッドとして動作する。

Switch::Switch(int p, Direction d, int v) :
    ...
{
    // start thread
    start();
}

run()ではスイッチの状態を取得し、値に応じてシグナルを発生させる。
スイッチの状態が変化するとswitch_eventシグナルが発生する。

void Switch::run()
{
    while(true){
        getValue();
        if(1 == value){
            emit switch_event(SW_PUSH);
        } else {
            emit switch_event(SW_RELEASE);
        }
    }
 }

イベントに応じた画面制御

connect関数で、イベント(シグナル)とそれに応じた処理を設定する。
wオブジェクトは、画面制御を行うMainWindowオブジェクトである。
以下は、sw1オブジェクトのswitch_eventシグナルが発生した場合、wオブジェクトのsw1_event関数が呼ばれることを意味している。

connect(sw1, SIGNAL(switch_event(Switch::SwitchEvent)), w, SLOT(sw1_event(Switch::SwitchEvent)));

sw1_event関数では、押されたスイッチのFrameをスタイルシートにより緑色にする処理を行っている。

void MainWindow::sw1_event(Switch::SwitchEvent event)
{
    setFrameSWStyleSheet(ui->frame_sw1, event);
    checkSWState();
}

checkSWState関数では、スイッチの状況に応じてLEDの発光/消灯を行うled_eventシグナルを発生させる。

void MainWindow::checkSWState()
{
    if(ui->frame_sw1->styleSheet() == "background-color:Green" ||
       ui->frame_sw2->styleSheet() == "background-color:Green" ||
       ui->frame_sw3->styleSheet() == "background-color:Green" ){
       emit led_event(Led::LedEvent::LED_ON);
    } else {
       emit led_event(Led::LedEvent::LED_OFF);
    }
}

LEDの点灯制御

LEDの点灯程度なら一瞬で処理が終わるが、ハードウェア処理に時間がかかる場合は画面制御とは別のスレッドで実行する必要がある。ハードウェア制御を画面制御と同じスレッドで処理する場合、ハードウェア制御が終わるまで画面が固まってしまう。
これを避けるために、QThreadオブジェクトを使用し、LEDの発光制御を行うledオブジェクトをmoveToThreadでスレッド処理にする。

    Led *led = new Led(511, Gpio::OUT);
    QThread* th_led = new QThread;

    led->moveToThread(th_led);
    th_led->start();

wオブジェクトのled_eventシグナルが発生した場合、ledオブジェクトのled_event関数が呼ばれるように設定する。

connect(w, SIGNAL(led_event(Led::LedEvent)), led, SLOT(led_event(Led::LedEvent)));

ledオブジェクトのled_event関数では、状態に応じてLEDの点灯制御を行う。

void Led::led_event(Led::LedEvent event)
{
    LED_ON == event ? setValue(1) : setValue(0);
}

補足

Observerパターンの実装は他にもBoost Signal2が有名らしい。QThreadに依存しない設計にしたい場合はこれを使うと良い。