シリーズ:実験シリーズ(フェーズ2)
対応ロードマップ:フェーズ2 / E2-05
この記事で扱う範囲:IDLE / RUN / ERROR の3状態に分けて、待機中・動作中・異常時の処理を整理して書けるようにする。
1. 目的
前回までで、
delayで待っていると他の処理が止まること- 1msごとの時刻を作ること
- 周期処理を複数動かせること
を確認してきました。
ただ、処理が増えてくると、今は何をしている場面なのか が分かりにくくなります。
たとえば、待っている途中なのか、動いている途中なのか、異常で止まっているのかが整理しにくくなります。
そこで今回は、処理を次の3つの状態に分けます。
IDLE:待機中RUN:通常動作中ERROR:異常で停止中
今回のゴールは、
状態ごとに“やること”を分けて書けるようになること です。
この形にしておくと、今後のロボット制御でも、
- 通常動作は
RUN - 異常時は
ERROR - 復帰待ちは
IDLE
このように分けておくと、あとで機能を増やすときに、何をどこに書けばよいか迷いにくくなります。
2. 前提・環境
2-1. 前提
この記事は、次の実験が終わっている前提で進めます。
- E1-05:ボタン入力でLED(ポーリング)
- E1-08:ボタン入力を安定させる(デバウンス)
- E2-02:1msごとの時刻を作る
- E2-03:タイマで周期処理
- E2-04:複数周期タスク
2-2. 使用するもの
- 評価ボード:RTK7EKA8M2S00001BE
- e² studio + FSP
- 使用部品
- LED × 1
- 抵抗:1kΩ × 1
- タクトスイッチ × 1
- ブレッドボード
- ジャンパ線
- 使用するGPIO
- 外付けLED用のGPIO:P409(J25-9)
- ボタン入力用のGPIO:P704(J25-10)
- GND:J25-11
2-3. 今回の動作イメージ
今回は次のように動かします。
IDLE- LEDは消灯
- ボタンが押されるまで待つ
RUN- 500msごとにLEDを点滅する
- 一定時間たったらテスト用に
ERRORへ入る
ERROR- LEDを消灯して安全停止する
- ボタンが押されるまで復帰待ちする
- ボタンが押されたら
IDLEに戻る
ここで大事なのは、1つの while の中で全部をぐちゃぐちゃに書かず、状態ごとに分ける ことです。
3. 今回の変更点
3-1. 配線変更
- E1-05 ボタン入力でLEDを点ける(ポーリング):押したら点灯、離したら消灯(リンク)の配線をそのまま使います
3-2. 設定変更
- E1-05 ボタン入力でLEDを点ける(ポーリング):押したら点灯、離したら消灯(リンク)の設定に、
E2-02 ミリ秒のタイマを作る(1msカウンタ)(リンク)の1msタイマの設定を追加したもの
3-3. コード変更
stateという状態変数を追加switchでIDLE/RUN/ERRORを分けるRUNでは周期処理を実行ERRORでは安全停止と復帰待ちを行う
4. 手順
4-1. 状態を決める
まずは、使う状態を決めます。
typedef enum
{
APP_STATE_IDLE = 0,
APP_STATE_RUN,
APP_STATE_ERROR
} app_state_t;今回は3つだけです。
最初は増やしすぎず、この3つで十分です。
4-2. 状態ごとの役割を決める
状態に名前を付けただけでは足りません。
その状態で何をするのか を先に決めます。
IDLE- LEDを消灯する
- ボタンが押されるのを待つ
RUN- 500msごとにLEDを反転する
- 異常条件になったら
ERRORへ移る
ERROR- LEDを消灯する
- RUN中の処理を止める
- ボタンが押されたら
IDLEへ戻る
この「状態ごとの役割」を先に言葉で決めておくと、コードがかなり書きやすくなります。
4-3. switch で状態ごとに分ける
状態分けは if でも書けますが、今回は見やすさのため switch を使います。
switch (g_state)
{
case APP_STATE_IDLE:
/* IDLEの処理 */
break;
case APP_STATE_RUN:
/* RUNの処理 */
break;
case APP_STATE_ERROR:
/* ERRORの処理 */
break;
default:
g_state = APP_STATE_ERROR;
break;
}こうしておくと、今どの状態の処理を書いているのか が見やすくなります。
4-4. RUNの中に周期処理を書く
前回までの実験で作った1msタイマを使って、RUN のときだけ周期処理を動かします。
たとえば、
- 500msごとにLED反転
- 5000ms経過したらテスト用に
ERRORに遷移
のようにします。
これにより、周期処理そのものと、その周期処理を“いつ動かすか” を分けて考えられるようになります。
4-5. ERRORでは安全停止に集中する
ERROR に入ったら、まず 動作を止める ことを優先します。
今回はLED実験なので、
- LEDを消灯
RUNで行っていたLED点滅処理を止める- ボタンを押すまで復帰しない
という構成にします。
5. コード
※下記は考え方を分かりやすくするためのシンプルな例です。
※LEDピン名、ボタンピン名、1msカウンタ変数名は、自分のプロジェクトに合わせて置き換えてください。
#include "hal_data.h"
#include <stdbool.h>
typedef enum
{
APP_STATE_IDLE = 0,
APP_STATE_RUN,
APP_STATE_ERROR
} app_state_t;
/* 1msごとに増えるカウンタ(E2-02で作成したものを使う) */
volatile uint32_t g_ms_count = 0;
/* 状態 */
static app_state_t g_state = APP_STATE_IDLE;
/* RUN開始時刻 */
static uint32_t g_run_start_ms = 0;
/* LED点滅管理 */
static uint32_t g_led_toggle_ms = 0;
static bool g_led_on = false;
/* ここは自分の環境に合わせて実装する */
static bool button_is_pressed(void);
static void led_on(void);
static void led_off(void);
static void led_toggle(void);
/* 1msタイマコールバック */
void timer0_callback(timer_callback_args_t * p_args)
{
FSP_PARAMETER_NOT_USED(p_args);
g_ms_count++;
}
void hal_entry(void)
{
while (1)
{
switch (g_state)
{
case APP_STATE_IDLE:
{
/* 待機中はLED消灯 */
led_off();
g_led_on = false;
/* ボタンが押されたらRUNへ */
if (button_is_pressed())
{
g_run_start_ms = g_ms_count;
g_led_toggle_ms = g_ms_count;
g_state = APP_STATE_RUN;
}
break;
}
case APP_STATE_RUN:
{
/* 500msごとにLED反転 */
if ((g_ms_count - g_led_toggle_ms) >= 500U)
{
g_led_toggle_ms = g_ms_count;
led_toggle();
g_led_on = !g_led_on;
}
/* テスト用:5秒経過したらERRORへ */
if ((g_ms_count - g_run_start_ms) >= 5000U)
{
g_state = APP_STATE_ERROR;
}
break;
}
case APP_STATE_ERROR:
{
/* 安全停止:LED消灯 */
led_off();
g_led_on = false;
/* 復帰待ち:ボタンが押されたらIDLEへ戻る */
if (button_is_pressed())
{
g_state = APP_STATE_IDLE;
}
break;
}
default:
{
g_state = APP_STATE_ERROR;
break;
}
}
}
}
/* --- 下は例。自分のGPIO設定に合わせて修正する --- */
static bool button_is_pressed(void)
{
bsp_io_level_t level = BSP_IO_LEVEL_HIGH;
/* 例:内部プルアップ入力で、押したらLOWになる想定 */
R_IOPORT_PinRead(&g_ioport_ctrl, BUTTON_PIN, &level);
if (level == BSP_IO_LEVEL_LOW)
{
return true;
}
else
{
return false;
}
}
static void led_on(void)
{
/* Active-Low の場合は LOW/HIGH を逆にする */
R_IOPORT_PinWrite(&g_ioport_ctrl, LED_PIN, BSP_IO_LEVEL_HIGH);
}
static void led_off(void)
{
/* Active-Low の場合は LOW/HIGH を逆にする */
R_IOPORT_PinWrite(&g_ioport_ctrl, LED_PIN, BSP_IO_LEVEL_LOW);
}
static void led_toggle(void)
{
if (g_led_on == false)
{
led_on();
}
else
{
led_off();
}
}6. 実行結果
6-1. 確認したい動き
今回の実験では、次の流れになればOKです。
- 電源投入直後の状態は
IDLE- LEDは消えている
- ボタンを押す
- 状態が
RUNになる - LEDが500msごとに点滅する
- 状態が
- 5秒たつ
- 状態が
ERRORになる - LEDが消灯して止まる
- 状態が
- もう一度ボタンを押す
- 状態が
IDLEに戻る - LED消灯の待機状態に戻る
- 状態が
6-2. 今回確認できればよいこと
今回の段階では、次が確認できれば十分です。
IDLE/RUN/ERRORで処理が分けられているRUNのときだけ周期処理が動くERRORではLEDを消灯したままにできるERRORに入ったあと、ボタンが押されるまで待ち、押されたらIDLEに戻せる
7. ハマりポイント/原因と対策
7-1. ボタンを押してもRUNに入らない
原因
- ボタンを押したことを、プログラムが正しく読めていない
- 「押したときON」と思っていたが、実際は「押したときOFF」の設定になっている
- ボタン入力が安定していない
対策
- まず、ボタンを押したときに
button_is_pressed()がtrueになるか確認する - 押したときに
falseのままなら、押した/離したの判定を逆にする - E1-05、E1-08 と同じ配線・同じ入力設定にそろえる
- ボタンがふらつく場合は、デバウンスした入力処理を使う
7-2. RUNに入るがLEDが点滅しない
原因
- 1msカウンタが動いていない
- 500msごとの判定がうまくできていない
- LEDのON/OFFの設定が合っていない
対策
g_ms_countの値が増え続けているか確認する- 増えていなければ、1msタイマやコールバック処理を見直す
g_led_toggle_ms = g_ms_count;を入れて、次の500msを正しく測れるようにする- LEDが逆の動きをする場合は、
led_on()とled_off()の中身を見直す
7-3. ERRORに入ってもLEDが消えない
原因
ERRORに入っても、RUNのときの点滅処理を続けてしまっているERRORの中で LED を消灯していない
対策
ERRORの処理に入ったら、最初にled_off();を書くRUNのときだけ点滅処理を行う形になっているか見直すERRORでは LED を消灯したままにする、と先に決めてからコードを書く
7-4. ERRORから復帰できない
原因
- ボタンが押されたら
IDLEに戻る処理が入っていない - 押したときの判定が逆になっている
- ボタン入力が安定していない
対策
if (button_is_pressed()) { g_state = APP_STATE_IDLE; }があるか確認する- ボタンを押しても戻らない場合は、押したときの判定を見直す
- 配線を見直し、必要ならデバウンス済みの入力処理にする
8. 今回わかったこと
今回の実験で大事なのは、難しい状態を作ることではありません。
大事なのは次の3つです。
- 今の状態に名前を付ける
- 状態ごとにやることを分ける
- 異常時の処理と通常処理を分ける
これができると、
「動く処理」
「止める処理」
「復帰を待つ処理」
を整理して書けるようになります。
フェーズ2では、これで十分です。
まずは、周期処理を状態で分けて書けること が大事です。
9. 次回やること
E3-00 I2C配線の“詰まりどころ”を先に潰す(電圧/プルアップ/GND)
タイマと状態で処理を整理できるようになったので、次はセンサ実験に入る前に、I2C配線でつまずきやすいポイントを先に確認します。
10. 関連リンク
- 実験シリーズ:E2-01 delayの限界を体験(リンク)
- 実験シリーズ:E2-02 タイマで1ms毎にカウントアップするストップウォッチを作る(リンク)
- 実験シリーズ:E2-03 タイマで周期処理(正確な点滅)(リンク)
- 実験シリーズ:E2-04 複数周期タスク(リンク)
- 基礎シリーズ:組み込み開発とは(リンク)
- 基礎シリーズ:C言語のはじめ(main/ループ/関数/変数)(リンク)
- 基礎シリーズ:はじめての状態遷移(IDLE/RUN/ERROR)(リンク)