02. 初识HAL库
一、初识HAL库
STM32 开发中常说的 HAL 库开发,指的是利用 HAL 库固件包里封装好的 C 语言编写的驱动文件,来实现对 STM32 内部和外围电器元件的控制的过程。但只有 HAL 库还不能直接驱动一个 STM32 的芯片,其它的组件已经由 ARM 与众多芯片硬件、软件厂商制定的通用的软件开发标准 CMSIS 实现了。
因为基于 Cortex 系列芯片采用的内核都是相同的,区别主要为核外的片上外设的差异,这些差异却导致软件在同内核,不同外设的芯片上移植困难。为了解决不同的芯片厂商生产的 Cortex 微控制器软件的兼容性问题,ARM 与芯片厂商建立了 CMSIS 标准 (Cortex MicroController Software Interface Standard)。所谓 CMSIS 标准,实际是新建了一个软件抽象层。

从图中可以看出这个标准分级明显,从用户程序到内核底层实现做了分层。按照这个分级,HAL 库属于 CMSIS-Pack 中的 “Peripheral HAL” 层。CMSIS 规定的最主要的 3 个部分为:核内外设访问层(由 ARM 负责实现)、片上外设访问层和外设访问函数(后面两个由芯片厂商负责实现)。ARM 整合并提供了大量的模版,各厂商根据自己的芯片差异修改模版,这其中包括汇编文件 startup_device.s、system_.h 和 system_.c 这些与初始化和系统相关的函数。

二、HAL库简介
库函数的引入,大大降低 STM 系列主控芯片开发的难度。ST 公司为了方便用户开发 STM32 芯片开发提供了三种库函数,从时间产生顺序是:标准库、HAL 库和 LL 库。目前 ST 已经逐渐暂停对部分标准库的支持,ST 的库函数维护重点对角已经转移到 HAL 库和 LL 库上。
【1】、标准外设库(Standard Peripheral Libraries)
标准外设库(Standard Peripherals Library)是对 STM32 芯片的一个完整的封装,包括所有标准器件外设的器件驱动器,是 ST 最早推出的针对 STM 系列主控的库函数。标准库的设计的初衷是减少用户的程序编写时间,进而降低开发成本。几乎全部使用 C 语言实现并严格按照 Strict ANSI-C、MISRA-C 2004 等多个 C 语言标准编写。但标准外设库仍然接近于寄存器操作,主要就是将一些基本的寄存器操作封装成了 C 函数。开发者仍需要关注所使用的外设是在哪个总线之上,具体寄存器的配置等底层信息。
ST 为各系列提供的标准外设库稍微有些区别。此外,在内部的实现上也稍微有些区别,这个在具体使用(移植)时,需要注意一下!但是,不同系列之间的差别并不是很大,而且在设计上是相同的。
【2】、HAL 库(Hardware Abstraction Layer)
HAL 是 Hardware Abstraction Layer 的缩写,即硬件抽象层。是 ST 为可以更好的确保跨 STM32 产品的最大可移植性而推出的 MCU 操作库。这种程序设计由于抽离应用程序和硬件底层的操作,更加符合跨平台和多人协作开发的需要。HAL 库是基于一个非限制性的 BSD 许可协议(Berkeley Software Distribution)而发布的开源代码。
【3】、LL 库(Low Layer)
LL 库(Low Layer)目前与 HAL 库捆绑发布,它的设计为比 HAL 库更接近于硬件底层的操作,代码更轻量级,代码执行效率更高的库函数组件,可以完全独立于 HAL 库来使用,但 LL 库不匹配复杂的外设,如 USB 等。所以 LL 库并不是每个外设都有对应的完整驱动配置程序。
三、HAL库框架结构
HAL 库头文件和源文件在 STM32Cube 固件包的 STM32F4xx_HAL_Driver 文件夹中。STM32F407xx_HAL_Driver 文件夹下的 Src(Source 的简写)文件夹存放是所有外设的驱动程序源码,Inc(Include 的简写)文件夹存放的是对应源码的头文件。Release_Notes.html 是HAL 库的版本更新信息。
打开 Src 和 Inc 文件夹,大家会发现基本都是 stm32f4xx_hal_和 stm32f4xx_ll_开头的 .c 和 .h文件。stm32f4xx_hal_开头的是 HAL 库,stm32f4xx_ll_开头的文件 LL 库的文件。

stm32f4xx_hal_ppp (c/h)中的函数和变量命名也严格按照命名规则。

对于 HAL 的 API 函数,常见的有以下几种:
- 初始化/反初始化函数:
HAL_PPP_Init(),HAL_PPP_DeInit() - 外设读写函数:
HAL_PPP_Read(),HAL_PPP_Write(),HAL_PPP_Transmit(),HAL_PPP_Receive() - 控制函数:
HAL_PPP_Set(),HAL_PPP_Get() - 状态和错误:
HAL_PPP_GetState(),HAL_PPP_GetError()
HAL 库封装的很多函数都是通过定义好的结构体将参数一次性传给所需函数,参数也有一定的规律,主要有以下三种:
- 配置和初始化用的结构体:一般为
PPP_InitTypeDef或PPP_ConfTypeDef的结构体类型,根据外设的寄存器设计成易于理解和记忆的结构体成员。 - 特殊处理的结构体:专为不同外设而设置的,带有
Process的字样,实现一些特异化的中间处理操作等。 - 外设句柄结构体:HAL 驱动的重要参数,可以同时定义多个句柄结构以支持多外设多模式。HAL 驱动的操作结果也可以通过这个句柄获得。有些 HAL 驱动的头文件中还定义了一些跟这个句柄相关的一些外设操作。如用外设结构体句柄与 HAL 定义的一些宏操作配合,即可实现一些常用的寄存器位操作。

HAL 库的回调函数,这部分允许用户重定义,并在其中实现用户自定义的功能,也是我们使用 HAL 库的最常用的接口之一。

四、HAL库的用户配置文件
stm32f4xx_hal_conf.h 用于裁剪 HAL 库和定义一些变量,官方没有直接提供这个文件,但在 STM32Cube_FW_F4_V1.26.0\Drivers\STM32F4xx_HAL_Driver\Inc 这个路径下提供了一个模版文件《 stm32f4xx_hal_conf_template.h 》,我们可以直接复制这个文件重命名为 stm32f4xx_hal_conf.h,做一些简单的修改即可。
stm32f4xx_hal_conf.h 文件 84 行左右,有个 HSE_VALUE 参数,这个参数表示我们的外部高速晶振的频率。这个参数请务必根据我们板子外部焊接的晶振频率来修改,官方默认是 25M。这里,我们使用的开发板,晶振频率为 8MHz。
#if !defined (HSE_VALUE)
/* 外部高速振荡器的值,单位 HZ */
#define HSE_VALUE ((uint32_t)8000000)
#endif /* HSE_VALUE */
源码在 112 行左右,还有一个参数就是外部低速晶振频率,这个官方默认是 32.768KHZ。
#if !defined (LSE_VALUE)
/* 外部低速振荡器的值,单位 HZ */
#define LSE_VALUE ((uint32_t)32768)
#endif /* LSE_VALUE */
用户配置文件可以用来选择使能何种外设,源码配置在 35 行到 74 行。我们只要屏蔽某个外设的宏,则这个外设的驱动代码机会被屏蔽,从而不可用。
#define HAL_MODULE_ENABLED
#define HAL_ADC_MODULE_ENABLED
#define HAL_CAN_MODULE_ENABLED
/* #define HAL_CAN_LEGACY_MODULE_ENABLED */
#define HAL_CRC_MODULE_ENABLED
/* ...中间省略... */
#define HAL_WWDG_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
#define HAL_PCD_MODULE_ENABLED
#define HAL_HCD_MODULE_ENABLED
stm32f4xx_hal_conf.h 文件的 136 行左右,宏定义 TICK_INT_PRIORITY 是滴答定时器的优先级。这个优先级很重要,因为如果其它的外设驱动程序的延时是通过滴答定时器提供的时间基准,来实现延时的话,又由于实现方式是滴答定时器对寄存器进行计数,所以当我们在其它中断服务程序里调用基于此时间基准的延迟函数 HAL_Delay,那么假如该中断的优先级高于滴答定时器的优先级,就会导致滴答定时器中断服务函数一直得不到运行,从而程序卡死在这里。所以滴答定时器的中断优先级一定要比这些中断高。
/*!< tick interrupt priority */
#define TICK_INT_PRIORITY ((uint32_t)0x0F)
这个时间基准可以是滴答定时器提供,也可以是其他的定时器,默认是用滴答定时器。
五、HAL库初始化文件
这个文件内容比较多,包括 HAL 库的初始化、系统滴答、基准电压配置、IO 补偿、低功耗、EXTI 配置等都集合在这个文件里面。
HAL 库初始化函数的源码在 157 行到 183 行,简化函数如下(下面的代码只针对 F4 的 HAL 固件 1.26.0 版本,其它版本可能有差异):
HAL_StatusTypeDef HAL_Init(void)
{
/* 设置中断优先级分组 */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* 使用滴答定时器作为时钟基准,配置 1ms 滴答(重置后默认的时钟源为 HSI) */
if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
return HAL_ERROR;
}
/* 初始化硬件 */
HAL_MspInit();
/* 返回函数状态 */
return HAL_OK;
}
该函数是 HAL 库的初始化函数,在程序中必须优先调用,其主要实现如下功能:
- 设置 NVIC 优先级分组为 4
- 配置滴答定时器每 1ms 产生一个中断
- 在这个阶段,系统时钟还没有配置好,因此系统还是默认使用内部高速时钟源 HSI 在跑程序。对于 F4 来说,HSI 的时钟频率是 16MHZ。所以如果用户不配置系统时钟的话,那么系统将会使用 HSI 作为系统时钟源
- 调用 HAL_MspInit 函数初始化底层硬件,HAL_MspInit 函数在 stm32f4xx_hal.c 文件里面做了弱定义。
在源码在 253 行到 274 行,有 HAL_InitTick() 函数简化函数如下:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* 配置滴答定时器 1ms 产生一次中断 */
if (HAL_SYSTICK_Config(SystemCoreClock / (1000UL / (uint32_t)uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* 配置滴答定时器中断优先级 */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* 返回函数状态 */
return HAL_OK;
}
该函数用于初始化滴答定时器的时钟基准,主要功能如下:
- 配置滴答定时器 1ms 产生一次中断
- 配置滴答定时器的中断优先级。
- 该函数是__weak 定义的 “弱函数”,用户可以重新定义该函数。
该函数可以通过 HAL_Init() 或者 HAL_RCC_ClockConfig() 重置时钟。在默认情况下,滴答定时器是时间基准的来源。如果其他中断服务函数调用了 HAL_Delay(),必须小心,滴答定时器中断必须具有比调用了 HAL_Delay()函数的其他中断服务函数的优先级高(数值较低),否则会导致滴答定时器中断服务函数一直得不到执行,从而卡死在这里。
滴答定时器相关的函数的源码在 303 行到 435 行,相关函数如下:
/* 该函数在滴答定时器时钟中断服务函数中被调用,一般滴答定时器 1ms 中断一次,
所以函数每 1ms 让全局变量 uwTick 计数值加 1 */
__weak void HAL_IncTick(void)
{
uwTick += (uint32_t)uwTickFreq;
}
/* 获取全局变量 uwTick 当前计算值 */
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
/* 获取滴答时钟优先级 */
uint32_t HAL_GetTickPrio(void)
{
return uwTickPrio;
}
/* 设置滴答定时器中断频率 */
HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq)
{
HAL_StatusTypeDef status = HAL_OK;
HAL_TickFreqTypeDef prevTickFreq;
assert_param(IS_TICKFREQ(Freq));
if (uwTickFreq != Freq)
{
/* 备份滴答定时器中断频率 */
prevTickFreq = uwTickFreq;
/* 更新被 HAL_InitTick()调用的全局变量 uwTickFreq */
uwTickFreq = Freq;
/* 应用新的滴答定时器中断频率 */
status = HAL_InitTick(uwTickPrio);
if (status != HAL_OK)
{
/* 恢复之前的滴答定时器中断频率 */
uwTickFreq = prevTickFreq;
}
}
return status;
}
/* 获取滴答定时器中断频率 */
HAL_TickFreqTypeDef HAL_GetTickFreq(void)
{
return uwTickFreq;
}
/*HAL 库的延时函数,默认延时单位 ms */
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait)
{
}
}
/* 挂起滴答定时器中断,全局变量 uwTick 计数停止 */
__weak void HAL_SuspendTick(void)
{
/* 禁止滴答定时器中断 */
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
}
/* 恢复滴答定时器中断,恢复全局变量 uwTick 计数 */
__weak void HAL_ResumeTick(void)
{
/* 使能滴答定时器中断 */
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
}
六、HAL库中断处理
中断是 STM32 开发的一个很重要的概念,这里我们可以简单地理解为:STM32 暂停了当前手中的事并优先去处理更重要的事务。而这些 “更重要的事务” 是由软件开发人员在软件中定义的。

设置外设的控制句柄结构体 PPP_HandleType 和初始化 PPP_InitType 结构体的参数,然后调用 HAL 库对应这个驱动的初始化 HAL_PPP_Init(),由于这个 API 中有针对外设初始化细节的接口 HAL_PPP_Msp_Init(),我们需要重新实现这个函数并完成外设时钟、IO 等细节差异的设置, 完成各细节处理后,使用 HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ() 来使能我们的外设中断;定义中断处理函数 PPP_IRQHandler(),并在中断函数中调用 HAL_PPP_function_IRQHandler() 来判断和处理中断标记;HAL 库中断处理完成后,根据对应中的调用我们需要自定义的中断回调接口 HAL_PPP_ProcessCpltCallback(),然后,我们在这个函数中实现我们对外设想做的处理;中断响应处理完成后,STM32 芯片继续顺序执行我们定义的主程序功能,按照以上处理的标准流程完成了一次中断响应。
七、创建HAL库工程
7.1、VSCode下载EIDE插件
VSCode 的 EIDE 插件需要 .NET 运行环境。如果我们直接安装 EIDE 插件,它会自动下载 .NET 运行环境,但下载的环境可能比较老。因此,我们可以从微软的官网下载最新长期支持版的 .NET 运行环境:https://dotnet.microsoft.com/zh-cn/download。

这里,我们只需要下载 .NET 运行环境就可以了。

然后,我们在 VSCode 中安装 EIDE 插件。

7.2、安装ARM-GCC工具链
为了将我们编写的 C 程序,编译成单片机可以执行的二进制文件,我们需要从 ARM 的官网下载 GCC 工具链,https://developer.arm.com/downloads/-/arm-gnu-toolchain-download。

下载完 ARM-GCC 安装程序之后,我们双击安装。

7.3、下载OpenOCD
当我们编译工程生成二进制文件之后,我们还需要借助烧录软件将二进制文件烧入到单片机中。对于 STM32 单片机,我们可以使用 STLink(只能 STM32 单片机使用)或 DAP(其它 ARM 架构的 32 位单片机也可以使用)工具进行烧入。如果想使用 DAP,则需要下载 OpenOCD 或 PyOCD 驱动文件。
OpenOCD 的官网(https://openocd.org))只提供源码,不提供编译好的二进制文件,需要自己编译二进制文件生成固件,我们可以从一些第三方网站下载编译好的 OpenOCD 固件,例如:https://github.com/openocd-org/openocd/releases 或 https://gnutoolchains.com/arm-eabi/openocd 或 https://github.com/espressif/openocd-esp32/releases(这是针对 ESP32 的定制版本,但也支持 STM32)。
7.4、配置EIDE插件
在下载完必要的工具之后,我们配置 EIDE 插件。


这里,我们先设置 EIDE 插件使用的工具链。

插件默认输入的编译器的前缀,就是我们安装的 ARM-GCC 编译器的前缀,如果不一样,需要我们手动修改。

设置完 ARM-GCC 编译器之后,我们还需要设置 OpenOCD 的路径。

7.5、下载STM32CubeF4包
我们可以从 GitHub 下载【STM32CubeF4】包的内容:https://github.com/STMicroelectronics/STM32CubeF4。


当我们直接使用下载压缩包的方式下载该仓库时,该仓库下的子仓库的源码不会被下载。因此,我们还需要从 GitHub 下载【stm32f4xx-hal-driver】的源码:https://github.com/STMicroelectronics/stm32f4xx-hal-driver 和 【cmsis-device-f4】驱动的源码:https://github.com/STMicroelectronics/cmsis-device-f4。
7.6、新建工程
在安装完 EIDE 插件之后,我们需要通过 EIDE 插件新建一个工程。

7.7、复制文件到工程中
再新建完工程之后,我们创建如下文件夹用来分层管理我们的工程。
- 【Applications】:用来存放 应用文件,该文件夹下有两个子文件夹【Inc】和【Src】,分别用于存放 头文件 和 源码文件 的。
- 【Inc】:用来存放 应用文件 的 头文件。
- 【Src】:用来存放 应用文件 的 源码文件。
- 【Drivers】:用来存放驱动文件的。
- 【Devices】:用来存放 不同设备的驱动文件 的,该文件夹下会存在许多 子文件夹,每一个 子文件的名字 就用来表示一个 设备,该子文件下存放对应设备的头文件和源码文件。
- 【Peripherals】:该文件用来存放 单片机的片上外设的驱动。该文件夹下有两个子文件夹【Inc】和【Src】,分别用于存放 头文件 和 源码文件 的。
- 【Inc】:用来存放 片上外设驱动 的 头文件。
- 【Src】:用来存放 片上外设驱动 的 源码文件。
- 【Middlewares】:用来存放 中间件。
- 【SDK】:用来存放 STM32 的开发工具包。
- 【Toolkits】:用来存放 工具文件。
接着,我们将【STM32CubeF4】包下【Drivers】文件夹下的【CMSIS】文件夹和【STM32F4xx_HAL_Driver】拷贝到自己工程下的【SDK】文件夹下。然后,我们进入到【CMSIS】文件夹下,只保留其下的【Device】文件夹和【Include】文件夹,其它的文件和文件夹都删除。

接着,我们进入【CMSIS】文件夹【Device】文件夹下的【ST】文件夹下的【STM32F4xx】文件夹。然后,将从 GitHub 下载的【cmsis-device-f4】驱动文件夹的【Include】文件夹和【Source】文件夹拷贝到【STM32F4xx】文件夹下。

然后,我们进入到【Include】文件夹,该文件夹下存放将 STM32F4 不同系列的头文件。这里,我们将其它系列的头文件删除,只保留需要使用的 stm32f4xx.h、 stm32f407xx.h 和 system_stm32f4xx.h 文件。

然后,我们返回上级目录,进入【Source】文件夹下的【Templates】文件夹。该文件夹下存放不同编译器需要使用的汇编文件。这里,我们使用 GCC 平台,将其它两个平台的汇编文件删除。

接着,我们进入【gcc】文件夹,该文件下存放 STM32F4 不同系列的汇编文件。这里,我们将其它系列的汇编文件删除,只保留需要使用的 startup_stm32f407xx.s 汇编文件。

接着,我们进入【CMSIS】文件夹【Include】文件夹,将不需要的头文件删减,只保留 cmsis_compiler.h、cmsis_gcc.h、cmsis_version.h、core_cm4.h 和 mpu_armv7.h 文件。

我们解压 HAL 库源码文件夹【stm32f4xx-hal-driver】下的【Inc】文件夹和【Src】文件夹复制到我们工程下的【STM32F4xx_HAL_Driver】目录下。

然后,我们进入其下的【Inc】文件夹,将其下的中的 template 文件重命名,去掉 _template:stm32_assert_template.h、stm32f4xx_hal_conf_template.h 文件。

然后,我们返回上级目录,进入【Src】文件夹下,删除其下的带 _template 的文件:stm32f4xx_hal_msp_template.c、stm32f4xx_hal_timebase_rtc_alarm_template.c、stm32f4xx_hal_timebase_rtc_wakeup_template.c、stm32f4xx_hal_timebase_tim_template.c 文件。

我们进入到【STM32CubeF4】下的【Projects】下文件夹,该文件夹下存放 ST 官方不同板子的工程文件。这里,我们随便找一个 STM32F4 的工程(这里以 STM32F4-Discovery 为例),进入该工程,将【Demonstrations】文件夹(有的工程可能是 【Templates】文件夹下)下【Inc】文件夹下的stm32f4xx_it.h 文件复制到【Applications】文件夹下的【Inc】文件夹下。

然后,我们返回上级目录,进入【Src】文件夹,将该文件夹下的 stm32f4xx_it.c 文件复制到到【Applications】文件夹下的【Src】文件夹下。

然后,我们返回上级目录,进入【STM32CubeIDE】目录,将该目录下的 STM32F407VGTX_FLASH.ld FLASH 链接文件复制到【SDK】目录下。

7.8、配置EIDE工程
当我们把 STM32CubeF4 包的内容复制到工程之后,我们需要将存放源码的文件夹的一级目录添加到工程中。

然后我们在工程中引入 HAL 库所需要的头文件:SDK\CMSIS\Include、SDK\CMSIS\Device\ST\STM32F4xx\Include、SDK\STM32F4xx_HAL_Driver\Inc 和、SDK\STM32F4xx_HAL_Driver\Inc\Legacy。

然后,我们在新建的工程中添加如下宏定义:STM32F407xx、USE_HAL_DRIVER 和 USE_FULL_LL_DRIVER。

添加完宏定义之后,我们还需要配置单片机的 CPU 类型和链接 FLASH 脚本文件。

STM32 的外设功能需要在配置文件中 stm32f4xx_hal_conf.h 文件中开启。

一般,我们还需要配置 HAL 库中 HSE 外部晶振的频率。一般 ST 官方开发板的晶振频率采用的是 25MHz,而我们常用的开发板的晶振频率为 8MHz。

#if !defined (HSE_VALUE)
#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
7.9、编译工程
在配置完工程之后,我们在【Applications】文件夹下的【Src】文件夹下新建一个 main.c 文件。在该文件中,我们编写 main() 函数,main() 函数是 C 语言程序的启动函数。
#include "stm32f4xx_hal.h"
int main(void)
{
HAL_Init();
while (1)
{
}
return 0;
}
然后,我们编译工程,会发现工程工程报错。这是因为,我们自己工程使用的stm32f4xx_it.c 和 stm32f4xx_it.h 文件从 STM32CubeF4 包下的其它工程复制到我们自己的工程,它们工程的两个文件中引入 main.h 头文件,而我们自己的工程中没有。

因此我们需要stm32f4xx_it.c 文件中 #include "main.h" 语句、extern PCD_HandleTypeDef hpcd; 语句和编写的外设中断服务函数(EXTI0_IRQHandler() 和 OTG_FS_IRQHandler())都删除掉。


因此我们需要 stm32f4xx_it.h 文件中 #include "main.h" 语句、外设中断服务函数的函数声明(EXTI0_IRQHandler() 和 OTG_FS_IRQHandler())都删除掉。

我们再次编译程序,会发现 stm32f4xx_it.c 文件的 SysTick_Handler() 中断服务函数会报错,这是因为,我们没有包含 stm32f4xx_hal.h 头文件。
#include "stm32f4xx_hal.h"

此时,我们再次构建,会发现构建工程成功。

7.10、配置下载器
在编译完成之后,我们需要使用下载器将编译好的二进制文件烧入到单片机中。

配置下载器之后,我们可以点击下载按钮将之前构建好的二进制文件烧入到单片机中。

7.11、仿真调试
有时我们编写的程序意外死机或者出现没有正常工作的情况,这是我们需要进行仿真调试分析问题。此时,我们需要在 VSCode 中安装【Cortex-Debug】插件来增强仿真功能。

安装完插件之后,我们就可以点击调试按钮进行调试。


浙公网安备 33010602011771号