Raspberry Pi PicoでFreeRTOSを動かす

[Pico SDK](https://github.com/raspberrypi/pico-sdk)のソースコードを覗いていると`pico-sdk/lib/tinyusb/lib`の下[^1]に[FreeRTOS](https://www.freertos.org/)のソースがあったので、動かしてみました。

[^1]: Pico SDKのsubmoduleであるtinyusbの更にsubmoduleのため、cloneの際に--recursiveを指定しないと出てきません。元々tinyusbの他のアーキテクチャ向けのビルドで使われるもので、Pico SDKでtinyusbを使ってもOSなしのコンフィグレーションでビルドされます。

# ソースコード 

https://github.com/yunkya2/pico-freertos-sample にあります。

以下の手順でsubmoduleもあわせてcloneしてください[^2]。

```
$ git clone https://github.com/yunkya2/pico-freertos-sample
$ cd pico-freertos-sample
$ git submodule update --init
```

[^2]: 当初はPico SDK内のFreeRTOSのソースをそのまま使うつもりだったのですが、Getting Started の手順に従うとrecursive指定がなくてFreeRTOSのソースコードが取得されないのと、recursive指定を行うとFreeRTOSの他にPico SDKでは使用しないリポジトリが大量にcloneされてしまって煩わしいので、結局サンプルソース本体側にsubmoduleとして入れることにしました。

環境変数 `PICO_SDK_PATH` が設定されていれば、ビルド手順は通常のPico SDK用アプリと同様です。

```
$ mkdir build
$ cd build
$ cmake ..
$ make
```

# ファイルの説明

```
pico-freertos-sample/
├── CMakeLists.txt          ... 全体をビルドするためのCMakeLists.txt
├── LICENSE                 ... ライセンス(3項BSD)
├── README.md               ... README
├── blink
│   ├── CMakeLists.txt      ... サンプルアプリをビルドするCMakeLists.txt
│   └── blink.c             ... サンプルアプリのソースコード
├── freertos
│   ├── CMakeLists.txt      ... FreeRTOSをビルドしてライブラリを生成するCMakeLists.txt
│   ├── FreeRTOS-Kernel     ... このフォルダ以下にFreeRTOSカーネルがcloneされる
│   └── FreeRTOSConfig.h    ... FreeRTOSの設定ファイル
└── pico_sdk_import.cmake   ... Pico SDKを参照するため設定(pico-sdk/externalからコピー)
```

アプリのCMakeLists.txtのtarget_link_libraries()に、blink/CMakeLists.txtと同様にfreertosを追加すると、FreeRTOSのライブラリもリンクされます。

```
target_link_libraries(blink pico_stdlib freertos)
```

# サンプルの内容

指定された周期で指定されたGPIOのLEDを点滅させるタスク(`led_task()`)を3つ生成して、GPIO 13～15に繋がったLEDを異なる周期で点滅させます。

```c
struct led_task_arg {
    int gpio;
    int delay;
};

void led_task(void *p)
{
    struct led_task_arg *a = (struct led_task_arg *)p;

    gpio_init(a->gpio);
    gpio_set_dir(a->gpio, GPIO_OUT);
    while (true) {
        gpio_put(a->gpio, 1);
        vTaskDelay(pdMS_TO_TICKS(a->delay));
        gpio_put(a->gpio, 0);
        vTaskDelay(pdMS_TO_TICKS(a->delay));
    }
}

int main()
{
    setup_default_uart();
    printf("Start LED blink\n");

    struct led_task_arg arg1 = { 15, 100 };
    xTaskCreate(led_task, "LED_Task 1", 256, &arg1, 1, NULL);

    struct led_task_arg arg2 = { 14, 200 };
    xTaskCreate(led_task, "LED_Task 2", 256, &arg2, 1, NULL);

    struct led_task_arg arg3 = { 13, 300 };
    xTaskCreate(led_task, "LED_Task 3", 256, &arg3, 1, NULL);

    vTaskStartScheduler();

    while (true)
        ;
}
```

# 実行

Raspberry Pi PicoのGPIO 13,14,15にそれぞれLEDと抵抗(330Ω)を直列に繋いでください。
以下の動画のように点滅します。それぞれ100ms, 200ms, 300msの周期で点灯・消灯を繰り返します。

![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/5cc9b13a-9381-2880-224c-5533e0f88288.gif)

# 注意点

- Pico SDKのAPIはシングルタスクでの動作しか考慮されていないはずです。GPIO操作程度の単純なAPIであれば問題ないですが、例えばLED点滅の元にしたSDKサンプルで使われている`sleep_ms()`はFreeRTOSのマルチタスク環境では正常に動作しません。
  他にも同様のAPIがあるはずなので、Pico SDKを使って開発するアプリをFreeRTOSで動作させる場合は、FreeRTOSに対応するAPIがあればそれで置き換えたり、`vTaskSuspendAll()`/`xTaskResumeAll()`を使って処理中のタスク切り替えを防ぐ、などの対応が必要になると思います。
- OpenOCDのkernel aware debugが使えません(原因は未調査)。
- 本記事投稿時点のソースコードではRP2040のハードウェア除算器を用いた除算処理に問題がありましたが、既に修正されています([Raspberry Pi Picoの整数除算について (とPico版FreeRTOSの不具合修正)](https://qiita.com/yunkya2/items/e594add8f03a0e24bce6)を参照)。