STM32マイコンでFreeRTOSを用いてLEDを制御する(Lチカ)

## はじめに
FreeRTOSは組み込みシステムを対象にしたRTOSです．

ここでは，STM32マイコンをFreeRTOSでLチカさせるまでの流れを説明します．

### 開発環境
- IDE: Eclipse IDE for C/C++ Developers (Neon.3 Release (4.6.3))
- ツールチェイン: SW4STM32
- CubeMX: 4.21.0.201705091708

## CubeMXでの設定
MCUやBoardを各自選択します．
ここでは，Nucleo-F446REを使って説明します．

まずは，`Pinout` タブでLチカを行うための `GPIO_Output` ピンを用意します．
任意のピンを選択してください．

次に，図のように`FREERTOS`をEnableにします．

![FreeRTOS_check.png](https://qiita-image-store.s3.amazonaws.com/0/138656/9c21861a-97f9-672a-dd8f-1fd0f631c56d.png)

### タスクの設定[追記: 2017/09/13]
CubeMX上でタスクの設定ができます．
こちらから設定する場合は，「Lチカを実装する」の「タスク関数の実装」以外は飛ばして構いません．

`Configuration` タブから `FREERTOS` を選択します．
`Tasks and Queues` タブからタスクの設定を行います．

`Add` を選択すると，下のようなウィンドウが出てきます(もしくはすでにあるdefaultTaskを編集してもいいです)．

![tasks.png](https://qiita-image-store.s3.amazonaws.com/0/138656/5047cc21-e1cc-a001-d058-fbd4103cd85b.png)


![addtask.png](https://qiita-image-store.s3.amazonaws.com/0/138656/62c1714d-edb7-82aa-3555-433653945f32.png)

主にいじるのは以下のものです．
- Task Name : タスクの名前です．今回はLチカを行うので `LchikaTask` とします．
- Priority : タスクの優先度を表します．優先したいタスクは優先度を上げてください．
- Stack Size : タスクで使用する変数を保存する領域です[^stack]．
- Entry Function : タスクを開始する関数の名前です．あとで処理を実装するところになります．

[^stack]: http://miqn.net/rtos/61.html

### Code Generation
早速コード生成してみましょう．
プロジェクトの生成には，ここを参考にしました．

[CubeMX & C++ でSTM32の開発を行う - Qiita](http://qiita.com/FlechaMaker/items/de9c663d1b394e5a2862)

![Warning.png](https://qiita-image-store.s3.amazonaws.com/0/138656/404621f7-29eb-17ad-ce23-c665661c9705.png)

すると，そのままではWarningが出てしまいます．
どうやら，FreeRTOSを使用する場合は，HAL Timebase SourceにSystick以外を使用することが **強く** 推奨されるようです．

そこで，次の図のように `SYS` の `Timebase Source` をSystick以外のものに変更します(タイマのペリフェラルは機能が非常に多く，他にも使う可能性が高いため，機能の少なそうなものを適当に選択しました)．

![timesource.png](https://qiita-image-store.s3.amazonaws.com/0/138656/0442c341-0065-b172-056b-e91ac5850964.png)

再びコード生成してみましょう．
今度は何事もなく生成できるはずです．

## Lチカを実装する
早速実装していきましょう．
生成されたプロジェクトの中から，Application/User にある，`freertos.c` を開きます．

デフォルトで用意されているタスクがありますが，今回は新たにタスクを追加作成する方法を説明します．
基本的にはすでにあるものを写していきます．

### タスクハンドルの作成
RTOSが実行するタスクのハンドルを作成します．

```c:freertos.c
/* Variables -----------------------------------------------------------------*/
osThreadId defaultTaskHandle;

/* USER CODE BEGIN Variables */
osThreadId LchikaTaskHandle;         // ここにタスクのハンドルを追加！
/* USER CODE END Variables */
```

### タスク関数の実装
Lチカするタスクの処理を実装します．
次の項目で説明しますが，タスクを作成する際にはタスクが実行する関数が必要になります．

```c:freertos.c

/* Function prototypes -------------------------------------------------------*/
void StartDefaultTask(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/* USER CODE BEGIN FunctionPrototypes */
void StartLchikaTask(void const * argument);    // Lチカのタスク関数
/* USER CODE END FunctionPrototypes */

:
(中略)
:

/* USER CODE BEGIN Application */
/* StartLchikaTask function */
void StartLchikaTask(void const * argument)
{

  /* Infinite loop */
  for(;;)
  {
    // 出力を反転
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  // 要 "stm32fXxx_hal.h"
    // 500ms待機
    osDelay(500);
  }
}
/* USER CODE END Application */
```

ここで，`osDelay(500);` といった処理があります．
HALを使用していると，`HAL_Delay()` が馴染み深いかもしれませんが，FreeRTOSでは優先度の高いタスクを優先して実行するといった動作をするため，考え方が少し変わってきます．
`HAL_Delay()` では **処理の流れで遅延を挿入する** といったような振る舞いをします．
対して `osDelay()` では **タスクの遅延とともに遅延状態にあることをOSに知らせる** といった振る舞いをします[^delay]．

[^delay]: HAL_Delay()はポーリングで遅延を導入しているためのようです．(https://stackoverflow.com/questions/42276313/freertos-osdelay-vs-hal-delay)

優先度の高いタスクが `HAL_Delay()` で遅延をし続けた場合，OSはタスクが遅延中であることを知らないため，そのタスクよりも優先度の低いタスクが実行されることはありません．
タスクが遅延中であることがあらかじめわかっていれば，優先度の低いタスクを実行することができます．
そのため，"遅延中に他のタスクを実行させたくない" などの理由がない限りは，`osDelay()` を使うのが望ましいでしょう．

### タスクを作成する
ここで言うタスクの作成は，タスクの実体をプログラム上に作成すると言う意味です．
はじめに作成したハンドルで，先ほど作成した関数を実行するタスクが結びつけられるといった感じです．

```c:freertos.c
// void MX_FREERTOS_Init(void) 関数内

/* Create the thread(s) */
/* definition and creation of defaultTask */
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
osThreadDef(LchikaTask, StartLchikaTask, osPriorityAboveNormal, 0, 128);
LchikaTaskHandle = osThreadCreate(osThread(LchikaTask), NULL);
/* USER CODE END RTOS_THREADS */
```

まずは，`osThreadDef`[^itismacro] でタスクの型のようなものを作成します．  
第1引数から順にタスクの名前，タスク関数，優先度，インスタンスの最大数，スタックサイズとなっています．
ここではデフォルトの優先度より1つ高くしています．

[^itismacro]: マクロです．関数ではありません．

次に，`osThreadCreate()` でタスクの実体を作成し，ハンドルに渡します．

あとは，`main.c` 内で `osKernelStart()` が呼び出され，タスクがスタートします．

## 動作結果
実行結果は，上記コードの場合，500msでトグル出力されるため，1HzのLチカ動作となりました．

## おわりに
RTOSと聞くと，ちょっと難しそうに聞こえたましたが，タスクの作成もから実行まであまり詰まることなくできたので，正直少し驚きました．
また，かなり正確な時間で制御することができました．私の場合，外部クロックを用いたのですが，6時間Lチカを動作させ続けて，ずれている様子は全く見られませんでした．

他の割り込みをいれた場合はどのように振る舞うのかは，今の所詳しくはわかっていません．GPIO入力割り込みでUART出力する程度ならほぼ問題なく動作しているようでした．
また，`osDelay()` では **一定時間タスクを止める** といった動作をするため，タスクの処理によって処理時間がまばらになってしまう場合等は，一定周期で制御を行いたい場合等で不都合が出てきてしまいます．
次回があったら，その辺りに触れてみたいと思います．