前言

本文内容比较多,废话也挺多,大家可以逐步跳跃查看。本文能够带那些使用MDK成瘾的童鞋一个改过自新的机会,由于本人也是一个非常乐于折腾的人,所以基本能踩得坑都踩了,希望对你有所帮助!

为什么使用VS Code

为什么要使用VS Code呢?还是先列举一下目前的STM32系列(和一些对标STM32的国货)芯片的各个IDE,对比一下他们的优缺点:

  • MDK:这可是老牌的IDE了,优点很多:安装一个MDK就基本完事了,并且在CubeMX中就可以直接生成MDK的工程。在最近的几个版本中,MDK推出了V6版本的编译器,编译HAL库的速度得到了飞一样的提升。此外MDK的Debug功能可能是目前所有开发环境都无法超越的。但是缺点也很明显,作为一个22世纪依旧在使用的IDE,MDK老旧的代码编辑界面以及智障一样的代码提示功能,确实让人非常难受。最后,也是博主今天写这篇文章的最重要原因,MDK收费,当然对于个人来说无所谓,但是对于公司来说,就是极其具有风险的行为。
  • IAR:这个我个人用的也不是很多,但是可以肯定是,IAR的界面老旧程度和代码提示功能的难用程度绝对不亚于MDK,同样的,也是收费软件。
  • CubeIDE:免费、官方、与CubeMX耦合很深,作为ST公司的官方IDE,开发STM32当然不存在任何的问题。但是基于eclipse开发的IDE都有一个非常离谱的设定:代码的自动提示功能非常老旧,只有一些结构体才会进行提示,想要强行提示,需要按下快捷键才可以,具体的情况大家有兴趣的可以自己尝试一下,相信你一定会🐎它。
  • rt-thread studio:这个IDE同样是使用eclipse魔改的,但是其魔改程度可以说是非常之大,代码提示的功能非常完善,并且还内置了串口控制台。但是rt-thread studio与RTT操作系统高度耦合,针对裸机的开发不是很友好。当然,你可以通过导入MDK或者IAR的工程来实现裸机的开发,但是可能存在某些Bug(我这边导入MDK的工程后编译无法生成elf调试文件)。
  • VSCode + Keil Assistant插件:这种方式我个人认为是最难受的,为了摆脱MDK编辑上带来的困难,选择使用VSCode进行代码编辑,Keil进行Debug调试。这样需要两个软件来回的切换,并且Keil Assistant插件还存在一些找不到头文件的Bug。

总结了上述各个IDE的优缺点后,正式拿出我们的重头戏,一套高度定制的编译系统,整个编译过程完全由自己掌握的终极STM32开发方式:gcc-arm-none-aebi + CMake。

这里为什么不提到VSCode呢,其实针对本文的这种方式,VScode仅仅是我个人比较喜欢的一个编辑器,实际上,这种构建STM32程序的方式是完全不依赖于VSCode的,因为它使用CMake来构建整个系统,VSCode仅仅作为写代码的工具而已。并且这种构建方法可以轻松实现跨平台,我个人在编写本文之前实际的流程是:首先在CentOS远程服务器上构建成功,然后转移到本地Windows系统上,在转移过程中,除去部署开发环境所需要的时间,对项目本身内容的修改仅仅是添加了两行代码!

使用截图

瞎说无用,先来几张截图看看效果:

首先是编译过程以及与MDK相似的编译后输出编译文件大小:

img

然后是Debug过程中的截图。

进入Debug界面:

img

然后是VSCode比较实用的一个功能,在断点处添加条件,只有条件满足的情况下才会进入断点:

img

对变量的监控:

img

好了,暂时就这么多,接下来开搞!

编译环境介绍

这里的介绍都是按照我个人的理解和知识水平总结的,详细的大家可以百度。

Make

make是一个用于构建C/C++代码的构建工具,在我们使用MDK进行开发的时候,左侧的代码结构以及设置中的头文件目录其实就是一种间接的构建方式,这种构建方式以可视化的方式进行设置,你只需要设置好头文件的搜索目录、所有的.c源文件,点击Build即可实现编译功能。但是你在使用CubeIDE或者rt-thread studio就可以发现其中有一个makefile文件,这个文件实际上就是用于组建文件结构的一种方式,这些IDE通过可视化的方式来修改makefile文件(修改过程由IDE自行完成,用户无需关注),然后通过make指令来进行构建,最终生成hex或者bin文件。

那么有没有一种可能,我们自己写一个makefile文件,然后手动执行make指令,也能实现对整个工程的构建呢?答案当然是可以,IDE能干,我们更能!实际上在使用CubeMX生成代码的时候就有一个选项就是生成makefile文件:

image-20240202133727128

这样我们生成工程以后,在工程目录下执行make指令,就能实现构建。(前提是你装了make)

但是你打开makefile文件你就会发现,这个文件非常的复杂,主要是一大堆$、@、% 看着非常头疼。
image-20240202133745443

那么是不是还有一种可能,我创造一种更简单的工具,能够直接生成复杂而又丑陋的makefile文件呢?我为了不用一个文件一个文件的编译所以写了makefile来一次性构建一个工程,那我能不能为了不写这么麻烦的makefile文件再整一个工具来生成makefile呢?所以,CMake来了!~

CMake

cmake是一个用于生成makefile的工具,其实看名字也能看出来,Create Make,生成make。CMake能够通过更简洁的语法来生成makefile文件,并且提供一些附加的功能。

实际上CMake是很复杂的,比CMake更加简单的构建工具很多有很多,比如:SCons(使用Python进行构建)、XMake(更简洁的语法格式)等等。这类工具的作用就是设置编译器的属性,增加一些编译指令,搜索源文件以及头文件。由于CMake出现的比较早,使用面积比较广(比如著名的OpenCV就是使用CMake进行构建),所以本文也就使用了CMake。

rt-thread操作系统使用的构建系统是SCons。

CMake强就强在它是一个与平台无关的工具,也就是说你在Linux与Windows下是不用修改CMakeLists.txt文件就能用(基本不用修改QAQ)。

gcc-arm-none-aebi

我们在windows下编译C语言的程序,会得到一个 exe可执行文件,同样的在Linux下会得到一个默认为 a.out的可执行文件,但是我们的STM32需要的是hex、bin文件。所以gcc-arm-none-aebi就是一套编译工具,能够在Linux或者Windows下编译出嵌入式所需要的可执行文件。

这里大家需要区分一点:MDK的编译器是armcc,其实是和gcc-arm-none-aebi处于平等关系的一套编译器,但是armcc是完全由MDK公司去管理的,属于闭源软件!

资源下载

本站提供的所有下载资源均来自互联网,大家也可以自己去官网下载。这里所提供的内容截止到:2023-2-25均为最新版本,且经过本人测试。PS:如果你百度了半天还是整不明白环境变量,可以在下面留言。

网盘资源

所有的资源都可以通过下面的链接进行下载(受服务器带宽的限制,大概1MB的速度):

[【昕某人の网盘-VSCode开发STM32的开发环境】](文件分享 - 外链分享 - 大伙云 - Powered by kodbox (xinmouren.cn))

image-20240202161411346

安装CMake

下载CMake的安装包后按照提示下一步即可,这里需要注意的是:

image-20240202161652475

按照上面的选就可以自己添加环境变量。

注意:第一选项为不添加到环境变量,第二个选项为添加为系统环境变量,第三个选项为添加到用户环境变量,选择第二个需要你具备管理员权限才可以!

安装完成后打开PowerShell(其实更加推荐使用GitBash,大家自己选择吧),输入:

cmake --version

出现如下界面则说明安装成功:

image-20240202161942144

安装MinGW

make工具是GUN套件中的,但是在Windows下的官方的编译器是VS,所以我们需要使用一个GUN的编译器,即:MinGW。MinGW中包含了make工具。

打开上面的下载链接,可以看到两个文件:

image-20240202162006226

第一个是安装包,第二个是免安装的,也就是绿色版本。

我这边在使用安装文件安装的时候,会出现错误,网上说的是因为服务器在国外,需要科学上网。

推荐直接下载第二个压缩包,解压之后打开bin目录,复制mingw32-make.exe,然后重命名为make.exe即可。(不重命名也行,就是每次编译的时候输入的命令不一样)

image-20240202163223190

然后将bin文件夹添加到环境变量中即可。(添加环境变量的问题请百度!!!!)

接着打开命令行,输入以下指令:

make -v

能看到版本号的输出就说明安装成功了:

image-20240202162237024

安装gcc-arm-none-aebi

前面提到过,gcc-arm-none-aebi是一个交叉编译链,能够让我们在Linux或者Windows下编译出STM32(其实所有的ARM内核的芯片都可以,本文仅仅讲解STM32系列)所需要的hex或者bin文件。

安装方法也很简单,直接下载解压后,将bin目录添加到环境变量即可。

然后在命令行输入如下代码可以验证是否安装成功:

arm-none-eabi-gcc  -v

安装成功会有如下的提示:

image-20240202162252967

至此,编译阶段的文件我们就都凑齐了,这里我们先保证能够编译通过,再去考虑使用Jlink下载的问题。

编写CMakeList.txt

接下来最重要的就是编写CMakeList.txt,CMakeList.txt用于组织整个工程,包括设置编译器,设置编译器的参数,设置头文件的搜索目录,设置源文件(类似MDK的给左侧添加文件),最后设置链接参数。

详细的CMake教程大家可以自行百度,本篇文章仅仅针对STMCubeMX生成的工程。

示例工程大家可以到上面的链接进行下载!

小试牛刀

本小结的内容是按照上面提供的例程来讲解的,仅仅是为了让大家先体验一下这种编译的方式!

首先在VSCode中安装CMakeTool的插件(不安装也可以),这个插件能够对CMakeList文件提供语法高亮,同时提供一键编译的功能。

image-20240202162436671

上图可以看到,插件安装完成后,就会自动扫描电脑中安装的编译链,我们还可以点击下面的No active kit来手动选择编译链。

这里我们当然选择arm-none-eabi的编译套件。

但是这里我个人是不太喜欢这个插件的,为什么不喜欢我也说不上来,所以我还是按照不安装插件的方式来。

进入代码所在的根目录,打开一个终端,并执行以下代码:

cmake . -G "MinGW Makefiles"  -B Build

上述代码的作用代码是在当前目录下(cmake指令后的点代表当前目录)寻找CMakeList.txt文件,并且指定使用MinWG来成适用于minGW的makefile文件(-G ”MinGW Makefiles“),并将编译后的文件统一存放到Build目录下。(注意,不要存在中文路径)

这里简单说一下,我个人是推荐将所有的编译产生的文件统一存放到一个特定的目录,这样后面我们编译出的hex、bin等文件都是在这个目录下,如果我们修改了CMakeList.txt文件,是需要清除缓存的,缓存文件就是CMakeCache.txt,如果是统一存放,那么直接给那个目录删除就行。

一些使用过make的朋友应该比较熟悉make clean指令,用于清除编译产生的文件,但是CMake生成的makefile中是不存在这个指令的。

image-20240202162759551

这里需要关注一下上面这张图里边所标识的C、C++编译器是否正确。

然后就可以看到在Build目录下生成了makefile:

image-20240202162833017

接着进入Build目录,执行make指令即可。

image-20240202162856324

CMakeList.txt详解

接着我们来详细说一下这个CMake文件(需要有一定的CMake基础)。完整的CMakeList.txt如下:

CMakeList.txt

#以下用于生成hex等文件
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

cmake_minimum_required(VERSION 3.0)
project(TestProject)


#以下用于屏蔽 error: unrecognized command line option ‘-rdynamic’
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")

#指定编译工具
set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
set(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
set(CMAKE_ASM_COMPILER "arm-none-eabi-gcc")
set(CMAKE_AR "arm-none-eabi-ar")
set(CMAKE_OBJCOPY "arm-none-eabi-objcopy")
set(CMAKE_OBJDUMP "arm-none-eabi-objdump")
set(CMAKE_SIZE "arm-none-eabi-size")

#编译相关选项
set(MCU "-mcpu=cortex-m4")
set(FPU "-mfloat-abi=hard -mfpu=fpv4-sp-d16")
set(MCU_FLAGS "${MCU} -mthumb ${FPU}")
set(CMAKE_C_FLAGS "${MCU_FLAGS} -w -Wno-unknown-pragmas") #-w -Wall
set(CMAKE_C_FLAGS_DEBUG "-O0 -g2 -ggdb")
set(CMAKE_C_FLAGS_RELEASE "-O3")

#设置宏定义,对应MDK里Target Options里的选项
add_definitions(
    -DSTM32F407xx
    -DUSE_HAL_DRIVER
)

#设置头文件包含目录
include_directories(
    Core/Inc
    Drivers/STM32F4xx_HAL_Driver/Inc 
    Drivers/STM32F4xx_HAL_Driver/Inc/Legacy 
    Drivers/CMSIS/Device/ST/STM32F4xx/Include 
    Drivers/CMSIS/Include
)

#startup文件是STM32CubeMX生成的
enable_language(ASM)
set(SRC_STARTUP "startup_stm32f407xx.s")

# 查找用户自己写的源文件
file(GLOB USER_SRC_LIST

)

# 设置由cubeMX生成的源文件
set(HAL_SRC_LIST
    Core/Src/main.c 
    Core/Src/stm32f4xx_it.c 
    Core/Src/stm32f4xx_hal_msp.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_usart.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c 
    Core/Src/system_stm32f4xx.c  
)

#连接生成,ld文件是STM32CubeMX生成的
set(LINKER_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
set(CMAKE_EXE_LINKER_FLAGS "-specs=rdimon.specs -specs=nosys.specs -T${LINKER_SCRIPT} -lc -lm -lnosys -Wl,-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map,--cref -Wl,--gc-sections")

#生成可执行文件
add_executable(${PROJECT_NAME}.elf  ${USER_SRC_LIST} ${SRC_STARTUP} ${HAL_SRC_LIST})

set(ELF_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf)
set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)

add_custom_command(TARGET "${PROJECT_NAME}.elf" POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -Obinary ${ELF_FILE} ${BIN_FILE}
    COMMAND ${CMAKE_OBJCOPY} -Oihex  ${ELF_FILE} ${HEX_FILE}
    COMMENT "Building ${PROJECT_NAME}.bin and ${PROJECT_NAME}.hex"

    COMMAND ${CMAKE_COMMAND} -E copy ${HEX_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.hex"
    COMMAND ${CMAKE_COMMAND} -E copy ${BIN_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.bin"

    COMMAND ${CMAKE_SIZE} --format=berkeley ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
    COMMENT "Invoking: Cross ARM GNU Print Size"
)
#以下用于生成hex等文件
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

这两行的作用是告知编译器不要生成类似.exe的用于windows平台的可执行文件。实际测试发现这两行代码在Linux下是不用加的。

cmake_minimum_required(VERSION 3.0)
project(TestProject)

这两行是在设置CMake的最小版本和工程的名字。

#以下用于屏蔽 error: unrecognized command line option ‘-rdynamic’
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")

这两句看注释大家也明白,就不多说了。

#指定编译工具
set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
set(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
set(CMAKE_ASM_COMPILER "arm-none-eabi-gcc")
set(CMAKE_AR "arm-none-eabi-ar")
set(CMAKE_OBJCOPY "arm-none-eabi-objcopy")
set(CMAKE_OBJDUMP "arm-none-eabi-objdump")
set(CMAKE_SIZE "arm-none-eabi-size")

这几句是在设置我们工程所需要的编译器,这些变量基本都是CMake的内置变量,我们需要指定各个编译器,如果你在上面没有添加环境变量,那么这里需要填入的是编译链的完整路径。

#编译相关选项
set(MCU "-mcpu=cortex-m4")
set(FPU "-mfloat-abi=hard -mfpu=fpv4-sp-d16")
set(MCU_FLAGS "${MCU} -mthumb ${FPU}")
set(CMAKE_C_FLAGS "${MCU_FLAGS} -w -Wno-unknown-pragmas") #-w -Wall
set(CMAKE_C_FLAGS_DEBUG "-O0 -g2 -ggdb")
set(CMAKE_C_FLAGS_RELEASE "-O3")

上面的几句非常重要,首先是设置MCU的类型,然后设置是否打开FPU,然后设置是否显示警告,最后两句用于设置Debug模式下编译和Release模式下编译的优化等级,Debug模式下必须设置-ggdb参数,这样才能使用elf文件进行调试,同样的,Release模式下的优化程度比较高,所以编译出的文件体积比较小。

如果上面的参数在你更换芯片以后不知道怎们设置,可以参考由Cube MX生成的MakeFile文件,里面有对MCU参数、FPU参数的描述和设置。

#设置宏定义,对应MDK里Target Options里的选项
add_definitions(
    -DSTM32F407xx
    -DUSE_HAL_DRIVER
)

这里是添加全局宏定义,同样参考CubeMX生成的MakeFile文件来设置。

#设置头文件包含目录
include_directories(
    Core/Inc
    Drivers/STM32F4xx_HAL_Driver/Inc 
    Drivers/STM32F4xx_HAL_Driver/Inc/Legacy 
    Drivers/CMSIS/Device/ST/STM32F4xx/Include 
    Drivers/CMSIS/Include
)

上面的函数是设置头文件的包含目录,可以参考CubeMX生成的MakeFile文件来添加由CubeMX生成的头文件的目录,当后面我们自己添加一些目录的时候,需要将自己的文件目录添加进来。

这一步可以与MDK对比,使用CubeMX生成MDK的工程后,你可以看到在设置里边的Include选项中默认添加了几个路径,MDK默认添加的路径与上面的一样。同样的,我们在新建一些头文件的路径后,也是需要自己在MDK中添加进去的。

#startup文件是STM32CubeMX生成的
enable_language(ASM)
set(SRC_STARTUP "startup_stm32f407xx.s")

这两句的作用是告诉CMake如何正确的处理汇编文件。

# 查找用户自己写的源文件
file(GLOB USER_SRC_LIST

)

上面的这句目前为空,因为我们没有自己添加新的目录来存放我们的.c源文件,如果后面自己添加了新的目录来存放源文件,就需要在上面填入,这样CMake就会自动到你设置的目录下寻找.c源文件并添加进工程中。

# 设置由cubeMX生成的源文件
set(HAL_SRC_LIST
    Core/Src/main.c 
    Core/Src/stm32f4xx_it.c 
    Core/Src/stm32f4xx_hal_msp.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_usart.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c 
    Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c 
    Core/Src/system_stm32f4xx.c  
)

这里作用就是好比在MDK的左侧添加文件一样,需要将各个源文件都添加进去。其实这里完全可以使用上面的file函数来实现,但是我的开发习惯一般是将那些自动生成的和自己写的东西全部分开,所以这里将所有的由CubeMX生成的源文件都手动添加进来,同样,这里的文件列表是来自CubeMX生成的MakeFIle文件。

#连接生成,ld文件是STM32CubeMX生成的
set(LINKER_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
set(CMAKE_EXE_LINKER_FLAGS "-specs=rdimon.specs -specs=nosys.specs -T${LINKER_SCRIPT} -lc -lm -lnosys -Wl,-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map,--cref -Wl,--gc-sections")

然后是一个比较长的指令,这里大家无需过多理解,你只要知道STM32系列的芯片这样写就没有文件,如果想要详细了解每一个参数作用,可以自己查看gcc的文档。

#生成可执行文件
add_executable(${PROJECT_NAME}.elf  ${USER_SRC_LIST} ${SRC_STARTUP} ${HAL_SRC_LIST})

set(ELF_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf)
set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)

上面就是最终步骤了,设置可执行文件的生成规则,也就是生成一个与工程名字相同的elf文件,这个elf文件依赖的文件有:用户添加的源文件、CubeMX生成的那些源文件、还有启动文件。并且同时编译出hex、bin文件。

add_custom_command(TARGET "${PROJECT_NAME}.elf" POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -Obinary ${ELF_FILE} ${BIN_FILE}
    COMMAND ${CMAKE_OBJCOPY} -Oihex  ${ELF_FILE} ${HEX_FILE}
    COMMENT "Building ${PROJECT_NAME}.bin and ${PROJECT_NAME}.hex"

    COMMAND ${CMAKE_COMMAND} -E copy ${HEX_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.hex"
    COMMAND ${CMAKE_COMMAND} -E copy ${BIN_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.bin"

    COMMAND ${CMAKE_SIZE} --format=berkeley ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
    COMMENT "Invoking: Cross ARM GNU Print Size"
)

这个的作用就是在编译完成后打印可执行文件的大小,与MDK编译后一样。

经过上面的介绍,相信你已经能够大致理解整个CMake的过程,这里再简单总结一下:

  • 告知GCC编译以后我所需要的文件是hex、bin等类型的
  • 设置工程的最小版本以及名字
  • 设置编译器
  • 设置编译参数
  • 设置工程全局宏定义
  • 设置头文件搜索路径
  • 处理汇编文件
  • 添加工程源文件
  • 链接生成

至此,我们已经能够编译出适用于STM32的文件,这里再提一嘴,你更改某个文件以后,是不需要重新执行cmake命令的,只有你添加文件、给工程改名字等等比较大的改动才需要重新执行cmake,一般情况下直接执行make指令即可。

小技巧:执行make -j 可以使用多线程编译,电脑越牛逼,编译越快。

由于篇幅原因(其实是懒得写,后天再写),下一篇文章将会讲解如何将我们的文件烧录进STM32,如何启动调试以及VSCode自动化编译下载等功能。