ロゴ:株式会社テクノスフィア
NEW 採用情報
技術 田中 翔太 AI 執筆

STM32 + FreeRTOSでリアルタイムセンサー監視システムを構築する方法

#STM32 #FreeRTOS #RTOS #センサー #組込み開発 #IoT

はじめに:なぜFreeRTOSが必要なのか?

組込みシステムで複数のセンサーを同時に監視したい——そんなとき、ベアメタル(OSなし)で書いたコードでは限界が出てきます。

たとえば、温度センサーの読み取り中にCAN通信の受信を取りこぼしたり、LCD表示の更新中にアラーム判定が遅れたり。複数の処理を確実に並行実行するには、リアルタイムOS(RTOS)が有効です。

本記事では、STM32マイコンとFreeRTOSを使って、リアルタイムセンサー監視システムを構築する方法を、実装コード付きで解説します。

FreeRTOSの基礎から学びたい方へ:タスク・キュー・セマフォ・ミューテックスといったFreeRTOSの基本概念は、技術コラムの STM32 + FreeRTOS 入門|タスク・キュー・セマフォを使ったRTOS実装ガイド で体系的に解説しています。本記事はその実践編にあたります。


本記事で構築するシステム

構成図

[温度センサー] ──I2C──┐
[湿度センサー] ──I2C──┤
[振動センサー] ──ADC──┤── STM32F4 + FreeRTOS ──UART──→ [ログ出力]
[CAN Bus] ──CAN──┤ ──CAN───→ [上位システム]
└── LCD表示

タスク構成

タスク名優先度周期役割
SensorReadTaskHigh100msI2C/ADCセンサー読み取り
AlarmCheckTaskHigh200ms閾値判定・アラーム発報
CANCommTaskMedium50msCAN通信の送受信
DisplayTaskLow500msLCD画面更新
LogTaskLow1000msUARTシリアルログ出力


環境構築

必要なもの

  • マイコンボード: STM32F407 Discovery(またはNucleo-F446RE等)

  • 開発環境: STM32CubeIDE 1.15+

  • ミドルウェア: FreeRTOS(CMSIS-RTOS v2 API)

  • センサー: BME280(温湿度・気圧)、ADXL345(振動)など

STM32CubeMXの設定

  1. Middleware → FREERTOS を有効化(CMSIS_V2を選択)

  2. I2C1 を有効化(センサー接続用)

  3. ADC1 を有効化(振動センサー用)

  4. CAN1 を有効化(上位システム通信用)

  5. 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_OVERFLOW2 に設定してオーバーフロー検出を有効化

  • 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によるリアルタイムセンサー監視システムの構築方法を解説しました。

ポイントの振り返り:

  1. タスク間データ共有はキュー(Queue) を使い、グローバル変数を避ける

  2. 共有リソース(I2Cバス等)はミューテックスで保護する

  3. 優先度継承を有効にして優先度逆転を防止する

  4. スタックサイズは余裕を持って設定し、オーバーフロー検出を有効化する

テクノスフィアでは、STM32をはじめとするマイコンの組込み開発を20年以上手がけてきました。FreeRTOSを活用したリアルタイムシステムの設計・実装も多数の実績があります。

組込みシステムの開発でお困りのことがあれば、お気軽にご相談ください

関連記事:

記事一覧に戻る お問い合わせ
ENGINEER WANTED

この記事のような開発を、仕事にしませんか?

テクノスフィアでは、組込み・AI・画像認識・Webシステム開発を一緒に手がけるエンジニアを募集しています。
新卒・中途・未経験から歓迎。リモートワーク可、外部研修・書籍購入支援など成長環境を整えています。

採用情報を見る