2022-11-12 18:03:18 +08:00
# VSCode+Ozone开发STM32的方法
< center > < b > < font face = "楷体" > neozng1@hnu.edu.cn< / font > < / b > < / center >
[TOC]
2022-11-12 19:37:16 +08:00
> TODO:
>
> 1. 添加一键编译+启用ozone调试/一键编译+下载的脚本,使得整个进一步流程自动化
> 2. 增加更多的知识介绍
2022-11-12 18:03:18 +08:00
## 前言
了解过嵌入式开发的你一定接触过Keil, 这款20世纪风格UI的IDE伴随很多人度过了学习单片机的岁月。然而由于其缺少代码补全、高亮和静态检查的支持, 以及为人诟病的一系列逆天的设置、极慢的编译速度( 特别是在开发HAL库时) , 很多开发者开始转向其他IDE。
2022-11-12 19:46:11 +08:00
IAR、CubeIDE等都是广为使用的“其他”IDE, 但是他们也有各自的缺点, 不能让笔者满意。作为IDE界的艺术家, JetBrains推出的Clion也在相当程度上完善了对嵌入式开发的支持。不过, 在体验过多款IDE后, 还是**VSCode**这款高度定制化的编辑器最让人满意。强大的补全和snippet以及代码高亮、定义跳转甩KEIL十条街。
2022-11-12 18:03:18 +08:00
而Ozone则是SEGGER(做jilnk的)推出的调试应用, 支持变量实时更新, 变量曲线可视化, SEGGER RTT日志, DBG虚拟串口等功能, 大大扩展了调试的功能。很多人习惯使用串口进行可视化调试, 如vofa, 串口调试助手等。然而通过这些方式进行调试, 都是对内核有**侵入性**的, 会占有内核资源并且导致定时器的时间错乱。由于DBG有单独连接到FLASH和CPU寄存器的高速总线( 类似于DMA) , 可以在不影响程序正常运行的情况下以极高的频率直接获取变量值。
下面, 将从工具链介绍、环境配置以及调试工作流三个方面介绍以VSCode为编辑器, Ozone为调试接口的开发环境。
开发的大致流程为:
~~~mermaid
graph LR
CubeMX进行初始化 --> VSCode编写代/进行编译/简单调试 --> Ozone变量可视化调试+log
~~~
2022-11-12 19:46:11 +08:00
***本教程不仅希望教会你如何配置环境,同样会告诉你每一步究竟是在做什么,而不是简单的复制黏贴邯郸学步。***
2022-11-12 18:03:18 +08:00
## 前置知识
1. 计算机速成课:[Crash Course Computer Science](https://www.bilibili.com/video/av21376839/?vd_source=ddae2b7332590050afe28928f52f0bda)
2. 从零到一打造一台计算机:
[编程前你最好了解的基本硬件和计算机基础知识(模拟电路) ](https://www.bilibili.com/video/BV1774114798/?spm_id_from=333.788.recommend_more_video.11&vd_source=ddae2b7332590050afe28928f52f0bda )
[编程前你最好了解的基本硬件和计算机基础知识(数字电路) ](https://www.bilibili.com/video/BV1Hi4y1t7zY/?spm_id_from=333.788.recommend_more_video.0 )
[从0到1设计一台计算机 ](https://www.bilibili.com/video/BV1wi4y157D3/?spm_id_from=333.788.recommend_more_video.0&vd_source=ddae2b7332590050afe28928f52f0bda )
3. C语言基础: [程序设计入门——C语言](https://www.icourse163.org/course/ZJU-199001?from=searchPage& outVendor=zw_mooc_pcssjg_)
***务必学完以上课程再开始本教程的学习。***
## 预备知识
1. 软件安装(队伍NAS和资料硬盘内提供了所有必要的依赖,安装包和插件)
2. C语言从源代码到.bin和.hex等机器代码的编译和链接过程
3. C语言的内存模型
4. C语言标准, 动态链接库和静态编译的区别, 一些编译器的常用选项
5. STM32F4系列的DBG外设工作原理
### 编译全过程
C语言代码由固定的词汇( 关键字) 按照固定的格式( 语法) 组织起来, 简单直观, 程序员容易识别和理解, 但是CPU只能识别二进制形式的指令, 并且这些指令是和硬件相关的( 感兴趣的同学可以搜索**指令集**相关内容) 。这就需要一个工具, 将C语言代码转换成CPU能够识别的二进制指令, 对于我们的x86平台windows下的程序就是.exe后缀的文件; 对于单片机, 一般来说是.bin或.hex等格式的文件( 调试文件包括axf和elf) 。
能够完成这个转化过程的工具是一个特殊的软件,叫做**编译器( Compiler) **。常见的编译器包括开源的GNU GCC, windows下微软开发的visual C++, 以及apple主导的llvm/clang。编译器能够识别代码中的关键字、表达式以及各种特定的格式, 并将他们转换成特定的符号, 也就是**汇编语言**(再次注意汇编语言是平台特定的),这个过程称为**编译( Compile) **。
对于单个.c文件, 从C语言开始到单片机可识别的.bin文件, 一般要经历以下几步:
2022-11-12 20:00:24 +08:00
![img ](assets\v2-2797ea99d0d38eb9996993bb0ad77ab2_720w.webp )
2022-11-12 18:03:18 +08:00
首先是编译**预处理**Preprocessing, 这一步会展开宏并删除注释, 将多余的空格去除。预处理之后会生成.i文件。
然后,开始**编译**Compilation的工作。编译器会将源代码进行语法分析、词法分析、语义分析等, 根据编译设置进行性能优化, 然后生成汇编代码.s文件。汇编代码仍然是以助记符的形式记录的文本, 比如将某个地址的数据加载到CPU寄存器等, 还需要进一步翻译成二进制代码。
下一步就是进行**汇编**Assemble, 编译器会根据汇编助记符和机器代码的查找表将所有符号进行替换, 生成.o .obj等文件。但请注意, 这些文件并不能直接使用( 烧录) , 我们在编写代码的时候, 都会包含一些**库**,因此编译结果应当有多个.o文件。我们还需要一种方法将这些目标文件缝合在一起, 使得在遇到函数调用的时候, 程序可以正确地跳转到对应的地方执行。
最后一步就由链接器Linker( 也称LD) 完成, 称为**链接**Linking。比如你编写了一个motor.c文件和.h文件, 并在main.c中包含了motor.h, 使用了后者提供的`MotorControl()`函数。那么,链接器会根据编译器生成.obj文件时留下的函数入口地址, 将main.o里的调用映射到生成的motor.o中。链接完成后, 就生成了单片机可以识别的可执行文件, 通过支持的串口或下载器烧录, 便可以运行。
> 另外,上图可以看到左侧的**静态库**,包括`.lib .a`, 比如我们在STM32中使用的DSP运算库就是这种文件。他在本质上和.o文件相同, 只要你在你编写的源文件中包含了这些库的头文件, 链接器就可以根据映射关系找到头文件中声明的函数在库文件的地址。( 直接提供库而不是.c文件, 就可以防止源代码泄露, 因此一些不开源的程序会提供函数调用的头文件和接口具体实现的库; 你也可以编写自己的库, 感兴趣自行搜索)
链接之后,实际上还要进行不同代码片段的重组、地址重映射,详细的内容请参看:[C/C++语言编译链接过程](https://zhuanlan.zhihu.com/p/88255667), 这篇教程还提供了以GCC为例的代码编译示例。
### C语言内存模型
2022-11-12 20:00:24 +08:00
< img src = "assets \image-20221112160213066.png" alt = "image-20221112160213066" style = "zoom:80%;" />
2022-11-12 18:03:18 +08:00
以上是C语言常见的内存模型, 即C语言的代码块以及运行时使用的内存( 包括函数、变量等) 的组织方式。
> 有些平台的图与此相反,栈在最下面(内存低地址),其他区域都倒置,不影响我们理解
**代码段**即我们编写的代码,也就是前面说的编译和链接之后最终生成的可执行文件占据的空间。一些常量,包括字符串和使用`const`关键字修饰的变量被放在常量存储区。`static`修饰的静态变量(包括函数静态变量和文件静态变量)以及全局变量放在常量区上面一点的全局区(也称静态区)。
然后就是最重要的**堆**和**栈**。在一个代码块内定义的变量会被放在栈区,一旦离开作用域(出了它被定义的`{}`的区域),就会立刻被销毁。在调用函数或进入一个用户自定义的`{}`块, 都会在栈上开辟一块新的空间, 空间的大小和内存分配由操作系统或C库自动管理。**一般来说,直接通过变量访问栈内存,速度最快**(对于单片机)。而堆则是存储程序员自行分配的变量的地方,即使用`malloc(),realloc() ,new`等方法获取的空间,都被分配在这里。
> 在CubeMX初始化的时候, Project mananger标签页下有一个Linker Setting的选项, 这里是设置最小堆内存和栈内存的地方。如果你的程序里写了大规模的数组, 或使用`malloc()`等分配了大量的空间, 可能出现栈溢出或堆挤占栈空间的情况。需要根据MCU的资源大小, 设置合适的stack size和heap size。
### C language标准和编译器
不同的C语言标准( 一般以年份作代号) 支持的语法特性和关键字不同, 拥有的功能也不同。一般来说语言标准都是向前兼容的, 在更新之后仍然会保存前代的基本功能支持( legacy support) 。不过, 为了程序能够正常运行, 我们还需要一些硬件或平台支持的组件。比如`malloc()`这个函数, 在linux平台和windows平台上的具体实现就相去甚远, 跟单片机更是差了不止一点。前两者一般和对应的操作系统有关, 后者在裸机上则是直接通过硬件或ST公司提供的硬件抽象层代码实现。
然而, 不同编译器提供的代码实现也不尽相同, 比如使用clang和gcc这两种c语言编译器, 他们对于一些标准库( 也称C库, 包括stdio, stdlib, string等在内的实现) 的函数的实现就不太一样。再如`__packed`是arm-cc提供的一个字节不对齐关键字, 在一些其他编译器中就不支持这种实现。
以前大家常用的KEIL使用的是ARM提供的arm-cc工具链( 非常蛋疼, 甚至不支持uint8_t=0b00001111这种二进制定义法) , 而该教程选用的是开源的**Arm GNU Toolchain**。在非目标机且和目标机平台不同的平台上进行开发被成为**跨平台开发**,进行的编译也被成为**交叉编译**(在一个平台上生成另一个平台上的 可执行代码)。
> 工具链包含了编译器, 链接器以及调试器等开发常用组件。我们使用的Arm GNU toolchain中, 编译器是`arm-none-eabi-gcc.exe`,链接器是`arm-none-eabi-ld.exe`,调试器则是`arm-none-eabi-gdb.exe`。通过跨平台调试器和j-link/st-link/dap-link, 我们就可以在自己的电脑上对异构平台( 即单片机) 的运行进行调试了。
### Debug外设工作原理
2022-11-12 20:00:24 +08:00
![image-20221112145717063 ](assets\image-20221112145717063.png )
2022-11-12 18:03:18 +08:00
DBG支持模块( 红框标注部分, 也可以看作一个外设) 通过一条专用的AHB-AP总线和调试接口相连( Jtag或swd) , 并且有与**数据**和**外设**总线直接相连的桥接器。它还同时连接了中断嵌套管理器( 因此同样可以捕获中断并进行debug) 和ITM、DWT、FPB这些调试支持模块。因此DBG可以直接获取内存或片上外设内的数据而不需要占用CPU的资源, 并将这些数据通过专用外设总线发送给调试器, 进而在上位机中读取。
FPB是flash patch breakpoint闪存指令断点的缩写, 用于提供代码断点插入的支持, 当CPU的指令寄存器读取到某一条指令时, FPB会监测到它的动作, 并通知TPIU暂停CPU进行现场保护。
DWT是data watch trace数据观察与追踪单元的缩写, 用于比较debug变量的大小, 并追踪变量值的变化。当你设定了比较断点规则( 当某个数据大于/小于某个值时暂停程序) 或将变量加入watch进行查看, DWT就会开始工作。DWT还提供了一个额外的计时器, 即所有可见的TIM资源之外的另一个硬件计时器( 因为调试其他硬件定时器的计时由于时钟变化可能定时不准, 而DWT定时器是始终正常运行的) 。它用于给自身和其他调试器模块产生的信息打上时间戳。我们的bsp中也封装了dwt计时器, 你可以使用它来计时。
ITM是instrument trace macrocell指令追踪宏单元的缩写, 它用于提供非阻塞式的日志发送支持( 相当于大家常用的串口调试) , SEGGER RTT就可以利用这个模块, 向上位机发送日志和信息。
以上三个模块都需要通过TPIU( trace port interface unit) 和外部调试器( j-link等) 进行连接, TPIU会将三个模块发来的数据进行封装并通过DWT记录时间, 发送给上位机。
## 环境配置
2022-11-12 19:46:11 +08:00
- ***所有需要编辑的配置文件都已经在basic_framework的仓库中提供, 如果不会写, 照猫画虎。***
2022-11-13 14:40:21 +08:00
- 安装STM32CubeMX, 并安装F4支持包和DSP库支持包
2022-11-12 18:03:18 +08:00
2022-11-13 15:27:40 +08:00
- 安装VSCode, 并安装以下插件:
- C/C++: 提供C/C++的调试和代码高亮支持
- Better C++ Syntax: 提供更丰富的代码高亮和智能提示
- C/C++ Snippets: 提供代码块( 关键字) 补全
- Cortex-Debug, Cortex-Debug: Device Support Pack - STM32F4: 提供调试支持
- IntelliCode, Makfile Tools: 提供代码高亮支持
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172157533 ](assets\image-20221112172157533.png )
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172208749 ](assets\image-20221112172208749.png )
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172221756 ](assets\image-20221112172221756.png )
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172239386 ](assets\image-20221112172239386.png )
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172254809 ](assets\image-20221112172254809.png )
2022-11-12 18:03:18 +08:00
- 安装MinGW, 等待界面如下:
2022-11-12 20:00:24 +08:00
![image-20221112172051589 ](assets\image-20221112172051589.png )
2022-11-12 18:03:18 +08:00
安装好后, 打开MinGW后将所有的支持包勾选, 然后安装:
2022-11-12 20:00:24 +08:00
![image-20221112172348408 ](assets\image-20221112172348408.png )
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172420037 ](assets\image-20221112172420037.png )
2022-11-12 18:03:18 +08:00
安装完以后, 将MinGW的bin文件夹添加到环境变量中的path下, 按下菜单键搜索**编辑系统环境变量**打开之后:
2022-11-12 20:00:24 +08:00
![image-20221112172716320 ](assets\image-20221112172716320.png )
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
图片看不清请打开原图。验证安装:
2022-11-12 18:03:18 +08:00
打开命令行( win+R, cmd, 回车) , 输入`gcc -v`,如果没有报错,并输出了一堆路径和参数说明安装成功。
2022-11-13 14:40:21 +08:00
- 配置gcc-arm-none-eabi环境变量, **把压缩包解压以后放在某个地方**, 然后同上, 将工具链的bin添加到PATH:
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112172858593 ](assets\image-20221112172858593.png )
2022-11-12 18:03:18 +08:00
< center > 安装路径可能不一样,这里要使用你自己的路径而不是直接抄< / center >
验证安装:
打开命令行,输入`arm-none-eabi-gcc -v`,如果没有报错,并输出了一堆路径和参数说明安装成功。
> 添加到环境变量PATH的意思是, 当一些程序需要某些依赖或者要打开某些程序时, 系统会自动前往PATH下寻找对应项。**一般需要重启使环境变量生效。**
2022-11-13 14:40:21 +08:00
- **将OpenOCD解压到一个文件夹里**, 稍后需要在VSCode的插件中设置这个路径。
2022-11-12 18:03:18 +08:00
- CubeMX生成代码的时候工具链选择makefile
2022-11-12 20:00:24 +08:00
![image-20221112173534670 ](assets\image-20221112173534670.png )
2022-11-12 18:03:18 +08:00
生成的目录结构如下:
2022-11-12 20:00:24 +08:00
![image-20221112174211802 ](assets\image-20221112174211802.png )
2022-11-12 18:03:18 +08:00
Makefile就是我们要使用的构建规则文件。
2022-11-13 14:40:21 +08:00
> **如果你使用basic_framework, 不需要重新生成代码。**
2022-11-12 18:03:18 +08:00
## VSCode编译和调试配置
2022-11-12 19:37:16 +08:00
VSCode常用快捷键包括:
| 功能 | 快捷键 |
| ---------------------- | ------------- |
| 选中当前行 | Ctrl+L |
| 删除当前行 | Ctrl+Shift+K |
| 重命名变量 | F2 |
| 跳转到定义 | Ctrl+点击 |
| 在打开的文件页中切换 | Ctrl+Tab |
| 在当前文件查找 | Ctrl+F |
| 在整个项目文件夹中查找 | Ctrl+Shift+F |
| 查找所有引用 | Alt+Shift+F12 |
| 返回上一动作 | Alt+左 |
2022-11-12 19:46:11 +08:00
更多快捷键可以按ctrl+K再按ctrl+S显示, 并且可以修改成你最习惯的方式。此外, 使用Snippets可以大幅度提高重复性的代码编写速度, 它可以直接帮你补全一个代码块( 如for、while、switch) ; 补全和snippet都使用`Tab`键接受代码提示的提议,通过↑和↓键切换提示。
2022-11-12 19:37:16 +08:00
2022-11-12 18:03:18 +08:00
### 编译
2022-11-13 15:27:40 +08:00
为了提供完整的代码高亮支持, 需要配置Makefile tools插件的make程序路径, `ctrl+,`打开设置, 搜索make path找到设置并填写:
![image-20221113152513343 ](assets\image-20221113152513343.png )
> mingw32-make就是下面介绍的make工具( 配合makefile替代手动调用gcc) 。这里之所以只要输入mingw32-make而不用完整路径, 是因为我们将mingw的bin文件夹加入环境变量了, 因此系统会在PATH下自动寻找对应项
用VSCode打开创建的项目文件夹, **Makefile Tools插件会询问你是否帮助配置intellisense, 选择是。**
此时就可以享受intellicode带来的各种便利的功能了。我们的项目使用Makefile进行编译, 在之前的编译介绍中, 以GCC编译器为例, 如果需要编译一个文件, 要输入如下命令:
2022-11-12 19:37:16 +08:00
```shell
gcc your_source_code_name.c -o output
```
然而,你面对的是一个拥有几百个.c和.h文件以及大量的链接库, 如果要将所有文件都输入进去, 那将是一件苦恼的事。Makefile在gcc命令上提供了一层抽象, 通过编写makefile来指定参与编译的文件和编译选项, 再使用`make`命令进行编译, 它会自动将makefile的内容“翻译”为gcc命令。这样, 编译大型项目就不是一件困难的事了。
> 实际上, 在使用keil MDK开发的时候, 它调用的仍然是底层的arm cc工具链中的编译器和链接器, 在配置“魔术棒”添加项目文件以及包含目录的时候, 实际做的使其和makefile差不多。keil使用的参数可以在魔棒的C/C++选项卡下看到。
对于一个已经拥有makefile的项目, 打开一个终端, 输入:
```shell
mingw32-make -j24 # -j参数表示参与编译的线程数,一般使用-j12
```
> 注意, 多线程编译的时候输出的报错信息有时候可能会被打乱( 多个线程同时往一个terminal写入程序运行的信息) , 要是看不清报错, 请使用`mingw32-make`,不要进行多线程编译。
2022-11-12 20:00:24 +08:00
![image-20221112191712534 ](assets\image-20221112191712534.png )
2022-11-12 19:37:16 +08:00
就会开始编译了。你可以看到大致如下的输出:
```shell
arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -DUSE_HAL_DRIVER -DSTM32F407xx -DARM_MATH_CM4 -DARM_MATH_MATRIX_CHECK -DARM_MATH_ROUNDING -IHAL_N_Middlewares/Inc -IHAL_N_Middlewares/Drivers/STM32F4xx_HAL_Driver/Inc -IHAL_N_Middlewares/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy -IHAL_N_Middlewares/Drivers/CMSIS/Device/ST/STM32F4xx/Include -IHAL_N_Middlewares/Drivers/CMSIS/Include -IHAL_N_Middlewares/Drivers/CMSIS/DSP/Include -IHAL_N_Middlewares/Middlewares/ST/STM32_USB_Device_Library/Core/Inc -IHAL_N_Middlewares/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc -IHAL_N_Middlewares/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS -IHAL_N_Middlewares/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F -IHAL_N_Middlewares/Middlewares/Third_Party/FreeRTOS/Source/include -IHAL_N_Middlewares/Middlewares/Third_Party/FreeRTOS/Source/include -IHAL_N_Middlewares/Middlewares/Third_Party/SEGGER/RTT -IHAL_N_Middlewares/Middlewares/Third_Party/SEGGER/Config -IHAL_N_Middlewares/Middlewares/ST/ARM/DSP/Inc -Iapplication -Ibsp -Imodules/algorithm -Imodules/imu -Imodules/led_light -Imodules/master_machine -Imodules/motor -Imodules/referee -Imodules/remote -Imodules/super_cap -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_pwr_ex.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_pwr_ex.lst HAL_N_Middlewares/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c -o build/stm32f4xx_hal_pwr_ex.o
```
仔细看你会发现, make命令根据makefile的内容, 调用arm-none-eabi-gcc编译器, 传入了一堆的参数以及编译选项然后运行。
最后输出的结果如下:
```shell
text data bss dec hex filename
31100 484 35916 67500 107ac build/basic_framework.elf
arm-none-eabi-objcopy -O ihex build/basic_framework.elf build/basic_framework.hex
arm-none-eabi-objcopy -O binary -S build/basic_framework.elf build/basic_framework.bin
```
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
代表了生成的可执行文件的大小以及格式和内容。.elf文件就是我们需要传递给调试器的东西, 在[使用VSCode调试](###简单调试)部分会介绍。
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
当然了,你可能觉得每次编译都要在命令行里输入参数,太麻烦了。我们可以编写一个`task.json`, 这是VSCode的一个任务配置, 内容大致如下:
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
```json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"label": "build task", // 任务标签
"type": "shell", // 任务类型,因为要调用mingw32-make,是在终端(CMD)里运行的,所以是shell任务
"command": "mingw32-make -j24",// 任务命令
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
```
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
这样, 你就可以点击VSCode工具栏上方的Terminal->Run task选择你刚刚配置的任务开始编译了。**更方便的方法是使用快捷键:`ctrl+shift+B`。**
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112192133103 ](assets\image-20221112192133103.png )
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
> 还没配置任务的时候, 需要在Terminal标签页中选择Configure Tasks... 创建一个新的.json文件。
>
> P.S. VSCode中的大部分配置都是通过json文件保存的。当前工作区的配置在项目文件夹中的.vscode下, 全局配置在设置中修改。全局配置在当前工作区没有配置的时候会生效, 反之被前者覆盖。
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
### 如果你编写了新的代码文件...
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
Makefile的大部分内容在CubeMX初始化的时候就会帮你生成。如果新增了.c的源文件, 你需要在`C_SOURCES`中新增:
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112192509718 ](assets\image-20221112192509718.png )
2022-11-12 18:03:18 +08:00
2022-11-12 19:37:16 +08:00
换行需要在行尾加反斜杠\\
如果新增了头文件,在`C_INCLUDES`中新增头文件所在的文件夹:
2022-11-12 20:00:24 +08:00
![image-20221112192610543 ](assets\image-20221112192610543.png )
2022-11-12 19:37:16 +08:00
换行需要在行尾加反斜杠\\
**添加完之后,重新编译即可**。
> 和KEIL新增文件的方式很相似, 但是更方便。
2022-11-12 18:03:18 +08:00
2022-11-13 14:40:21 +08:00
### 简单的调试配置
2022-11-12 18:03:18 +08:00
> 在VSCode中调试不能像Keil一样查看变量动态变化, 但是支持以外的所有操作, 如查看外设和反汇编代码, 设置断点触发方式等。
2022-11-13 14:40:21 +08:00
用于调试的配置参考这篇博客:[Cortex-debug 调试器使用介绍](https://blog.csdn.net/qq_40833810/article/details/106713462)。你需要配置**arm gnu工具链的路径**, **OpenOCD的路径**( 使得GDB调试器可以找到OpenOCD并调用它, 从而连接硬件调试器如j-link等) , 该工作区( 文件夹) 的**launch.json文件**( 用于启动vscode的调试任务) 。
VSCode `ctrl+,` 进入设置, 通过搜索找到cortex-debug插件的设置。
1. 搜索armToolchainPath, 设置你的arm gcc toolchain的`bin`文件夹( bin是binary的缩写, 实际上文件夹内部是一些可执行文件) 。
2. 搜索openocdPath, 设置你的openocd路径( 需要包含到openocd的可执行文件) 。
**注意**, windows下路径需要使用两个反斜杠`\\`代表下一级文件夹。
***其他配置需要的文件已经全部在basic_framework中提供***,包括`openocd.cfg STM32F407.svd .vscode/launch.json`。
2022-11-12 18:03:18 +08:00
2022-11-12 20:40:36 +08:00
2022-11-12 18:03:18 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113125439857 ](assets\image-20221113125439857.png )
< center > 主要需要配置这2个路径< / center >
如果教程中的启动json文件看不懂, 请看仓库里的`.vscode`下的`launch.json`,照葫芦画瓢。
2022-11-12 18:03:18 +08:00
根目录下已经提供了C板所需的.svd和使用无线调试器时所用的openocd.cfg配置文件。
2022-11-12 19:37:16 +08:00
然后选择run and debug标签页, 在选项中选择你配置好的选项, 开始调试。**或者使用快捷键:`F5`。**
2022-11-12 18:03:18 +08:00
2022-11-12 20:00:24 +08:00
![image-20221112180103750 ](assets\image-20221112180103750.png )
2022-11-11 21:49:03 +08:00
2022-11-13 14:40:21 +08:00
我们的仓库中默认提供了两种下载器的支持, dap-link( 无线调试器属于这一种) 和j-link( 包括小的j-link OB和黑色大盒子jlink) 。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
### 调试介绍
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
开始调试后,显示的界面如下:
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![](assets\vscodedebug.png)
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
1. 变量查看窗口, 包括当前调用栈( 当前作用域或代码块) 内的局部变量、当前文件的静态变量和全局变量。register选项卡可以查看cpu内核的寄存器数值。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
2. 变量watch窗口。右键单击要查看的变量, 选择watch加入查看。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113131044191 ](assets\image-20221113131044191.png )
还支持直接运行到指针所选处( Run to Cursor) 以及直接跳转到指针处执行( Jump to Cursor) 。添加行内断点( 若一个表达式由多个表达式组成) 也是很方便的功能, 可以帮助进一步定位bug。
右键点击添加到watch窗口的变量, **可以临时修改它们的值。**调参的时候非常好用。
VSCode提供的一个最大的便利就是, 你可以将鼠标悬停在需要查看的变量上, **不需要添加到watch就能观察变量值。**如果是指针还可以自动解析,获取解引用后的值。结构体也支持直接展开。
![image-20221113133624273 ](assets\image-20221113133624273.png )
3. 调用栈。表明在进入当前代码块之前调用了哪些函数,称之为栈也是因为调用的顺序从下至上。当前函数结束之后栈指针会减小,控制权会返还给上一级的调用者。通过调用栈可以确认程序是**如何**(按怎样的顺序)运行到当前位置的。
4. 片上外设。这里可以查看外设的**控制寄存器**和**状态寄存器**的值, 如果通过断点无法定位bug, 则需要查找数据手册和Cortex M4指南的相关内容, 根据寄存器值来判断程序当前的情况。
5. 断点。所有添加的断点都会显示于此, 注意, 不像我们自己的电脑, 单片机的DBG外设对断点的数量有限制( 资源所限) , 超过5个断点会导致debug失败, 此时将断点减少即可。
6. 调试控制台。调试器输出的信息会显示在这里,要**查看**和**追踪**的变量的信息也会显示在这里。如果调试出现问题, 报错信息同样也会在这里显示。要是出现异常, 可以复制这里的信息在搜索引擎里查找答案, 不过最好的方法是查询gdb和openocd的官方文档。
7. 调试控制。
- 复位:单片机复位
- 继续运行/暂停
- 单步跳过,如果这一行有函数调用,不会进入内部
- 进入,如果这一行有函数调用,会进入函数内部
- 跳出, 跳出当前调用栈顶层的函数, 即如果在函数内部会直接运行到return
- 重启调试器(当然单片机也会复位,一般出现异常的时候使用这个按钮)
- 终止调试
> **如果你希望在编译之后立刻启动调试**,不要分两次点击,你可以在`launch.json`中添加一个`prelaunchtask`(意为在启动调试之前要运行的任务),将他设置为我们在[编译章节](###编译)介绍的构建任务。我们已经提供了这个选项,取消注释即可使用。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
---
---
2022-11-12 19:37:16 +08:00
## Ozone可视化调试和LOG功能
2022-11-13 14:40:21 +08:00
> Ozone暂时只支持jlink。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
### 软件安装
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
安装Ozone和J-link工具箱( 驱动、gdb以及各种调试工具) 。安装包都在网盘里。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
### 配置调试项目
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
打开ozone后会显示一个new project wizard, 如果没有打开, 在工具栏的File-> New -> New project wizard。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113133904084 ](assets\image-20221113133904084.png )
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
选择M4内核, 为了能够查看外设寄存器的值还需要svd文件。所有mcu的svd都在图中的文件夹里提供, 当然你也可以使用我们仓库根目录下的文件。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113134025339 ](assets\image-20221113134025339.png )
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
接口选择swd, 接口速度不需要太高, 如果调试的时候需要观察大量的变量并且使用日志功能, 可以调高这个值。如果连接了jlikn, 下面的窗口中会显示。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113134252407 ](assets\image-20221113134252407.png )
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
选择构建之后生成的.elf文件。这是调试器专用的文件格式, 对其内容感兴趣可以自行搜索细节。此外ozone还支持.bin .hex .axf( 最后一个是amr-cc, 也就是keil的工具链会生成的) 等格式。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113134605331 ](assets\image-20221113134605331.png )
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
这页不要动。如果希望保存jlink的调试日志, 最后一个选项选择一个文件或者新建一个日志文件。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
### 常用调试窗口和功能
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
下图的配置是笔者常用的layout。每个窗口是否显示、放在什么位置等都是可以自己定义的。通过工具栏的view选项卡可以自行选择需要展示的窗口。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![](assets\ozone.png)
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
1. 调试控制: 和vscode类似
2. 变量watch窗口, 这里的变量不会实时更新, 只有在暂停或遇到断点的时候才会更新。若希望实时查看, 在这里右键选择需要动态查看的变量, 选择Graph, 他就会出现在**窗口8**的位置。
3. 断点和运行追踪管理
4. 调试控制台,输出调试器的信息。
5. 终端, 支持一些jlink script的命令。**单片机通过log模块发送的日志也会显示在这里。**
6. 代码窗口, 用于添加断点、添加查看等。鼠标悬停在变量上可以快速查看变量值和类型。希望打开整个项目文件, 点击工具栏的view选项卡, 单击Source Files就可以打开一个项目中所有源文件的窗口。右键点击函数或变量可以跳转到定义和声明、查看汇编代码等。按**F12**跳转到定义。
7. **变量可视化窗口, 这就是Ozone的大杀器。**在变量添加到查看( watch) 之后, 右键点击watch中的变量选择Graph, 变量会被添加到可视化查看中。你可以选择“示波器”的显示时间步长以及颜色等信息, 还可以更改采样率。
8. 窗口8和7配合。在窗口8中会实时显示变量值, 并且统计平均值和最大最小值, **而且还会将所有采样值保存到一个csv文件当中**,如果需要进一步分析可以导出这个数据文件。
9. 内存视图。可以直接查看任意内存位置的值。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
> 再次注意,这些窗口是否开启以及位置都是可以自定义的。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
#### 变量动态查看(可视化)
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
- 如果没有打开窗口, 现在view->timeline中打开可视化窗口。动态变量查看的窗口也在view->data sampling。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
启用动态变量查看的流程如下:
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
```mermaid
graph LR
在代码窗口中选中需要观察的变量 --> 添加到watch窗口 --> 在watch选择要动态查看的变量 --> 添加到Datasample窗口
```
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
第一步的快捷键是`ctrl+w`,选中变量之后按。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
第二部的快捷键是`ctrl+g`, 选中watch中的变量后按。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
第三步可以修改示波器的步长和采样频率。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
- 如果当前文件没有你要的变量, 你想查看项目中的其他文件夹, 在view-> source files中可以打开该项目所有的源文件, 双击可以打开源文件。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
![image-20221113142448939 ](assets\image-20221113142448939.png )
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
#### 日志打印
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
在Terminal窗口查看, 还可以通过命令直接控制单片机的运行( 不过不常用) 。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
未打开窗口则在view-> terminal中打开。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
#### 外设查看
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
在view-> register中打开窗口, 选择Peripherals可以查看所有外设寄存器
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
CPU选项卡可以查看CPU的寄存器。
#### 调用栈
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
在view-> call stack中打开窗口。
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
### 常用快捷键
2022-11-12 19:37:16 +08:00
2022-11-13 14:40:21 +08:00
| 组合 | 功能 |
| -------------------- | ---------------------------------------------------- |
| ctrl+w | 添加到查看 |
| ctrl+g | 添加到动态查看(需要先添加到查看) |
| f12 | 跳转到定义 |
| f5 | 启动调试 |
| f10 | 单步跳过 |
| f11 | 单步进入 |
| shift+f11 | 单步跳出 |
| 右键+break on change | 当变量发生变化的时候进入此断点 |
| ctrl+H | 展示调用图,会列出该函数调用的所有函数(内部调用栈) |
### 保存调试项目
退出时可以将调试项目保存在项目的根目录下,方便下次调试使用,不需要重新设置。