前回 のつづきですが CMake について記述しています。

このプロジェクトテンプレートは気軽に機能実装するために使用しようと思っていたのですが少しだけ育ててみます。

https://github.com/jfcamel/stm32f405rg_template

FreeRTOS の Build 環境の効率化

タスクの詳細を知るために、サンプル実装をいくつか用意しようと思ってますが、その前に CMake を整備しました。

FreeRTOS は Build 時に MACRO で定義された値で機能や挙動を変えられます。主に FreeRTOSCOnfig.h に定義される値を(configUSE_PREEMPTION など) 書き換えたり、ファイルを置き換えたりします。

しかしFreeRTOSの設定可能な項目は多いです。いくつかの機能を試すために何度か FreeRTOSConfig.h を用意して既に非効率な作業だと感じました。

目的が機能や挙動を変えた生成物を複数用意すること、というそもそもファイルの重複を発生させやすいという事にも起因すると思いますが、ここの効率性を改善していきます。

どうやったら効率が良いか

  1. 一つの FreeRTOSConfig.h を元に必要箇所だけ書き換えできるようにする
  2. 配布物をに手を入れずに再利用性を高めたい

まず思いついたのがこの二つ。

FindFreeRTOS.cmake

CMake でのマナーや挙動が一部分からないところはあるものの、Find~ 系にまとめます。

1は、初期値を定義しておいた FreeRTOSConfig.h を作成する。各定義は以下のように cmake から上書きできるようにする

FreeRTOSConfig.h

...
#ifndef configUSE_PREEMPTION
#define configUSE_PREEMPTION 1
#endif // configUSE_PREEMPTION
...

この手の作業は Editor のマクロ機能を使えば一瞬です。 書き換え方法は以下のように記述する想定。

CMakeLists.txt

...
target_compile_definitions(${TARGET} PRIVATE
    configuUSE_PREEMPTION=0
    ...
)
...

これで十分です。

2は、配布物のファイル階層を前提に環境変数 FREERTOS_ROOT を参照するようにして CMake 関数を作成することで解決。呼び出しは以下の2行(例)で行う。

include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindFreeRTOS.cmake)
FindFreeRTOS(COMPILER GCC PROCESSOR ARM_CM4F HEAP heap_1)

この呼び出しで変数が用意されるので各ターゲットに以下のように適用する。

target_sources(${TARGET}
    ...
    ${FREERTOS_SOURCES}
    ...)

target_include_directories(${TARGET} PRIVATE
    ...
    ${FREERTOS_INCLUDE_PATH}
    ...)

環境変数を export するだけなのでこれなら大した手間にはならないでしょう。

サンプル実装を追加

試しに static allocation でサンプル実装を追加してみます。

上に挙げた内容以外にも、以下の記述でディレクトリ名を target 名に使用できるので汎用化できます。

get_filename_component(EXECNAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" EXECNAME ${EXECNAME})

つづいて、書き換える宣言は以下。

configSUPPORT_STATIC_ALLOCATION=1
configSUPPORT_DYNAMIC_ALLOCATION=0

また、heap 実装も要らないので

FindFreeRTOS(COMPILER GCC PROCESSOR ARM_CM4F)

を別途呼び出します。

ARM で必須の記述が必要なファイルは新たに用意

  • 割り込み処理関連
  • LinkerScript
  • main.cpp
  • system_stm32f4xx.c (共通でもよかったが、今回は追加)

ファイル階層の全体図は

samples/static_tasks/
samples/static_tasks/Src
samples/static_tasks/Src/system_stm32f4xx.c
samples/static_tasks/Src/main.c
samples/static_tasks/Src/static_tasks.c
samples/static_tasks/Src/stm32f4xx_it.c
samples/static_tasks/Inc
samples/static_tasks/Inc/main.h
samples/static_tasks/Inc/static_tasks.h
samples/static_tasks/Inc/stm32f4xx_it.h
samples/static_tasks/STM32F405RGTx_FLASH.ld
samples/static_tasks/CMakeLists.txt

CMakeLists.txt の全体

get_filename_component(EXECNAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" EXECNAME ${EXECNAME})

FindFreeRTOS(COMPILER GCC PROCESSOR ARM_CM4F)

set(SOURCES_${EXECNAME}
  ${C_SOURCES_COMMON}
  ${FREERTOS_SOURCES}
  ${CMAKE_CURRENT_LIST_DIR}/Src/static_tasks.c
  ${CMAKE_CURRENT_LIST_DIR}/Src/stm32f4xx_it.c
  ${CMAKE_CURRENT_LIST_DIR}/Src/system_stm32f4xx.c
  ${CMAKE_CURRENT_LIST_DIR}/Src/main.c
  ${ASM_SOURCES_Core}
  )

foreach(SOURCE_${EXECNAME} ${SOURCES_${EXECNAME}})
  set_property(SOURCE ${SOURCE_${EXECNAME}} PROPERTY LANGUAGE C)
endforeach()

add_executable(${EXECNAME}
  ${SOURCES_${EXECNAME}}
  )

target_compile_options(${EXECNAME}
  ${C_FLAGS_OPTIONS}
  )

target_link_options(${EXECNAME}
  ${C_FLAGS_OPTIONS}
  PRIVATE -T${CMAKE_CURRENT_LIST_DIR}/STM32F405RGTx_FLASH.ld
  PRIVATE --specs=rdimon.specs
  PRIVATE -Wl,--gc-sections
  )

target_include_directories(${EXECNAME} PRIVATE
  ${INCLUDES_COMMON}
  ${FREERTOS_INCLUDE_DIRS}
  ${CMAKE_CURRENT_LIST_DIR}/Inc/
  )

target_compile_definitions(${EXECNAME} PRIVATE
  ${DEFINITIONS_COMMON}
  configSUPPORT_STATIC_ALLOCATION=1
  configSUPPORT_DYNAMIC_ALLOCATION=0
  )

target_link_libraries(${EXECNAME}
  -lm -lnosys
  )

add_custom_target(${EXECNAME}.bin ALL
  COMMAND
  ${CMAKE_OBJCPY} -O binary ${CMAKE_CURRENT_BINARY_DIR}/${EXECNAME} ${CMAKE_CURRENT_BINARY_DIR}/${EXECNAME}.bin
  )

add_dependencies(${EXECNAME}.bin ${EXECNAME})

このスクリプトはサブディレクトリに配置して Inc/ にヘッダーを Src/ に ソース、といくつかの記述に従えば、書き換えが必要になる箇所は数ヶ所になります。その数ヶ所も全体を関数化して引数で変えられるようにしても良さそうです。

覚えておきたい箇所、注意点

C++ としてコンパイルする時の C++ flagsの例

foreach(TEST_SOURCE ${TEST_SOURCES})
  set_property(SOURCE
    ${TEST_SOURCE} PROPERTY LANGUAGE CXX)
endforeach()

として C++ 用に flag をセット.

target_compile_options(${PROJ}-test
  ${C_FLAGS_OPTIONS}

  PRIVATE -fexceptions
  PRIVATE -fnon-call-exceptions
  PRIVATE -fno-common
  PRIVATE -fpermissive
  PRIVATE -fno-rtti
  PRIVATE -fno-use-cxa-atexit
  )

C としてコンパイルすると

cc1: warning: command line option '-fpermissive' is valid for C++/ObjC++ but not for C
cc1: warning: command line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
cc1: warning: command line option '-fno-use-cxa-atexit' is valid for C++/ObjC++ but not for C

となるので注意。

Specs file の指定について

標準テキストのアウトプットを確認してタスク挙動を見ているので rdimon.specs を使用しているがこれは release binary では使用しないほうが良い。リアルタイム性が求められるような用途では問題となることがある(というかあった)。

target_link_options(${PROJ}
  ${C_FLAGS_OPTIONS}
  PRIVATE -T${CMAKE_CURRENT_LIST_DIR}/STM32F405RGTx_FLASH.ld
  PRIVATE $<$<CONFIG:>:--specs=rdimon.specs>
  PRIVATE $<$<CONFIG:Debug>:--specs=rdimon.specs>
  PRIVATE $<$<CONFIG:Release>:--specs=nosys.specs>
  PRIVATE -Wl,--gc-sections
  )

CONFIG で切り替えるように変更した。

Heap 実装の選択方法

FindFreeRTOS(COMPILER GCC PROCESSOR ARM_CM4F HEAP heap_1)

引数の終わりに heap_1 を指定している。これは

${FREERTOS_ROOT}FreeRTOS/Source/portable/MemMang/heap_1.c

を指定している。

がもっと良い方法がある気がしている。気に入ってない

おわりに

検索すると stm32 向けに FindFreeRTOS 実装がいくつかありましたが、個人的な用途には向かなかったので新規に作成しました。

自分のツールを揃えた、という話でした。個人的には重宝しそうな気がしてます。