第10章: FreeRTOSで同じことをやってみる#

自作 OS と同じ課題を FreeRTOS で書き、設計の違いを比べます。

サンプルコード#

  • ディレクトリ: docs/os-on-arduino/code/10_freertos

  • ビルドと書き込み:

uv run pio run -d 10_freertos -t upload
uv run pio device monitor -b 115200

本物の FreeRTOS の例は 10_freertos_real にあり、例ごとに環境(-e で選ぶビルド設定)が 分かれています。integrated を basic・preemptive・task_control・semaphore・ queue に替えると、ほかの例になります。

uv run pio run -d 10_freertos_real -e integrated -t upload

コードを見る: docs/os-on-arduino/code/10_freertos

ソースコード#

この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。

docs/os-on-arduino/code/10_freertos#

src/main.cpp — 229 行

GitHub で開く

  1/**
  2 * Chapter 10: FreeRTOS Implementation
  3 *
  4 * Demonstrates how to implement the same functionality using FreeRTOS.
  5 * Shows the mapping between our custom OS and FreeRTOS APIs.
  6 *
  7 * Note: Arduino Uno R4 WiFi has FreeRTOS support through Arduino_FreeRTOS library.
  8 * This example shows the concepts - actual FreeRTOS requires the library.
  9 */
 10
 11#include <Arduino.h>
 12#include "Arduino_LED_Matrix.h"
 13#include "servo_pwm.h"
 14
 15// This is a simplified demonstration showing FreeRTOS concepts
 16// For actual FreeRTOS, include <Arduino_FreeRTOS.h>
 17
 18ArduinoLEDMatrix matrix;
 19ServoPWM servo;
 20
 21// Arduino UNO R4 WiFi は基板上に "L" と印字された LED を持っています。
 22// 追加部品なしで動かせるよう、これを主 LED として使います。
 23#define LED1_PIN LED_BUILTIN
 24#define LED2_PIN 3
 25#define SERVO_PIN 9
 26
 27// Simulated FreeRTOS-like API
 28// In real FreeRTOS, these would be the actual FreeRTOS functions
 29
 30typedef void (*TaskFunction_t)(void*);
 31
 32struct FakeTask {
 33    TaskFunction_t func;
 34    const char* name;
 35    void* param;
 36    unsigned long delay_until;
 37    bool suspended;
 38};
 39
 40#define MAX_FAKE_TASKS 8
 41FakeTask fake_tasks[MAX_FAKE_TASKS];
 42int fake_task_count = 0;
 43
 44// xTaskCreate equivalent
 45void xTaskCreate(TaskFunction_t func, const char* name, int stack, void* param, int priority, void* handle)
 46{
 47    if (fake_task_count < MAX_FAKE_TASKS) {
 48        fake_tasks[fake_task_count].func = func;
 49        fake_tasks[fake_task_count].name = name;
 50        fake_tasks[fake_task_count].param = param;
 51        fake_tasks[fake_task_count].delay_until = 0;
 52        fake_tasks[fake_task_count].suspended = false;
 53        fake_task_count++;
 54        Serial.print("[FreeRTOS] Task created: ");
 55        Serial.println(name);
 56    }
 57}
 58
 59// vTaskDelay equivalent (simulated)
 60unsigned long vTaskDelay_ms = 0;
 61void vTaskDelay(unsigned long ticks)
 62{
 63    vTaskDelay_ms = millis() + ticks;
 64}
 65
 66// vTaskDelayUntil equivalent
 67void vTaskDelayUntil(unsigned long* lastWake, unsigned long period)
 68{
 69    *lastWake += period;
 70    while (millis() < *lastWake) {
 71        // In real FreeRTOS, this would yield to scheduler
 72        delay(1);
 73    }
 74}
 75
 76// ============================================
 77// FreeRTOS-style Task Implementations
 78// ============================================
 79
 80// In real FreeRTOS, tasks are functions that never return
 81// and use vTaskDelay() or vTaskDelayUntil() for timing
 82
 83void vTaskLED1(void* param)
 84{
 85    // Real FreeRTOS task would have:
 86    // while(1) {
 87    //     digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
 88    //     vTaskDelay(pdMS_TO_TICKS(200));
 89    // }
 90    (void)param;
 91    static bool state = false;
 92    state = !state;
 93    digitalWrite(LED1_PIN, state);
 94}
 95
 96void vTaskLED2(void* param)
 97{
 98    (void)param;
 99    static bool state = false;
100    state = !state;
101    digitalWrite(LED2_PIN, state);
102}
103
104void vTaskServo(void* param)
105{
106    (void)param;
107    static int angle = 0;
108    angle = (angle + 10) % 180;
109    servo.write(angle);
110}
111
112void vTaskSerial(void* param)
113{
114    (void)param;
115    static int count = 0;
116    Serial.print("[FreeRTOS Task] Count: ");
117    Serial.println(count++);
118}
119
120void vTaskDisplay(void* param)
121{
122    (void)param;
123    static uint8_t frame[8][12];
124    static int pos = 0;
125
126    memset(frame, 0, sizeof(frame));
127
128    // Moving dot animation
129    frame[3][pos] = 1;
130    frame[4][pos] = 1;
131    pos = (pos + 1) % 12;
132
133    matrix.renderBitmap(frame, 8, 12);
134}
135
136// ============================================
137// Comparison: Our OS vs FreeRTOS
138// ============================================
139
140void show_api_comparison(void)
141{
142    Serial.println();
143    Serial.println("============================================");
144    Serial.println("       Our OS  vs  FreeRTOS  Comparison");
145    Serial.println("============================================");
146    Serial.println();
147    Serial.println("Task Creation:");
148    Serial.println("  Our OS:   os_create_task(func, name, stack)");
149    Serial.println("  FreeRTOS: xTaskCreate(func, name, stack, param, priority, handle)");
150    Serial.println();
151    Serial.println("Delay:");
152    Serial.println("  Our OS:   os_sleep(ms)");
153    Serial.println("  FreeRTOS: vTaskDelay(pdMS_TO_TICKS(ms))");
154    Serial.println();
155    Serial.println("Task Control:");
156    Serial.println("  Our OS:   os_suspend_task(id) / os_resume_task(id)");
157    Serial.println("  FreeRTOS: vTaskSuspend(handle) / vTaskResume(handle)");
158    Serial.println();
159    Serial.println("Semaphores (FreeRTOS only):");
160    Serial.println("  xSemaphoreCreateBinary()");
161    Serial.println("  xSemaphoreTake(sem, timeout)");
162    Serial.println("  xSemaphoreGive(sem)");
163    Serial.println();
164    Serial.println("Queues (FreeRTOS only):");
165    Serial.println("  xQueueCreate(length, itemSize)");
166    Serial.println("  xQueueSend(queue, &item, timeout)");
167    Serial.println("  xQueueReceive(queue, &item, timeout)");
168    Serial.println();
169    Serial.println("============================================");
170    Serial.println();
171}
172
173// Simulated scheduler loop
174unsigned long task_timers[MAX_FAKE_TASKS] = {0};
175unsigned long task_periods[] = {200, 500, 100, 1000, 150};
176
177void fake_scheduler_run(void)
178{
179    unsigned long now = millis();
180    for (int i = 0; i < fake_task_count && i < 5; i++) {
181        if (!fake_tasks[i].suspended && now - task_timers[i] >= task_periods[i]) {
182            task_timers[i] = now;
183            fake_tasks[i].func(fake_tasks[i].param);
184        }
185    }
186}
187
188void setup()
189{
190    Serial.begin(115200);
191    while (!Serial) { }
192
193    Serial.println();
194    Serial.println("=== FreeRTOS Concepts Demo ===");
195    Serial.println();
196    Serial.println("This demonstrates FreeRTOS-style task creation.");
197    Serial.println("Note: For actual FreeRTOS, use Arduino_FreeRTOS library.");
198    Serial.println();
199
200    // Initialize hardware
201    pinMode(LED1_PIN, OUTPUT);
202    pinMode(LED2_PIN, OUTPUT);
203    servo.attach(SERVO_PIN);
204    matrix.begin();
205
206    // Create tasks FreeRTOS-style
207    xTaskCreate(vTaskLED1, "LED1", 128, NULL, 1, NULL);
208    xTaskCreate(vTaskLED2, "LED2", 128, NULL, 1, NULL);
209    xTaskCreate(vTaskServo, "Servo", 256, NULL, 2, NULL);
210    xTaskCreate(vTaskSerial, "Serial", 512, NULL, 1, NULL);
211    xTaskCreate(vTaskDisplay, "Display", 256, NULL, 1, NULL);
212
213    Serial.println();
214
215    // Show API comparison
216    show_api_comparison();
217
218    Serial.println("Starting FreeRTOS-style scheduler...");
219    Serial.println();
220
221    // In real FreeRTOS, you would call: vTaskStartScheduler();
222    // The scheduler never returns in FreeRTOS
223}
224
225void loop()
226{
227    // Simulated FreeRTOS scheduler
228    fake_scheduler_run();
229}
include/servo_pwm.h — 61 行

GitHub で開く

 1/**
 2 * Simple PWM-based Servo Control for Arduino Uno R4 WiFi
 3 *
 4 * Since the standard Servo library is not available in PlatformIO for Renesas,
 5 * this provides a simple software PWM implementation.
 6 */
 7
 8#ifndef SERVO_PWM_H
 9#define SERVO_PWM_H
10
11#include <Arduino.h>
12
13class ServoPWM {
14private:
15    int pin;
16    int angle;
17    bool attached;
18
19    void writeMicroseconds(int us) {
20        if (!attached) return;
21        // Standard servo pulse: 500-2500us for 0-180 degrees
22        // Period: 20ms (50Hz)
23        digitalWrite(pin, HIGH);
24        delayMicroseconds(us);
25        digitalWrite(pin, LOW);
26    }
27
28public:
29    ServoPWM() : pin(-1), angle(90), attached(false) {}
30
31    void attach(int p) {
32        pin = p;
33        pinMode(pin, OUTPUT);
34        attached = true;
35    }
36
37    void detach() {
38        attached = false;
39    }
40
41    void write(int ang) {
42        if (!attached) return;
43        angle = constrain(ang, 0, 180);
44        // Map angle (0-180) to pulse width (500-2500us)
45        int us = map(angle, 0, 180, 500, 2500);
46        writeMicroseconds(us);
47    }
48
49    int read() {
50        return angle;
51    }
52
53    // Call this in loop() to maintain servo position
54    void refresh() {
55        if (!attached) return;
56        int us = map(angle, 0, 180, 500, 2500);
57        writeMicroseconds(us);
58    }
59};
60
61#endif // SERVO_PWM_H

docs/os-on-arduino/code/10_freertos_real#

src/basic.cpp — 35 行

GitHub で開く

 1// 第10章 10.3「FreeRTOSの基本」の掲載例
 2#include <Arduino.h>
 3#include <Arduino_FreeRTOS.h>
 4
 5// タスク関数
 6void task_led(void *pvParameters)
 7{
 8    pinMode(2, OUTPUT);
 9
10    for (;;) {  // FreeRTOSではwhile(1)の代わりにfor(;;)が慣例
11        digitalWrite(2, !digitalRead(2));
12        vTaskDelay(pdMS_TO_TICKS(500));  // 500ms待機
13    }
14}
15
16void setup()
17{
18    // タスク作成
19    xTaskCreate(
20        task_led,      // タスク関数
21        "LED",         // タスク名
22        128,           // スタックサイズ(ワード単位)
23        NULL,          // パラメータ
24        1,             // 優先度
25        NULL           // タスクハンドル
26    );
27
28    // スケジューラ開始
29    vTaskStartScheduler();
30}
31
32void loop()
33{
34    // FreeRTOS使用時は到達しない
35}
src/integrated.cpp — 103 行

GitHub で開く

  1// freertos_integrated.ino(第10章 10.10 の掲載例)
  2#include <Arduino.h>
  3#include <Arduino_FreeRTOS.h>   // semphr.h などもここから取り込まれる
  4#include <Servo.h>
  5
  6// 基板上の「L」LED。追加部品なしで動かせるようにこれを使います
  7#define LED_PIN LED_BUILTIN
  8#define SERVO_PIN 9
  9#define POT_PIN A0
 10
 11// 共有データ
 12// R4 同梱の FreeRTOS はミューテックスが無効なので、
 13// バイナリセマフォを「1 個の鍵」として使って排他する (10.7 参照)
 14volatile int g_pot_value = 0;
 15SemaphoreHandle_t xDataMutex = NULL;
 16
 17// サーボ
 18Servo servo;
 19
 20// タスク: センサ読み取り
 21void task_sensor(void *pvParameters)
 22{
 23    for (;;) {
 24        int value = analogRead(POT_PIN);
 25
 26        if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
 27            g_pot_value = value;
 28            xSemaphoreGive(xDataMutex);
 29        }
 30
 31        vTaskDelay(pdMS_TO_TICKS(50));
 32    }
 33}
 34
 35// タスク: サーボ制御
 36void task_servo(void *pvParameters)
 37{
 38    servo.attach(SERVO_PIN);
 39
 40    for (;;) {
 41        int value;
 42        if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
 43            value = g_pot_value;
 44            xSemaphoreGive(xDataMutex);
 45        }
 46
 47        int angle = map(value, 0, 1023, 0, 180);
 48        servo.write(angle);
 49
 50        vTaskDelay(pdMS_TO_TICKS(20));
 51    }
 52}
 53
 54// タスク: LED点滅
 55void task_led(void *pvParameters)
 56{
 57    pinMode(LED_PIN, OUTPUT);
 58
 59    for (;;) {
 60        digitalWrite(LED_PIN, !digitalRead(LED_PIN));
 61        vTaskDelay(pdMS_TO_TICKS(500));
 62    }
 63}
 64
 65// タスク: 状態表示
 66void task_monitor(void *pvParameters)
 67{
 68    for (;;) {
 69        Serial.println("=== FreeRTOS Status ===");
 70        Serial.print("Tick: ");
 71        Serial.println(xTaskGetTickCount());
 72        Serial.print("Free heap: ");
 73        Serial.println(xPortGetFreeHeapSize());
 74        Serial.print("Pot value: ");
 75        Serial.println(g_pot_value);
 76        Serial.println();
 77
 78        vTaskDelay(pdMS_TO_TICKS(2000));
 79    }
 80}
 81
 82void setup()
 83{
 84    Serial.begin(115200);
 85    while (!Serial) { }
 86
 87    Serial.println("=== FreeRTOS Integrated Demo ===\n");
 88
 89    // 排他用のバイナリセマフォを作成し、最初は「空き」にしておく
 90    xDataMutex = xSemaphoreCreateBinary();
 91    xSemaphoreGive(xDataMutex);
 92
 93    // タスク作成
 94    xTaskCreate(task_sensor, "Sensor", 128, NULL, 2, NULL);
 95    xTaskCreate(task_servo, "Servo", 128, NULL, 2, NULL);
 96    xTaskCreate(task_led, "LED", 128, NULL, 1, NULL);
 97    xTaskCreate(task_monitor, "Monitor", 256, NULL, 1, NULL);
 98
 99    // スケジューラ開始
100    vTaskStartScheduler();
101}
102
103void loop() { }
src/preemptive.cpp — 48 行

GitHub で開く

 1// freertos_preemptive.ino(第10章 10.4 の掲載例)
 2#include <Arduino.h>
 3#include <Arduino_FreeRTOS.h>
 4
 5void task_led1(void *pvParameters)
 6{
 7    pinMode(2, OUTPUT);
 8    for (;;) {
 9        digitalWrite(2, !digitalRead(2));
10        vTaskDelay(pdMS_TO_TICKS(100));
11    }
12}
13
14void task_led2(void *pvParameters)
15{
16    pinMode(3, OUTPUT);
17    for (;;) {
18        digitalWrite(3, !digitalRead(3));
19        vTaskDelay(pdMS_TO_TICKS(300));
20    }
21}
22
23// 重い計算タスク(yield不要!)
24void task_heavy(void *pvParameters)
25{
26    pinMode(4, OUTPUT);
27    for (;;) {
28        volatile long sum = 0;
29        for (long i = 0; i < 1000000; i++) {
30            sum += i;
31        }
32        digitalWrite(4, !digitalRead(4));
33        // vTaskDelayなしでもプリエンプションで切り替わる
34    }
35}
36
37void setup()
38{
39    Serial.begin(115200);
40
41    xTaskCreate(task_led1, "LED1", 128, NULL, 1, NULL);
42    xTaskCreate(task_led2, "LED2", 128, NULL, 1, NULL);
43    xTaskCreate(task_heavy, "Heavy", 256, NULL, 1, NULL);
44
45    vTaskStartScheduler();
46}
47
48void loop() { }
src/queue.cpp — 54 行

GitHub で開く

 1// 第10章 10.8「キュー(メッセージパッシング)」の掲載例
 2#include <Arduino.h>
 3#include <Arduino_FreeRTOS.h>   // queue.h などもここから取り込まれる
 4
 5QueueHandle_t xQueue = NULL;
 6
 7typedef struct {
 8    int sensorId;
 9    int value;
10} SensorData;
11
12void task_sensor(void *pvParameters)
13{
14    for (;;) {
15        SensorData data;
16        data.sensorId = 1;
17        data.value = analogRead(A0);
18
19        // キューに送信
20        xQueueSend(xQueue, &data, portMAX_DELAY);
21
22        vTaskDelay(pdMS_TO_TICKS(100));
23    }
24}
25
26void task_processor(void *pvParameters)
27{
28    SensorData received;
29
30    for (;;) {
31        // キューから受信
32        if (xQueueReceive(xQueue, &received, portMAX_DELAY) == pdTRUE) {
33            Serial.print("Sensor ");
34            Serial.print(received.sensorId);
35            Serial.print(": ");
36            Serial.println(received.value);
37        }
38    }
39}
40
41void setup()
42{
43    Serial.begin(115200);
44
45    // キュー作成(10要素)
46    xQueue = xQueueCreate(10, sizeof(SensorData));
47
48    xTaskCreate(task_sensor, "Sensor", 128, NULL, 1, NULL);
49    xTaskCreate(task_processor, "Processor", 256, NULL, 2, NULL);
50
51    vTaskStartScheduler();
52}
53
54void loop() { }
src/semaphore.cpp — 44 行

GitHub で開く

 1// 第10章 10.7「セマフォの例」の掲載例
 2#include <Arduino.h>
 3#include <Arduino_FreeRTOS.h>   // semphr.h などもここから取り込まれる
 4
 5SemaphoreHandle_t xSemaphore = NULL;
 6volatile int sharedData = 0;
 7
 8void task_producer(void *pvParameters)
 9{
10    for (;;) {
11        sharedData++;
12
13        // セマフォを与える(シグナル)
14        xSemaphoreGive(xSemaphore);
15
16        vTaskDelay(pdMS_TO_TICKS(500));
17    }
18}
19
20void task_consumer(void *pvParameters)
21{
22    for (;;) {
23        // セマフォを待つ
24        if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
25            Serial.print("Received: ");
26            Serial.println(sharedData);
27        }
28    }
29}
30
31void setup()
32{
33    Serial.begin(115200);
34
35    // バイナリセマフォ作成
36    xSemaphore = xSemaphoreCreateBinary();
37
38    xTaskCreate(task_producer, "Producer", 128, NULL, 1, NULL);
39    xTaskCreate(task_consumer, "Consumer", 128, NULL, 1, NULL);
40
41    vTaskStartScheduler();
42}
43
44void loop() { }
src/task_control.cpp — 62 行

GitHub で開く

 1// freertos_task_control.ino(第10章 10.5 の掲載例)
 2#include <Arduino.h>
 3#include <Arduino_FreeRTOS.h>
 4
 5TaskHandle_t ledTaskHandle = NULL;
 6
 7void task_led(void *pvParameters)
 8{
 9    pinMode(2, OUTPUT);
10    for (;;) {
11        digitalWrite(2, !digitalRead(2));
12        vTaskDelay(pdMS_TO_TICKS(200));
13    }
14}
15
16void task_shell(void *pvParameters)
17{
18    for (;;) {
19        if (Serial.available()) {
20            String cmd = Serial.readStringUntil('\n');
21            cmd.trim();
22
23            if (cmd == "suspend") {
24                if (ledTaskHandle != NULL) {
25                    vTaskSuspend(ledTaskHandle);  // タスク停止
26                    Serial.println("LED task suspended");
27                }
28            }
29            else if (cmd == "resume") {
30                if (ledTaskHandle != NULL) {
31                    vTaskResume(ledTaskHandle);   // タスク再開
32                    Serial.println("LED task resumed");
33                }
34            }
35            else if (cmd == "info") {
36                // タスク情報表示
37                Serial.print("Free heap: ");
38                Serial.println(xPortGetFreeHeapSize());
39
40                Serial.print("Tick count: ");
41                Serial.println(xTaskGetTickCount());
42            }
43        }
44        vTaskDelay(pdMS_TO_TICKS(10));
45    }
46}
47
48void setup()
49{
50    Serial.begin(115200);
51    while (!Serial) { }
52
53    Serial.println("=== FreeRTOS Task Control Demo ===");
54    Serial.println("Commands: suspend, resume, info");
55
56    xTaskCreate(task_led, "LED", 128, NULL, 1, &ledTaskHandle);
57    xTaskCreate(task_shell, "Shell", 256, NULL, 2, NULL);
58
59    vTaskStartScheduler();
60}
61
62void loop() { }

実機での出力#

Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。

[FreeRTOS Task] Count: 2
[FreeRTOS Task] Count: 3
[FreeRTOS Task] Count: 4
[FreeRTOS Task] Count: 5
[FreeRTOS Task] Count: 6
[FreeRTOS Task] Count: 7
[FreeRTOS Task] Count: 8
[FreeRTOS Task] Count: 9
[FreeRTOS Task] Count: 10
[FreeRTOS Task] Count: 11
...
../_images/ch10_freertos.gif

10_freertos を書き込んでモニタを開いたところ。FreeRTOS 風の API(xTaskCreate など)で作ったタスクを loop() の中の簡易スケジューラが回していて、[FreeRTOS Task] のタスクが 1 秒ごとに Count を増やしていきます#

本物の FreeRTOS(integrated 例)#

=== FreeRTOS Status ===
Tick: 4014
Free heap: 3640
Pot value: 205

=== FreeRTOS Status ===
Tick: 6020
Free heap: 3640
Pot value: 204
...
../_images/ch10_freertos_real.gif

uv run pio run -d 10_freertos_real -e integrated -t upload(-e は platformio.ini の中の環境(ビルド設定)の名前)で書き込んでモニタを開いたところ。 本物の FreeRTOS の上で、2 秒ごとに Tick(FreeRTOS のティック数)・Free heap・ A0 の読み値を出します。A0 に何もつないでいないと Pot value は 200 前後の定まらない 値になります(可変抵抗をつなぐと、回した位置に応じて 0〜1023 で変わります)#

つまずきやすいポイント#

(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)