FreeRTOS について、オフィシャル提供のPDFを読んでみたのでまとめておきます。 STM32F405 向けのプロジェクトでFreeRTOSが実際に動作するところまでを記述しています。

TOC

今回の投稿用に覗いたレポジトリは ‘Classic’ FreeRTOS と言う位置づけらしいです。意味よくわかってませんが kernel だけ分離したものがあるのでレポジトリの構成的な話なんですかね? Kernel は submodule 化してあり同じものです。

FreeRTOS はアマゾンに買収されたこともあり統合されているプロジェクトは多く、そういった開発環境では関数を呼び出すだけですぐ利用でき、ある程度 processor や thread、context switch などを理解している人なら関数のコメントなどからも大体の使い方なども分かるかと思います。特にこのドキュメントを読まなくても済むかもしれません。

より厳密な task scheduling や FreeRTOS をプロジェクトに組み込むため、Debug を用意にするため、など明確に目的がある場合には知っていたほうが良さそうです。

FreeRTOS とは

RTOS とは時間的要件のあるアプリケーション向けのシステムを表す用語で、FreeRTOS はその実装の一つ。 FreeRTOS は基準となる時間単位を用いて処理を実行、管理する multi-task を提供する Library です。RTOS の本質的な機能を OS で使われる用語と同じく kernel と表現してます。この kernel は約 20種の compiler と 30種以上の processor 向けに build/run できるようになっているようです。ドキュメントでは compiler+processor の組み合わせを “FreeRTOS port” と表現して、移植対象のひとつを表すようです。 利用するにはこの kernel + 移植コードをプロジェクトにインストールすることになります。

ドキュメント(PDF)

https://www.freertos.org/Documentation/RTOS_book.html

バージョンは kernel V10.4.3 でした。

FreeRTOS 機能

機能については一部は必須ではなく選択可能なものになっており、抜粋すると以下となります。

  • Pre-emptive or co-operative operation
  • Very Flexible task priority assignment
  • Flexible, fast and light weight task notification mechanism
  • Queues
  • Binary semaphores
  • Counting semaphores
  • Mutexes
  • Recursive Mutexes
  • Software timers
  • Event Groups
  • Tick hook functions
  • Idle hook functions
  • Stack overflow checking
  • Trace recording
  • Task run-time statistics gathering
  • Optional commercial licensing and support
  • Full interrupt nesting model
  • A tick-less capability for extreme low power applications
  • Software managed interrupt stack when appropriate (this can help save RAM)

ライセンス

MIT License で配布されてます。

  • 商用アプリケーションに使用可能
  • だれでも使用可能
  • 知的財産の保有権を維持できる

と記述されているので、問題になることはなさそうです。

他にも

  • 商用ライセンス向けに OpenRTOS
  • 国際的に認められた様々な安全関連規格に準拠する場合は SafeRTOS

もあるようです。

FreeRTOS Distoribution

新しいFreeRTOSプロジェクトを作成するには自前で作成する方法を別にすると同封されるDemoアプリケーションのコピーを元に新しくプロジェクトを作成する方法が推奨されるようです。

今回は自前で用意しました。 対象コンパイラは GCC, アーキテクチャは ARM Cortex-M4F です。

手順は以下、

  • toolchain を選択し FreeRTOS を含んでいない project を用意
  • build/downloaded/executed が想定どおりの動作か確認する
  • FreeRTOS source files を追加する
FreeRTOS/Source/tasks.c
FreeRTOS/Source/queue.c
FreeRTOS/Source/list.c
FreeRTOS/Source/timers.c
FreeRTOS/Source/event_groups.c
FreeRTOS/Source/portable/GCC/ARM_CM4F/*
FreeRTOS/Source/portable/MemMang/heap_n.c (optional. n は 1~5)

FreeRTOSConfig.h をcopyする Include path に

FreeRTOS/Source/include/
FreeRTOS/Source/portable/[compiler]/architecture]/
FreeRTOSConfig.h

Compiler settings を copy する いずれかの必要になるかもしれないFreeRTOS 割り込みハンドラを導入する。web page や demo project を参照のこと。 ヒープ空間が他の変数によって使用されるメモリにまで成長してしまうと、デバッグが困難なエラーの原因になることがあります。

Source file 構成

FreeRTOS の core 実装

  • tasks.c
  • list.c

Queue操作、Semaphoreを提供する

  • queue.c

Software Timerを提供

  • timers.c

EventGroup 機能

event_groups.c

Co-routine

croutine.c

Install FreeRTOS

前回STM32F411 をターゲットとしてましたが、今回から STM32F405RGをターゲットにしたプロジェクトを新たに用意しました。 手順は同じなのでプロジェクトの生成処理は割愛します。以下のリポジトリの内容が生成されたものです。

https://github.com/jfcamel/stm32f405rg_template

プロジェクトがあるところから、FreeRTOS を導入します。

FreeRTOS は以下の zip を download し

https://github.com/FreeRTOS/FreeRTOS/releases/download/202012.00/FreeRTOSv202012.00.zip

必要なファイルを展開していきます。完了したディレクトリツリーは以下のようにしました。

$ find Drivers/FreeRTOS/ -type d
Drivers/FreeRTOS/
Drivers/FreeRTOS/Test
Drivers/FreeRTOS/Source/include
Drivers/FreeRTOS/Source/portable
Drivers/FreeRTOS/Source/portable/GCC
Drivers/FreeRTOS/Source/portable/GCC/ARM_CM4F
Drivers/FreeRTOS/Source/portable/MemMangしていきます。
Drivers/FreeRTOS/License

FreeRTOS/Source/portable ディレクトリには必要ないファイルが含まれるので、それら以外をコピーする。 加えて、FreeRTOS/Demo/CORTEX_M4F_STM32F407ZG-SK/FreeRTOSConfig.h を

Drivers/FreeRTOS/

にコピーする。

FreeRTOSConfig.h まわり

ARM_CORTEX_M だと以下の割り込みが FreeRTOS で使用される。

FreeRTOSConfig.h の抜粋

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

stm32f4xx_it.c からこれらの重複する割り込みを一旦削除。

メモリ割り当ては動的なものを選択。こちらが動作させるのに楽だった。

FreeRTOSConfig.h の抜粋

#define configSUPPORT_STATIC_ALLOCATION  0
#define configSUPPORT_DYNAMIC_ALLOCATION 1

Scheduler の最適化もオフ。

FreeRTOSConfig.h の抜粋

#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0

Application Hooks

もともとの値を元に最低限動作が確認できるように変更する。

#define configUSE_TICK_HOOK             1
#define configUSE_MALLOC_FAILED_HOOK    1
#define configUSE_IDLE_HOOK             1
#define configCHECK_FOR_STACK_OVERFLOW  2

この hook 設定により以下の簡易実装を追加する。

void vApplicationTickHook( void );
void vApplicationMallocFailedHook( void );
void vApplicationIdleHook( void );
void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName );

この他に気になった点

CubeMx で生成したプロジェクトで 1msベースの SystemTimer が起動している。

CubeMx生成のプロジェクトでは別実装の RTOS が同封されるので SystemTimer を起動する関数が初期化処理で呼ばれていたりします。 main.c 内の SystemClock_Config で LL_Init1msTick が呼ばれている箇所をコメントアウト。

void SystemClock_Config(void)
{
  LL_FLASH_SetLatency(LL_FLASH_LATENCY_3);
  while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_3)
  {
  }
  LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);
  LL_RCC_HSE_Enable();

   /* Wait till HSE is ready */
  while(LL_RCC_HSE_IsReady() != 1)
  {

  }
  LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLM_DIV_16, 200, LL_RCC_PLLP_DIV_2);
  LL_RCC_PLL_Enable();

   /* Wait till PLL is ready */
  while(LL_RCC_PLL_IsReady() != 1)
  {

  }
  LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
  LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_4);
  LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_2);
  LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);

   /* Wait till System clock is ready */
  while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
  {

  }
  // LL_Init1msTick(100000000); <= ここ
  LL_SetSystemCoreClock(100000000);
}

LLを使用してますが、HALを使用し HAL_Init を呼び出すプロジェクトでも 1ms で設定されているようなので要調整。 1ms == 1000Hz、 FreeRTOS で正しく動作していることを確かめたいので違う設定に変更し 400Hz としました。

FreeRTOSConfig.h

#define configTICK_RATE_HZ              ( ( TickType_t ) 400 )

サンプル

ここまでで動作確認ができます。シンプルな Task で確認します。

// Core/Inc/app.h
#pragma once

#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

  void app_init(void);
  void app_process(void);

  /* FreeRTOS Related */

  void vApplicationTickHook( void );

  /* vApplicationMallocFailedHook() will only be called if
     configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h.  It is a hook
     function that will get called if a call to pvPortMalloc() fails.
     pvPortMalloc() is called internally by the kernel whenever a task, queue,
     timer or semaphore is created.  It is also called by various parts of the
     demo application.  If heap_1.c or heap_2.c are used, then the size of the
     heap available to pvPortMalloc() is defined by configTOTAL_HEAP_SIZE in
     FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used
     to query the size of free heap space that remains (although it does not
     provide information on how the remaining heap might be fragmented). */
  void vApplicationMallocFailedHook( void );

  /* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set
     to 1 in FreeRTOSConfig.h.  It will be called on each iteration of the idle
     task.  It is essential that code added to this hook function never attempts
     to block in any way (for example, call xQueueReceive() with a block time
     specified, or call vTaskDelay()).  If the application makes use of the
     vTaskDelete() API function (as this demo application does) then it is also
     important that vApplicationIdleHook() is permitted to return to its calling
     function, because it is the responsibility of the idle task to clean up
     memory allocated by the kernel to any task that has since been deleted. */
  void vApplicationIdleHook( void );

  /* Run time stack overflow checking is performed if
     configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2.  This hook
     function is called if a stack overflow is detected. */
  void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName );


#ifdef __cplusplus
}
#endif // __cplusplus
// Core/Src/app.c
#include "app.h"

#include "FreeRTOS.h"
#include "task.h"

#include <stdio.h>


void app_init(void) {
  printf("app initialization started\n");
  xTaskCreate((TaskFunction_t)app_process,
              "app task",
              1024,
              NULL,
              0,
              NULL
    );

  printf("app initialized\n");
}

void app_process(void) {
  for ( ;; )
  {
    printf("Hello world\n");
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void vApplicationTickHook( void )
{
  printf(".");
}

void vApplicationMallocFailedHook( void )
{
  taskDISABLE_INTERRUPTS();
  for( ;; );
}

void vApplicationIdleHook( void )
{
}

void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
{
  taskDISABLE_INTERRUPTS();
  for( ;; );
}

“Hello World\n” を出力し1秒毎待機する Task と、TickHook で “.” を出力する例です。

app initialization started
app initialized
Hello world
................................................................................................................................................................................................................................................................................................................................................................................................................Hello world
................................................................................................................................................................................................................................................................................................................................................................................................................Hello world
................................................................................................................................................................................................................................................................................................................................................................................................................Hello world
................................................................................................................................................................................................................................................................................................................................................................................................................Hello world

’.’ x 400で1秒分の時間とカウントされ”Hello world”で実行が確認できました。

次回つづく (?)

全然進んでないですが、今回はここまでにします。 次回は task の詳細をまとめたいと思います。