はじめに
この記事では,STM32でFreeRTOSを作させるプロジェクトの作成を行います.
通常,CubeMXを用いてFreeRTOSプロジェクトを作成すると,CMSISによるFreeRTOSのwrapper(CMSIS-FreeRTOS)を使用することになります.ですが,今回はCMSIS-FreeRTOSは使用せず,より一般的なネイティブのFreeRTOSを用いてプロジェクトの作成を行います.
以下では,主に公式:新規プロジェクト作成を参考にしながら,FreeRTOSプロジェクトの作成を行っていきます
ハードウェアは,STM32F401RE(NUCLEO-F401RE)を使用します.
作成したプロジェクトはGitHubに上げています.
FreeRTOSに必要なファイルの準備
FreeRTOSを動かすのに必要なファイルを用意します.
まずは,公式:FreeRTOSのダウンロードまたはGitHubをダウンロードします.
ダウンロードしたフォルダの中には,サンプルプログラムなどの多くのファイルが含まれています.しかし,プロジェクトに作成には,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メモリについての詳細は,公式:ヒープメモリについてに記載があります.
CubeMXによるプロジェクトの作成
今回,クロックやペリフェラルの設定はCubeMXのGUI上で行います.
その際に,デフォルトから変更する点が3つあります.
- HALの
Timebase Sorce
の変更 - 割込みの優先度グループ割り当て・優先度の変更
- 例外・割込みハンドラのコード生成の無効化
- ツールチェーンの
Makefile
への変更,etc
HALのTimebase Sorce
の変更
FreeRTOSは,Sys Tick
を使用するので,それとの競合を避けるために,HALライブラリのTimebase Sorce
にSys Tick
以外のタイマを割り当てることが推奨されます.
今回は,TIM2
を割り当てます.
割込み(NVIC)の優先度グループ割り当て・優先度の変更
Cortexの割込み(NVIC)優先度についての詳細は公式:cortex-m3,4での割込み優先度についてに記載があります
-
優先度グループ割り当て
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でのコード生成を無効化します
詳細は公式F&Q集に記載があります
ツールチェーンのMakefile
への変更,etc
Project Manager > Project
のToolchain/IDE
をMakefile
に変更します.
また,好みになりますが,Project Manager > Code Generator
について,以下にチェックを入れます.
Copy only the necessary library files
Generate peripheral initialization as ~
設定ファイルFreeRTOSconfigの用意
チュートリアル通り,GitHubをベースにカスタマイズします.詳細は,公式:FreeRTOSConfig.hについてに記載があります.
今回は,以下の変更を行いますが,FreeRTOSの様々な機能を使用・詳細の動作を制御したい際には,更に追加の変更が必要となることに,注意ください.
クロック・タイマーの設定
-
configCPU_CLOCK_HZ
- CPUクロックの設定
-
configTICK_TYPE_WIDTH_IN_BITS
- FreeRTOS内タイマーカウンタのビット長の設定
作成したプロジェクトに沿って,CPUクロックの設定とFreeRTOS内タイマーカウンタのビット長を以下のように設定します.
#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
の範囲で設定する必要があるようです.
// #define configKERNEL_INTERRUPT_PRIORITY 0
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 16
// #define configMAX_API_CALL_INTERRUPT_PRIORITY 0
割込み・例外ハンドラの登録
Cortex-M3, M4, M4Fでは,特定の例外・割込みが起こった際,FreeRTOSの想定する処理が実行されるように,ハンドラを登録します.さきほど,これらのハンドラのコード生成を無効化したのは,このためです.
#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に追記します.
-
ソースファイルの追加
# 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
-
インクルードパスの追加
# C includes # ... C_INCLUDES += \ -IDrivers/FreeRTOS-Kernel/include \ -IDrivers/FreeRTOS-Kernel/portable/GCC/ARM_CM4F
スタックオーバーフローチェックフック関数の実装
各タスクで使用するスタックメモリが,予め定められた量を超えた際に,フックされる関数の実装を記述します.
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
に設定します.オーバヘッドが生じるので,開発時(デバック時)のみ有効にすることが推奨されます.
詳しくは,公式:スタックオーバーフローチェックを参照ください.
タスクの作成・登録
今回は,main.c
の中で,タスクの作成・登録を記述します.
ヘッダの記述
main.c
のヘッダを追加して,FreeRTOSライブラリを使用できるようにします.
※ #include <string.h>
は,シリアル通信に使用しているだけで,FreeRTOSと直接関係はありません.
#include "FreeRTOS.h"
#include "task.h"
#include <string.h>
タスクの宣言
二つのタスクLEDBlink
,B1Pushed
を用意します.
-
LEDBlink
1secごとにLEDを点滅させるタスク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に通知するタスク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ループ処理を行う前に呼び出します.
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!"がシリアル表示されれば,成功です.