STM32のFreeRTOS(not CMSIS)プロジェクトを構築する



# はじめに

この記事では，STM32でFreeRTOSを作させるプロジェクトの作成を行います．

通常，CubeMXを用いてFreeRTOSプロジェクトを作成すると，CMSISによるFreeRTOSのwrapper(CMSIS-FreeRTOS)を使用することになります．ですが，今回はCMSIS-FreeRTOSは使用せず，より一般的なネイティブのFreeRTOSを用いてプロジェクトの作成を行います．

以下では，主に[公式：新規プロジェクト作成](https://www.freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/03-Build-your-first-project)を参考にしながら，FreeRTOSプロジェクトの作成を行っていきます

ハードウェアは，STM32F401RE(NUCLEO-F401RE)を使用します．

作成したプロジェクトはGitHubに上げています．

https://github.com/Y-Yoshimura1997/STM32FreeRTOSBasicSample


# FreeRTOSに必要なファイルの準備

FreeRTOSを動かすのに必要なファイルを用意します．

まずは，[公式：FreeRTOSのダウンロード](https://www.freertos.org/a00104.html)または[GitHub](https://github.com/FreeRTOS/FreeRTOS-Kernel/tree/V11.1.0)をダウンロードします．

ダウンロードしたフォルダの中には，サンプルプログラムなどの多くのファイルが含まれています．しかし，プロジェクトに作成には，`FreeRTOSvyyyymm.dd/FreeRTOS/Sorce`または`FreeRTOS-Kernel`フォルダ内にある3つのソースファイルとヘッダファイル群
- `tasks.c`
- `queue.c`
- `list.c`
- `include/*.h`

とその更に下の階層にある3つのファイル
- `/portable/MemMang/heap_[x].c`
- `/portable/[compiler]/[architecture]/port.c`
- `/portable/[compiler]/[architecture]/portmacro.h`

のみを使用します．

`[compiler]`と`[architecture]`の組み合わせによっては，`/portable/[compiler]/[architecture]/`内に，上記に記載がないヘッダファイル`*.h`やアセンブラファイル`*.asm`が含まれていることがあります．その場合は，それらも追加で含める必要があります．

ここで，`[compiler]`は使用するコンパイラ，`[architecture]`は使用するCPUアーキテクチャーを指定します．
今回は，コンパイラにarm-none-eabi-gcc"を使用するので`[compiler] = "GCC"`，ターゲットCPUとしてCoretex-M4FであるSTM32F401REを使用するので，`[architecture] = "C4F"`となります．

また，`[x]`には，FreeRTOSでのheapメモリの確保方法を選択します．
今回は，推奨されている`heap4.c`を用いるので，`[x] = "4"`を指定します．
heapメモリについての詳細は，[公式：ヒープメモリについて](https://www.freertos.org/a00111.html)に記載があります．





# CubeMXによるプロジェクトの作成

今回，クロックやペリフェラルの設定はCubeMXのGUI上で行います．

その際に，デフォルトから変更する点が3つあります．
- HALの`Timebase Sorce`の変更
- 割込みの優先度グループ割り当て・優先度の変更
- 例外・割込みハンドラのコード生成の無効化
- ツールチェーンの`Makefile`への変更，etc


### HALの`Timebase Sorce`の変更

FreeRTOSは，`Sys Tick`を使用するので，それとの競合を避けるために，HALライブラリの`Timebase Sorce`に`Sys Tick`以外のタイマを割り当てることが推奨されます．

今回は，`TIM2`を割り当てます．

![systime.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459012/12c0e41b-5cc3-52de-8d12-d8b983066048.png)


### 割込み(NVIC)の優先度グループ割り当て・優先度の変更

Cortexの割込み(NVIC)優先度についての詳細は[公式：cortex-m3,4での割込み優先度について](https://www.freertos.org/RTOS-Cortex-M3-M4.html)に記載があります

![change_nvic_priority.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459012/9a5ed86e-e335-1ecc-2a99-6383db8ea6cc.png)

- 優先度グループ割り当て

    Cortex-Mアーキテクチャでは，割込み(NVIC)優先度を8bitで設定します．さらに，8bitをメインorサブにグルーピングできるものもあります．ですが，8bitのうちユーザが設定可能なbit数やメインorサブのグループ分けの有無は，ベンダー依存になります．
    
    ここで，FreeRTOSを使用する際は，全てメインに割り当てることが推奨されます．
    
    今回，STM32F401REでは，設定可能bitは上位4bitでメインorサブのグループ分けが有効であるので，4bitすべてをメイングループに割り当てます．


-  優先度の変更

    Cortex-Mアーキテクチャにおいて，割込み(NVIC)関数から，FreeRTOSのAPIを呼ぶ際には，割込み(NVIC)の優先度を`configMAX_SYSCALL_INTERRUPT_PRIORITY`以下の優先度に設定する必要があります．
    
    ここで，FreeRTOSでは設定数値が大きいほど優先度が上がるのに対して，Cortex-Mでは設定数値が大きいほど優先度が下がることに注意が必要です．

    今回は，割込み(NVIC)を用いたり，さらにその中からFreeRTOSのAPIは呼ばないので，すべてデフォルト`0`のままとします．
    

### 割込み・例外ハンドラコード生成の無効化

Cortex-M3, M4, M4Fでは，幾つかの割込み・例外ハンドラは，FreeRTOSライブラリ内にある処理を使用するので，CubeMXでのコード生成を無効化します

![no_generate_code.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459012/be1e398b-45af-9064-788e-37ae2aca04b6.png)

詳細は[公式F&Q集](https://www.freertos.org/Why-FreeRTOS/FAQs/Troubleshooting)に記載があります

### ツールチェーンの`Makefile`への変更，etc

`Project Manager > Project`の`Toolchain/IDE`を`Makefile`に変更します．

また，好みになりますが，`Project Manager > Code Generator`について，以下にチェックを入れます．
- `Copy only the necessary library files`
- `Generate peripheral initialization as ~`

## 設定ファイルFreeRTOSconfigの用意

チュートリアル通り，[GitHub](https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/V11.1.0/examples/template_configuration/FreeRTOSConfig.h)をベースにカスタマイズします．詳細は，[公式：FreeRTOSConfig.hについて](https://www.freertos.org/Documentation/02-Kernel/03-Supported-devices/02-Customization#configkernel_interrupt_priority-configmax_syscall_interrupt_priority-and-configmax_api_call_interrupt_priority)に記載があります．


今回は，以下の変更を行いますが，FreeRTOSの様々な機能を使用・詳細の動作を制御したい際には，更に追加の変更が必要となることに，注意ください．

### クロック・タイマーの設定
- `configCPU_CLOCK_HZ`　
    - CPUクロックの設定
- `configTICK_TYPE_WIDTH_IN_BITS`  
    - FreeRTOS内タイマーカウンタのビット長の設定 

作成したプロジェクトに沿って，CPUクロックの設定とFreeRTOS内タイマーカウンタのビット長を以下のように設定します．

```cpp
#define configCPU_CLOCK_HZ    ( ( unsigned long ) 16000000 )
#define configTICK_TYPE_WIDTH_IN_BITS              TICK_TYPE_WIDTH_32_BITS
```

### 割込み優先度に関する設定

割込み優先度に関する設定には以下の3つがありますが，ハードウェアによって使用される設定が少し異なります．

- `configKERNEL_INTERRUPT_PRIORITY`
    - FreeRTOS自身が使用する割込み優先度
    - 対象：ARM Cortex-M3, IC24, dsPIC, PIC32, SuperH, RX600
- `configMAX_SYSCALL_INTERRUPT_PRIORITY`
    - 割込み内でFreeRTOSのAPIを呼び出すことができる最高の割り込み優先度
    - 対象：IC32, RX600, ARM, Cortex-A/M 
- `configMAX_API_CALL_INTERRUPT_PRIORITY` 
    - `configMAX_SYSCALL_INTERRUPT_PRIORITY`の新しい名称
    - 対象：IC32, RX600, ARM, Cortex-A/M
    

STM32F401REでは，`configMAX_SYSCALL_INTERRUPT_PRIORITY`が使用されます．

先述の通り，今回は，割込み(NVIC)を用いたり，さらにその中からFreeRTOSのAPIは呼ばないので，`0`のままで良さそうですが，この値を`16=0x10`以上にする必要がありました．

というのも，STM32F401REでは．割込み(NVIC)優先度を決める8bitのうち上位4bitが有効であるので，`16=0x10`から`255=0xFF`の範囲で設定する必要があるようです．
```cpp
// #define configKERNEL_INTERRUPT_PRIORITY          0
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    16
// #define configMAX_API_CALL_INTERRUPT_PRIORITY    0
```


### 割込み・例外ハンドラの登録

Cortex-M3, M4, M4Fでは，特定の例外・割込みが起こった際，FreeRTOSの想定する処理が実行されるように，ハンドラを登録します．さきほど，これらのハンドラのコード生成を無効化したのは，このためです．

```c++:config.cpp
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
```

## 必要ファイルの配置・Makefileの修正


### 各ファイルの配置

今回は，/Drivers内にFreeRTOS-Kernelというフォルダを作成し，そこへ先ほど準備したファイルを配置します．

FreeRTOSConfig.hは，`Core/Inc`に置くこととします．

```
├─Core
│  ├─Inc
|  | └─FreeRTOSConfig.h
│  └─Src
└─Drivers
    ├─CMSIS
    ├─FreeRTOS-Kernel
    │  ├─include
    │  └─portable
    │      ├─GCC
    │      │  └─ARM_CM4F
    │      └─MemMang
    └─STM32F4xx_HAL_Driver
        ├─Inc
        └─Src
```

### Makefileの修正

追加したファイルをbuild対象に含めます．
具体的には，以下の内容をMakefileに追記します．

- ソースファイルの追加
    ```makefile
    # C sources
    # ...
    C_SOURCES += \
    $(wildcard Drivers/FreeRTOS-Kernel/*.c) \
    Drivers/FreeRTOS-Kernel/portable/GCC/ARM_CM4F/port.c \
    Drivers/FreeRTOS-Kernel/portable/MemMang/heap_4.c
    ```

- インクルードパスの追加
    ```makefile
    # C includes
    # ...
    C_INCLUDES +=  \
    -IDrivers/FreeRTOS-Kernel/include \
    -IDrivers/FreeRTOS-Kernel/portable/GCC/ARM_CM4F
    ```

## スタックオーバーフローチェックフック関数の実装

各タスクで使用するスタックメモリが，予め定められた量を超えた際に，フックされる関数の実装を記述します．

```cpp
void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )
{
  char* uart_mes = " Stack Over Flow!\r\n";
  HAL_UART_Transmit(&huart2, (uint8_t*)uart_mes, strlen(uart_mes), 1000);
}
```

このフックを無効化したい場合には，`configCHECK_FOR_STACK_OVERFLOW`を`0`に設定します．オーバヘッドが生じるので，開発時(デバック時)のみ有効にすることが推奨されます．

詳しくは，[公式：スタックオーバーフローチェック](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/09-Memory-management/02-Stack-usage-and-stack-overflow-checking)を参照ください．

## タスクの作成・登録

今回は，`main.c`の中で，タスクの作成・登録を記述します．

### ヘッダの記述

`main.c`のヘッダを追加して，FreeRTOSライブラリを使用できるようにします．

※ `#include <string.h>`は，シリアル通信に使用しているだけで，FreeRTOSと直接関係はありません．

```cpp
#include "FreeRTOS.h"
#include "task.h"
#include <string.h>
```

### タスクの宣言

二つのタスク`LEDBlink`，`B1Pushed`を用意します．

- `LEDBlink`
    1secごとにLEDを点滅させるタスク
    ```cpp
   void LEDBlink(void* pvParameters)
    {
      while (1)
      {
        HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
        char* uart_mes = "LED Blink!\r\n";
        HAL_UART_Transmit(&huart2, (uint8_t*)uart_mes, strlen(uart_mes), 1000);
        vTaskDelay(1000);   
      }
    }
    ```
- `B1Pushed`
    ボタンが押されているかを50msecのポーリングで監視し，ボタンが押されていることをUARTに通知するタスク
    ```cpp
    void B1Pushed(void* pvParameters)
    {
      while (1)
      {
        GPIO_PinState state =  HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
        if (state == GPIO_PIN_RESET) {
          char* uart_mes = "B1 Pushed!\r\n";
          HAL_UART_Transmit(&huart2, (uint8_t*)uart_mes, strlen(uart_mes), 1000);
        }
        vTaskDelay(50);
      }
    }
    ```

### タスクの登録・起動

用意したタスクを登録したのちに，タスクの起動を行います．
特に，タスクを起動する関数`vTaskStartScheduler()`は，システムクロックやペリフェラルの初期化が行われた後，mainループ処理を行う前に呼び出します．

```cpp
  xTaskCreate(LEDBlink, "LEDBlink", configMINIMAL_STACK_SIZE*2, NULL, 3, NULL);
  xTaskCreate(B1Pushed, "B1Pushed", configMINIMAL_STACK_SIZE*2, NULL, 3, NULL);
  vTaskStartScheduler();
  ```


## 動作確認

ここまでの作業で，STM32のFreeRTOSプロジェクトの作成は終了です．
後はコンパイルして，マイコン上に展開してください．

LEDが1secごとに光り，ボタン(B1)を押している間は50msecごとに"B1 Pushed!"がシリアル表示されれば，成功です．