組み込みプログラムでは、「1秒ごとに処理したい」「5秒後に処理したい」「一定時間応答がなかったらタイムアウトさせたい」といった時間を基準にした処理がよく登場します。
FreeRTOSでは、このような処理を実現する方法の1つとしてSoftware Timer(ソフトウェアタイマー)が用意されています。
Software Timer開始
↓
1000ms経過
↓
コールバック関数
↓
処理実行
Software Timerを使うと、専用のタスクを1つ作ってvTaskDelay()を繰り返さなくても、指定した時間が経過したときに処理を実行できます。
また、「1回だけ実行するタイマー」と「一定周期で繰り返すタイマー」の両方を作成できます。
この記事では、FreeRTOSのSoftware Timerとは何かという基本から、ワンショットタイマーと周期タイマー、Timer Service Task、コールバック関数、xTimerCreate()、xTimerStart()、vTaskDelay()との違いまで順番に解説します。
Software Timerとは?
Software Timerは、FreeRTOSがソフトウェアで管理するタイマー機能です。
タイマーを開始すると、設定した時間が経過したタイミングでコールバック関数が呼び出されます。
タイマー開始
↓
時間をカウント
↓
設定時間に到達
↓
コールバック関数
↓
処理を実行
たとえば1秒のSoftware Timerを作れば、1秒後に指定した関数を実行できます。
ただし、「ハードウェアタイマーが割り込みを発生させ、その割り込み内でコールバックが直接実行される」という仕組みではありません。
FreeRTOS内部のTimer Service Task(Timer Daemon Task)によって管理される点が重要です。
Software Timerとハードウェアタイマーの違い
マイコンにはハードウェアとしてタイマー周辺回路が搭載されていることがあります。
FreeRTOSのSoftware Timerは、それとは別の仕組みです。
| Software Timer | ハードウェアタイマー | |
|---|---|---|
| 管理 | FreeRTOS | マイコンのタイマー周辺回路 |
| 処理 | Timer Service Taskでコールバック実行 | 割り込みなどを利用 |
| 精密なタイミング | タスクスケジューリングの影響を受ける | より適する場合がある |
| 用途 | 周期処理・タイムアウトなど | 精密周期・波形生成・計測など |
Software Timerは便利ですが、厳密なマイクロ秒単位のタイミング制御などを目的とした機能ではありません。
Software Timerには2種類ある
Software Timerの基本的な動作は、大きく2種類に分けられます。
- ワンショットタイマー(One-shot Timer)
- 自動再読み込みタイマー(Auto-reload Timer)
ワンショットタイマー
ワンショットタイマーは、設定時間が経過すると1回だけコールバック関数を実行します。
開始
0ms ───────── 1000ms
↓
Callback
↓
終了
たとえば、
- 通信のタイムアウト
- ボタン長押し判定
- 一定時間後にLEDを消す
- 遅延して処理を開始する
といった用途に利用できます。
自動再読み込みタイマー
Auto-reload Timerは、設定時間が経過するたびにタイマーが自動的に再スタートし、コールバック関数を繰り返し実行します。
0ms 1000ms 2000ms 3000ms
│ │ │ │
開始 Callback Callback Callback
↓ ↓ ↓
再開始 再開始 再開始
周期的な処理に利用できます。
- 1秒ごとの状態確認
- 定期的なセンサー取得
- LEDの周期点滅
- 定期的なログ出力
Software Timerを作成する
FreeRTOSではxTimerCreate()を使ってSoftware Timerを作成できます。
TimerHandle_t timer;
timer = xTimerCreate(
"MyTimer",
pdMS_TO_TICKS(1000),
pdTRUE,
NULL,
timerCallback
);
xTimerCreate()の主な引数は次のとおりです。
| 引数 | 意味 |
|---|---|
"MyTimer" | タイマー名 |
pdMS_TO_TICKS(1000) | タイマー周期 |
pdTRUE | 自動再読み込みするか |
NULL | Timer ID |
timerCallback | コールバック関数 |
pdTRUEとpdFALSEの違い
xTimerCreate()の自動再読み込み指定によって、タイマーの動作が変わります。
// 周期タイマー
pdTRUE
// ワンショットタイマー
pdFALSE
| 設定 | 動作 |
|---|---|
pdFALSE | 1回だけ実行 |
pdTRUE | 周期的に繰り返し実行 |
pdMS_TO_TICKS()とは?
FreeRTOSでは時間をTick単位で扱うAPIが多くあります。
pdMS_TO_TICKS()を使うと、ミリ秒からTickへ変換できます。
pdMS_TO_TICKS(1000)
このように書けば、「1000ミリ秒」をFreeRTOSのTick数へ変換できます。
Tick周期はFreeRTOSの設定に依存するため、時間指定を単純な数値だけで書くより意図が分かりやすくなります。
コールバック関数を作る
タイマーの設定時間が経過すると、登録したコールバック関数が呼び出されます。
void timerCallback(
TimerHandle_t xTimer
)
{
Serial.println(
"Timer expired!"
);
}
この関数は、タイマー専用の割り込み処理として直接実行されるわけではありません。
Timer Service Taskのコンテキストで実行されます。
Timer Service Taskとは?
FreeRTOSでは、Software Timerを管理するためにTimer Service Taskが使われます。
Application Task
↓
xTimerStart()
↓
Timer Command Queue
↓
Timer Service Task
↓
タイマー管理
↓
時間経過
↓
Callback実行
xTimerStart()などの操作は、タイマーサービスへコマンドとして送られ、Timer Service Taskによって処理されます。
そしてタイマーが満了すると、そのTimer Service Taskがコールバック関数を実行します。
タイマーを開始する
タイマーを作成しただけでは動作しません。
xTimerStart()を使って開始します。
xTimerStart(
timer,
0
);
第2引数は、タイマーコマンドを送る際にコマンドキューへ空きがない場合、呼び出し元タスクが最大何Tick待つかを指定します。
0なら待たずに戻ります。
周期タイマーを作ってみよう
1秒ごとにメッセージを表示するSoftware Timerを作ってみます。
#include <Arduino.h>
TimerHandle_t timer;
void timerCallback(
TimerHandle_t xTimer
)
{
Serial.println(
"1 second!"
);
}
void setup()
{
Serial.begin(115200);
timer = xTimerCreate(
"Timer",
pdMS_TO_TICKS(1000),
pdTRUE,
NULL,
timerCallback
);
if (timer != NULL) {
xTimerStart(
timer,
0
);
}
}
void loop()
{
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
pdTRUEを指定しているため、1秒ごとにtimerCallback()が呼ばれます。
ワンショットタイマーを作ってみよう
次は、3秒後に1回だけ処理するタイマーです。
timer = xTimerCreate(
"OneShot",
pdMS_TO_TICKS(3000),
pdFALSE,
NULL,
timerCallback
);
pdFALSEなので、3秒後にコールバックが1回実行された後、タイマーは停止状態になります。
Software Timerを停止する
動作中のタイマーはxTimerStop()で停止できます。
xTimerStop(
timer,
0
);
Auto-reload Timerを途中で停止したい場合などに利用します。
タイマーをリセットする
xTimerReset()を使うと、タイマーの満了までの時間を再スタートできます。
タイマー開始 0秒 ↓ 2秒経過 ↓ xTimerReset() ↓ ここから再び 設定時間をカウント
たとえば「最後に操作されてから5秒後に画面を消す」といった処理に便利です。
xTimerReset(
timer,
0
);
タイマー周期を変更する
xTimerChangePeriod()を使えば、Software Timerの周期を変更できます。
xTimerChangePeriod(
timer,
pdMS_TO_TICKS(500),
0
);
この例ではタイマー周期を500msへ変更します。
Software TimerとvTaskDelay()の違い
Software Timerを学ぶと、「vTaskDelay()で待てば同じでは?」という疑問が出てきます。
両者は似た用途で使える場合がありますが、考え方が異なります。
| vTaskDelay() | Software Timer | |
|---|---|---|
| 対象 | 呼び出したタスク自身 | FreeRTOSのタイマー機能 |
| 待機中 | タスクがBlockedになる | アプリ側タスクを待たせる必要はない |
| 時間経過後 | タスクがReadyになる | コールバックが実行される |
| 複数タイマー | 通常は処理ごとの設計が必要 | 複数タイマーを管理可能 |
| 向いている用途 | タスク自身の周期・待機 | タイムアウト・遅延イベントなど |
周期タスクならvTaskDelayUntil()もある
一定周期で「タスクそのもの」を実行したい場合は、Software TimerだけでなくvTaskDelayUntil()も候補になります。
たとえば、100ms周期でセンサーを読み、そのまま演算や通信まで行う独立したタスクなら、周期タスクとして設計する方が分かりやすい場合があります。
周期的に
タスク自身を動かしたい
↓
vTaskDelayUntil()
時間経過をきっかけに
短い処理を実行したい
↓
Software Timer
コールバック関数で長時間処理しない
Software Timerを使ううえで非常に重要なのが、コールバック関数を長時間占有しないことです。
複数のSoftware Timerのコールバックは、同じTimer Service Taskのコンテキストで処理されます。
Timer A満了 ↓ Callback A ↓ 長時間処理…… Timer B満了 ↓ Callback Bを実行したい ↓ 待たされる
そのため、1つのコールバックが長時間処理すると、他のSoftware Timerの処理にも影響する可能性があります。
コールバックで避けたい処理
- 長時間のループ
- 重い計算
- 長時間の通信処理
- 長い待ち時間を伴う処理
- ブロックする可能性がある処理
コールバックでは、フラグを変更したり、QueueやTask Notificationを使って別タスクへ処理を依頼したりする設計が有効です。
Task Notificationと組み合わせる
たとえばSoftware TimerのコールバックからTask Notificationを送ることができます。
Software Timer
↓
時間経過
↓
Callback
↓
Task Notification
↓
Worker Task
↓
時間のかかる処理
void timerCallback(
TimerHandle_t xTimer
)
{
xTaskNotifyGive(
workerTaskHandle
);
}
コールバックを短く保ちながら、実際の処理を別タスクへ任せられます。
Task Notificationについては「Task Notificationとは?FreeRTOSの軽量なタスク通知を初心者向けに解説」も参照してください。
Software TimerとEvent Groupsを組み合わせる
タイマー満了を1つのイベントとして扱い、Event Groupsへ反映することもできます。
Software Timer
↓
5秒経過
↓
TIMER_BITをセット
↓
Event Group
Wi-Fi接続 ──────┐
│
センサー準備 ───┼→ 全条件成立
│
5秒経過 ────────┘
Event Groupsについては「Event Groupsとは?FreeRTOSで複数イベントを待ち合わせる仕組みを初心者向けに解説」を参照してください。
Timer IDとは?
xTimerCreate()にはTimer IDを指定する引数があります。
xTimerCreate(
"Timer",
pdMS_TO_TICKS(1000),
pdTRUE,
myData,
timerCallback
);
Timer IDにはアプリケーション側で使用する任意の値やポインタを関連付けられます。
コールバック側ではpvTimerGetTimerID()で取得できます。
void *data =
pvTimerGetTimerID(
xTimer
);
同じコールバック関数を複数のタイマーで共有するときにも便利です。
複数のSoftware Timerを使える
Software Timerは1つだけではなく、複数作成できます。
Timer A 500ms ↓ LED処理 Timer B 1000ms ↓ 状態確認 Timer C 5000ms ↓ タイムアウト処理
これらをFreeRTOSのTimer Service Taskが管理します。
タイマーごとに専用タスクを1つずつ作らなくても、複数の時間イベントを扱えるのがSoftware Timerの利点の1つです。
ISRからSoftware Timerを操作する
割り込みからSoftware Timerを開始・リセットする場合は、ISR用APIを使用します。
たとえばタイマーをリセットする場合はxTimerResetFromISR()を使用できます。
BaseType_t higherPriorityTaskWoken =
pdFALSE;
xTimerResetFromISR(
timer,
&higherPriorityTaskWoken
);
if (higherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
たとえばボタン割り込みが発生するたびにワンショットタイマーをリセットし、「最後の入力から一定時間経過したら処理する」といった設計に利用できます。
KUMITATE-C3でSoftware Timerを試してみよう
KUMITATE-C3では、GPIO0のLEDを使ってSoftware Timerを簡単に確認できます。
今回は500ms周期のSoftware Timerを作り、LEDを点滅させます。
Software Timer
500ms
↓
Callback
↓
LED反転
GPIO0
↓
500ms後
↓
Callback
↓
LED反転
KUMITATE-C3のサンプルコード
#include <Arduino.h>
const int LED_PIN = 0;
TimerHandle_t ledTimer;
bool ledState = false;
void ledTimerCallback(
TimerHandle_t xTimer
)
{
ledState = !ledState;
digitalWrite(
LED_PIN,
ledState ? HIGH : LOW
);
}
void setup()
{
pinMode(
LED_PIN,
OUTPUT
);
ledTimer = xTimerCreate(
"LED Timer",
pdMS_TO_TICKS(500),
pdTRUE,
NULL,
ledTimerCallback
);
if (ledTimer != NULL) {
xTimerStart(
ledTimer,
0
);
}
}
void loop()
{
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
500msが経過するたびにledTimerCallback()が呼び出され、LEDの状態が反転します。
この例ではコールバック処理が非常に短いため、Software Timerの基本例として適しています。
Software Timerが向いている処理
- 一定時間後のタイムアウト処理
- 一定時間後に1回だけ実行する処理
- 比較的軽い周期処理
- LEDの点滅
- 状態監視のきっかけ
- ボタン長押し判定
- 入力後の一定時間待ち
- 通信再試行のタイミング管理
Software Timerが向かない処理
- 非常に高精度な時間制御
- マイクロ秒単位の厳密な周期処理
- 長時間かかる処理を直接コールバックで実行する用途
- 重い演算処理
- 長時間ブロックする可能性のある処理
高精度な周期制御が必要な場合は、マイコンのハードウェアタイマーや専用周辺回路なども検討します。
Software Timerの主なAPI
| API | 用途 |
|---|---|
xTimerCreate() | Software Timerを作成 |
xTimerStart() | タイマーを開始 |
xTimerStop() | タイマーを停止 |
xTimerReset() | タイマーをリセット |
xTimerChangePeriod() | 周期を変更 |
xTimerDelete() | タイマーを削除 |
xTimerIsTimerActive() | 動作中か確認 |
pvTimerGetTimerID() | Timer IDを取得 |
Software Timerを使うときの注意点
- コールバック関数は短くする
- コールバック内で長時間ブロックしない
- 高精度タイマーとして考えない
- Timer Service Task上で実行されることを理解する
- 重い処理は別タスクへ渡す
- ISRから操作するときはFromISR系APIを使う
- タイマー作成に失敗していないか確認する
- 必要に応じてAPIの戻り値も確認する
vTaskDelay・Software Timer・ハードウェアタイマーの使い分け
タスク自身を
しばらく待たせたい
↓
vTaskDelay()
一定周期でタスクを
実行したい
↓
vTaskDelayUntil()
一定時間後に
短い処理を発生させたい
↓
Software Timer
高精度な時間制御や
ハードウェア制御
↓
Hardware Timer
まとめ
Software Timerは、FreeRTOSで時間を基準に処理を実行するための仕組みです。
- 指定時間が経過するとコールバック関数が実行される
- ワンショットと自動再読み込みの2つの動作がある
xTimerCreate()でタイマーを作成するxTimerStart()でタイマーを開始するxTimerStop()で停止できるxTimerReset()で時間を再スタートできるxTimerChangePeriod()で周期を変更できる- Software TimerはTimer Service Taskによって管理される
- コールバックもTimer Service Taskのコンテキストで実行される
- コールバック関数では長時間処理を避ける
- 重い処理はTask NotificationやQueueなどで別タスクへ渡す
vTaskDelay()とは用途が異なる- 高精度な時間制御にはハードウェアタイマーの方が適する場合がある
Software Timerを理解するうえで特に重要なのは、「タイマーごとに専用タスクが動いているわけではなく、FreeRTOSのTimer Service Taskが複数のSoftware Timerをまとめて管理している」という点です。
そのため、コールバックを短く保ち、「時間が来たことをきっかけに処理を開始する」という使い方をすると、Software Timerを効果的に利用できます。

