深入理解工具链-自己搭建STM32编程IDE
目录一、前言二、编译器组成与编译流程2.1 编译流程概述2.2 Gcc For Arm编译器2.3 预编译2.4 编译2.5 汇编2.6 链接2.7 生成HEX镜像2.8 通过Makefile编译代码三、调试流程3.1 Openocd调试工具3.2 GDB调试器四、自己搭建IDE4.1 准备工作4.2 生成Makefile工程4.3 安装Vscode及插件4.4 安装配置GCC For Arm编译
目录
一、前言
嵌入式开发,必然离不开各自开发平台下的工具链,通常情况下,工具链都被封装为一个完整的IDE,供开发者使用。深度封装的IDE可以让开发者快速上手,避免新手一开始就接触复杂繁琐的底层原理,从而专注于代码本身。实际开发过程中,IDE也确实能胜任开发过程中大部分需求,起到它应用的作用。
在某些特殊场景下,一旦需要自定义开发流程,或对工程进行深入改动。就要求开发者了解IDE的底层原理及代码编译流程。本文是作者探索开发工具链过程中的总结,实际探索过程进行了多个平台和芯片的尝试,在本文中,以流行的STM32平台为例进行记录,文中提及的工具技术,每一个都值得单独开篇详述,限于篇幅,无法在本文中深入研究,以后有机会再探索并单独撰文记录。本文先对编译以及调试流程作了简介,帮助读者先构建基本的知识框架,以便理解后面搭建IDE过程中的每个步骤及其目的,如果读者已对编译及调试流程很熟悉,可以直接跳到搭建IDE部分,参考本文搭建自己的IDE。作者水平有限,如有错漏,烦请指正。
二、编译器组成与编译流程
2.1 编译流程概述
C语言是一门高级语言,其高级语言的描述其实是相对于汇编语言以及机器语言来的。目标代码必须在特定平台上执行才有意义,不同平台架构及指令集的不同注定了在C语言下同样的动作,最终必须转换为目标平台CPU所能识别的特定二进制序列才能正常运行,即:“可执行文件”。从编写完成的C代码到最终的可执行文件所经历的过程,即是被我们泛化的概念:“编译”。而实际上,“编译”仅是转换过程中的一部分。
真实的编译过程,包含了预编译->编译->汇编->链接四个部分,每个部分执行后,均有对应的输出文件,这些中间文件在编译过程中是必需,但最终不被目标平台所使用,因此,通常会在生成可执行文件后被清除。这四个部分所使用的工具,被打包为一个程序集,根据不同的输入参数,执行不同的命令以完成编译过程的各个阶段任务,该程序集,即:“编译器”,实际上,现在的编译器,不仅包含了编译过程本身的工具,还包含着调试以及镜像转换等一系列工具集。针对ARM平台,常见的编译器有ARMCC(Keil)、IAR(IAR)、GCC For ARM(除前两者外的IDE使用)、LLVM(暂未大规模使用)。本文的主角是GCC For ARM,目前主要由ARM在维护及更新,基于GNU开源工具链,主要用于ARM平台的交叉编译。
2.2 Gcc For Arm编译器
在此给出Gcc For Arm的官网下载链接:
gcc-arm-none-eabi-10-2020-q4-major-win32.zip
以上编译器压缩包解压后,进入其根目录,将看到以下内容:
上图展示了ARM提供的GCC For Arm工具链中的所有工具,其中标红的是在嵌入式开发过程中常用的几个,下面对几个常用工具逐一讲解,在讲解之前,请对整个编译过程有大概了解:
如上图所示,C代码编译的过程及每个阶段都将产生对应的中间文件,中间文件的格式根据目标平台的不同和编译器的不同会有所区别,但在其所属阶段产生的文件内容,实际上并无本质差异。
有了以上宏观了解后,可以逐个将编译器的工具与编译流程对应:
1.arm-none-eabi-gcc.exe
该文件是编译器的主体,其作用范围涵盖编译流程中的预编译、编译、汇编、链接四个阶段,后文将逐一对以上过程展开讲解。
2.arm-none-eabi-ld.exe
该文件是链接器主体,其作用是将编译器汇编后输出的一系列*.o目标文件合并以及重定位,最终生成目标平台的可执行文件(*.elf)。
3.arm-none-eabi-gdb.exe
该文件是gdb调试服务的可执行文件,其作用是加载由链接器产生的可执行文件(*.elf)至目标芯片的RAM或者Flash,并具有运行、调试、增删断点、读取变量、修改变量等常见调试功能。其作用范围,覆盖嵌入式开发的调试阶段。
4.arm-none-eabi-objcopy.exe
该文件是镜像拷贝程序的主体,由于链接阶段所产生的*.elf目标文件,不止包含了程序的运行地址和数据,还包含了大量信息供调试使用,而程序烧录时,仅需*.flash的地址及对应地址的数据,因此需要将这些信息,从*.elf中提取出来,转换为常见的Flash镜像格式,如:*.hex、*.S19、*.Bin。可以简单理解为:*.elf文件调试时使用,*.hex烧录时使用,关于HEX文件的格式,可以参考作者之前的文章:深入理解工具链-Hex文件详解。
5.arm-none-eabi-objdump.exe
该文件是反汇编程序的主体,某些特殊情况下,C代码层面的逐行调试并不能满足我们的调试需求,需要进一步知道其汇编语言层面的动作。此时,我们可以使用反汇编程序,将目标平台的二进制机器码转换为汇编代码,以便调试过程中确定问题,反汇编后的格式,不同平台稍有区别,通常为*.lst格式,也有*.lss和直接使用*.txt格式的,但其大都为文本文件,均可使用记事本直接打开。
6.arm-none-eabi-size.exe
该文件主要用于打印当前程序的存储占用情况,如:.data段、.bss段、*.text段大小。通过调用该工具,开发者可以准确知道程序占用的硬件存储资源分布情况,通常,.data段、.bss段大小之和即为所占RAM空间大小;.data段、.text段大小之和为所占Flash空间的大小。IDE编译完成后所打印的大小信息,本质上是调用该工具实现。
2.3 预编译
有别于初学C语言时所有C代码均在一个*.C文件中编写,大型工程为了便于分层与模块解耦,将不同模块的代码封装在不同的*.C文件中。为了让其他模块或上层代码使用本模块中的宏定义、全局变量或函数,常规做法是将模块宏定义以及变量及函数声明放在对应的*.H文件中,*.H文件在被使用的模块中通过#include预编译指令包含。因此,预编译阶段主要的工作,即:将*.H文件的内容,在每一个包含了该*.H文件的*.C文件中按照预编译指令展开。除了#include的展开操作,还有其他的一些预编译指令,但本质上,预编译阶段的内容,都在文本编辑层面,大部分操作,均为复制、粘贴、替换等。经过预编译阶段后,编译器所产生的中间文件格式为*.i。
假设,当前工程下,包含main.c 、main.h、clock.c、clock.h共4个文件,那么,经过预编译阶段,将输出main.i、clock.i这2个文件。
以上过程,由编译器执行预编译命令完成:
执行上述命令后,在源文件目录下,将生成对应的.i文件:
每个*.i的内容 = *.c + *.h
在上述命令中,arm-none-eabi-gcc为编译指令,编译指令后,紧跟指令参数,“-E”表示仅执行预编译动作; “-o”代表执行结果输出到文件 “-o”后紧跟输出文件名。
为了在任意工作目录都能使用该命令,需要提前将该命令所在路径加入环境变量“PATH”中,对于Windows平台,其加入方法如下:
- 复制arm-none-eabi-gcc所在bin文件夹路径,对于本文示例,路径为:C:\Develop_Software\GNU Arm Embedded Toolchain\bin
- 进入:控制面板->所有控制面板项->系统->高级系统设置->环境变量->系统变量->Path->编辑->新建,添加以上路径。
这一步骤,保证了编译器命令在各个工作路径下都能正常执行。
下面是界面示意,后文中用到的工具,若需要加入环境变量“Path”的,其目的和添加方法都一样,后文中将不再重复讲解添加路径操作。
2.4 编译
经过预编译阶段后,每一个*.C均已包含了本文件的代码逻辑以及其他文件的函数及变量声明。此时,每个*.i经过编译器继续执行编译动作。编译这一过程,本质上是将C代码这一高级语言转换为汇编代码,和预编译过程一样,其执行后,每个*.i都将生成对应的*.s文件。
编译命令如下所示:
其命令格式与预编译类似,输入参数由“-E”变为了“-S”,该参数表示编译器仅执行编译操作,不进行汇编与链接。执行该命令后,输出目录下将出现*.s的汇编代码文件,其内容与处理器架构直接关联,不同架构处理器具有不同汇编指令,示例生成的main.s内容如下:
2.5 汇编
经过前面部分的讲解,我们可以合理的猜测,汇编阶段即是将汇编代码转换为目标平台的机器码。和我们猜测的一样,汇编阶段所作的操作,就是为每个*.s文件执行汇编,将其翻译为机器码。但有个需要注意的地方是,此处的*.s文件,不仅包含了每个*.c逐步骤生成的*.s,还包含了该芯片的启动代码:startup.s。启动代码一般是由芯片厂商编写,在执行main函数前执行的初始化代码。其中包括了诸如:堆栈空间分配、初始化中断向量表、RAM初始化、看门狗初始化、时钟初始化、以及跳转main函数等操作,每个处理器在启动代码部分的操作有细微差异,但其目的,都是为了初始化硬件,为main函数运行提供合适的运行环境。其中,堆栈分配自然不必多说,对于嵌入式代码,通常堆空间可以不分配,但栈空间的分配是必须的;RAM初始化主要对全局变量进行重定向及初始化,对于C代码来说,有初始值的全局变量将被分配到.data段,无初始值以及初始值为0的全局变量将被分配到.bss段,因此,初始化代码需要将有初值的全局变量其初值从Flash复制到RAM,将无初值以及初值为0的全局变量赋值为0,其中,初始化.data段这部分即被称为“重定向”。关于启动代码,内容与硬件直接相关,其实可以单独撰文详述,有时间作者将在别的文章中详细讲解。
回到主题:汇编阶段,会为需要汇编的每个*.s文件,都生成对应的*.o文件,不同编译器和目标平台所生成的文件后缀可能不一致,例如:IAR平台下,MP430单片机在这部分扩展名为*.r43,AVR单片机的扩展名为*.r82。但其文件内容,都是相似的,包含了C代码中所有的默认段(*.data、*.bss、*.common、*.text)和用户自定义段。
汇编命令如下所示:
-c参数表示编译器仅执行编译与汇编动作,不进行链接。
以上示例不仅汇编了main.s以及clock.s,还汇编了STM32F407单片机的启动代码,此时,输出的*.o文件不再是文本文件,我们已无法使用记事本打开查看,但每个*.o文件,都包含了代码执行所必须的段以及符号表。所谓符号表,即是C代码中全局变量的变量名,函数名,以及汇编代码中的程序段标号,它们将在链接阶段,被分别合并到最终可执行文件对应的段以及符号表中。这部分内容,读者有兴趣可以参考神作:《深入理解计算机系统》第七章,链接部分。此时我们将这些*.o文件称为:目标文件,它们的运行地址,均以0开始,真实运行地址将在链接阶段被重新指定,因此,它们更完整的名称应该是:可重定向目标文件。
2.6 链接
在前面的阶段中,编译器均是对单个文件的处理,其生成的*.o目标文件中,也仅包含本文件的符号,但程序要想运行,一定要明确每个变量和函数的运行地址,而此时,*.o文件中的地址,都是以0开始,显然,直接合并会导致程序的变量和函数地址重叠,这样的程序无法运行,因此,需要链接器来进行最终的统筹工作,为*.o中的每个符号分配合适的地址。
在这个过程中,我们除了可以让源工程的目标文件(*.o)参与之外,还可以让其他可重定向目标文件参与。例如别的*.o文件、*.a、*.dll、*.lib等,它们,都属于可重定向目标文件,其内部的数据,已经符合目标处理器的指令集规则可以直接执行。链接器则需要进一步让他们有序的在存储器中排列,保障运行时程序有序执行,除此之外,部分数据(如代码段.text)我们将其指定链接到NOR-Flash中,即:从Flash中取指令,加载到CPU执行;另一部分(如数据段.data、.bss)将其运行地址链接到RAM中,以便在运行过程中改变数据。试想另一种情况,将全局变量链接到Flash是否可行呢?
显然,逻辑上是可行的,但是,程序运行时,读操作没有问题,写操作一定会异常,因为Flash的写操作,需要Flash驱动的参与,CPU的地址总线读写指令,显然不具备这样的能力。为了让全局变量运行时,能正常读写,我们将其运行地址重定向到RAM中。由于RAM的掉电丢数据特性,在程序main函数运行前,需要将.data段的数据(有初值全局变量的初值)复制到对应的RAM地址中,这部分工作不由链接器完成,而是由启动代码来执行,此时,连接器的重定向和启动代码的初始化RAM动作,有了因果关系。由于链接过程通常在非目标机器上完成,显然链接器只能做到指定运行地址,而无法搬移数据。这整个过程,我们称之为链接,而实际上,链接器还做了重定向工作。
链接是个复杂的过程,如果通过敲命令行进行链接,文件数量和参数少还好,一旦参数变多,那么,工作量将极其庞大,且容易出错。
于是,我们引入了链接脚本的概念,链接脚本中,将链接过程中需要指定的地址、存储分布、各个段的大小及起始地址,加载地址和运行地址,都做了约束。在链接时,我们仅需加载链接脚本文件,即可让链接器根据我们的约束,执行链接操作。关于链接脚本的编写和使用,读者可以根据需要扩展学习,在示例中,仅用于演示编译流程,并未使用链接脚本,以后有机会作者将在其他文章中详述链接脚本的语法格式。
链接命令我们可以使用继续使用arm-none-eabi-gcc指令,也可以使用arm-none-eabi-ld指令,均能生成可执行文件。二者的不同是,gcc指令可以输入*.c文件,直接执行到链接步骤,生成可执行文件,而ld指令,其输入只能是可重定向的目标文件。
链接指令如下所示:
上述两个指令均能生成elf可执行文件。
2.7 生成HEX镜像
大多数情况下,都需要生成特定格式的Flash镜像文件(.hex、.s19、.bin),以便进行烧录及BootLoader升级。该需求可以使用arm-none-eabi-objcopy命令完成。
操作指令如下:
通过执行arm-none-eabi-objcopy指令,我们成功生成了hex文件。
2.8 通过Makefile编译代码
文章至此,C代码编译的整个过程,基本介绍完毕,但在此处,还要特别引入MakeFile,尽管有了方便的编译工具帮助我们实现代码编译。源文件和编译参数的输入仍然是一件头疼的事,因此,Makefile的作用,即是将调用编译器编译的过程自动化,通过Makfile文件中约定编译参数、输入和输出,再执行Make命令,解析Makefile文件,帮助我们自动编译和构建可执行文件。
针对前文的示例,我们可以编写简单的Makefile,使用Make指令,解析Makefile,一个命令完成所有编译操作,生成*.elf可执行文件、*.hex文件、*.map文件,反汇编文件。
Makefile的语法不在本文讨论范围内,因此,不要求读者自己写Makefile,仅需会用Makefile完成编译即可。作者将在其他文章中深入探索Makefile语法,并总结记录成文。
本文的示例仅包含四个源文件,为其编写Makefile相对简单,在源文件目录下新建文本文件,去除扩展名,将其重命名为Makefile,输入如下内容,注意第2行和第3行需要以TAB键开头,否则无法将其识别为makefile文件:
Makefile文件需要由make指令调用,make工具很多,其主要作用是解析makefile文件并执行命令,作者电脑里安装了多个make工具,有x86平台的mingw32-make.exe,该工具安装x86平台Gcc编译器后即可用,另外,还有mingw附带的make.exe,无论使用哪一个,将其路径加入环境变量后,均可解析Makefile文件进行编译。
分别使用mingw32-make.exe和make.exe编译结果如下:
实测两个工具均可以成功解析Makefile,并编译生成elf和hex文件,make工具的获取和安装将在后文中讲到。
同链接脚本一样,Makefile的编写是件非常考验技术人员的事,本文中的Makefile仅适合演示简单工程,并不适合大型工程使用,但Makefile的使用方法是一样的。自己动手编写启动代码、Makefile和链接脚本需要对编译器和目标平台足够熟悉,并且需要持续学习,有兴趣的读者可以深入探索。目前常规的开发流程下,其实启动代码、Makefiel和链接脚本并不需要自己编写,芯片厂商一般都会提供,只需要会用即可,某些场景下,需要进行简单的修改,仅此而已,是否需要深入学习,完全取决于兴趣及工程需要。
三、调试流程
常规的调试需求包含烧录加载程序、增删断点、查看修改变量等操作,除了Keil和IAR这样的封闭IDE外,大部分基于eclipse的集成开发环境大都支持开源调试工具链,这样能以最快的速度支持自家芯片,并与市面上大部分仿真器兼容。而GDB调试器和OpenOcd是其中的代表。本文的最终目标:自己搭建IDE,同样依赖于这两项强大的调试工具。下面将对GDB调试器和OpenOcd调试服务逐个进行介绍。
3.1 Openocd调试工具
Openocd是一套开源的调试工具,目前在很多IDE中流行,跨平台支持着众多目标芯片的仿真和调试,如果说GDB是调试的人机接口,那么Openocd则是GDB调试器与不同芯片之间的接口,其负责将调试指令下发至仿真器驱动直接和目标芯片进行交互,从而实现GDB所希望达到的命令效果。因此OpenOcd充当着GDB调试器和不同目标芯片之间的翻译器。Openocd的代码完全开源,其官网地址为:http://openocd.org/getting-openocd/。
官方并不直接提供其可执行文件,因为OpenOcd支持了太多硬件仿真器,通常不是所有仿真器都需要,可以根据需要自行下载并编译出合适的版本。当前最新版本为0.11,官网虽然不直接给出可执行文件,但在其主页推荐了4个编译维护OpenOcd的网站,它们都有提供可以直接使用的可执行文件程序包。
本文没有使用这四个中的任何一个,而是在VisualGDB的主页找到了支持广泛且自己测试下来更好用的编译版本,这个版本支持的仿真器更多,兼容性更强,在此附上下载地址:openocd-20210625.7z。
下载完成后,同样需要将Openocd的可执行文件所在路径加入环境变量以便使用。添加方法已在前文提及,不在此重复。
Openoce需要加载对应的配置文件才能正常使用,通常需要加载接口配置文件以及目标配置文件,接口配置文件描述了所使用的仿真器类型,如常用的CMSIS-DAP、ST-LINK、JLINK等,在Openocd的interface目录下均能找到对应的配置文件;而所谓目标配置文件描述了需要调试的目标芯片,在Openocd的target目录下,列出了openocd所支持的众多芯片的配置文件。
在开始调试前,需要建立Openocd与硬件的连接,并在后台为GDB调试器开放调试端口,一般在硬件连接成功后,即可打开默认的调试端口:3333。
建立Openocd与目标芯片的连接使用如下命令:
出现上图的提示信息即表示成功打开调试端口,此时,可以进行下一步,使用GDB调试器进行芯片程序的加载,运行与调试。
3.2 GDB调试器
在前面介绍GCC For Arm时,简单介绍过,对Arm平台调试,通常需要借助arm-none-eabi-gdb工具,该工具提供调试过程中,增删断点,调试目标等一系列操作的交互接口。GDB调试器其全称是:The GNU Project Debugger,从名称可以看出,其来源是大名鼎鼎的GNU项目,GDB一开始用于Linux平台的C代码调试,随着版本的迭代,现在则被Windows平台下的嵌入式开发IDE大量使用。arm-none-eabi-gdb是GDB的Arm平台版本,目前在众多ARM开发环境中被使用,以STM32为例,除Keil和IAR之外,近年来官方大力推广的TrueStudio和CubeIDE,二者都是基于eclipse的集成开发环境,都使用arm-none-eabi-gcc作为编译器,并集成了arm-none-eabi-gdb调试工具。
注:命令行下使用gdb务必先将gdb所在路径加入环境变量
下面对GDB的常用命令进行介绍:
1.启动gdb调试工具并加载elf文件
arm-none-eabi-gdb file_name.elf
2.连接到Openocd的调试端口(默认为:3333)
target remote localhost:3333
3.烧录程序
load
4.main函数处增加断点
b main
5.删除所有断点
delet
6.打印变量值
p 变量名
7.设置变量值
set 变量名 = value
8.继续执行程序
continue
下面附上部分STM32F4通过GDB命令行工具调试的记录:
四、自己搭建IDE
通过上文的讲解,读者应对整个代码编译及调试流程有了一定了解,清楚了IDE所需的工具都有哪些。最后需要做的是:将这些工具组合起来,让命令行工具链以可视化方式与开发者交互。,最终,能在自己搭建的IDE上实现代码编辑、编译、烧录下载、调试,IDE的搭建就算完成。
4.1 准备工作
在开始搭建IDE前,我们需要先列举一下搭建IDE所需的准备工作,主要是下载IDE所需的各部分替代工具,以及对它们进行配置。大部分内容已在前文中进行详述,并给出工具的下载链接,为了读者阅读方便,所有用到的工具,将在本节再次汇总其获取和配置方法。
下列工具,已链接了超链接,点击即可直接跳转下载。
- STM32CubeMX
- VSCODE
- C\C++插件
- Cortex Debug插件
- GCC For Arm
- Msys2
- Make工具(通过Msys2安装)
- Openocd 0.11
4.2 生成Makefile工程
前面提到,工具链都准备齐全了以后,自己搭建IDE还有三大障碍:启动代码、Makefile、链接脚本。以上三个文件都由开发者手动完成的话,工作量实在不小,实际上,如果都能自己写这三个文件了,其实IDE也没有必要了。
因此,开发STM32不需要自己会写这三个东西,通常芯片官方都会有提供,需要注意的是,对于STM32标准库而言,官方给出的标准库中,其启动代码和链接脚本,分别适配了Keil、IAR、以及GCC,如果使用标准库,提取时需要注意区分。若使用标准库,必须自己编写Makefile,因为Makefile文件的内容与工程文件结构直接关联,官方无法给出固定的Makefile。其实,面对这种情况,也不是无解,还可以通过Cmake工具,编写Cmake文件,自动生成Makefile。关于Cmake这部分内容,作者没有研究,有空研究再开篇单独说。
STM32近年来,大力推广HAL库及其代码配置工具STM32CubeMx,不少人用过,初始化配置自动生成代码确实方便。仔细观察其生成选项,除了可以生成Keil工程、IAR工程、TrueStudio工程、STM32CudeIDE工程,还可以生成Makefile工程。是的,生成的Makefile工程,已经同时包含了我们需要的启动代码、链接脚本、以及Makefile,这就是我们想要的。
CubeMx只用来生成工程,编译过程不需要它,因此,无需将其加入环境变量,下面省略其安装过程直接以图文方式讲解生成步骤,第一次使用需要安装库,可能会稍慢。
1.选择目标芯片,本文以STM32F407开发板为硬件平台,配置编写简单的点灯示例作为演示
2.配置晶振和LED引脚,作者开发板LED引脚为PF9与PF10,将其配置为GPIO_Output,配置PH0及PH1为晶振时钟输入引脚。
3.配置RCC选择时钟源,有外部晶振时,根据需要选择即可。
4.配置Debug调试方式,根据实际情况选择,不能不使能Debug,否则会导致初次烧录完成后无法通过Debug烧录。
5.根据需要配置GPIO引脚别名,笔者此处将其配置为:“LED0”、“LED1”。
6.配置系统时钟,注意选择时钟源、锁相环及时钟频率,填写时钟频率后,CubeMx会根据时钟源自动计算锁相环增益。
7.配置生成Makefile工程,生成后启动文件及链接脚本会自动包含在生成的工程中。
8.其他配置,作者此处勾选了将外设初始化单独放在各自的.C和.H文件中,否则,所有初始化代码都会直接在main.c中定义,实在别扭,此处读者可以根据个人习惯决定。
9.生成工程代码。
生成的工程目录内容如下,可以看到,除了HAL库和初始化配置代码外,启动代码、Makefile、链接脚本都在在里面:
4.3 安装Vscode及插件
Vscode轻量而强大,原本只是一个文本编辑器,但其丰富强大的插件库,可以适应众多应用场景:各种形式的文本编辑、代码管理、语法高亮、智能补全、文件解析对比等,被众多开发者和普通用户喜爱。在笔者电脑上,VScode已经成为装机必备的万能工具,替代了电脑中原先的多个软件,真正的在朝着All IN ONE的方向发展,这次,被笔者用来作为ARM平台的嵌入式开发IDE,它完美诠释了小而强大:编辑体验碾压Keil和IAR,UI界面碾压Eclipse,加上其他插件,可以做到一个软件搞定大部分开发需求
安装过程此处省略,要想使用Vscode进行嵌入式开发,需要安装两个组件: C/C++插件、Cortex Debug,前者用以分析C代码,语法高亮和智能补全,后者用来配置调试工具以及调试代码。
Vscode插件安装方法很简单,在其扩展商店中直接搜索,安装即可:
安装C/C++插件:
安装Cortex Debug插件:
以上两个插件安装完毕后,Vscode基本可用了。
其他插件,读者可以自己选择性的安装:
Intel HEX format,可解析HEX文件
Hex Editor,可编辑二进制文件
Mapfile Syntax,可解析.map文件
LinkerScript,可解析链接脚本.ld文件
4.4 安装配置GCC For Arm编译器
GCC For Arm在前文花了大量篇幅讲述,本文给出的链接下载后是压缩包,因此,解压即可使用,无需安装。
为了让GCC在任意目录下均可用,笔者将GCC For Arm工具链路径加入环境变量,添加方法在2.3节有详细说明,请参照前文。
为验证arm-none-eabi-gcc工具是否正确加入环境变量,可以打开powershell控制台,在控制台输入arm-none-eabi-gcc,若出现如下提示,即表示GCC For Arm已经全局可用。
4.5 通过Msys2获取Make工具
本节主要目的是获取使用Makfile编译所必须的Make工具,由于历史原因,GCC原本是linux平台下的编译套件,Make工具也集成在其中,因此需要安装x86平台的GCC编译器以获取Make工具,x86平台的GCC,其包名称为:mingw-w64。
GCC下载渠道很多,但笔者这里通过msys2安装,msys2是一个包管理工具,正如其官方介绍:“MSYS2 is a collection of tools and libraries providing you with an easy-to-use environment for building, installing and running native Windows software”。Windows平台下的很多开源工具一开始都在linux下流行,因此,其很多依赖库都是linux移植而来,Msys2就是这样一个工具,可以方便的下载这些依赖,以支持开发者在Windows平台开发和运行原生应用。本文中,我们仅用它安装x86平台GCC以获取Make工具,实际上,开发过程中的很多依赖,都可以用它下载,下面开始安装步骤。
- 安装Msys2,Msys2的安装,选择合适的路径,进度条走完即可。
2.打开Msys,运行命令:pacman -Syu,更新数据库。
3.运行命令:pacman -S mingw-w64-x86_64-toolchain,安装GCC工具链,出现提示时,回车全部安装即可,当前的Msys2已添加中科大和清华镜像源,下载安装很快。
安装完成后,进入Msys2安装目录,GCC的bin目录下的mingw32-make.exe即是我们需要的make工具。
4.将mingw32-make.exe所在目录加入环境变量,在控制台输入命令:mingw32-make,出现如下提示即表示加入全局变量成功。
5.提取make工具至GCC For Arm,这一步是可选的,不是必须,如有需要,可将mingw32-make.exe复制到arm-none-eabi-gcc所在目录,更名为make.exe。此时,make也全局可用。
4.6配置OpenOCD
经过以上步骤,生成的工程可以正常编译,并生成烧录文件,而调试功能,除了依赖于arm-none-eabi-gdb,还需要配置OpenOcd以适配仿真器,建立目标芯片,仿真器以及PC的连接。
本文给出的OpenOcd版本为0.11,解压即可使用,为了开发方便,需要将openocd.exe的路径加入环境变量,方法请参照2.3节。
通常调试目标芯片都使用确定的仿真器,因此,为了使用方便,可以直接从Openocd的目录中,将接口配置文件和目标配置文件复制到STM32的工程目录,以便使用:
- 拷贝接口配置文件,进入Openocd目录下的scripts目录,从interface目录中复制仿真器配置文件至STM32工程根目录。本文示例使用ST-LINK,因此拷贝stlink-v2.cfg文件,读者可根据需要拷贝其他仿真器的配置文件。
- 拷贝目标配置文件,本文以STM32F407作为目标芯片,因此拷贝scripts\target目录下的stm32f4x.cfg文件
拷贝完成后,工程目录内容如下所示:
4.7 使用Make编译STM32工程
在4.2中生成的工程已经包含了Makefile,至此,编译器和make工具均已就绪,我们可以尝试在命令行下编译STM32工程。
- 在工程目录下打开命令行
进入到4.2节生成的STM32工程根目录(Makefile所在目录),在该目录下进入控制台,对于WIN10系统,可以按住Shift,然后在根目录下右键打开powershell。
2.执行mingw32-make或make指令,指令后跟-j参数,可启动并行编译,充分利用处理器多核性能,加快编译速度
可以看到,Make编译完成后,控制台打印了程序大小信息,生成了elf文件,以及hex和bin文件以及一堆lst反汇编文件,这些文件以及编译的中间文件,都存放在工程下自动新建的build目录。其中:
- Elf文件可用于下一步骤,GDB调试使用
- Hex文件可用于BootLoader烧录
- Map文件可以查看变量和函数运行地址
- lst文件可以查看反汇编代码
4.8 使用arm-none-eabi-gdb烧录程序
GDB调试的前提是OpenOcd调试工具已经正确完成硬件连接,并开启调试端口,因此,调试前需要运行openpcd命令,连接目标芯片,且调试过程中,Openocd服务需要持续运行。
操作步骤如下:
1.连接开发板,上电,确保仿真器正确连接。
2.打开调试端口,在工程目录下打开powershell,由于已经将接口配置文件和目标配置文件复制到工程目录,直接使用目录下的配置文件即可,执行指令:openocd.exe -f .\stlink-v2.cfg -f .\stm32f4x.cfg.执行结果如下,打开3333调试端口成功:
3.开启gdb调试器,加载上一节中编译生成的elf文件,由于Openocd需要持续运行,我们新开一个powershell终端,执行如下指令(elf文件名根据实际情况填写):
arm-none-eabi-gdb.exe .\build\STM32_LED.elf
终端将打印如下内容:
4.连接至3333调试端口
执行target指令即可连接至openocd开启的3333调试端口,成功连接后,gdb提示产生了复位中断,一切正常。
5.烧录elf文件至目标芯片
在GDB工具中输入指令:load,即可将启动gdb时加载的elf文件烧录至目标芯片。烧录成功的打印内容如下,其他调试指令读者可参照3.2节自行尝试:
五、利用Vscode将命令行工具图形化
5.1 让Vscode正确识别工程
VScode本质上只是一个文本编辑器,如果要使其自动识别工程下的C语言源文件,需要对C/C++插件进行正确的配置才能获得我们想要的效果:
1.源文件搜索路径
有些工程的源文件并不在工程目录下,可能在别的SDK路径下,此时如果不添加源文件路径,C/C++插件无法找到源文件并正确跳转。(本文示例工程不存在SDK路径,所有用到的源文件均在工程目录下,因此添加工程路径即可)
2.预编译宏定义
许多厂商提供的库都支持自家多款处理器,通常使用不同的预编译宏定义来进行条件编译以适配不同处理器,因此,需要在工程中添加预编译宏定义才能正确编译和高亮。(对于本文示例来说,编译部分由Makefile决定,因此,Makefile中已经添加了预编译宏,编译不会出错。然而,C/C++显然它不会去读取Makefile内容获取工程应加的预编译宏,无法高亮显示正确的代码,因此预编译宏应由我们手动添加)
3.编译器路径
插件要想参与编译,必然要获取编译器的路径,在VScode的C/C++插件中,编译器路径必须完整到编译器的可执行文件。
4.C语言标准
不同的C语言标准下,支持的语法会稍有区别,因此,必须让C/C++插件知道工程的源文件代码以哪一个版本的C语言标准提供识别支持(如果不知道请填C99,大部分人所学的C语言版本普遍为C99,使用GNU工具链则填写GNU99)。
5.高亮感知模式
这部分在该插件下提供了众多可选项,目的是为了更好的适配目标平台进行代码高亮。(本示例中,选用“gnu11”)
以上内容的配置,网上大部分教程通常直接让读者新建c_cpp_properties.json文件配置,该方法适合对Vscode有一定了解的读者,对新手并不友好。其实,Vscode本身提供了图形化的配置界面。
选择图形化配置界面后,将本节提到的几个参数作如下配置:
此处要特别注意预编译宏的添加,具体添加什么内容,可从Makefile中的C_DEFS字段获知。
做完以上更改后,工程目录夹下将会新增.vscode文件夹,目录下新增了c_cpp_properties.json配置文件,打开该文件,可以看到,里面已经有了自定义的ARM配置。
删除并不需要的默认Win32配置后,c_cpp_properties.json中的配置即为以后该工程的默认配置:
5.2 在Vscode下使用Make编译工程
上一节我们通过配置C/C++插件,让代码编辑和语法高亮,变量函数跳转这些功能正常使用,体验甚至超过Keil、IAR、Eclipse等主流IDE,本节我们尝试新建build任务,让Vscode可以在图形界面下编译工程。新建build任务笔者暂未找到图形化配置方法,但可以参考Vscode的build任务模板进行配置。
1.创建build任务模板,操作如下图所示:
2.配置build任务和clean任务,最终配置如下
3.尝试执行build命令编译工程
在Vscode界面下按Ctrl+Shfit+B可以唤出任务列表,可以看到task.json中配置的两个任务,一个build,一个clean。
执行build任务后,可以看到,其效果与在命令行下执行make -j或者mingw32-make -j的效果一致,都在build目录下生成了.elf与.hex文件,其本质是编译Makefile中的all伪目标,因此,make -j all才是命令的完整写法,我们使用了简化写法。
4.尝试执行clean命令清除编译输出文件
某些情况下,需要清除所有输出文件,重新进行全新编译而不是增量编译,此时,需要编译Makefile中的clean伪目标,查看Makefile可以知道,其真实操作是在powershell下执行了rm指令,删除中间文件,只不过由make工具来帮助完成解析执行。
执行clean任务后,build目录下的所有文件都将被清除,读者可以自行尝试。
5.3 在Vscode下烧录调试
经过上一节的内容,我们已经可以在Vscode下正常编辑编译代码,本节我们将通过Cortex Debug插件,在Vscode下实现正常的烧录,调试。
通过前面的介绍,我们知道,调试需要先开启Opencod调试服务器端口,再使用gdb调试器连接至调试端口,有了这些基础,才能正常调试。而Vscode下的Cortex Debug插件则在GDB调试器的基础上做了深度集成,支持众多调试服务器。如果抽象一点,可以这样理解:Cortex Debug = GDB + GDB Server,其中,GDB Server可以是Openocd GDB Server、J-Link GDB Server、ST-LINK GDB Server、pyOCD GDB Server等的任意一种,本文使用的是Openocd GDB Server。
配置Cortex Debug的步骤如下:
1.新建调试配置模板
2.复制SVD文件至工程目录
由于Cortex Debug支持加载SVD文件,因此,需要自己找到目标芯片的SVD文件,拷贝至工程根目录,供Cortex Debug加载使用。SVD是芯片的资源描述文件,相当于数据化的芯片手册,有了SVD文件的支持,调试时可以查看芯片寄存器和外设的状态。
SVD文件可以在芯片的支持包中找到,具体到本文示例,作者是在Keil的包路径中将所需SVD文件提取出来,其路径为:xx\Keil\Packs\Keil\STM32F4xx_DFP\2.13.0\CMSIS\SVD。其中包含了STM32系列F4系列所有芯片的SVD文件:
本文示例所用芯片为STM32F407,因此将STM32F40x.svd文件拷贝至工程目录下:
3.配置Openocd调试服务器
步骤1后,.vscode文件夹下将新建launch.json文件,我们需要对其默认内容稍作修改:
4.写个简单的流水灯
修改\Core\Src\main.c,在GPIO初始化函数MX_GPIO_Init()下,添加如下两行代码,使LED0与LED1的初始状态相反:
HAL_GPIO_WritePin(GPIOF,LED0_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOF,LED1_Pin,GPIO_PIN_RESET);
修改\Core\Src\main.c,在While主循环中添加如下两行代码,使LED状态50ms反转一次。
HAL_GPIO_TogglePin(GPIOF,LED0_Pin|LED1_Pin);
HAL_Delay(50);
5.调试运行流水灯
在Vscode界面按下F5,或者点击Debug按钮,后台将执行编译任务,若编译无误,将自动启动Openocd服务,烧录调试,开始运行时会先进入复位中断,点击运行即可正常运行,可以在运行过程中随时打断点,查看寄存器和变量,单步运行。
编辑界面如下,输入时可以智能提示补全:
调试界面如下:
六、总结
至此,本文的最终目的已经达到了,目标虽然是自己搭建集成开发环境,但实际是为了深入理解工具链、编译流程、调试原理。很多技术细节限于篇幅无法更加深入的探索,而Makefile、启动文件、链接脚本、Openocd、编译器参数、每一个都值得深究,本文算是挖坑文,希望以后有空继续在深入理解工具链的系列文章中补全,再会!
本文用到的所有示例代码及最终工程,已开源,请在本人代码仓库中下载:
更多推荐
所有评论(0)