GCC + Vscode 搭建 STM32 开发环境(二) 您所在的位置:网站首页 stm32底层代码 GCC + Vscode 搭建 STM32 开发环境(二)

GCC + Vscode 搭建 STM32 开发环境(二)

2023-04-15 15:49| 来源: 网络整理| 查看: 265

Cmake 管理工程灵活性很高,且 Cmake 官方文档并没有提供一个完整的模板教用户如何去较好的组织一个项目。 结合工程实践,我整理出了一套自己的使用方法。在我的项目里面,一共有三类 Cmake 文件:

公共的 *.cmake,这部分主要提供了编译器及其参数、处理器等信息的描述; 模块的 CmakeList.txt,用来描述项目里会引用不同的模块(自己创建的或应用第三方的库); 工程的 CmakeList.txt,该文件指定了具体的编译规则,并最终生成可执行文件;这个文件会引用 1、2 两个文件;1. 公共的 *.cmake

这部分的文件后缀是 cmake,主要提供在使用 Cmake 管理工程时的共用部分。

这里面包含了两类文件:编译器说明文件和内核说明文件。

1.1 编译器说明

这个文件说明了在编译工程时使用的编译套件以及编译参数,具体可阅读代码的注释。

代码清单:arm-none-eabi.cmake

# 编译工具链; # 请确保已经添加到环境变量; # 如果使用的是 linux 环境,需要将后面的 '.exe' 移除; SET(CMAKE_C_COMPILER "arm-none-eabi-gcc.exe") SET(CMAKE_CXX_COMPILER "arm-none-eabi-g++.exe") SET(AS "arm-none-eabi-as.exe") SET(AR "arm-none-eabi-ar.exe") SET(OBJCOPY "arm-none-eabi-objcopy.exe") SET(OBJDUMP "arm-none-eabi-objdump.exe") SET(SIZE "arm-none-eabi-size.exe") # 使用的 C 语言版本; SET(CMAKE_C_STANDARD 99) # 使用的 cpp 版本; SET(CMAKE_CXX_STANDARD 17) # 生成 compile_commands.json,可配合 clangd 实现精准的代码关联与跳转; SET(CMAKE_EXPORT_COMPILE_COMMANDS True) # 彩色日志输出; SET(CMAKE_COLOR_DIAGNOSTICS True) # 路径查找; 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) # this makes the test compiles use static library option so that we don't need to pre-set linker flags and scripts SET(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # 包含gcc头文件路径 SET(SYSTEM_PATH "-isystem C:/~Arm_Development_Toolchains/gcc-arm-none-eabi-10.3-2021.10/arm-none-eabi/include") # 定义通用编译器参数; # ${MCPU_FLAGS} 处理器内核信息 # ${VFP_FLAGS} 浮点运算单元类型 # ${SYSTEM_PATH} 编译器头文件路径 SET(CFCOMMON "${MCPU_FLAGS} ${VFP_FLAGS} ${SYSTEM_PATH} --specs=nano.specs -specs=rdimon.specs --specs=nosys.specs -Wall -fmessage-length=0 -ffunction-sections -fdata-sections" ) # 定义最快运行速度发行模式的编译参数; SET(CMAKE_C_FLAGS_RELEASE "-Os ${CFCOMMON}") SET(CMAKE_CXX_FLAGS_RELEASE "-Os ${CFCOMMON} -fno-exceptions") SET(CMAKE_ASM_FLAGS_RELEASE "${MCPU_FLAGS} ${VFP_FLAGS} -x assembler-with-cpp") # 定义最小尺寸且包含调试信息的编译参数; SET(CMAKE_C_FLAGS_RELWITHDEBINFO "-Os -g ${CFCOMMON}") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-Os -g ${CFCOMMON} -fno-exceptions") SET(CMAKE_ASM_FLAGS_RELWITHDEBINFO "${MCPU_FLAGS} ${VFP_FLAGS} -x assembler-with-cpp") # 定义最小尺寸的编译参数; SET(CMAKE_C_FLAGS_MINSIZEREL "-Os ${CFCOMMON}") SET(CMAKE_CXX_FLAGS_MINSIZEREL "-Os ${CFCOMMON} -fno-exceptions") SET(CMAKE_ASM_FLAGS_MINSIZEREL "${MCPU_FLAGS} ${VFP_FLAGS} -x assembler-with-cpp") # 定义调试模式编译参数; SET(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CFCOMMON}") SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g ${CFCOMMON} -fno-exceptions") SET(CMAKE_ASM_FLAGS_DEBUG "${MCPU_FLAGS} ${VFP_FLAGS} -x assembler-with-cpp") IF("${CMAKE_BUILD_TYPE}" STREQUAL "Release") MESSAGE(STATUS "**** Maximum optimization for speed ****") ELSEIF("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") MESSAGE(STATUS "**** Maximum optimization for size, debug info included ****") ELSEIF("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") MESSAGE(STATUS "**** Maximum optimization for size ****") ELSE() # "Debug" MESSAGE(STATUS "**** No optimization, debug info included ****") ENDIF()1.2 处理器内核说明

该文件描述了当前工程使用的处理器的内核信息,如内核版本、指令集类型、浮点运算单元类型等等。

代码清单:cortex_m4.cmake

SET(CMAKE_SYSTEM_NAME Generic) SET(CMAKE_SYSTEM_PROCESSOR cortex-m4) SET(THREADX_ARCH "cortex_m4") SET(THREADX_TOOLCHAIN "gnu") ADD_DEFINITIONS(-DARM_MATH_CM4 -DARM_MATH_MATRIX_CHECK -DARM_MATH_ROUNDING -D__FPU_PRESENT=1) SET(MCPU_FLAGS "-mthumb -mcpu=cortex-m4") SET(VFP_FLAGS "-mfloat-abi=soft") MESSAGE(STATUS "**** Platform: ${MCPU_FLAGS} ${VFP_FLAGS} ****") INCLUDE(${CMAKE_CURRENT_LIST_DIR}/arm-none-eabi.cmake)

代码清单:cortex_m0.cmake

SET(CMAKE_SYSTEM_NAME Generic) SET(CMAKE_SYSTEM_PROCESSOR cortex-m0) SET(THREADX_ARCH "cortex_m0") SET(THREADX_TOOLCHAIN "gnu") SET(MCPU_FLAGS "-mcpu=cortex-m0 -mthumb") SET(VFP_FLAGS "") MESSAGE(STATUS "**** Platform: ${MCPU_FLAGS} ${VFP_FLAGS} ${FLOAT_ABI} ****") INCLUDE(${CMAKE_CURRENT_LIST_DIR}/arm-none-eabi.cmake)

其他内核的写法依葫芦画瓢就可以了。

值得注意的是 INCLUDE(${CMAKE_CURRENT_LIST_DIR}/arm-none-eabi.cmake) 这句话,这里是引用了第 1.1 小节提到的编译器描述文件。这有点类似 c 语言的 #include “xxxxx.h”。

而工程的 Cmake 文件又会引用处理器的描述文件。通过这种层层引用的操作,会把构建的必要参数传递给构建过程。

2. 模块的 CmakeList.txt 文件

模块的 CmakeList.txt 用来组织功能模块的编译规则。这里以一个 LED 指示灯的驱动模块为例说明。 模块文件夹内容如图, ![[Cmake LED指示灯模块目录结构.png]] drv_led.c 和 drv_led.h 为具体的源代码,这里不展开。CmakeLists.txt 就是该模块的规则描述文件了,文件内容及说明如下。

代码清单:CmakeLists.txt

# 要连接到构建目标的源文件; TARGET_SOURCES( ${PROJECT_NAME} PRIVATE # {{BEGIN_TARGET_SOURCES}} ${CMAKE_CURRENT_LIST_DIR}/drv_led.c # {{END_TARGET_SOURCES}} ) # 将模块头文件路径添加到目标; TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})

其中提到的 构建目标 是由工程的 CmakeLists.txt 指定的,在这里我们使用变量替代,在构建过程中会用实际的目标名称替换该变量。 在组织工程的时候,将需要的模块的子目录添加到工程的 CmakeLists.txt 中便可以完成对该模块的调用。这类似于 Keil 或 IAR 中工程右键添加文件或目录,只不过他们在后台帮你完成了构建脚本的修改。

3. 工程的 CmakeList.txt 文件

工程的 CmakeList.txt 是整个项目的编译入口,主要: - 指定工程名称(构建目标名称); - 构建规则声明; - 依赖管理; - 预定义宏; - ....

代码清单:CmakeList.txt

# ###################################################################################################################### # 0、硬件平台信息与编译器信息; # ###################################################################################################################### SET(PATH_WORKSPACE_ROOT ${CMAKE_SOURCE_DIR}/../../..) INCLUDE("${PATH_WORKSPACE_ROOT}/components/toolchain/cmake/cortex_m4f.cmake") # ###################################################################################################################### # 1、工程信息 # ###################################################################################################################### # 设置CMAKE最低版本 CMAKE_MINIMUM_REQUIRED(VERSION 3.20) # 设置当前的工程名称 PROJECT( demo VERSION 0.0.1 LANGUAGES C CXX ASM) MESSAGE(STATUS "**** Building project: ${CMAKE_PROJECT_NAME}, Version: ${CMAKE_PROJECT_VERSION} ****") # 指定链接文件; SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/stm32f429bit_flash.ld) # 指定启动文件; SET(STARTUP_ASM ${PATH_WORKSPACE_ROOT}/components/cmsis/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f429xx.S) # 项目底层公共头文件; INCLUDE_DIRECTORIES(${PATH_WORKSPACE_ROOT}/include) # ###################################################################################################################### # 2、编译控制; # ###################################################################################################################### # 是否开启更详细的编译过程信息显示 SET(CMAKE_VERBOSE_MAKEFILE OFF) # ###################################################################################################################### # 3、预定义宏; # ###################################################################################################################### # 平台相关宏定义 ADD_DEFINITIONS( -DUSE_STDPERIPH_DRIVER -DSTM32 -DSTM32F429_439xx -DHSE_VALUE=8000000 -DCMAKE_DEMO -DUSING_NON_RTOS=0 # 不使用 RTOS -DUSING_FREERTOS=1 # 使用 FreeRTOS -DUSING_THREADX=2 # 使用 Threadx -DUSING_RTOS=USING_NON_RTOS # 选择使用的 RTOS ) # ###################################################################################################################### # 4、差异化构建配置; # ###################################################################################################################### OPTION(OPEN_LOG_OMN_DEBUG "Open log output for debug" OFF) # 修改该变量的值,可以修改输出文件的名称; SET(OUTPUT_EXE_NAME "demo") # 优化级别的差异配置 # ###################################################################################################################### IF("${CMAKE_BUILD_TYPE}" STREQUAL "Release") ADD_DEFINITIONS() ELSEIF("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") ADD_DEFINITIONS() ELSEIF("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") ADD_DEFINITIONS() ELSE() IF(OPEN_LOG_OMN_DEBUG) ADD_DEFINITIONS(-DLOG_BACKEND=LOG_BACKEND_NONE) ELSE() ADD_DEFINITIONS(-DLOG_BACKEND=LOG_BACKEND_NONE) ENDIF() ENDIF() MESSAGE(STATUS "**** Build for ${CMAKE_BUILD_TYPE} ****") # ###################################################################################################################### # 5、设置文件输出路径; # ###################################################################################################################### # 设置库输出路径 SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib_obj) SET(ELF_FILE ${PROJECT_BINARY_DIR}/${OUTPUT_EXE_NAME}.elf) SET(HEX_FILE ${PROJECT_BINARY_DIR}/${OUTPUT_EXE_NAME}.hex) SET(BIN_FILE ${PROJECT_BINARY_DIR}/${OUTPUT_EXE_NAME}.bin) # ###################################################################################################################### # 6、组织公共库源文件; # ###################################################################################################################### # ###################################################################################################################### # 7、组织用户源文件; # ###################################################################################################################### # 用户源码; # ###################################################################################################################### INCLUDE_DIRECTORIES( # 应用层头文件包含路径; ${PATH_WORKSPACE_ROOT}/projects/demo/source/applications ${PATH_WORKSPACE_ROOT}/projects/demo/source/config/board # 硬件驱动头文件路径; ${PATH_WORKSPACE_ROOT}/common_src/drivers/internal_driver ${PATH_WORKSPACE_ROOT}/common_src/drivers/module_driver) SET(USER_SOURCE ${PATH_WORKSPACE_ROOT}/projects/demo/source/applications/stm32f4xx_it.c ${PATH_WORKSPACE_ROOT}/projects/demo/source/config/board/system_stm32f4xx.c ${PATH_WORKSPACE_ROOT}/projects/demo/source/main.c) # ###################################################################################################################### # 8、编译、连接,生成可执行文件 # ###################################################################################################################### # 定义连接器参数; --gc-sections:指示链接器去掉不用的 section SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T ${LINKER_SCRIPT} -Wl,-Map=${PROJECT_BINARY_DIR}/${OUTPUT_EXE_NAME}.map -Wl,--gc-sections,--print-memory-usage" ) # 生成可执行文件 ADD_EXECUTABLE(${PROJECT_NAME} ${COMMON_SERVICES_SOURCE} ${USER_SOURCE} ${LINKER_SCRIPT} ${STARTUP_ASM}) # 添加依赖; SET(PATH_COMPONENTS ${PATH_WORKSPACE_ROOT}/components) ADD_SUBDIRECTORY(${PATH_COMPONENTS}/cmsis/ ${LIBRARY_OUTPUT_PATH}/cmsis) ADD_SUBDIRECTORY(${PATH_COMPONENTS}/soc_std_driver/stm32f4xx ${LIBRARY_OUTPUT_PATH}/soc_std_driver/stm32f4xx) ADD_SUBDIRECTORY(${PATH_COMPONENTS}/bsp/stm32/ ${LIBRARY_OUTPUT_PATH}/bsp/stm32/) ADD_SUBDIRECTORY(${PATH_COMPONENTS}/driver/led ${LIBRARY_OUTPUT_PATH}/led) ADD_SUBDIRECTORY(${PATH_COMPONENTS}/libraries/fifo/ ${LIBRARY_OUTPUT_PATH}/fifo) ADD_SUBDIRECTORY(${PATH_COMPONENTS}/libraries/link_list/ ${LIBRARY_OUTPUT_PATH}/link_list) # ###################################################################################################################### # 9、生成 hex 和 bin 文件 # ###################################################################################################################### ADD_CUSTOM_COMMAND( TARGET "${PROJECT_NAME}" POST_BUILD # Build .hex and .bin files COMMAND ${OBJCOPY} -Obinary "${PROJECT_NAME}" "${OUTPUT_EXE_NAME}.bin" COMMAND ${OBJCOPY} -Oihex "${PROJECT_NAME}" "${OUTPUT_EXE_NAME}.hex" COMMENT "Building ${OUTPUT_EXE_NAME}.bin and ${OUTPUT_EXE_NAME}.hex" # Display sizes COMMAND ${SIZE} --format=berkeley ${PROJECT_NAME} COMMENT "Invoking: Cross ARM GNU Print Size")4. vscode 工作空间的说明

我通常习惯在 VScode 的工作空间放置两个目录, 具体看下图:

第 1 个目录是工程的目录,这样可以快速的查看工程的信息。

通常我的一个项目里面可能存在多个工程,而这些工程会引用一些公共过的库文件,所以这些文件会放置在项目的公共目录里面,如 components 目录。

那么这个时候,我会把整个项目的目录添加到工作空间,这样我就可以方便的查阅公共模块的代码或其他文件了。你会发现,在上述截图中的第二个目录会包含第一个目录的全部内容。

这只是我的习惯,你可以按照实际情况酢情处理。

5. 构建与编译

到此为止,我们可以开始启动构建,并得到最终的二进制文件了。

5.1 使用 VScode 任务

展开 demo/.vscode 目录,创建 task,json 文件,这个文件会定义具体的生成与构建任务。

代码清单:tasks.json

{ "version": "2.0.0", "tasks": [ { /// 如果你使用的是 J-link 调试,并配置了 RTT 打印,那么开启该任务可以在终端打印 RTT 日志; "label": "0. Segger-RTT", "type": "shell", "command": "C:/'Program Files (x86)'/SEGGER/JLink/JLinkRTTClient.exe", "args": [], "problemMatcher": [], "group": { "kind": "build", "isDefault": true } }, { /// 执行该任务,你可以生成构建脚本,我这里使用的是 Ninja; "label": "1. Reload Cmake Project (Orion675FS)", "type": "shell", "command": "clear ; Remove-Item -Recurse ./build ; mkdir ./build ; cd ./build ; cmake -G \"Ninja\" -DOPEN_LOG_OMN_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug ..", "options": { "cwd": "${workspaceFolder}/gcc/" }, "group": { "kind": "build", "isDefault": true } }, { /// 执行该任务,你可以执行编译,并得到可用的二进制文件; "label": "2. Build (Debug)", "type": "shell", "command": "clear ; cd ./build ; cmake -G \"Ninja\" -DOPEN_LOG_OMN_DEBUG=ON .. ; ninja -j8", "options": { "cwd": "${workspaceFolder}/gcc/" }, "group": { "kind": "build", "isDefault": true }, } ] }

该文件中具体的每个参数的含义这里不解释,具体的命令的含义在命令行编译的方式里面会有详细的说明。

创建好文件后,在 VScode 中使用快捷键 ctrl + shift + B,可以调出上述任务,点击对应的任务就可以执行了。

当然,你也可以通过菜单栏 Terminal / Run Task 打开相同的界面。

5.2 命令行

在 gcc 目录上右键,在弹出的菜单中点击 Open in Integrated Terminal,会打开一个终端。 在终端输入命令:

mkdir build && cd build

创建构建的过程文件以及最终输出文件的存放路径,你可以取其他名称。

当然了,你也可以直接在 gcc 目录启动构建,但是你的目录可能变得乱七八糟。 执行完该命令后,会进入该目录。

在终端输入如下命令,生成构建脚本,这里以 Ninja 为例。

cmake -G "Ninja" -DOPEN_LOG_OMN_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug ..

命令内容解释:

cmake -G "Ninja" 生成适用于 ninja 的构建脚本;如果需要其他的,请在终端输入 cmake -G -help 查阅帮助。 -DOPEN_LOG_OMN_DEBUG=ON,传递一个开关宏的值,通常我们可以在 cmake 文件中定义一些开关宏,在生成的时候指定这些宏的值,这可以方便的实现差异化构建。-DCMAKE_BUILD_TYPE=Debug 告诉 cmake 在生成构建脚本时的优化类型,可选 Debug、MinSizeRel、RelWithDebInfo、Release。

目前,我们已经完成了构建脚本的生成,接下来可以启动构建了。

在终端输入如下命令,执行构建。

ninja -j8

其中, -j8 是开启多线程编译,后面的数值表示使用的线程数。 编译输出的内容如下。

$ ninja -j8 [51/51] Linking C executable demo Memory region Used Size Region Size %age Used CCMRAM: 0 GB 64 KB 0.00% RAM: 9264 B 192 KB 4.71% FLASH: 2340 B 2 MB 0.11% EXTRAM: 0 GB 1 MB 0.00% text data bss dec hex filename 2316 24 9248 11588 2d44 demo

build 目录的内容如图。

6 总结Cmake 和 tasks.json 可以有很灵活写法,文章只是写了我常用的形式,希望你可以在理解的基础上总结出适合自己的方式和方法;上述方法对 clion 同样适用;上述 Demo 的工程源码,你可以通过下面的命令获取。git clone https://jihulab.com/liuy928/cmake_arm_gcc_demo.git


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有