STM32CubeIDEでFreeRTOS自前導入（CMSIS-RTOSを使わない！）

# STM32でFreeRTOS

最も普及しているArm Cortex-MマイコンであろうSTM32で、最も普及しているフリーのRTOSであろうFreeRTOSを使いたい。

その際に障壁となるのは、
__多くの解説記事が「CubeIDE (CubeMX) のコード生成によるCMSIS-RTOSラッパーを通したFreeRTOSの使用」を前提としている__
ことです。

CubeIDE (CubeMX) でコード生成をすれば手間いらずでFreeRTOSが動くプロジェクトを生成できるので非常に便利です。
しかし、CMSIS-RTOSラッパーが同時に生成されデフォルトでそちらのラッパーが使われるため、人によってはこれが邪魔だと感じる方もいるのではないでしょうか。
また、CubeIDE(CubeMX) の自動生成頼りでは自分の好きなバージョンのFreeRTOSを使うことができません。

FreeRTOS公式のSTM32向けデモアプリケーションはIAR向けが多く、Atollic向けのものはありますがCubeIDE用のものはありません。そしてほんの一部のMCU・評価基板にしか対応していません。


ここでは、__STM32CubeIDE (CubeMX) でプロジェクトの大枠を作成しつつ、FreeRTOSをプロジェクト内に自前で導入する__ 際の注意事項についてまとめていきます。

# 前提環境
本記事では以下のバージョン、MCUを用いています。

- STM32CubeIDE 1.5.1
- FreeRTOS V10.4.3
- STM32L432KC

# CubeIDE (CubeMX) でのプロジェクト生成

CubeIDE (CubeMX) でプロジェクトを作成します。当然FreeRTOSはここではプロジェクトに含めません。
プロジェクト作成の際の注意事項は以下の通りです。

## SYS
![SYS](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45036/ddb823a0-3f33-5a46-89c6-242006f9ed68.png)


ここで注意する点はTimebase Sourceです。
デフォルトではFreeRTOSがSystickを使用するため、HALのTimebase SourceをSystick以外に設定することが推奨されています。ここではハードウェアタイマーTIM16を用いました。

## NVIC
![NVIC1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45036/a616d49f-26d2-74c4-a1e8-bb443c6b3a23.png)


NVICの設定でまず注意するのはPriority Groupです。通常FreeRTOSはSub-priorityなしのPriority Groupで動作させます。
また、割り込みハンドラ内でFreeRTOSのISR safeなAPIを呼ぶ場合、その割り込みの優先度は`FreeRTOSConfig.h`で設定される`configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`と等しいあるいは大きな数字（Cortex-Mでは数字が大きい方が優先度が低い）である必要があります。
画像の例ではEXTIのハンドラからISR safeなAPIを呼ぶことを前提としており、`configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`を`5`と設定しているため優先度は`5`かそれより大きい数字に設定します。

![NVIC2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45036/68e574c2-507a-ada6-3ac9-06c2ba7e0734.png)


また、NVICのコード生成の設定も修正します。SVC, PendSV, SystickのハンドラはFreeRTOSのものを使うため、CubeIDE (CubeMX) での生成を抑止します。

## Project Manager
![PM](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45036/f3929813-f81b-68ad-b170-881c6956ec42.png)


FreeRTOSのメモリ管理を用いる場合、標準ライブラリ用のHeapを確保しておく必要はありません。`Minimum Heap Size`は`0x0`でOKです。`Minimum Stack Size`は適正量を設定しておきます。
※とはいえこれらの値はリンカスクリプトに反映されRAMの残量チェックに用いられるだけなので、あまり気にしなくても大丈夫です。

ここまで設定したらコード生成をします。

# FreeRTOSのプロジェクトへの追加
コード生成後、プロジェクトフォルダにFreeRTOSを追加します。
ここではFreeRTOS-Kernelをプロジェクトファイル配下にコピーすることにします。
ただし、portableディレクトリ以下は一部のみが必要です。不必要なファイルを含めてしまうとコンパイルエラーやシンボルの重複によるリンクエラーが生じてしまうので、必要なファイルのみをコピーするか、プロジェクトの設定でフィルターを設定します。

1. `portable/GCC/ARM_CM4F`
2. `portable/MemMang/heap_4.c`

のみが必要です。
1番目はコアのアーキテクチャ等によって適切なものを選択します。2番目は使用するヒープ管理のアルゴリズムのソースを選択します。上に示したのはheap_4を使用する場合です。

参考：[FreeRTOSのHeapのアルゴリズム（公式）](https://www.freertos.org/a00111.html)

また、__`portable/GCC/ARM_CM4F`をインクルードディレクトリに含めるのも忘れないようにします 。__

# FreeRTOSConfig.h

`FreeRTOSConfig.h`を作成しインクルードディレクトリ内に配置します。
とりあえず動かす上で注意するべきなのは`configCPU_CLOCK_HZ`です。ここに正しくCPUクロックの値を設定します。
以下にCPUクロックが80 MHzの場合の一例を示します。

```c:FreeRTOSConfig.h
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/

#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ (80000000UL)
#define configTICK_RATE_HZ ((TickType_t)1000)
#define configMAX_PRIORITIES (5)
#define configMINIMAL_STACK_SIZE ((unsigned short)70)
#define configTOTAL_HEAP_SIZE ((size_t)(7 * 1024))
#define configMAX_TASK_NAME_LEN (10)
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 0
#define configGENERATE_RUN_TIME_STATS 0
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 0

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES (2)

/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (3)
#define configTIMER_QUEUE_LENGTH 5
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE)

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1

/* Use the system definition, if there is one */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

/* The lowest priority. */
#define configKERNEL_INTERRUPT_PRIORITY \
    (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
/* Priority 5, or 95 as only the top four bits are implemented. */
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
    (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

#define configASSERT(x)           \
    if ((x) == 0) {               \
        taskDISABLE_INTERRUPTS(); \
        for (;;)                  \
            ;                     \
    }

#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

#endif /* FREERTOS_CONFIG_H */
```

以上でFreeRTOSが利用できるプロジェクトが完成します。
スタートアップコードやリンカスクリプトを編集する必要はありません！