フェーズ2

E2-04 複数周期タスク(例:100msと700msを同時に回す)

シリーズ:実験シリーズ(フェーズ2)
対応ロードマップ:フェーズ2 / E2-04
今回の役割:E2-02で作った1msのタイマと、E2-03で作った周期処理の考え方を使って、周期の違う複数の処理を同時に動かす形を作る。

1. 目的

今回は、1msごとに増えるタイマを使って、100ms周期の処理と700ms周期の処理を同時に動かす 実験を行います。
これにより、1つのdelayで待つのではなく、複数の処理をそれぞれの周期で並行に動かす 感覚をつかむのが目的です。

2. 前提・環境

  • 評価ボード:RTK7EKA8M2S00001BE
  • 開発環境:e² studio + FSP
  • 前提
    • E2-01:delayの限界を体験(他処理できない)ができている
    • E2-02:タイマで1ms毎にカウントアップするタイマを作る ができている
    • E2-03:時間になったら処理(周期処理:正確な点滅)ができている
  • 今回使うもの
    • 評価ボード本体
    • オンボードLED
  • 今回の考え方
    • E2-02で作った 1msごとのタイマ を使う
    • E2-03では「500msたったらLED反転」という 1つの周期処理 を作った
    • 今回はそれを広げて、100ms処理700ms処理 を同時に持つ

3. 今回の変更点

3-1. 配線変更

  • なし
  • 評価ボードのオンボードLEDをそのまま使います

3-2. 設定変更

  • なし(E2-02 / E2-03のまま)
  • 1ms周期のタイマ設定をそのまま使います

3-3. コード変更

E2-03 では、500msごとにLEDを反転する 1つの周期処理 を作りました。
今回はそこから進めて、100msごとに動く処理700msごとに動く処理 を同時に持てるようにします。
大事なのは、

  • 100msの処理は100msのタイミングで動く
  • 700msの処理は700msのタイミングで動く
  • どちらかが動いているからといって、もう片方が止まるわけではない

ということです。

4. 手順

4-1. まず今回やりたいことを日本語で整理する

今回は、次の流れです。

  1. タイマ割り込みで、1msごとに時刻を1増やす
  2. メインループは止まらず、ずっと回り続ける
  3. 100msたったら、100ms用の処理を1回動かす
  4. 700msたったら、700ms用の処理を1回動かす
  5. これをずっと繰り返す

ここで大事なのは、「100ms待ってから700ms待つ」のではなく、それぞれの処理が“自分の周期になったら動く” ことです。

4-2. 1つの周期処理と何が違うか

E2-03 では、考える対象は1つだけでした。

  • 500msたったか?
  • たっていたらLEDを反転する

今回は、同じような判定が2つになります。

  • 100msたったか?
  • 700msたったか?

つまり、「時間になったかどうかを確認する対象が増える」 だけです。
この形にすると、今後は

  • ボタン監視は10msごと
  • 状態表示は100msごと
  • センサ取得は20msごと
  • ログ出力は1000msごと

のように、役割ごとに周期を分けていけます。

4-3. 今回の考え方

今回は、2つの周期用に 別々のカウンタ を持ちます。

  • g_task_100ms_count
  • g_task_700ms_count

そして、1msごとに両方を増やします。

  • g_task_100ms_count が 100 以上になったら、100msタスクのフラグを立てる
  • g_task_700ms_count が 700 以上になったら、700msタスクのフラグを立てる

実際の処理は、割り込みの中ではなく、メインループ側 で行います。

4-4. 今回は何を処理させるか

今回は次のようにします。

  • 100msタスク
    100msごとに、ソフト上のフラグを反転する
    → 「短い周期の処理が動いている」ことを表す
  • 700msタスク
    700msごとに、オンボードLEDを反転する
    → 目で見て確認できる

こうすると、

  • 速い周期の処理
  • 遅い周期の処理
    を同時に持つイメージがつかみやすくなります。

4-5. FSP設定を確認する

今回は E2-02 / E2-03 の設定をそのまま使います。
E2-02 / E2-03で使用した設定内容は次の通りです。

  1. FSP Configurator(configuration.xml)を開く
  2. 1ms周期のタイマが設定されていることを確認する
  3. コールバック関数が設定されていることを確認する
  4. Generate Project Content」を実行する

※今回は新しいタイマを増やすのではなく、1つの1msタイマを土台にして、複数の周期処理を作ること が目的です。

5. コード

5-1. 考え方

今回のコードでやることは、次の5つです。

  1. 1msタイマのコールバックで、100ms用カウンタと700ms用カウンタを増やす
  2. 周期になったら、それぞれのフラグを立てる
  3. メインループで100msタスクのフラグを見る
  4. メインループで700msタスクのフラグを見る
  5. フラグが立っていたら、その周期の処理を1回だけ実行する

5-2. コード例

※オンボードLEDのピン名は、これまでの実験で使っているものに合わせてください。
※下の例では、以前と同じく BSP_IO_PORT_06_PIN_00 を使っています。
※タイマ名やコールバック名は、FSPで生成された名前に合わせて読み替えてください。

#include "hal_data.h"
#include <stdbool.h>
#include <stdint.h>

volatile uint32_t g_task_100ms_count = 0;
volatile uint32_t g_task_700ms_count = 0;

volatile bool g_task_100ms_flag = false;
volatile bool g_task_700ms_flag = false;

void timer0_callback(timer_callback_args_t * p_args)
{
    FSP_PARAMETER_NOT_USED(p_args);

    g_task_100ms_count++;
    g_task_700ms_count++;

    if (g_task_100ms_count >= 100)
    {
        g_task_100ms_count = 0;
        g_task_100ms_flag = true;
    }

    if (g_task_700ms_count >= 700)
    {
        g_task_700ms_count = 0;
        g_task_700ms_flag = true;
    }
}

void hal_entry(void)
{
    fsp_err_t err;
    bool led_on = false;
    bool task_100ms_state = false;

    /* 1msタイマ開始 */
    err = R_AGT_Open(&g_timer0_ctrl, &g_timer0_cfg);
    if (FSP_SUCCESS != err)
    {
        while (1)
        {
        }
    }

    err = R_AGT_Start(&g_timer0_ctrl);
    if (FSP_SUCCESS != err)
    {
        while (1)
        {
        }
    }

    while (1)
    {
        /* 100msごとの処理 */
        if (g_task_100ms_flag)
        {
            g_task_100ms_flag = false;

            task_100ms_state = !task_100ms_state;

            /* 今回は見えやすい処理の代わりに、
               ソフト上の状態を反転するだけにしている
               将来はボタン監視や状態更新などを入れられる */
        }

        /* 700msごとの処理 */
        if (g_task_700ms_flag)
        {
            g_task_700ms_flag = false;

            led_on = !led_on;

            if (led_on)
            {
                R_BSP_PinWrite(BSP_IO_PORT_06_PIN_00, BSP_IO_LEVEL_HIGH);
            }
            else
            {
                R_BSP_PinWrite(BSP_IO_PORT_06_PIN_00, BSP_IO_LEVEL_LOW);
            }
        }

        /* メインループは止まらず回り続ける */
    }
}

5-3. このコードで見てほしいポイント

1) 周期ごとにカウンタを分けている

今回は、

  • g_task_100ms_count
  • g_task_700ms_count

を別々に持っています。
これにより、

  • 100msの判定
  • 700msの判定

を独立して扱えます。

2) 周期ごとにフラグも分けている

周期が違えば、「今どの処理を動かすか」の合図も別にした方が分かりやすいです。

  • g_task_100ms_flag
  • g_task_700ms_flag

として分けることで、
どの周期の処理が実行対象なのか をはっきりできます。

3) 割り込みの中では最低限の処理だけ行う

今回も、割り込みの中では

  • カウンタを増やす
  • 周期になったらフラグを立てる

だけにしています。
実際の処理はメインループ側で行います。
この形にしておくと、あとで処理が増えても整理しやすくなります。

4) 「複数周期タスク」といっても、特別な仕組みではない

最初は難しく見えるかもしれませんが、やっていることは単純です。

  • 1msの土台がある
  • 周期ごとにカウンタを持つ
  • 時間になったらフラグを立てる
  • メインループでそのフラグを見て処理する

つまり、E2-03 では 1つの周期処理 だけを動かしましたが、
今回はそれを 100ms用と700ms用の2つの処理にした だけです。

6. 実行結果

  • オンボードLEDが 約700msごとに点灯/消灯 した
  • 100ms側の処理も、700ms側とは独立して動かせる形になった

7. 今回わかったこと

今回の実験で一番大事なのは、次の1点です。

  • 1つの時刻を土台にすると、周期の違う複数の処理を同時に動かせる

これができると、今後のロボット制御で必要になる

  • 周期更新
  • 状態更新
  • 入力確認
  • ログ出力

などを、役割ごとに分けて考えやすくなります。

8. ハマりポイント/原因と対策

8-1. LEDがまったく点滅しない

原因

  • タイマが開始できていない
  • コールバック関数が呼ばれていない
  • LEDピン名が違う

対策

  • R_AGT_Open() または R_AGT_Start() の戻り値が FSP_SUCCESS でない場合は、タイマ設定や生成コードを見直し、必要なら FSP の設定確認後に Generate Project Content をやり直す
  • timer0_callback() が呼ばれていない場合は、FSPのタイマ設定で コールバック関数名がコード中の名前と一致しているか を見直す
  • LEDピン名が違う場合は、これまでの実験で動作確認できたオンボードLEDのピン名に修正する

8-2. 100ms側または700ms側のどちらかしか動いていない

原因

  • 片方のカウンタしか増やしていない
  • 片方のフラグしか見ていない
  • 条件値(100 / 700)が想定と違っている

対策

  • コールバック内で g_task_100ms_countg_task_700ms_count両方を増やすように修正する
  • メインループ側で g_task_100ms_flagg_task_700ms_flag両方を見て、それぞれ処理する形に直す
  • 条件が >= 100>= 700 になっていない場合は、意図した周期になるように数値を修正する
  • 100ms側はLEDでは見えないため、分かりにくい場合は task_100ms_state をデバッガで監視して、変化しているか確認する

8-3. LEDが1回だけ変わって止まる

原因

  • フラグをfalseに戻していない
  • カウンタを0に戻していない
  • コールバックが継続して呼ばれていない

対策

  • フラグを使ったあとに g_task_700ms_flag = false; を入れて、1回処理したらフラグを下ろすようにする
  • 周期ごとに繰り返すにはカウンタを戻す必要があるため、g_task_700ms_count = 0; を入れて、次の周期を数え直す形に修正する
  • それでも止まる場合は、デバッガでカウンタが増え続けているか確認し、増えていなければタイマ開始やコールバック設定に戻って見直す
  • まずは700ms側だけに絞って動作確認し、そのあと100ms側を戻すと切り分けしやすい

8-4. 周期が不安定に見える

原因

  • タイマ周期が1msになっていない
  • 割り込みの中で重い処理をしている
  • メインループ側に重すぎる処理を入れている

対策

  • E2-02 の設定を見直し、土台のタイマが本当に 1ms 周期になるように修正する
  • 割り込みの中には、カウンタ更新とフラグセットだけを残し、LED操作や長い処理はメインループ側へ移す
  • メインループ側で処理を増やしすぎた場合は、まず今回の最低限のコードに戻して安定動作を確認してから、少しずつ処理を足す
  • 不安定の原因が分かりにくい場合は、100ms側または700ms側のどちらか一方だけにして、まず単独で安定するか確認する

8-5. コードは動くが、「複数周期の意味」がよく分からない

原因

  • 100ms処理と700ms処理の役割の違いが見えていない
  • 「何のために周期を分けるのか」がまだつかめていない

対策

  • 周期を分けるのは、役割ごとにちょうどよい速さで処理するため だと考える
  • たとえば
    • ボタン確認は短い周期
    • LED表示は少し遅い周期
    • ログ出力はもっと遅い周期
      のように、処理ごとに向いた周期が違う と考えると分かりやすい
  • 今回はまず、「1つの時刻を土台にして、周期の違う処理を別々に動かせる」ことが分かれば十分 と考える

9. 次回やること

次は、E2-05 状態入門(IDLE/RUN/ERROR) に進みます。
複数周期で動く処理の土台の上に、状態ごとにやることを分ける書き方 を加えていきます。

10. 関連リンク

  • E2-01 delayの限界を体験(他処理できない)(リンク)
  • E2-02 タイマで1ms毎にカウントアップするストップウォッチを作る(リンク)
  • E2-03 時間になったら動く(周期処理:正確な点滅)(リンク)
  • E2-05 状態入門(IDLE/RUN/ERROR)(リンク)
  • 基礎:組み込み開発とは(リンク)
  • 基礎:マイコン(RA8M2):はじめに読む(リンク)
  • 基礎:C言語のはじめ(LED点滅で必要なところだけ)(リンク)

-フェーズ2