第9回

列挙型と状態管理

プログラムの「状態」を数値ではなく名前で扱う。

enum

enum Mode {
    MANUAL,
    AUTO,
    ERROR
};

enum class

enum class Mode {
    MANUAL,
    AUTO,
    ERROR
};

使用:

Mode mode = Mode::MANUAL;

switchとの組み合わせ

switch (mode) {
case Mode::MANUAL:
    break;

case Mode::AUTO:
    break;

case Mode::ERROR:
    break;
}

状態が名前で書かれているため、case 0:のような数値よりはるかに読みやすい。

マイコンでの利用

例えば飛行制御なら、

enum class FlightMode {
    MANUAL,
    LEVEL_TURN,
    CLIMBING_TURN,
    EIGHT_TURN,
    LANDING
};

のように状態を管理できる。int mode = 3;のような書き方と比べ、何を表しているかがコード上で明確になる。

動かして確かめる

演習に入る前に、まずこのサンプルを動かして結果を見る。追加の部品は要らない。光るのはボードに載っている内蔵LEDで、それ以外はシリアルモニタの出力で確かめられる。code/examples/09-robot-state/を開き、pio run -t uploadで書き込んでからpio device monitorで出力を見る(手順と、外付けのLEDなどをつなぐ場合は開発環境にまとめてある)。

code/examples/09-robot-state/src/main.cpp
// 第9回:状態を enum class で管理する

#include <Arduino.h>

enum class RobotState {
    STOP,
    MOVE,
    ERROR
};

// ボード上の内蔵LED。配線なしで光る(このボードではGPIO27のRGB LED)
const int LED_PIN = LED_BUILTIN;

RobotState state = RobotState::STOP;

/**
 * @brief 状態を切り替える
 * @details 切り替えをこの関数1つに通しておくと、
 *          どこから状態が変わったのかを追いやすい。
 * @param next 次の状態
 */
void setState(RobotState next) {
    state = next;
}

/**
 * @brief 現在の状態に応じてLEDを制御する
 */
void updateLed() {
    switch (state) {
    case RobotState::STOP:
        digitalWrite(LED_PIN, LOW);
        break;

    case RobotState::MOVE:
        digitalWrite(LED_PIN, HIGH);
        break;

    case RobotState::ERROR:
        digitalWrite(LED_PIN, HIGH);
        delay(100);
        digitalWrite(LED_PIN, LOW);
        delay(100);
        break;
    }
}

void setup() {
    pinMode(LED_PIN, OUTPUT);
    setState(RobotState::MOVE);
}

void loop() {
    updateLed();
    delay(50);
}

setup()でMOVEにしているのでRGB LEDが点灯する。setState()の引数をSTOPやERRORに書き換えて、見え方を比べる。

確かめること。

  • switchのcaseがRobotState::MOVEと名前で書かれている。case 1:より何を表しているかが読み取れる
  • enum classなのでstate = 1;とは書けない。間違った値が入らない
  • 状態を増やすときはenum classに1行足す。switchにcaseを書き忘れると、コンパイラが警告を出すことがある
  • 状態の切り替えをsetState()という関数1つに通している。どこから状態が変わったのかを追いやすい

演習:簡単なロボットの状態を作る

enum class RobotState {
    STOP,
    MOVE,
    ERROR
};

状態によってLEDの動作を変える。

  • STOP:消灯
  • MOVE:点灯
  • ERROR:点滅

loop()の中でswitchにより分岐させる。状態を切り替える関数も用意してみる。