0%

STM32 CMake 构建工程

参考资料

  1. STM32 CMake Project Template
  2. stm32l4_demo - skb666

为什么使用 CMake?

很早就想过用 CMake 来代替 eclipse 构建工程,优点是可以使用 VsCode 编译工程进行开发。另外还方便在 Jenkins 上使用脚本构建工程,目前使用 eclipse 构建工程,需要把生成的 Makefile 和多个 .arg 文件传 SVN,Jenkins 上通过 eclipse 生成的 Makefile 脚本编译工程,需要管控很多 Make 相关的文件,而且工程文件目录调整后,需要重新用 eclipse 生成一份 Makefile 供 Jenkins 使用。

遇到的问题

一开始使用 CMake 遇到很多问题,主要还是我在使用 Windows 开发环境,有些地方需要注意。我后来使用 WSL 使用CMake 的时候还是很顺利的,而且 Linux 中的包管理器很好用,准备开发环境非常方便,确实比 Windows 开发优秀很多。如果可以的话,我也想拥有一台电脑装 Linux 操作系统做日常软件开发。

现在改了 CMake 脚本,在 Windows 下也可以完成构建和编译了!,主要是加了 set(CMAKE_SYSTEM_NAME Generic)CMAKE_SYSTEM_NAME 变量用于指定项目的目标操作系统名称,”Generic” 通常表示你不是针对特定的操作系统进行构建,而是希望以更通用或跨平台的方式构建项目,这通常用于编写可以在多个不同平台上编译和运行的代码。否则链接时会出现以下报错:

1
2
3
4
5
6
7
[  2%] Linking C executable C:/Users/33110/Projects/stm32l4_demo/output/stm32l4_demo.elf.exe
c:/program files (x86)/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld.exe: unrecognized option '--major-image-version'
c:/program files (x86)/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld.exe: use the --help option for usage information
collect2.exe: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/stm32l4_demo.elf.dir/build.make:764:C:/Users/33110/Projects/stm32l4_demo/output/stm32l4_demo.elf.exe] 错误 1
make[1]: *** [CMakeFiles/Makefile2:83:CMakeFiles/stm32l4_demo.elf.dir/all] 错误 2
make: *** [Makefile:91:all] 错误 2

CMake 脚本

CMake 脚本分为两个,将不同平台编译工具链相关的抽离出来,在一份 CMake 工程可以编译出不同平台的结果。脚本内容如下:

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
cmake_minimum_required(VERSION 3.10)

# 定义工程名的变量
set(PROJECT_NAME "my_ytm32_prj")

project(${PROJECT_NAME} LANGUAGES C CXX)

# 更详细的编译信息
set(CMAKE_VERBOSE_MAKEFILE on)

MESSAGE(STATUS "PROJECT_NAME: " ${PROJECT_NAME})
MESSAGE(STATUS "CMAKE_TOOLCHAIN_FILE: " ${CMAKE_TOOLCHAIN_FILE})
MESSAGE(STATUS "MCU: " ${MCU})
MESSAGE(STATUS "LD_SCRIPT: " ${LD_SCRIPT})
MESSAGE(STATUS "ASM_SOURCES: " ${ASM_SOURCES})
MESSAGE(STATUS "MAP_FILE: " ${MAP_FILE})

# 添加编译参数
add_compile_options(-O2 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -fno-strict-aliasing)
add_link_options(-T${LD_SCRIPT} -Xlinker --gc-sections -Wl,-Map=${MAP_FILE})

# 递归调用子文件的 CMakeLists.txt
# add_subdirectory(lib)

# 汇编文件配置编译选项
set_property(SOURCE ${ASM_SOURCES} PROPERTY LANGUAGE C)
set_source_files_properties(${ASM_SOURCES} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -DSTART_FROM_FLASH")

# 使用file命令的GLOB_RECURSE选项递归搜索所有C文件
file(GLOB_RECURSE SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/*.c" "${CMAKE_SOURCE_DIR}/Project_Settings/Startup_Code/*.c")
# MESSAGE(STATUS "SOURCE_FILES: " ${SOURCE_FILES})

# 目标所需的源文件
add_executable(${PROJECT_NAME}.elf ${SOURCE_FILES} ${ASM_SOURCES})

# 目标所需宏定义
target_compile_definitions(${PROJECT_NAME}.elf PUBLIC
CPU_YTM32B1ME0
YTM32B1ME0
)

# 目标所需的库
target_link_libraries(${PROJECT_NAME}.elf PUBLIC m)

# 目标所需的头文件路径
target_include_directories(${PROJECT_NAME}.elf PUBLIC
"${CMAKE_SOURCE_DIR}/Project_Settings/Startup_Code"
"${CMAKE_SOURCE_DIR}/Project_Settings/Startup_Code/CMSIS/Core/Include"
"${CMAKE_SOURCE_DIR}/source/inc"
)

# 添加预编译目标
add_custom_target(
PRE_BUILD_DUMMY ALL
)
# 目标编译前自定义指令
add_custom_command(
TARGET PRE_BUILD_DUMMY PRE_BUILD
COMMAND ${CMAKE_SOURCE_DIR}/../Bin/generate_version.sh
WORKING_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}
)
# 将预编译步骤作为主目标依赖
add_dependencies(${PROJECT_NAME}.elf PRE_BUILD_DUMMY)

# 目标编译后自定义指令
add_custom_command(
TARGET ${PROJECT_NAME}.elf POST_BUILD
COMMAND ${CMAKE_SOURCE_DIR}/../Bin/BuildTools/PostBuild.bat ${PROJECT_NAME} ${TOOLCHAINS_PATH} ${TOOLCHAINS_PREFIX}
COMMAND ${TOOLCHAINS_PATH}/${TOOLCHAINS_PREFIX}objcopy -O binary -S ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${TOOLCHAINS_PATH}/${TOOLCHAINS_PREFIX}objcopy -O binary -S ${PROJECT_NAME}.elf ${PROJECT_NAME}.srec
COMMAND ${TOOLCHAINS_PATH}/${TOOLCHAINS_PREFIX}objcopy -O binary -S ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
WORKING_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}
)

toolchains.cmake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)

# 选择编译版本(可以通过 vscode 指定)
# set(CMAKE_BUILD_TYPE Release)
set(CMAKE_BUILD_TYPE Debug)

set(TOOLCHAINS_PATH "C:/Yuntu/YuntuIDE/tools/bin")
set(TOOLCHAINS_PREFIX "arm-none-eabi-")

# 交叉编译器(可以通过 vscode 指定)
set(CMAKE_C_COMPILER "${TOOLCHAINS_PATH}/${TOOLCHAINS_PREFIX}gcc.exe")
set(CMAKE_CXX_COMPILER "${TOOLCHAINS_PATH}/${TOOLCHAINS_PREFIX}g++.exe")

# 跳过编译器检查
# set(CMAKE_C_COMPILER_WORKS 1)
# set(CMAKE_CXX_COMPILER_WORKS 1)

# 生成目标的存放目录
set(CMAKE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY})
# 默认存放静态库的文件夹位置
# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}/archive)
# 默认存放动态库的文件夹位置
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}/library)

set(CPU "-mcpu=cortex-m33")
set(FPU "")
set(FLOAT-ABI "")
set(MCU "${CPU} -mthumb ${FPU} ${FLOAT-ABI}")

set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "${MCU}")
set(CMAKE_C_FLAGS_DEBUG "-g3")
set(CMAKE_C_FLAGS_RELEASE "")

# 如果CMAKE_CXX_STANDARD_REQUIRED设置为ON则必须使用CMAKE_CXX_STANDARD指定的版本
# 如果CMAKE_CXX_STANDARD_REQUIRED设置为OFF则CMAKE_CXX_STANDARD指定版本的为首选版本如果没有会使用上一版本
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${MCU}")
set(CMAKE_CXX_FLAGS_DEBUG "-g3")
set(CMAKE_CXX_FLAGS_RELEASE "")

set(LD_SCRIPT "${PROJECT_SOURCE_DIR}/Project_Settings/Linker_Files/flash.ld")
set(ASM_SOURCES "${PROJECT_SOURCE_DIR}/Project_Settings/Startup_Code/YTM32B1ME0_startup_gcc.S")
set(MAP_FILE "${CMAKE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.map")

set(CMAKE_EXE_LINKER_FLAGS "--specs=nosys.specs")

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

构建及编译指令

1
2
3
cd Project
cmake -G "Unix Makefiles" -S. -BDebug -DCMAKE_TOOLCHAIN_FILE=toolchains.cmake
cmake --build Debug --target all -- -j8

写cmake脚本时遇到的问题

  1. vscode生成cmake时需要加入指定工具链的命令-DCMAKE_TOOLCHAIN_FILE=toolchains.cmake,这个需在vscode cmake拓展设置中配置。
  2. vscode生成cmake可以选择是Debug还是Release,还可选择工具链,既然我通过上面一个方案传入工具链的cmake,vscode配置工具链成未指定就可以了。
  3. cmake生成时会有检查工具链,有些工具链可能会报错,gcc10.5需要加set(CMAKE_EXE_LINKER_FLAGS "--specs=nosys.specs")
  4. gcc4.9就不支持--specs=nosys.specs这个选项,跳过编译器检查需要加 set(CMAKE_C_COMPILER_WORKS 1) set(CMAKE_CXX_COMPILER_WORKS 1)
  5. 跨平台的编译需要加set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR ARM),否则会链接错误。
  6. 更详细的编译信息set(CMAKE_VERBOSE_MAKEFILE on)需要加在project(${PROJECT_NAME} LANGUAGES C CXX)后面,否则不起作用。
  7. set(CMAKE_SYSTEM_NAME Generic)需要加在project(${PROJECT_NAME} LANGUAGES C CXX)前面,否则不起作用,这类问题还没搞清楚为什么有的需要在project前有的需要在后,解决方法是由于未起作用试出来的。

待优化

  1. 现在链接顺序和 eclipse 编译出来的不一致,导致二进制不同(经测试,手动改变链接顺序,改成与 eclipse 一致,编译结果 bin 一致)。
  2. 没根据系统选择不同的cmake脚本,linux 下可能不兼容。