FreeRTOS as a CMake interface library

I’m wondering if it is worth redesigning the way FreeRTOS is being consumed by CMake projects. Currently, I’m only allowed to have a single configuration for the entire project. I have to specify the config library, port, and memory management strategy before I even call add_subdirectory() in CMake. If I have a multi-target project ( various targets with different cores ) and I want them to use the same RTOS files, my only option is to reconfigure the entire project for each case… Wouldn’t it be a much more “elastic” approach if the kernel, (each)port, and memory management files were simply interface libraries so each of CMake targets will only have to link the libraries they need, and each target will define its own compile options? Are there any constraints to that?

For example, I will be able to do something like this:

add_subdirectory(freertos)
target_link_libraries(MY_CM0_target PRIVATE freertos_kernel freertos_CM0_Port freertos_HEAP4 freertos_config_for_CM0_target )
target_link_libraries(MY_CM4_target PRIVATE freertos_kernel freertos_CM4_Port freertos_HEAP2 freertos_config_for_CM4_target)
etc...

Hi @eMKa94,
IMHO, the idea should work but your proposal may not work as your expectation. To build FreeRTOS files as library, you have to provide several customized files, such as port, config files, etc. Those customized files make it not possible to build single library for different targets. Instead, you still need to have multiple freertos libraries for different targets to include different customized files.

Thank you.

For example:

set( FREERTOS_KERNEL_COMMON_SOURCES
    # common source files )
set( FREERTOS_KERNEL_COMMON_INCLUDE_PATH
    # common include path )

# CM0
add_library( freertos_kernel_CM0 )
target_sources( freertos_kernel_CM0
    ${FREERTOS_KERNEL_COMMON_SOURCES}
    # other files, like memory management, port files, FreeRTOS config file )
target_include_directories( freertos_kernel_CM0
    ${FREERTOS_KERNEL_COMMON_INCLUDE_PATH}
    # other include paths
)

target_link_libraries(MY_CM0_target PRIVATE freertos_kernel_CM0 )

# CM4
add_library( freertos_kernel_CM4 )
target_sources( freertos_kernel_CM4
    ${FREERTOS_KERNEL_COMMON_SOURCES}
    # other files, like memory management, port files, FreeRTOS config file )
target_include_directories( freertos_kernel_CM4
    ${FREERTOS_KERNEL_COMMON_INCLUDE_PATH}
    # other include paths
)

target_link_libraries(MY_CM4_target PRIVATE freertos_kernel_CM4 )

@ActoryOu

I mannaged to do it the way i wanted.

I’m aware that i need multiple instances. The problem i wanted to solve is that with default CMake for FreeRTOS the configuration for particular port / heap is based on CMake variables which needs to be set before call to add_subdirectory(FreeRTOS). And also OBJECT library is being created so it is compiled once per configuration. In such case I cannot compile FreeRTOS sources for multiple targets in single CMake build process. I have to reconfigure whole project and build all code again for each target…

I’ve changed library type to INTERFACE for the kernel, and create a small ones for port, and heap libraries so they are not being compiled as separated libraries but become sources of target that consumes them as libraries.

target_sources(freertos_kernel INTERFACE
    croutine.c
    event_groups.c
    list.c
    queue.c
    stream_buffer.c
    tasks.c
    timers.c
)
add_library(freertos_kernel_mem_heap4 INTERFACE)
target_sources(freertos_kernel_mem_heap4 INTERFACE MemMang/heap_4.c)

add_library(freertos_kernel_port_CM0 INTERFACE)
target_sources(freertos_kernel_port_CM0 INTERFACE GCC/ARM_CM0/port.c GCC/ARM_CM0/portasm.c GCC/ARM_CM0/mpu_wrappers_v2_asm.c)
target_include_directories(freertos_kernel_port_CM0 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/GCC/ARM_CM0)

And now, when creating actual targets they can consume the set they need, for example:

target_link_libraries(CM0_based_target PRIVATE
freertos_kernel
freertos_kernel_port_CM0
freertos_kernel_mem_heap4
)

target_link_libraries(CM4_based_target PRIVATE
freertos_kernel
freertos_kernel_port_CM4
freertos_kernel_mem_heap2
)

As U can see now each my target is able to pick the correct heap / port sources and provide specific set of compile options. IMO much better usage then the original one.

2 Likes

Hi @eMKa94,
Actually I like the idea. I’ll bring it up with team internally and get back to you with our decision.

Thank you for bringing this to us!

We’ll update the build system. And we’ll take this feedback into account.
Again, we appreciate your input!

1 Like

The architecture described here now seems to be widely adopted. Eg. this generic example [1] adds the config header as an INTERFACE library. Now I’ve used it for a bit, I’m wondering about guidance on minimising the drawbacks.

Specifically, I find the architecture quickly becomes cumbersome if your config depends on your app.

Typically this would be the configASSERT() definition. Other possibilities are configCPU_CLOCK_HZ, but that can often be avoided by setting it equal to a variable that you declare extern, such as extern uint32_t SystemCoreClock; so the dependancy resolution is delayed until link time.

But in the case of configASSERT(), generally you define it to be something defined in your app. At that point, the CMakeLists.txt file needs to include your app’s include directories. But they’ll typically include the (STM32 or other) HAL, so the CMakeLists.txt needs to include those directories as well. But a HAL is typically configured with compiler defines, so then you need to include those as well! At some point, you end up manually recreating the CMake environment of the wider application, within the interface library of freertos_config.

I’ve hit this tip over point with libraries and CMake a couple of times now, and wondering if there’s a sensible way to improve the situation. Is there a simple way to inherit the project’s CMake environment within the library’s CMake environment, or is there a point were you abandon the library approach by removing add_library and changing all the freertos_config targets to ${CMAKE_PROJECT_NAME}. Or maybe one should make the dependancies “externable” somehow, with a dedicated header that satisfies the compiler but doesn’t require providing the actual definitions until link time?

[1] Link disabled because I’m a noob. But you can find it at github dot com /FreeRTOS/FreeRTOS/blob/5424d9d36a364ba9c73955c500d16773f543bb9c/FreeRTOS/Demo/Posix_GCC/CMakeLists.txt#L53

What you are describing is the pain that comes with the architecure of the FreeRTOS-Kernel and it is something I hope to address using forum posts exactly like this one. Ideally, the freertos_kernel library target could be built standalone without a dependency on port or some header config, but because FreeRTOS.h includes FreeRTOSConfig.h (and those configurations are injected into the kernel code through the inclusion of FreeRTOS.h), you can’t build the freertos kernel library unless FreeRTOSConfig.h exists, and the FreeRTOSConfig.h file must be provided by the application environment… similar story with ports and portmacro.h FreeRTOS.h includes portmacro.h, so you cannot build a standalone freertos kernel library without providing it…. so essentially in order to create a linkable FreeRTOS Kernel library target it needs to have both the FreeRTOSConfig.h and the port, which is annoying. Even more annoyingly, is you can’t even have the port code be standalone as port typically include FreeRTOS.h to be configured by FreeRTOSConfig.h. And as you point out, the FreeRTOSConfig.h itself may depend on headers or functions from the application code… its a circular dependency mess. I think the interface approach here is a good approach to handle this kind of stuff. But I think a real solution to this would be to eliminate circular dependencies of the architecture itself (although that is a very tall ask for 20+ year old RTOS that is widely adopted :))

For example, one could eliminate typedeffing needed kernel types like BaseType_t in portmacro.h using fast types from C99 (although doing so would mean that the kernel is no longer c89 compliant, which could cause customers using c89 compilers a headache).

Mmm, interesting, thank you.

I would suggest it’s not unique to FreeRTOS. Low level embedded libraries often run into the challenge of providing an interface for the application to satisfy. For example, CanOpenNodeSTM32, or the UWB DW3000 library, or FatFs. All require a “port” that provides some target specific definitions, so are inherently coupled.

The easiest solution is to just include the library as application source. At the other extreme is relying on function pointers, so the hook up is done at run time. It’s the vast middle that gets tricky - maintaining a standalone build while still permitting application specific definitions. I don’t really know what the pros and cons are here.