はじめに:なぜFreeRTOSが必要なのか?
組込みシステムで複数のセンサーを同時に監視したい——そんなとき、ベアメタル(OSなし)で書いたコードでは限界が出てきます。
たとえば、温度センサーの読み取り中にCAN通信の受信を取りこぼしたり、LCD表示の更新中にアラーム判定が遅れたり。複数の処理を確実に並行実行するには、リアルタイムOS(RTOS)が有効です。
本記事では、STM32マイコンとFreeRTOSを使って、リアルタイムセンサー監視システムを構築する方法を、実装コード付きで解説します。
FreeRTOSの基礎から学びたい方へ:タスク・キュー・セマフォ・ミューテックスといったFreeRTOSの基本概念は、技術コラムの STM32 + FreeRTOS 入門|タスク・キュー・セマフォを使ったRTOS実装ガイド で体系的に解説しています。本記事はその実践編にあたります。
本記事で構築するシステム
構成図
[温度センサー] ──I2C──┐
[湿度センサー] ──I2C──┤
[振動センサー] ──ADC──┤── STM32F4 + FreeRTOS ──UART──→ [ログ出力]
[CAN Bus] ──CAN──┤ ──CAN───→ [上位システム]
└── LCD表示
タスク構成
| タスク名 | 優先度 | 周期 | 役割 |
|---|---|---|---|
| SensorReadTask | High | 100ms | I2C/ADCセンサー読み取り |
| AlarmCheckTask | High | 200ms | 閾値判定・アラーム発報 |
| CANCommTask | Medium | 50ms | CAN通信の送受信 |
| DisplayTask | Low | 500ms | LCD画面更新 |
| LogTask | Low | 1000ms | UARTシリアルログ出力 |
環境構築
必要なもの
- マイコンボード: STM32F407 Discovery(またはNucleo-F446RE等)
- 開発環境: STM32CubeIDE 1.15+
- ミドルウェア: FreeRTOS(CMSIS-RTOS v2 API)
- センサー: BME280(温湿度・気圧)、ADXL345(振動)など
STM32CubeMXの設定
- Middleware → FREERTOS を有効化(CMSIS_V2を選択)
- I2C1 を有効化(センサー接続用)
- ADC1 を有効化(振動センサー用)
- CAN1 を有効化(上位システム通信用)
- USART2 を有効化(デバッグログ用)
FreeRTOSの設定で、タスクのスタックサイズは各タスク最低256ワード(1KB)を確保しておきます。
実装のポイント
1. タスク間データ共有にはキュー(Queue)を使う
グローバル変数でのデータ共有は競合状態(Race Condition)の原因になります。FreeRTOSのキューを使えば、タスク間のデータ受け渡しをスレッドセーフに行えます。
// センサーデータ構造体
typedef struct {
float temperature;
float humidity;
float pressure;
float vibration;
uint32_t timestamp;
} SensorData_t;// キューハンドル
osMessageQueueId_t sensorQueueHandle;
// キュー作成(main.c の初期化部分)
const osMessageQueueAttr_t sensorQueue_attr = {
.name = "sensorQueue"
};
sensorQueueHandle = osMessageQueueNew(16, sizeof(SensorData_t), &sensorQueue_attr);
2. センサー読み取りタスク
void SensorReadTask(void *argument)
{
SensorData_t data; for (;;)
{
// BME280から温湿度・気圧を取得(I2C)
data.temperature = BME280_ReadTemperature(&hi2c1);
data.humidity = BME280_ReadHumidity(&hi2c1);
data.pressure = BME280_ReadPressure(&hi2c1);
// ADXL345から振動値を取得(ADC)
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
uint32_t raw = HAL_ADC_GetValue(&hadc1);
data.vibration = (float)raw * 3.3f / 4096.0f;
// タイムスタンプ
data.timestamp = osKernelGetTickCount();
// キューに送信(アラームタスク・表示タスクが受信)
osMessageQueuePut(sensorQueueHandle, &data, 0, 0);
osDelay(100); // 100ms周期
}
}
3. アラーム判定タスク
#define TEMP_THRESHOLD_HIGH 40.0f // 40℃以上で警告
#define VIBRATION_THRESHOLD 2.5f // 2.5V以上で異常振動void AlarmCheckTask(void *argument)
{
SensorData_t data;
for (;;)
{
// キューからデータ受信(最大200ms待機)
if (osMessageQueueGet(sensorQueueHandle, &data, NULL, 200) == osOK)
{
// 温度異常チェック
if (data.temperature > TEMP_THRESHOLD_HIGH)
{
CAN_SendAlarm(ALARM_TEMP_HIGH, data.temperature);
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
}
// 振動異常チェック
if (data.vibration > VIBRATION_THRESHOLD)
{
CAN_SendAlarm(ALARM_VIBRATION, data.vibration);
HAL_GPIO_WritePin(LED_ORANGE_GPIO_Port, LED_ORANGE_Pin, GPIO_PIN_SET);
}
}
}
}
4. ミューテックスでI2Cバスを保護する
複数タスクが同じI2Cバスにアクセスする場合、ミューテックスで排他制御が必要です。
osMutexId_t i2cMutexHandle;// 初期化
const osMutexAttr_t i2cMutex_attr = {
.name = "i2cMutex",
.attr_bits = osMutexRecursive | osMutexPrioInherit
};
i2cMutexHandle = osMutexNew(&i2cMutex_attr);
// I2Cアクセス時にミューテックス取得
float BME280_ReadTemperature_Safe(I2C_HandleTypeDef *hi2c)
{
float temp = 0.0f;
if (osMutexAcquire(i2cMutexHandle, osWaitForever) == osOK)
{
temp = BME280_ReadTemperature(hi2c);
osMutexRelease(i2cMutexHandle);
}
return temp;
}
osMutexPrioInherit(優先度継承)を指定することで、優先度逆転問題を防止できます。
よくあるトラブルと対策
スタックオーバーフロー
FreeRTOSタスクのスタックサイズが不足すると、HardFault例外が発生します。
対策:
configCHECK_FOR_STACK_OVERFLOWを2に設定してオーバーフロー検出を有効化uxTaskGetStackHighWaterMark()で使用量を確認し、余裕を持ったサイズを設定
// FreeRTOSConfig.h
#define configCHECK_FOR_STACK_OVERFLOW 2// コールバック関数
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
printf("Stack overflow in task: %s\n", pcTaskName);
// LED点滅などでエラー通知
while(1);
}
タスク優先度の設計ミス
優先度が高いタスクがCPUを占有し続けると、低優先度タスクが実行されません。
対策:
- センサー読み取り・アラーム判定はHigh、表示やログはLow
- 必ず
osDelay()やosMessageQueueGet()でブロッキング状態を作り、CPUを解放する vTaskDelayUntil()で正確な周期実行を実現
ヒープメモリ不足
キューやミューテックスの生成に失敗する場合は、ヒープサイズが不足しています。
// FreeRTOSConfig.h
#define configTOTAL_HEAP_SIZE ((size_t)32768) // 32KB に拡張
ベアメタルとFreeRTOSの比較
| 観点 | ベアメタル | FreeRTOS |
|---|---|---|
| 複数処理の並行実行 | 割り込み+フラグで手動管理 | タスクとして自然に分離 |
| タイミング制御 | タイマー割り込みで実装 | osDelay/vTaskDelayUntilで簡潔 |
| データ共有 | グローバル変数(競合リスクあり) | キュー/セマフォで安全 |
| コードの保守性 | 処理が密結合しがち | タスク単位でモジュール化 |
| デバッグ | 困難(タイミング依存バグ) | タスク状態の可視化が可能 |
| メモリ使用量 | 最小限 | +10〜20KB程度 |
センサー2〜3個程度のシンプルな構成ならベアメタルで十分ですが、4つ以上の並行処理や、CAN通信のようなリアルタイム性が求められる場合はFreeRTOSの導入メリットが大きいです。
まとめ
STM32 + FreeRTOSによるリアルタイムセンサー監視システムの構築方法を解説しました。
ポイントの振り返り:
- タスク間データ共有はキュー(Queue) を使い、グローバル変数を避ける
- 共有リソース(I2Cバス等)はミューテックスで保護する
- 優先度継承を有効にして優先度逆転を防止する
- スタックサイズは余裕を持って設定し、オーバーフロー検出を有効化する
テクノスフィアでは、STM32をはじめとするマイコンの組込み開発を20年以上手がけてきました。FreeRTOSを活用したリアルタイムシステムの設計・実装も多数の実績があります。
組込みシステムの開発でお困りのことがあれば、お気軽にご相談ください。
関連記事:
- STM32 + FreeRTOS 入門|タスク・キュー・セマフォを使ったRTOS実装ガイド — FreeRTOSの基礎を体系的に解説
- STM32マイコンのUART・I2C・SPI・CAN通信 実装ガイド — 通信ペリフェラルの使い分けと実装
- 組込み制御・マイコン開発サービス — STM32受託開発のご相談