CMake进阶篇(一)——搭建stm32工程

前言:
   本篇文章探讨通过CMake搭建一个stm32工程。

1 下载

  本篇文章以STM32F10x芯片为例搭建CMake工程,现在芯片长官网下载标准库,以主流HAL库为例。登陆 STM32官网 ,在官网搜索框中输入 STM32CubeF1 来查找并下载完整的MCU固件包。这个压缩包内就包含了STM32F103ZE以及整个F1系列所有芯片的HAL库源码、示例项目和文档。

download.png

  解压下载的 stm32cubef1.zip 包,目录结构如下。

pkt.png

  搭建工程所需的文件就在 **stm32cubef1.zip里,或者也可以通过STM32Cube创建一个CMake工程的Demo,从Demo中获取搭建工程所需要的文件。

1.1 获取drv

  HAL库和CMSIS在 Drivers/STM32F1xx_HAL_Driver 路径下,将其拷贝到我们搭建的工程中,这里我放到 drv 目录中使用。CMSIS(Cortex Microcontroller Software Interface Standard)是ARM Cortex-M处理器的硬件抽象层,对于工程构建是必须的。

1.2 获取启动文件

  STM32F103ZE的启动文件为startup_stm32f103xe.s,在CMSIS中,路径为 CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc ,由于我们使用ARM GCC编译,所以要使用gcc目录下这个,而arm路径下的用于keil工程,iar路径下的用于AIR工程。我们将文件拷贝到自己工程的startup目录中。

1.3 获取ld

  stm32cubef1.zip包解压后,目录 Projects 内有官方例程,找到符合自己芯片型号的ld文件拷贝到工程中,这里我放到linker目录,如果没有找到,可以通过STM32Cube创建一个Demo,然后从Demo中获取。

1.4 获取core

  在stm32cubef1包的 stm32cubef1/STM32Cube_FW_F1_V1.8.0/Projects/STM32F103RB-Nucleo/Templates 目录提供了模板源码,将其中 SrcInc 拷贝自己工程中,这里我放到core目录中。

1.5 获取gcc-arm-none-eabi.cmake

  gcc-arm-none-eabi.cmake文件的作用是配置CMake的交叉编译环境,让CMake知道如何为ARM Cortex-M系列微控制器编译代码。文件名的含义如下。

  • gcc : 表示使用gcc编译,这里用arm gcc进行编译。
  • arm : 表示编译ARM架构,比如这里是STM32F103ZE Cortext-M3。
  • none : 目标操作系统,这里是裸机无操作系统,与其相对的有linux、rtos等。
  • eabi : 即Embedded Application Binary Interface,作用是定义嵌入式系统的二进制接口标准。

  该文件可以通过STM32Cube创建Demo获取。

2 编写CMake

  工程根目录如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
╰─ tree -L 2
.
├── cmake
│   └── gcc-arm-none-eabi.cmake
├── CMakeLists.txt
├── CMakeLists.txt.back
├── core
│   ├── inc
│   └── src
├── drv
│   ├── CMSIS
│   └── STM32F1xx_HAL_Driver
├── linker
│   └── STM32F103XX_FLASH.ld
└── startup
└── startup_stm32f103xe.s

  在工程根目录编写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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
cmake_minimum_required(VERSION 3.16)

# 使用C11标准编译C代码,必须支持C11,并且允许使用编译器的扩展功能。
set(CMAKE_C_STANDARD 11) # 指定使用C11标准。
set(CMAKE_C_STANDARD_REQUIRED ON) # 要求编译器必须支持`CMAKE_C_STANDARD` 指定的标准(这里是 C11)。
set(CMAKE_C_EXTENSIONS ON) # 允许使用编译器的GNU扩展(或 MSVC 等特定扩展)。

# 定义构建类型。
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()

set(CMAKE_PROJECT_NAME ord32) # 设置工程名宏。

set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) # 在构建过程中生成一个名为 `compile_commands.json` 的文件,这个文件记录了每个源文件(如 `.c`, `.cpp`)的完整编译命令(包括编译器路径、宏定义、包含路径、编译选项等)。

# 设置工具链
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/cmake/gcc-arm-none-eabi.cmake)

# 配置工程名,和支持语言,这里支持C和汇编ASM。
project(${CMAKE_PROJECT_NAME})
enable_language(C ASM)
# project(${CMAKE_PROJECT_NAME} LANGUAGES C ASM) # 和上面的配置等价。

add_executable(${CMAKE_PROJECT_NAME}) # 设置生成的可执行文件名,这里没有给出依赖的源文件列表。

# add_subdirectory(
# # Add subdirectory.
# )

# 链接目录设置。
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined library search paths.
)

# target_link_directories(<target> <visibility> directory1 directory2 ...)
# - `<target>`:目标名称,通常是通过 `add_executable()` 或 `add_library()` 创建的。
# - `<visibility>`:控制该链接目录如何被传递:
# - `PRIVATE`:仅用于当前目标,不对外暴露。
# - `INTERFACE`:仅对依赖此目标的其他目标可见(适用于库)。
# - `PUBLIC`:既用于自身,也传递给依赖者。

# 添加源文件。
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# Add user sources here.
startup/startup_stm32f103xe.s

# Core 源文件
core/src/main.c
core/src/stm32f1xx_it.c
core/src/system_stm32f1xx.c
core/src/stm32f1xx_hal_msp.c

# HAL 库核心文件
drv/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c
drv/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c
drv/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc.c
drv/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c
drv/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c
drv/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_exti.c
)

# 包含路径设置。
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined include paths.
core/inc

# CMSIS 头文件
drv/CMSIS/Include
drv/CMSIS/Device/ST/STM32F1xx/Include

# HAL 库头文件
drv/STM32F1xx_HAL_Driver/Inc
)

# 编译定义(宏定义)。
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined symbols.
STM32F103xE
USE_HAL_DRIVER
HSE_VALUE=8000000
)

# 添加链接库。
target_link_libraries(${CMAKE_PROJECT_NAME}
# Add user defined libraries.
m
c
nosys
)

  基于STM32Cube生成的gcc-arm-none-eabi.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
set(CMAKE_SYSTEM_NAME               Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER_ID GNU)
set(CMAKE_CXX_COMPILER_ID GNU)

# Some default GCC settings
# arm-none-eabi- must be part of path environment
set(TOOLCHAIN_PREFIX arm-none-eabi-)

set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc)
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy)
set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}size)

set(CMAKE_EXECUTABLE_SUFFIX_ASM ".elf")
set(CMAKE_EXECUTABLE_SUFFIX_C ".elf")
set(CMAKE_EXECUTABLE_SUFFIX_CXX ".elf")

set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

# MCU specific flags
set(TARGET_FLAGS "-mcpu=cortex-m3 ")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TARGET_FLAGS}")
set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp -MMD -MP")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fdata-sections -ffunction-sections")

set(CMAKE_C_FLAGS_DEBUG "-O0 -g3")
set(CMAKE_C_FLAGS_RELEASE "-Os -g0")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
set(CMAKE_CXX_FLAGS_RELEASE "-Os -g0")

set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fno-rtti -fno-exceptions -fno-threadsafe-statics")

set(CMAKE_EXE_LINKER_FLAGS "${TARGET_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T \"${CMAKE_SOURCE_DIR}/linker/STM32F103XX_FLASH.ld\"")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --specs=nano.specs")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Map=${CMAKE_PROJECT_NAME}.map -Wl,--gc-sections")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--print-memory-usage")
set(TOOLCHAIN_LINK_LIBRARIES "m")

3 编译

  执行以下指令在linux下构建和编译stm32工程,会在build目录生成elf文件。

1
2
3
4
mkdir build
cd build
cmake ..
make -j20

make.png

  GCC编译直接生成的编译产物为elf,而bin和hex文件是elf文件的衍生格式。一般调试时使用elf文件,其包含调试信息和符号表。

文件格式 生成方式 主要用途 包含内容
ELF 自动生成 调试、开发 代码+数据+调试信息+符号表
HEX 需要转换 烧录、生产 纯代码数据(Intel HEX格式)
BIN 需要转换 烧录、OTA升级 纯二进制代码

  要同时生成bin和hex文件,可以在根目录的cmake中添加如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
# ========== 在这里添加生成HEX/BIN文件的命令 ==========
# 生成HEX和BIN文件
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O ihex $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.hex
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.bin
COMMENT "Generating HEX and BIN files"
)

# 显示内存使用情况
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${CMAKE_PROJECT_NAME}>
COMMENT "Firmware size:"
)