在嵌入式开发和跨平台项目中,CMake作为构建系统的生成器已经成为行业标准工具。它通过编写声明式的CMakeLists.txt文件来描述项目的构建规则,相比直接编写Makefile更加高效和可维护。对于硬件工程项目而言,合理的文件组织结构和CMake配置直接影响着编译效率、代码复用性和团队协作体验。
典型的硬件工程目录结构通常遵循"分离源码与构建产物"的原则。如图所示的项目结构中:
这种结构将不同功能模块物理隔离,有利于:
提示:在硬件工程中,建议将MCU外设驱动、中间件、应用逻辑分层存放,例如增加Drivers/、Middlewares/等目录,这与STM32CubeMX生成的项目结构理念一致。
每个CMake项目都应该以cmake_minimum_required和project命令开头:
cmake复制cmake_minimum_required(VERSION 3.5)
project(MyFirmware LANGUAGES C)
这里需要特别注意:
对于示例中的项目结构,添加源文件主要有两种规范做法:
方法一:显式列举文件
cmake复制add_executable(${PROJECT_NAME}.elf
src/main.c
MultiButton/multi_button.c
)
这种方法:
方法二:使用file命令自动收集
cmake复制file(GLOB_RECURSE SOURCES
"src/*.c"
"MultiButton/*.c"
)
add_executable(${PROJECT_NAME}.elf ${SOURCES})
这种方法:
经验分享:在硬件工程中,推荐使用方法一。因为嵌入式项目通常文件数量有限,且需要精确控制参与编译的源文件,避免意外引入未经验证的代码。
为了让编译器找到头文件,需要添加包含路径:
cmake复制target_include_directories(${PROJECT_NAME}.elf PRIVATE
src/
MultiButton/
)
关键参数说明:
在硬件工程中,经常需要根据目标平台进行条件编译:
cmake复制# 检查MCU系列
if(${MCU_FAMILY} STREQUAL "STM32F4")
add_definitions(-DUSE_STM32F4_HAL)
target_sources(${PROJECT_NAME}.elf PRIVATE
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c
)
elseif(${MCU_FAMILY} STREQUAL "STM32H7")
# H7系列的特殊配置
endif()
针对嵌入式系统的特殊编译选项:
cmake复制target_compile_options(${PROJECT_NAME}.elf PRIVATE
-Wall -Wextra # 开启警告
-Os # 优化尺寸
-ffunction-sections # 函数分段(配合链接脚本)
-fdata-sections # 数据分段
)
target_link_options(${PROJECT_NAME}.elf PRIVATE
-Wl,--gc-sections # 链接时垃圾回收
-Wl,-Map=${PROJECT_NAME}.map # 生成内存映射文件
)
对于大型项目,推荐采用模块化CMake设计:
code复制project/
├── CMakeLists.txt # 主配置
├── cmake/
│ └── Modules/ # 自定义模块
├── src/
└── Libraries/ # 第三方库
└── MultiButton/
├── CMakeLists.txt # 模块配置
├── multi_button.c
└── multi_button.h
模块级CMakeLists.txt示例:
cmake复制# Libraries/MultiButton/CMakeLists.txt
add_library(MultiButton STATIC
multi_button.c
)
target_include_directories(MultiButton PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
主CMakeLists.txt中引用模块:
cmake复制add_subdirectory(Libraries/MultiButton)
target_link_libraries(${PROJECT_NAME}.elf PRIVATE
MultiButton
)
典型错误:
code复制fatal error: multi_button.h: No such file or directory
解决方案:
cmake复制message(STATUS "Include paths: ${CMAKE_CURRENT_SOURCE_DIR}/MultiButton")
典型错误:
code复制undefined reference to `button_init'
可能原因:
处理Windows/Linux路径差异:
cmake复制# 统一使用正斜杠
set(SOURCES
"src/main.c"
"MultiButton/multi_button.c"
)
# 或者使用CMake路径命令
file(TO_CMAKE_PATH "path\\with\\backslashes" normalized_path)
当修改CMakeLists.txt后构建行为未更新时:
bash复制cmake -B build -S . --fresh
针对ARM Cortex-M系列的基本工具链配置:
cmake复制set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(TOOLCHAIN_PREFIX arm-none-eabi-)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc)
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_AR ${TOOLCHAIN_PREFIX}gcc-ar)
set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy)
将链接脚本(.ld)加入构建系统:
cmake复制target_link_options(${PROJECT_NAME}.elf PRIVATE
-T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld
-specs=nano.specs
-specs=nosys.specs
)
添加自定义目标生成烧录文件:
cmake复制add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O ihex ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMENT "Generating HEX and BIN files"
)
在实际项目中,我发现模块化的CMake配置虽然初期工作量较大,但当项目规模增长到数十个源文件、多个硬件平台支持时,这种结构的优势就会充分显现。特别是在团队协作环境下,各模块负责人只需维护自己的CMakeLists.txt,不会意外影响其他部分的构建流程。