シリーズ:実験シリーズ(フェーズ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. まず今回やりたいことを日本語で整理する
今回は、次の流れです。
- タイマ割り込みで、1msごとに時刻を1増やす
- メインループは止まらず、ずっと回り続ける
- 100msたったら、100ms用の処理を1回動かす
- 700msたったら、700ms用の処理を1回動かす
- これをずっと繰り返す
ここで大事なのは、「100ms待ってから700ms待つ」のではなく、それぞれの処理が“自分の周期になったら動く” ことです。
4-2. 1つの周期処理と何が違うか
E2-03 では、考える対象は1つだけでした。
- 500msたったか?
- たっていたらLEDを反転する
今回は、同じような判定が2つになります。
- 100msたったか?
- 700msたったか?
つまり、「時間になったかどうかを確認する対象が増える」 だけです。
この形にすると、今後は
- ボタン監視は10msごと
- 状態表示は100msごと
- センサ取得は20msごと
- ログ出力は1000msごと
のように、役割ごとに周期を分けていけます。
4-3. 今回の考え方
今回は、2つの周期用に 別々のカウンタ を持ちます。
g_task_100ms_countg_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で使用した設定内容は次の通りです。
- FSP Configurator(
configuration.xml)を開く - 1ms周期のタイマが設定されていることを確認する
- コールバック関数が設定されていることを確認する
- 「Generate Project Content」を実行する
※今回は新しいタイマを増やすのではなく、1つの1msタイマを土台にして、複数の周期処理を作ること が目的です。
5. コード
5-1. 考え方
今回のコードでやることは、次の5つです。
- 1msタイマのコールバックで、100ms用カウンタと700ms用カウンタを増やす
- 周期になったら、それぞれのフラグを立てる
- メインループで100msタスクのフラグを見る
- メインループで700msタスクのフラグを見る
- フラグが立っていたら、その周期の処理を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_countg_task_700ms_count
を別々に持っています。
これにより、
- 100msの判定
- 700msの判定
を独立して扱えます。
2) 周期ごとにフラグも分けている
周期が違えば、「今どの処理を動かすか」の合図も別にした方が分かりやすいです。
g_task_100ms_flagg_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_countとg_task_700ms_countの両方を増やすように修正する - メインループ側で
g_task_100ms_flagとg_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点滅で必要なところだけ)(リンク)