**IDE:**CLion
HOST: Windows 11
**MinGW:**x86_64-14.2.0-release-posix-seh-ucrt-rt_v12-rev0
**GCC:**arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi
示例工程 :https://github.com/ichliebedich-DaCapo/STM32F407VET6
一、链接过程
在单片机开发中,我希望尽可能开发"低耦合高内聚"的代码,我理想中的工程依赖关系如下图:
(后续还考虑加入FreeRTOS、DSP、FATS什么的)
基于上面目的,于是把工程分成各个模块,采用独立编译成静态库,最后再链接的方法。
对于HAL库的静态编译可以使用file() 先收集需要文件到变量DRIVERS_SRC 中,然后使用set() 设置需要包含的头文件目录DRIVERS_DIR ,之后就可以使用add_library() 添加要编译生成的库的名称和所需的资源文件,STATIC是属性,表示要编译的是静态库
再使用**target_include_directories()**添加库所需的头文件目录
最后可以使用属性ARCHIVE_OUTPUT_DIRECTORY 来专门指定静态库的生成目录
cpp# 官方驱动库 file(GLOB_RECURSE DRIVERS_SRC "Drivers/STM32F4xx_HAL_Driver/Src/*.c") set(DRIVERS_DIR Drivers Drivers/STM32F4xx_HAL_Driver/Inc Drivers/STM32F4xx_HAL_Driver/Inc/Legacy Drivers/CMSIS/Device/ST/STM32F4xx/Include Drivers/CMSIS/Include ) add_library(libdrivers STATIC ${DRIVERS_SRC}) target_include_directories(libdrivers PUBLIC ${DRIVERS_DIR}) # 设置静态库的输出目录 set_target_properties(libdrivers PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR})
库的名称也是有规范的,一般都是"lib+xxx"组成的,比如这次,编译的库用于驱动,所以起名为lib+drivers -> libdrivers。当然由于这是基于STM32提供的HAL,所以也可以起名为libhal
依此方法,编译BSP时,由于BSP依赖HAL库,所以需要链接HAL库(即libdrivers)。即下方中的target_link_libraries() 函数,使用这种方法自然也可以链接一些第三方库,比如SDL
cpptarget_link_libraries(libbsp PUBLIC libdrivers)
cpp# ------BSP库------ file(GLOB_RECURSE BSP_SRC "BSP/src/*.c") set(BSP_DIR BSP/inc ) add_library(libbsp STATIC ${BSP_SRC}) target_include_directories(libbsp PUBLIC ${BSP_DIR}) target_link_libraries(libbsp PUBLIC libdrivers) # 设置静态库的输出目录 set_target_properties(libbsp PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR})
根据上面的方法可以链接成不同的静态库,再使用**target_link_libraries()**把它们全部链接为可执行文件
cpp# 设置链接目标 add_executable(${PROJECT_NAME}.elf ${SOURCES} ${LINKER_SCRIPT}) target_link_libraries(${PROJECT_NAME}.elf PRIVATE libapp libdsp libdata libgui libbsp libmodule) # 设置可执行文件的输出目录 set_target_properties(${PROJECT_NAME}.elf PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR})
二、链接问题
1,链接缺失系统调用函数
只不过不是所有文件都能采用上面链接方法的,比如系统调用、启动文件等一同链接成一个库,此时会显示缺少各种定义,而这些缺失的定义却显然是系统调用里的函数
这个我也不晓得是什么原因,可能是单独链接这些系统调用文件成静态库时,还需要添加编译器所带的库如libgcc等。
总之把这些底层文件单独收集起来
cppfile(GLOB_RECURSE SOURCES "Core/*.*" "Application/Base/ISR.cpp")
然后添加到可执行目标中,其他地方不变
cppadd_executable(${PROJECT_NAME}.elf ${SOURCES} ${LINKER_SCRIPT})
最后再链接时就不会出现问题了
2,使用链接优化出现缺失定义的错误
如果像前面把各种库独立编译,那么在开启链接优化时,会出先下面的报错,总之就是各种找不到定义,连main都找不到
这个其实很好理解,使用-flto链接优化时,会把所有资源文件编译后再链接优化一下。那么问题就出现在了编译这块,因为LTO 无法跨静态库边界进行优化,可能导致一些符号无法正确解析。
所以需要把所有资源文件统一添加到可执行目标时,LTO 优化的范围涵盖了所有源文件,这样才能正确地处理跨文件的优化和符号解析。
当然这也与编译器和连接器有关,不同的编译器和链接器在处理 LTO 时可能有不同的行为。某些编译器在处理多个静态库时可能不够智能,无法正确处理跨库的优化。
虽然理论上可以使用-flto=full选项,但有的编译器没有这个选项
当然,还有一种解决办法,就是使用ar工具手动把各种静态库合并,比如下面把lib1.a、lib2.a、lib3.a都合并为liball.a 。
cpp# 合并静态库 add_custom_command( OUTPUT liball.a COMMAND ${CMAKE_AR} crs liball.a $<TARGET_FILE:lib1> $<TARGET_FILE:lib2> $<TARGET_FILE:lib3> DEPENDS lib1 lib2 lib3 )
对于此处用到的编译器,上面两种方法都不如直接添加文件到可执行目标中快捷省事
cppinclude_directories(Application/Conf ${APP_DIR} ${DRIVERS_DIR} ${LVGL_DIR} # ${FREERTOS_DIR} ${BSP_DIR} ${DATA_DIR} ${GUI_DIR} ${MODULE_DIR} ${APPLICATION_DIR} ) file(GLOB_RECURSE SOURCES "Core/*.*" "Application/Base/ISR.cpp" ${DRIVERS_SRC} ${LVGL_SRC} # ${FREERTOS_SRC} ${BSP_SRC} ${DATA_SRC} ${GUI_SRC} ${MODULE_SRC} ${APPLICATION_SRC} )