この記事でわかること
- FreeRTOS Software Timer(ソフトウェアタイマ)で何ができるか
- Timerコールバックは「どこで動くか」
- 最小サンプル(ESP32 / ESP-IDFで動く)
- 主要API(Create / Start / Stop / Reset / ChangePeriod / FromISR)の役割
こんな方におすすめ
- 周期処理(センサー取得、監視、LED点滅)をキレイに書きたい
-
vTaskDelay()ループが増えてタスクが散らかってきた - タイマーのコールバックで何をしてよいか分からない
1. FreeRTOS Software Timerとは
FreeRTOSの Software Timer は、「指定時間後にコールバックを呼ぶ」「一定周期でコールバックを呼ぶ」ための仕組みです。
-
One-shot:1回だけ発火(タイムアウトなど)
-
Auto-reload:周期で発火(定周期処理)
重要ポイント:
- Timerコールバックは、あなたのタスクの中では動きません
- Timer Service Task上で実行されます
つまり、コールバックで重い処理をすると、他のタイマー処理も巻き込んで遅れる可能性があります。
2. Timerコールバックでやっていいこと/ダメなこと
やっていいこと(推奨)
-
フラグを立てる
-
Task Notification / Semaphore / EventGroup で「処理タスクを起こす」
-
最小限の処理(短時間で終わるもの)
ダメなこと(事故りやすい)
-
長い printf(ログ出力が詰まるとタイマサービスが止まる)
-
重い処理(JSONパース、ファイルI/O、通信など)
-
ブロックする可能性のある処理(ロック待ち、長い待ちなど)
結論:Timerは“起床トリガー”に徹して、処理は別タスクへ が安全です。
3. 【最小サンプル】1秒ごとに処理タスクを起こす(ESP32 + ESP-IDF)
やりたいこと
- Timer:1秒ごとに発火
- WorkerTask:Timerに起こされたらログを出す
#include <cstdio>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
static TaskHandle_t workerHandle = nullptr;
static TimerHandle_t periodicTimer = nullptr;
static void WorkerTask(void *pv)
{
uint32_t cnt = 0;
for (;;) {
// タイマーから通知が来るまでブロック(CPUを使わない)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// 本処理はタスク側で実施
printf("[Worker] tick %u\n", (unsigned)cnt++);
}
}
// Timerコールバック(Timer Service Task上で動く)
static void PeriodicTimerCb(TimerHandle_t xTimer)
{
// ここでは「通知だけ」にするのが安全
xTaskNotifyGive(workerHandle);
}
extern "C" void app_main(void)
{
xTaskCreate(WorkerTask, "WorkerTask", 2048, nullptr, 5, &workerHandle);
// 1秒周期のAuto-reloadタイマ
periodicTimer = xTimerCreate(
"Periodic",
pdMS_TO_TICKS(1000), // period
pdTRUE, // auto-reload
nullptr, // timer ID(必要なら使う)
PeriodicTimerCb
);
if (periodicTimer == nullptr) {
printf("xTimerCreate failed\n");
return;
}
// タイマ開始(コマンドキュー経由。通常はブロック時間0でOK)
xTimerStart(periodicTimer, 0);
}
動きのポイント
- タイマーは 1秒ごとに発火
- コールバックは通知だけ(軽い)
- WorkerTaskが起床してログ出力(本処理)
4. One-shot(1回だけ発火)の最短例
「3秒後に1回だけ何かしたい」なら、uxAutoReload = pdFALSE。
static TimerHandle_t oneShotTimer = nullptr;
static void OneShotCb(TimerHandle_t xTimer)
{
printf("[OneShot] fired!\n");
}
oneShotTimer = xTimerCreate(
"OneShot",
pdMS_TO_TICKS(3000),
pdFALSE, // 1回だけ
nullptr,
OneShotCb
);
xTimerStart(oneShotTimer, 0);
5. 主要API(Qiitaは薄め:役割だけ)
-
xTimerCreate():タイマ作成(失敗時はNULL) -
xTimerStart() / xTimerStartFromISR():開始 -
xTimerStop() / xTimerStopFromISR():停止 -
xTimerReset() / xTimerResetFromISR():今から周期を数え直す -
xTimerChangePeriod() / xTimerChangePeriodFromISR():周期変更 -
pvTimerGetTimerID():Timer ID取得(複数タイマを共通コールバックで捌く時に便利)
※ ISR(割り込み)から操作する場合は、通常版ではなく FromISR系 を使います。
6. よくあるハマりどころ
-
xTimerCreateしただけで動くと思う
→ xTimerStart() が必要 -
コールバックで重い処理をして全体が遅れる
→ コールバックは Timer Service Task上。ここが詰まると他タイマーも遅れる -
周期はTick基準
→ pdMS_TO_TICKS() を使う(Tick粒度より細かい精度は出ない)
7. まとめ
- Software Timerは「一定時間後/一定周期で起こす」仕組み
- コールバックは Timer Service Task上で動く(重要)
- コールバックは軽く、処理はタスクへ(通知して起床)が安全
- まず覚えるのは Create / Start(必要ならStop/Reset/ChangePeriod)
FreeRTOS × ESP32 記事シリーズ