sentry_chassis_hzz/VSCode+Ozone使用方法.md

16 KiB
Raw Blame History

VSCode+Ozone开发STM32的方法

neozng1@hnu.edu.cn

[TOC]

前言

了解过嵌入式开发的你一定接触过Keil这款20世纪风格UI的IDE伴随很多人度过了学习单片机的岁月。然而由于其缺少代码补全、高亮和静态检查的支持以及为人诟病的一系列逆天的设置、极慢的编译速度特别是在开发HAL库时很多开发者开始转向其他IDE。

IAR、CubeIDE等都是广为使用的“其他”IDE但是他们也有各自的缺点不能让笔者满意。作为IDE界的艺术家JetBrains推出的Clion也在相当程度上完善了对嵌入式开发的支持。不过在体验过多款IDE后还是VSCode这款高度定制化的编辑器最让人满意。

而Ozone则是SEGGER(做jilnk的)推出的调试应用支持变量实时更新变量曲线可视化SEGGER RTT日志DBG虚拟串口等功能大大扩展了调试的功能。很多人习惯使用串口进行可视化调试如vofa串口调试助手等。然而通过这些方式进行调试都是对内核有侵入性会占有内核资源并且导致定时器的时间错乱。由于DBG有单独连接到FLASH和CPU寄存器的高速总线类似于DMA可以在不影响程序正常运行的情况下以极高的频率直接获取变量值。

下面将从工具链介绍、环境配置以及调试工作流三个方面介绍以VSCode为编辑器Ozone为调试接口的开发环境。

开发的大致流程为:

graph LR
CubeMX进行初始化 --> VSCode编写代/进行编译/简单调试 --> Ozone变量可视化调试+log

前置知识

  1. 计算机速成课:Crash Course Computer Science

  2. 从零到一打造一台计算机:

    编程前你最好了解的基本硬件和计算机基础知识(模拟电路)

    编程前你最好了解的基本硬件和计算机基础知识(数字电路)

    从0到1设计一台计算机

  3. C语言基础程序设计入门——C语言

务必学完以上课程再开始本教程的学习。

预备知识

  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 GCCwindows下微软开发的visual C++以及apple主导的llvm/clang。编译器能够识别代码中的关键字、表达式以及各种特定的格式并将他们转换成特定的符号也就是汇编语言(再次注意汇编语言是平台特定的),这个过程称为编译Compile

对于单个.c文件从C语言开始到单片机可识别的.bin文件一般要经历以下几步

img

首先是编译预处理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++语言编译链接过程这篇教程还提供了以GCC为例的代码编译示例。

C语言内存模型

image-20221112160213066

以上是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库包括stdiostdlibstring等在内的实现的函数的实现就不太一样。再如__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外设工作原理

image-20221112145717063

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就可以利用这个模块向上位机发送日志和信息。

以上三个模块都需要通过TPIUtrace port interface unit和外部调试器j-link等进行连接TPIU会将三个模块发来的数据进行封装并通过DWT记录时间发送给上位机。

环境配置

  • 安装STM32CubeMX并安装F4支持包和DSP库支持包-

  • 安装VSCode并安装C/C++Cortex-DebugCortex-Debug: Device Support Pack - STM32F4Better C++ SyntaxIntelliCodeMakfile ToolsC/C++ Snippets插件

    image-20221112172157533

    image-20221112172208749

    image-20221112172221756

    image-20221112172239386

    image-20221112172254809

  • 安装MinGW等待界面如下

    image-20221112172051589

    安装好后打开MinGW后将所有的支持包勾选然后安装

    image-20221112172348408

    image-20221112172420037

    安装完以后将MinGW的bin文件夹添加到环境变量中的path下按下菜单键搜索编辑系统环境变量打开之后:

    image-20221112172716320

    验证安装:

    打开命令行win+Rcmd回车输入gcc -v,如果没有报错,并输出了一堆路径和参数说明安装成功。

  • 配置gcc-arm-none-eabi环境变量

    同上将工具链的bin添加到PATH

    image-20221112172858593

    安装路径可能不一样,这里要使用你自己的路径而不是直接抄

    验证安装:

    打开命令行,输入arm-none-eabi-gcc -v,如果没有报错,并输出了一堆路径和参数说明安装成功。

添加到环境变量PATH的意思是当一些程序需要某些依赖或者要打开某些程序时系统会自动前往PATH下寻找对应项。一般需要重启使环境变量生效。

  • 将OpenOCD解压到一个文件夹里稍后需要在VSCode中设置这个路径。

  • CubeMX生成代码的时候工具链选择makefile

    image-20221112173534670

    生成的目录结构如下:

    image-20221112174211802

    Makefile就是我们要使用的构建规则文件。

VSCode编译和调试配置

编译

用VSCode打开创建的项目文件夹Makefile Tools插件会询问你是否帮助配置intellisense选择是。

简单调试

在VSCode中调试不能像Keil一样查看变量动态变化但是支持以外的所有操作如查看外设和反汇编代码设置断点触发方式等。

用于调试的配置参考这篇博客:Cortex-debug 调试器使用介绍

你需要配置arm gnu工具链的路径OpenOCD的路径使得GDB调试器可以找到OpenOCD并调用它从而连接硬件调试器如j-link等该工作区文件夹launch.json文件用于启动vscode的调试任务

如果教程看不懂,请看.vscode下的launch.json,照葫芦画瓢。

根目录下已经提供了C板所需的.svd和使用无线调试器时所用的openocd.cfg配置文件。

然后选择run and debug标签页在选项中选择你配置好的选项开始调试。

image-20221112180103750