# FreeRTOS CMake porting guide A CMake listfile is required to list a newly qualified board on the FreeRTOS console. Please use the [`CMakeLists.txt`](../vendors/vendor/board/CMakeLists.txt) template to create a new CMake listfile file in the `/cmake/vendors//` directory (`` is the path to the root directory of FreeRTOS source code). **Note:** You need to understand how to write CMake build files for a project that has at least one executable target depending on one library target to follow this document. ## Table of contents - [CMake listfile Template](#cmake-listfile-template) - [FreeRTOS Console Metadata](#amazon-freertos-console-metadata) - [Compiler Settings](#compiler-settings) - [FreeRTOS Portable Layers](#amazon-freertos-portable-layers) - [Kernel porting target](#kernel-porting-target) - [Other modules](#other-modules) - [FreeRTOS Demos and Tests](#amazon-freertos-demos-and-tests) - [Build and Test FreeRTOS with CMake](#build-and-test-amazon-freertos-with-cmake) - [Setting up the toolchain file](#setting-up-the-toolchain-file) - [Build FreeRTOS](#build-amazon-freertos) - [Glossary](#glossary) ## CMake listfile Template The [`CMakeLists.txt`](../vendors/vendor/board/CMakeLists.txt) template file is divided into four sections: - [FreeRTOS Console Metadata](#amazon-freertos-console-metadata) - [Compiler Settings](#compiler-settings) - [FreeRTOS Portable Layers](#amazon-freertos-portable-layers) - [FreeRTOS Demos and Tests](#amazon-freertos-demos-and-tests) The `*.cmake` files located at `/cmake` provide 2 functions you need to use in your CMake listfile to define the required information for FreeRTOS: ```cmake # This function is used to define FreeRTOS Console metadata. afr_set_board_metadata( ) # This function is used to define the portable layer target associated with an # FreeRTOS module, e.g., secure sockets or WiFi. It will create a CMake # global INTERFACE IMPORTED target with a name AFR:::mcu_port. # If `DEPENDS` is used, additional targets will be linked via target_link_libraries. afr_mcu_port( [ [targets...]]) ``` ### FreeRTOS Console Metadata The first section of the template file defines the metadata that is required to display a board's data in the FreeRTOS console. Use the function `afr_set_board_metadata( )` to define each field listed in the template: ```cmake afr_set_board_metadata(ID "board") afr_set_board_metadata(DISPLAY_NAME "Board") afr_set_board_metadata(DESCRIPTION "Template Board for AmazonFreeRTOS") afr_set_board_metadata(VENDOR_NAME "Vendor") afr_set_board_metadata(FAMILY_NAME "Family") afr_set_board_metadata(DATA_RAM_MEMORY "0") afr_set_board_metadata(PROGRAM_MEMORY "0") afr_set_board_metadata(CODE_SIGNER "") afr_set_board_metadata(SUPPORTED_IDE "") afr_set_board_metadata(IDE__NAME "") afr_set_board_metadata(IDE__COMPILERS "") ``` Define each field according to the following descriptions: - `ID` - ID for the board to uniquely identify it. - `DISPLAY_NAME` - Name of the board displayed on the FreeRTOS Console. - `DESCRIPTION` - Short description of the board on the FreeRTOS Console. - `VENDOR_NAME` - Name of the vendor for the board. - `FAMILY_NAME` - Family of the board. - `DATA_RAM_MEMORY` - RAM size in suffixed with units (for example: KB). - `PROGRAM_MEMORY` - Program memory size suffixed with units (for example: MB). - `CODE_SIGNER` - Code signing platform used for OTA updates. Please Use AmazonFreeRTOS-Default for SHA256 Hash Algorithm and ECDSA encryption algorithm. For any other code signing please contact us. - `SUPPORTED_IDE` - Semicolon separated IDs of IDEs supported. - `IDE__NAME` - Name of the IDE. Please replace with IDE ID described above. - `IDE__COMPILERS` - Names of compilers supported for the corresponding IDE. Please replace with IDE ID described above. ### Compiler Settings The second section of the template file defines the compiler settings. We require you to use `afr_mcu_port` with a special name `compiler` to create a target `AFR::compiler::mcu_port` which holds the compiler settings information. Under the hood, the kernel will publicly link to this `INTERFACE` target so that the compiler settings are transitively populated to all modules. Please use standard CMake built-in functions to provide compiler settings. You can find an example for the TI CC3220 Launchpad in `/cmake/vendors/ti/cc3220_launchpad/CMakeLists.txt`. Here are some best practices to follow as you define the compiler settings: - Use `target_compile_definitions` to provide compile definitions/macros. - Use `target_compile_options` to provide compiler flags. - Use `target_include_directories` to provide include directories. - Use `target_link_options` to provide linker flags. - Use `target_link_directories` to provide linker search directories. - Use `target_link_libraries` to provide libraries to link against. **Note:** If you have defined the compiler settings information somewhere else (for example, if you're using CMake for your own code), then you don't need to duplicate the information in the compiler settings section of this CMake file. Simply call `afr_mcu_port` with `DEPENDS` to bring in the target definition from another location: ```cmake # is defined somewhere else, does not have to be in the same file. afr_mcu_port(compiler DEPENDS ) ``` When you call `afr_mcu_port` with `DEPENDS`, it will call `target_link_libraries(AFR::::mcu_port INTERFACE )` for you so that your compiler settings are populated to the required `AFR::compiler::mcu_port` target. If you support multiple compilers, you can use the `AFR_TOOLCHAIN` variable to dynamically select the compiler settings. This variable is set to the name of the compiler that you are using, which should be same as the toolchain file name found under `/cmake/toolchains`). For example, ```cmake if("${AFR_TOOLCHAIN}" STREQUAL "arm-gcc") afr_mcu_port(compiler DEPENDS my_gcc_settings). elseif("${AFR_TOOLCHAIN}" STREQUAL "arm-iar") afr_mcu_port(compiler DEPENDS my_iar_settings). else() message(FATAL_ERROR "Compiler ${AFR_TOOLCHAIN} not supported.") endif() ``` If you need more advanced control over your compiler settings to do things like set compiler flags based on programming language, or to change settings for release and debug configurations, you can use [CMake generator expressions]( https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html). These expressions are not evaluated at the configuration stage when CMake is reading all the listfiles, but at the generation stage when CMake finishes reading listfiles and is generating build files for the target build system. Here's an example, ```cmake set(common_flags "-foo") set(c_flags "-foo-c") set(asm_flags "-foo-asm") target_compile_options( my_compiler_settings INTERFACE $<$:${common_flags} ${c_flags}> # This only have effect on C files. $<$:${common_flags} ${asm_flags}> # This only have effect on ASM files. ) ``` ### FreeRTOS Portable Layers The third section of the template file defines all of the portable layer targets for FreeRTOS modules. **The only requirement is to use the `afr_mcu_port()` function to define portable layer target for every FreeRTOS module you're planning to implement.** You're free to use any CMake functions as long as the target `AFR::::mcu_port` created from `afr_mcu_port` provides the information we need to build the corresponding FreeRTOS module. The `afr_mcu_port` function creates a [GLOBAL INTERFACE IMPORTED library target]( https://cmake.org/cmake/help/latest/command/add_library.html?#interface-libraries) with the name `AFR::::mcu_port`. `GLOBAL` allows us to reference the target in our CMake listfiles. `INTERFACE` specifies that it should not be built as a standalone target/library, and will be compiled into the corresponding FreeRTOS module. And `IMPORTED` allows us to use namespace (double colon) in the target name like `AFR::kernel::mcu_port`. All modules without corresponding portable layer targets are disabled by default. If you run CMake to configure FreeRTOS without defining any portable layer target, you should see the following output: ```txt FreeRTOS modules: Modules to build: Disabled by user: Disabled by dependency: kernel, posix, pkcs11, secure_sockets, mqtt, ... Available demos: Available tests: ``` As you update the `CMakeLists.txt` file with porting layer targets, corresponding FreeRTOS modules wll be enabled. You should also be able to build any FreeRTOS module whose dependency requirements are satisfied. For example, if MQTT is enabled, Shadow will also be enabled since it only depends on MQTT. **Note:** The kernel dependency is a minimum requirement. The CMake configuration will fail if the kernel dependency is not satisfied. #### Kernel porting target The kernel target depends on FreeRTOS, your driver code and compiler settings. Compiler settings are already covered in the second section. We require you to use `afr_mcu_port` with the module name `kernel` to create the required portable layer target `AFR::kernel::mcu_port`, and provides the rest dependency information, FreeRTOS portable layer and driver code information, to this target. Here's a simple example, **Create a `STATIC` library target for the driver code:** ```cmake add_library(my_board_driver STATIC ${driver_sources}) # Use your compiler settings target_link_libraries( my_board_driver PRIVATE AFR::compiler::mcu_port # Or use your own target if you already have it. # PRIVATE ${compiler_settings_target} ) target_include_directories( my_board_driver PRIVATE "" PUBLIC "" ) ``` Or an `INTERFACE` library target: ```cmake # No need to specify compiler settings since kernel target has them. add_library(my_board_driver INTERFACE ${driver_sources}) ``` **Note:** `INTERFACE` library target does not have a build output, in this case the driver code will be compiled into the kernel library. **Configure the FreeRTOS portable layer:** ```cmake add_library(freertos_port INTERFACE) target_sources( freertos_port INTERFACE "${AFR_MODULES_DIR}/FreeRTOS/portable/GCC/ARM_CM4F/port.c" "${AFR_MODULES_DIR}/FreeRTOS/portable/GCC/ARM_CM4F/portmacro.h" "${AFR_MODULES_DIR}/FreeRTOS/portable/MemMang/heap_4.c" ) target_include_directories( freertos_port INTERFACE "${AFR_MODULES_DIR}/FreeRTOS/portable/GCC/ARM_CM4F" "${include_path_to_FreeRTOSConfig_h} ) ``` **Note:** This FreeRTOS portable layer target is an example and not required. You can optionally specify these source files and include directories directly in `AFR::kernel::mcu_port`. **Create the kernel portable layer target:** ```cmake # Bring in driver code and freertos portable layer dependency. afr_mcu_port(kernel DEPENDS my_board_driver freertos_port) # If you need to specify additional configurations, use standard CMake functions with # AFR::kernel::mcu_port as the target name. target_include_directories( AFR::kernel::mcu_port INTERFACE "${additional_includes}" # e.g. board configuration files ) target_link_libraries( AFR::kernel::mcu_port INTERFACE "${additional_dependencies}" ) ``` After the kernel portable layer is configured, you can compile the kernel as a static library. To test the configuration, you can write a simple hello world application to consume it and make the LED lights on your board blink. More details on how to build and test are covered in [Build and Test FreeRTOS with CMake](#build-and-test-amazon-freertos-with-cmake). ```cmake add_executable( my_led_demo main.c ) target_link_libraries( my_led_demo PRIVATE AFR::kernel ) ``` #### Other modules After you add the portable layer target for the kernel, you can add the portable layer for other FreeRTOS modules. Here's a simple example for Wi-Fi: ```cmake afr_mcu_port(wifi) target_sources( AFR::wifi::mcu_port INTERFACE "${AFR_MODULES_DIR}/wifi/portable///iot_wifi.c" ) ``` This example portable layer only has one implementation file that is based on your driver code. **Note:** All non-kernel FreeRTOS modules implicitly depend on the kernel. Their porting layers don't require you to explicitly specify the kernel as a dependency. POSIX, on the other hand, is defined as an optional kernel module, you need to explicitly include it in your kernel portable layer if you want to use it. For example: ```cmake # By default, AFR::posix target does not expose standard POSIX headers in its public # interface, i.e., you need to use "FreeRTOS_POSIX/pthread.h" instead of "pthread.h". # Link to AFR::use_posix instead if you need to use those headers directly. target_link_libraries( AFR::kernel::mcu_port INTERFACE AFR::use_posix ) ``` Take Secure Sockets module as another example. This module depends on TLS, making the portable layer target for this module slightly more complicated than Wi-Fi. FreeRTOS provides a default TLS implementation based on mbedtls: ```cmake afr_mcu_port(secure_sockets) target_sources( AFR::secure_sockets::mcu_port INTERFACE ${portable_layer_sources} ) target_link_libraries( AFR::secure_sockets::mcu_port AFR::tls ) ``` If your hardware is able to handle TLS by itself, you can just use your driver code directly. And since all FreeRTOS modules implicitly depend on kernel which includes your driver code, you don't need to call `target_link_libraries` in this case. Note that we use the standard CMake function `target_link_libraries` to state that the portable layer depends on `AFR::tls` in the example above. You can reference all FreeRTOS modules by using their target name `AFR::`. For example, you can use the same syntax to also depend on FreeRTOS-Plus-TCP: ```cmake target_link_libraries( AFR::secure_sockets::mcu_port AFR::freertos_plus_tcp AFR::tls ) ``` ### FreeRTOS Demos and Tests This section defines the demo and test targets for FreeRTOS. CMake targets will be created automatically for each demo and test that satisfy the dependency requirements. You need to define an executable target with `add_executable` and use `aws_tests` or `aws_demos` as the target name depending on whether you're compiling tests or not. You may also need to provide additional project settings such as linker scripts and post build commands. We will call `target_link_libraries` to link available CMake demo or test targets to your executable target. **Note:** To enable certain demos and tests you still need to modify `demos/common/demo_runner/aws_demo_runner.c` and `tests/common/test_runner/aws_test_runner.c`. ```cmake if(AFR_IS_TESTING) set(exe_target aws_tests) else() set(exe_target aws_demos) endif() set(CMAKE_EXECUTABLE_SUFFIX ".elf") add_executable(${exe_target} "${board_dir}/application_code/main.c") ``` For information about how to run post build commands, please check [add_custom_command]( https://cmake.org/cmake/help/latest/command/add_custom_command.html). You need to use the second signature, for example: ```cmake # This should run an external command "command --arg1 --arg2". add_custom_command( TARGET ${exe_target} POST_BUILD COMMAND "command" "--arg1" "--arg2" ) ``` **Note:** CMake itself supports many common operations in a platform independent way, like creating directories, copying files, etc. Check the reference [here]( https://cmake.org/cmake/help/latest/manual/cmake.1.html#command-line-tool-mode) for more information. You can reference CMake itself with the built-in variable `${CMAKE_COMMAND}`. ## Build and Test FreeRTOS with CMake ### Setting up the toolchain file By default, CMake targets your host OS as the target system. To use CMake for cross-compiling instead, you need to provide a toolchain file that specifies the compiler you want to use. Some examples can be found in `/cmake/toolchains`. If you're using a different compiler than what we already provide, you need to write this toolchain file first. ### Build FreeRTOS Follow this [README.md](../README.md) under the cmake folder. Make sure your directory structure is correct, it is required that you put the `CMakeLists.txt` file and the toolchain file under the expected location: ```txt ROOT_DIR |-- cmake | |-- vendors | | `-- | | `-- | | `-- CMakeLists.txt [REQUIRED] | `-- toolchains | `-- .cmake [REQUIRED] | |-- lib |-- demos |-- tests `-- CMakeLists.txt ``` ## Glossary - `FreeRTOS module`: A library from FreeRTOS, e.g., Secure Sockets, MQTT and OTA. Note that FreeRTOS itself is also an independent module and it's part of the kernel module. The configuration of each module still has to be done through their config files as of now, e.g., `FreeRTOSConfig.h`, `iot_mqtt_config.h`. - `Kernel`: A special FreeRTOS module that consist of FreeRTOS, portable layer and the optional POSIX modules. Modules within the kernel can depends on each other but not from modules outside the kernel. Regular modules outside kernel implicitly depend on the kernel module. - `Portable layer`: An implementation of an FreeRTOS module that requires porting from vendors. For example, OTA have a portable layer that vendor needs to implement while MQTT does not. - `Target`: CMake target, usually created via `add_executable` and `add_library`.