基于CMake和STM32CubeMX编译FreeRTOS固件工程

写作背景

  实际上作者在工作中很少会用到FreeRTOS嵌入式实时操作系统做项目。但是,作为想深耕嵌入式系统开发的工程师来讲,只要是自己没有玩过的就想去玩一下,碰巧的是作者在项目中遇到一些问题,使用FreeRTOS可以很好解决。所以,就花了些时间学习和实战了FreeRTOS。这篇博文,作者想将其中一些内容分享给广大读者。
  作者充分利用了STM32CubeMX工具带来的开发便利,但放弃了其官方集成的FreeRTOS中间件,而是选择了自己移植。因为ST官方对FreeRTOS进行了自己的封装,这无疑微微加重了学习的负担,关键是这种学习意义不大,换家Vendor可能就要重新熟悉原始的FreeRTOS API。另外,目前FreeRTOS Kernel已经使用了CMake进行项目开发维护。

STM32CubeMX生成固件工程

  STM32微控制器之所以受到很多开发者的欢迎,我想完善的开发工具应该是原因之一。STM32CubeMX开发工具可以帮助开发者在几分钟时间内生成一个标准的固件工程框架。这里,在生成工程时候,Toolchain选择 Makefile。我们会在此工程框架基础上重新实现CMake自动化构建项目。基于CMake和STM32CubeMX编译固件工程 (完整固件工程)

CMakeLists.txt(标记2)

  生成STM32F103XX_SDK。


cmake_minimum_required(VERSION 3.20)
set(CMSIS_PATH ${CMAKE_CURRENT_LIST_DIR}/CMSIS)
set(PERIPHERAL_PATH ${CMAKE_CURRENT_LIST_DIR}/STM32F1xx_HAL_Driver)
# micro define
set(TARGET_C_DEFINES STM32F103xE USE_HAL_DRIVER)
# Peripheral Drivers
set(TARGET_C_SOURCES
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_cortex.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_dma.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_exti.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_flash_ex.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_flash.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_gpio_ex.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_gpio.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_pwr.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_rcc_ex.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_rcc.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_tim_ex.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_tim.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal_uart.c
    ${PERIPHERAL_PATH}/Src/stm32f1xx_hal.c
)
# Peripheral headers
set(TARGET_C_INCLUDES
    ${CORE_PATH}/Inc
    ${PERIPHERAL_PATH}/Inc
    ${PERIPHERAL_PATH}/Inc/Legacy
    ${CMSIS_PATH}/Device/ST/STM32F1xx/Include
    ${CMSIS_PATH}/Include
)
# start-up file
if (NOT TARGET_STARTUP_ASM)
    set(TARGET_STARTUP_ASM ${PROJECT_SOURCE_DIR}/startup_stm32f103xe.s)
endif ()
message(STATUS "Use startup asm: " ${TARGET_STARTUP_ASM})
# linker file
if (NOT TARGET_LD_SCRIPT)
    set(TARGET_LD_SCRIPT  ${PROJECT_SOURCE_DIR}/STM32F103ZETx_FLASH.ld)
endif ()
message(STATUS "Use LD script: " ${TARGET_LD_SCRIPT})
# generate the sdk library
add_library(STM32F103XX_SDK STATIC ${TARGET_C_SOURCES} ${TARGET_STARTUP_ASM})
# Shared sources, includes and definitions
target_compile_definitions(STM32F103XX_SDK PUBLIC ${TARGET_C_DEFINES})
target_include_directories(STM32F103XX_SDK PUBLIC ${TARGET_C_INCLUDES})
# it's equal to the standard library
target_link_libraries(STM32F103XX_SDK PUBLIC "c" "m" "nosys")
# linker configuration
target_link_options(STM32F103XX_SDK
        PUBLIC "-T${TARGET_LD_SCRIPT}"
        PUBLIC "-Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.map,--cref"
)

toolchain.cmake(标记3)

  工具链配置



set(TOOLCHAIN_PATH "C:/gcc-arm-none-eabi-10.3-2021.10")
set(TARGET_TRIPLET "arm-none-eabi")
# bare-metal platform
set(CMAKE_SYSTEM_NAME Generic)
# tool chain path
set(TOOLCHAIN_SYSROOT  "${TOOLCHAIN_PATH}")
set(TOOLCHAIN_BIN_PATH "${TOOLCHAIN_PATH}/bin") #binary install path.
set(TOOLCHAIN_LIB_PATH "${TOOLCHAIN_PATH}/lib")
# toolchain components
find_program(CMAKE_C_COMPILER NAMES ${TARGET_TRIPLET}-gcc HINTS ${TOOLCHAIN_BIN_PATH})
find_program(CMAKE_CXX_COMPILER NAMES ${TARGET_TRIPLET}-g++ HINTS ${TOOLCHAIN_BIN_PATH})
find_program(CMAKE_OBJCOPY NAMES ${TARGET_TRIPLET}-objcopy HINTS ${TOOLCHAIN_BIN_PATH})
find_program(CMAKE_OBJDUMP NAMES ${TARGET_TRIPLET}-objdump HINTS ${TOOLCHAIN_BIN_PATH})
find_program(CMAKE_SIZE NAMES ${TARGET_TRIPLET}-size HINTS ${TOOLCHAIN_BIN_PATH})
# function: print_size_of_target
function(print_size_of_target TARGET)
    add_custom_target(${TARGET}_always_display_size
        ALL COMMAND ${CMAKE_SIZE} "$<TARGET_FILE:${TARGET}>"
        COMMENT "Target Sizes: "
        DEPENDS ${TARGET}
        )
endfunction()
# function: _generate_file
function(_generate_file TARGET VERSION CURRENT_DATE OPT OUTPUT_EXTENSION OBJCOPY_BFD_OUTPUT)
    get_target_property(TARGET_OUTPUT_NAME ${TARGET} OUTPUT_NAME)
    if (TARGET_OUTPUT_NAME)
        set(OUTPUT_FILE_NAME "${TARGET_OUTPUT_NAME}_${VERSION}_${CURRENT_DATE}${OPT}.${OUTPUT_EXTENSION}")
    else()
        set(OUTPUT_FILE_NAME "${TARGET}_${VERSION}_${CURRENT_DATE}${OPT}.${OUTPUT_EXTENSION}")
    endif()
    get_target_property(RUNTIME_OUTPUT_DIRECTORY ${TARGET} RUNTIME_OUTPUT_DIRECTORY)
    if(RUNTIME_OUTPUT_DIRECTORY)
        set(OUTPUT_FILE_PATH "${RUNTIME_OUTPUT_DIRECTORY}/${OUTPUT_FILE_NAME}")
    else()
        set(OUTPUT_FILE_PATH "${OUTPUT_FILE_NAME}")
    endif()

    add_custom_command(
        TARGET ${TARGET}
        POST_BUILD
        COMMAND ${CMAKE_OBJCOPY} -O ${OBJCOPY_BFD_OUTPUT} "$<TARGET_FILE:${TARGET}>" ${OUTPUT_FILE_PATH}
        BYPRODUCTS ${OUTPUT_FILE_PATH}
        COMMENT "Generating ${OBJCOPY_BFD_OUTPUT} file ${OUTPUT_FILE_NAME}"
    )
endfunction()
# function: generate_binary_file
function(generate_binary_file TARGET)
    _generate_file(${TARGET} "${VERSION}" "${CURRENT_DATE}" "${OPT}" "bin" "binary")
endfunction()
# function: generate_hex_file
function(generate_hex_file TARGET)
    _generate_file(${TARGET} "${VERSION}" "${CURRENT_DATE}" "${OPT}" "hex" "ihex")
endfunction()
# executable file suffix
set(CMAKE_EXECUTABLE_SUFFIX_C   .elf)
set(CMAKE_EXECUTABLE_SUFFIX_CXX .elf)
set(CMAKE_EXECUTABLE_SUFFIX_ASM .elf)
# This should be safe to set for a bare-metal cross-compiler
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Extra CFlags
set(TARGET_CFLAGS_EXTRA   "-Wall -fdata-sections -ffunction-sections -fno-common -fmessage-length=0")
set(TARGET_CXXFLAGS_EXTRA "-Wall -fdata-sections -ffunction-sections -fno-common -fmessage-length=0")
set(TARGET_LDFLAGS_EXTRA  "-Wl,--print-memory-usage")
# Device specific settings, goes to CFLAGS and LDFLAGS
set(TARGET_CFLAGS_HARDWARE "-mcpu=cortex-m3 -mfloat-abi=soft -mthumb -mthumb-interwork")
# additional compiler options
# DEBUG
set(CMAKE_C_FLAGS_DEBUG     "-DDEBUG=0 -O0 -g")
set(CMAKE_CXX_FLAGS_DEBUG   "-DDEBUG=0 -O0 -g")
set(CMAKE_ASM_FLAGS_DEBUG   "-DDEBUG=0 -O0 -g")
# RELEASE
set(CMAKE_C_FLAGS_RELEASE        "-DNDEBUG -O3") #  -flto
set(CMAKE_CXX_FLAGS_RELEASE      "-DNDEBUG -O3") #  -flto
set(CMAKE_ASM_FLAGS_RELEASE      "-DNDEBUG -O3") #  -flto
# Final compiler flags
set(CMAKE_C_FLAGS          "${CMAKE_C_FLAGS} ${TARGET_CFLAGS_HARDWARE} ${TARGET_CFLAGS_EXTRA}")
set(CMAKE_CXX_FLAGS        "${CMAKE_CXX_FLAGS} ${TARGET_CFLAGS_HARDWARE} ${TARGET_CXXFLAGS_EXTRA}")
set(CMAKE_ASM_FLAGS        "${CMAKE_ASM_FLAGS} ${CMAKE_C_FLAGS} -x assembler-with-cpp")
set(CMAKE_EXE_LINKER_FLAGS "--specs=nano.specs --specs=nosys.specs -Wall -Wextra -Wl,--gc-sections ${TARGET_LDFLAGS_EXTRA}")

FreeRTOS介绍

  FreeRTOS作为一款开源免费的嵌入式实时操作系统,拥有广大的开发群体。所以,这里就不去介绍FreeRTOS有哪些开发优势了,直接实战吧。FreeRTOSv202406.04-LTS.zip,进入官网进行源码下载。下载后,解压缩,其它的不用管,直接拷贝FreeRTOS-LTSFreeRTOSFreeRTOS-Kernel内核目录。将其放置到固件工程目录下即可。
图片[1] - 基于CMake和STM32CubeMX编译FreeRTOS固件工程 - 鹿快
  不需要改动任何文件。FreeRTOS-Kernel目录下的CMakeLists.txt文件已经对Kernel源代码进行了很好的组织和构建。读者要做的是直接将FreeRTOS-Kernel集成到自己的项目固件工程。

FreeRTOS Kernel集成

  受益于跨平台构建工具CMake,使得集成FreeRTOS-Kernel变得简单高效。读者只需要在固件工程下添加一个CMakeLists.txt文件,按照FreeRTOS CMakeLists.txt提示内容和CMake规范,添加相关编译规则即可。

CMakeLists.txt(标记1)


cmake_minimum_required(VERSION 3.20)
include(toolchain.cmake)
# project name
set(PROJECT_NAME "FreeRTOS_Project")
project(FreeRTOS_Project LANGUAGES C CXX ASM)
# version && date updation
set(VERSION_MAJOR 0)
set(VERSION_MINOR 0)
set(VERSION_PATCH 1)
set(VERSION "V${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
# Options
set(OPT "")
set(CORE_PATH ${PROJECT_SOURCE_DIR}/Core)
# Use custom startup.S
set(TARGET_STARTUP_ASM ${PROJECT_SOURCE_DIR}/startup_stm32f103xe.s)
# Use custom linker script
set(TARGET_LD_SCRIPT ${PROJECT_SOURCE_DIR}/STM32F103ZETx_FLASH.ld)
# FreeRTOS
set(FREERTOS_HEAP "4" CACHE STRING "" FORCE)
set(FREERTOSCONFIG_PATH ${PROJECT_SOURCE_DIR}/Core/Inc)
set(FREERTOS_PORT "GCC_ARM_CM3" CACHE STRING "FreeRTOS port name")
add_library(freertos_config INTERFACE)
target_include_directories(freertos_config INTERFACE ${FREERTOSCONFIG_PATH})
target_compile_definitions(freertos_config INTERFACE projCOVERAGE_TEST=0)
# add SDK
add_subdirectory(Drivers)
# add FreeRTOS Kernel
add_subdirectory(FreeRTOS-Kernel)
# user code
set(TARGET_C_SRC 
    ${PROJECT_SOURCE_DIR}/Core/Src/main.c
    ${PROJECT_SOURCE_DIR}/Core/Src/gpio.c
    ${PROJECT_SOURCE_DIR}/Core/Src/usart.c
    ${PROJECT_SOURCE_DIR}/Core/Src/tim.c
    ${PROJECT_SOURCE_DIR}/Core/Src/stm32f1xx_hal_msp.c
    ${PROJECT_SOURCE_DIR}/Core/Src/stm32f1xx_it.c
    ${PROJECT_SOURCE_DIR}/Core/Src/system_stm32f1xx.c
    ${PROJECT_SOURCE_DIR}/Core/Src/stm32f1xx_hal_timebase_tim.c
    ${PROJECT_SOURCE_DIR}/Core/Src/freertos_api.c
)
# generate the .elf file
add_executable(${PROJECT_NAME} ${TARGET_C_SRC})
target_link_libraries(${PROJECT_NAME} STM32F103XX_SDK freertos_kernel)
# target property
set_property(TARGET STM32F103XX_SDK PROPERTY C_STANDARD 90)
set_property(TARGET freertos_kernel PROPERTY C_STANDARD 90)
# Generate .bin and .hex
generate_binary_file(${PROJECT_NAME})
generate_hex_file(${PROJECT_NAME})

  上面的CMakeLists.txt 就可以将FreeRTOS Kernel集成到自己的项目固件工程中。执行 ./build.bat输出可执行文件。

build.bat


@rd /S /Q build
mkdir build
cd build && cmake -G "Unix Makefiles" ../ && make -j4

  参考资源:基于CMake和STM32CubeMX编译FreeRTOS固件工程 (完整固件工程) 。

FreeRTOSConfig.h裁剪配置头文件

  集成FreeRTOS-Kernel后,读者需要学习一下FreeRTOS配置头文件FreeRTOSConfig.h,为方便修改查看,同时为了适配STM32CubeMX固件工程目录结构,作者把 FreeRTOSConfig.h 直接放在了固件工程的Core/Inc目录下面。 FreeRTOSConfig.h 头文件可以直接选择从官方提供的示例工程中拷贝。而关于FreeRTOSConfig.h头文件中的内容解释,读者可以借助于网络或着AI工具自行学习。


set(FREERTOSCONFIG_PATH ${PROJECT_SOURCE_DIR}/Core/Inc)

FreeRTOS API开发

  要想顺利的在基于STM32CubeMX工具自动生成的固件工程基础上集成FreeRTOS Kernel进行API的开发,有几处坑需要重点关注。

  1. FreeRTOS强制使用SysTick作为时钟基准。所以HAL库的时钟基准要换成其它的外设定时器。

  2. FreeRTOS定义了xPortPendSVHandlervPortSVCHandler,为了让FreeRTOS能够成功调度起来,需要将 Core/Src/stm32f1xx_it.c 中的 void SVC_Handler(void) {}void PendSV_Handler(void) {} 注释掉,并在FreeRTOSConfig.h头文件中添加以下内容:

FreeRTOSConfig.h


/* FreeRTOS ISR */
#define xPortPendSVHandler                  PendSV_Handler
#define vPortSVCHandler                     SVC_Handler

stm32f1xx_it.c


// void SVC_Handler(void)
// {
  /* USER CODE BEGIN SVCall_IRQn 0 */

  /* USER CODE END SVCall_IRQn 0 */
  /* USER CODE BEGIN SVCall_IRQn 1 */

  /* USER CODE END SVCall_IRQn 1 */
// }

// void PendSV_Handler(void)
// {
  /* USER CODE BEGIN PendSV_IRQn 0 */

  /* USER CODE END PendSV_IRQn 0 */
  /* USER CODE BEGIN PendSV_IRQn 1 */

  /* USER CODE END PendSV_IRQn 1 */
// }

  3. 需要在 void SysTick_Handler(void) {} 中调用FreeRTOS的API函数 xPortSysTickHandler()

stm32f1xx_it.c


extern void xPortSysTickHandler( void );
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
  if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) 
  {
    xPortSysTickHandler(); 
  }
  /* USER CODE END SysTick_IRQn 0 */

  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

学习参考

FreeRTOS移植(两种方法:CubeMX配置/自行移植)
CPU使用率统计

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
远房网友小张的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容