一、什么是HAL库
HAL,英文全称 Hardware Abstraction Layer,即硬件抽象层。HAL 库是 ST 公司提供的外设驱动代码的驱动库,用户只需要调用库的 API 函数,便可间接配置寄存器。我们要写程序控制 STM32 芯片,其实最终就是控制它的寄存器,使之工作在我们需要的模式下,HAL 库将大部分寄存器的操作封装成了函数,我们只需要学习和掌握 HAL 库函数的结构和用法,就能方便地驱动 STM32 工作,以节省开发时间。
STM32 开发中常说的 HAL 库开发,指的是利用 HAL 库固件包里封装好的 C 语言 编写的驱动文件,来实现对 STM32 内部和外围电器元件的控制的过程。但只有 HAL 库还不能直接驱动一个 STM32 的芯片,其它的组件已经由 ARM 与众多芯片硬件、软件厂商制定的通用的软件开发标准 CMSIS 实现了。
HAL 库是基于一个非限制性的 BSD 许可协议(Berkeley Software Distribution)而发布的开源代码。ST 制作的中间件堆栈(USB 主机和设备库,STemWin)带有允许轻松重用的许可模式,只要是在 ST 公司的 MCU 芯片上使用,库中的中间件(USB 主机/设备库,STemWin)协议栈即被允许修改,并可以反复使用。至于基于其它著名的开源解决方案商的中间件(FreeRTOS,FatFs,LwIP 和 PolarSSL)也都具有友好的用户许可条款。
HAL 库是从 ST 公司从自身芯片的整个生产生态出发,为了方便维护而作的一次整合,以改变标准外设库带来各系列芯片操作函数结构差异大、分化大、不利于跨系列移植的情况。相比标准外设库,STM32Cube HAL 库表现出更高的抽象整合水平,HAL 库的 API 集中关注各外设的公共函数功能,这样便于定义一套通用的用户友好的 API 函数接口,从而可以轻松实现从一个 STM32 产品移植到另一个不同的 STM32 系列产品。但由于封闭函数为了适应最大的兼容性,HAL 库的一些代码实际上的执行效率要远低于寄存器操作。但即便如此,HAL 库仍是 ST 未来主推的库。
LL 库(Low Layer)目前与 HAL 库捆绑发布,它设计为比 HAL 库更接近于硬件底层的操作,代码更轻量级,代码执行效率更高的库函数组件,可以完全独立于 HAL 库来使用,但 LL 库不匹配复杂的外设,如 USB 等。所以LL库并不是每个外设都有对应的完整驱动配置程序。使用 LL 库需要对芯片的功能有一定的认知和了解,它可以:
- 独立使用,该库完全独立实现,可以完全抛开 HAL 库,只用 LL 库编程完成。
- 混合使用,和 HAL 库结合使用。
对于 HAL 库和 LL 库的关系,如下图的软件框架所示,可以看出它们设计为彼此独立的分支,但又同属于HAL库体系。
二、CMSIS标准
软件开发已经被嵌入式行业公认为最主要的开发成本,为了降低这个成本,ARM 与 Atmel、IAR、KEIL、SEGGER 和 ST 等诸多芯片和软件工具厂商合作,制定了一个将所有 Cortex 芯片厂商的产品的软件接口标准化的标准 CMSIS(Cortex Microcontroller Software Interface Standard)。
从图中可以看出这个标准分级明显,从用户程序到内核底层实现做了分层。按照这个分级,HAL 库属于 CMSIS-Pack 中的“Peripheral HAL”层。CMSIS 规定的最主要的 3 个部分为:核内外设访问层(由 ARM 负责实现)、片上外设访问层 和 外设访问函数(后面两个由芯片厂商负责实现)。ARM 整合并提供了大量的模版,各厂商根据自己的芯片差异修改模版,这其中包括汇编文件 startup_device.s、system_.h 和 system_.c 这些与初始化和系统相关的函数。
结合 STM32F1 的芯片来说,其 CMSIS 应用程序的简单结构框图,不包括实时操作系统和中间设备等组件,其结构如下图所示:
CMSIS 为软件包的内容制定了标准,包括文件目录的命名和内容构成,5.7.0 版本 CMSIS 规定软件包目录如表下所示:
文件/目录 | 描述 | |
---|---|---|
LICENSE.txt | Apache 2.0 授权的许可文件 | |
Device | 基于 Arm Contex-M 处理器设备的 CMSIS 参考实现 | |
ARM.CMSIS.pdsc | 描述该 CMSIS 包的文件 | |
CMSIS 组件 | Documentation | 这个数据包的描述文档 |
Core | CMSIS-Core(Cortex-M)相关文件的用户代码模板,在 ARM.CMSIS.pdsc 中引用 | |
Core_A | CMSIS-Core(Cortex-A)相关文件的用户代码模板,在 ARM.CMSIS.pdsc 中引用 | |
DAP | CMSIS-DAP 调试访问端口源代码和参考实现 | |
Driver | CMSIS 驱动程序外设接口 API 的头文件 | |
DSP_Lib | CMSIS-DSP 软件库源代码 | |
NN | CMSIS-NN 软件库源代码 | |
Include | CMSIS-Core(Cortex-M)和 CMSIS-DSP 需要包括的头文件等 | |
Lib | 包括 CMSIS 核心(Contex-M)和 CMSIS-DSP 的文件 | |
Pack | CMSIS-Pack 实例,包含设备支持、板支持和软件组件的软件包示例 | |
RTOS | CMSIS-RTOS 版本 1 以及 RTX4 参考实现 | |
RTOS2 | CMSIS-RTOS 版本 2 以及 RTX5 参考实现 | |
SVD | CMSIS-SVD 样例,规定开发者、制造商、工具制造上的分工和职能 | |
Utilities |
PACK.xsd(CMSIS-Pack 架构文件), PackChk.exe(检查软件包的工具), CMSIS-SVD.xsd(CMSIS-SVD 架构文件), SVDConv.exe(SVD文件的转换工具) |
三、STM32Cube固件包
HAL 库是 ST 推出的 STM32Cube 软件生态下的一个分支。STM32Cube 是 ST 公司提供的一套免费开发工具和 STM32Cube 固件包,旨在通过减少开发工作、时间和成本来简化开发人员的工作,并且覆盖整个 STM32 产品。它包含两个关键部分:
- 允许用户通过图形化向导来生成 C 语言工程的图形配置工具 STM32CubeMX。可以通过 CubeMX 实现方便地下载各种软件或开发固件包。
- 包括由 STM32Cube 硬件抽象层(HAL),还有一组一致的中间件组件(RTOS、USB、FAT文件系统、图形、TCP/IP 和 以太网),以及一系列完整的例程组成的 STM32Cube 固件包。
【1】、Drivers 文件夹
Drivers 文件夹包含 BSP,CMSIS 和 STM32F1xx_HAL_Driver 三个子文件夹。
Drivers 文件夹 | BSP 文件夹 | 也叫板级支持包,用于适配ST官方对应的开发板的硬件驱动程序,每一种开发板对应一个文件夹。 |
CMSIS 文件夹 | 符合CMSIS标准的软件抽象层组件相关文件。主要包括 DSP 库(DSP_LIB文件夹),Cortex-M 内核及其设备文件(Include 文件夹),微控制器专用头文件/启动代码/专用系统文件等(Device 文件夹)。 | |
STM32F1xx_HAL_Driver 文件夹 | 它包含了所有的 STM32F1xx 系列 HAL 库头文件和源文件。它的作用是屏蔽了复杂的硬件寄存器操作,统一了外设的接口函数。该文件夹包含 Src 和 Inc 两个子文件夹,其中 Src 子文件夹存放的是 .c 源文件,Inc 子文件夹存放的是与之对应的 .h 头文件。每个 .c 源文件对应一个 .h 头文件。源文件名称基本遵循 stm32f1xx_hal_ppp.c 定义格式,头文件名称基本遵循 stm32f1xx_hal_ppp.h 定义格式。 |
CMSIS 文件夹中的 Device 和 Include 这两个文件夹中的文件是我们工程中最常用到的。
文件 | 描述 |
---|---|
stm32f1xx.h | 包含了很多条件定义和常用的枚举变量类型,与宏定义配合,选择性包含某一特定的 STM32F1 系列芯片的头文件。这个文件使我们在使用 STM32F1 系列的不同型号芯片时,不需要每次都修改工程头文件,只需要修改宏定义并增加特定型号芯片的头文件即可快速选择使用不同类型的 F1 芯片。 |
stm32f103xg.h | STM32F1 大容量系列芯片通用的片上外设访问层头文件。Include 目录下有多个这样具体到型号的名字类似的头文件,我们具体使用哪个文件时需要根据实际使用的型号来确定。这个文件的主要作用是定义声明寄存器以及封装内存操作,以结构体和宏定义标识符的形式。 |
startup_stm32f103xe.s | STM32F103 系列芯片的启动文件,每个系列都有与之对应的启动文件。启动文件的作用主要是进行堆栈的初始化,中断向量表以及中断函数定义等。启动文件有一个很重要的作用就是系统复位后引导进入 main 函数。 |
system_stm32f1xx.h system_stm32f1xx.c |
主要是声明和定义系统初始化函数 SystemInit 以及系统时钟更新函数 SystemCoreClockUpdate。SystemInit 函数的作用是进行时钟系统的一些初始化操作以及中断向量表偏移地址设置,但它并没有设置具体的时钟值,这是与标准库的最大区别,在使用标准库的时候,SystemInit 函数会帮我们配置好系统时钟配置相关的各个寄存器。在启动文件 startup_stm32103xe.s 中会设置系统复位后,直接调用 SystemInit 函数进行系统初始化。SystemCoreClockUpdate 函数是在系统时钟配置进行修改后 ,调用这个函数来更新 SystemCoreClock 的值,变量 SystemCoreClock 是一个全局变量,开放这个变量可以方便我们在用户代码中直接使用这个变量来进行一些时钟运算。 |
Include 文件夹存放了符合 CMSIS 标准的 Cortex-M 内核头文件。对于 STM32F1 的工程,我们只要把我们需要的添加到工程即可,需要的头文件有:cmsis_armcc.h、cmsis_armclang.h、cmsis_compiler.h、cmsis_version.h、core_cm3.h 和 mpu_armv7.h。
core_cm3.h 是内核底层的文件,由 ARM 公司提供,包含一些 ARM 内核指令,如软件复位,开关中断等功能。它包含了一个重要的头文件 stdint.h。stdint.h 是从 C99 中引进的一个标准 C 库的文件。
【2】、Middlewares 文件夹
该文件夹下面有 ST 和 Third_Party 两个子文件夹。ST 文件夹下面存放的是 STM32 相关的一些文件,包括 STemWin 和 USB 库等。Third_Party 文件夹是第三方中间件,这些中间价都是非常成熟的开源解决方案。
Middlewares 文件夹 | ST 子文件夹 | STemWin 文件夹 | STemWin 工具包。Segger 提供。 |
STM32_USB_Device_Library 文件夹 | USB 从机设备支持包。 | ||
STM32_USB_Host_Library 文件夹 | USB 主机设备支持包。 | ||
Third_Party 子文件夹 | FatFs 文件夹 | FAT 文件系统支持包。采用的 FATFS 文件系统。 | |
FreeRTOS 文件夹 | FreeRTOS 实时系统支持包 | ||
LwIP 文件夹 | LwIP 网络通信协议支持包。 |
【3】、Projects 文件夹
该文件夹存放的是 ST 官方的开发板的适配例程,每个文件夹对应一个 ST 官方的 Demo 板,根据型号的不同提供 MDK 和 IAR 等类型的例程。
【4】、Utilities 文件夹
该文件夹是一些公用组件,也是主要为 ST 官方的 DEMO 板提供的。
【5】、其它几个文件
文件夹中还有几个单独的文件,用于声明软件版本或者版权信息,我们使用 ST 的芯片已经默认得到这个软件的版权使用授权。
- License.md:用于声明软件版权信息的文件
- package.xml:描述固件包版本信息的文件。
- Release_Notes.html:超文本文件,用浏览器打开可知它是对固件包的补充描述和固件版本更新的记录说明。
四、HAL库框架结构
HAL 库头文件和源文件在 STM32Cube 固件包的 STM32F1xx_HAL_Driver 文件夹中。
STM32F1xx_HAL_Driver 文件夹下的 Src(Source 的简写)文件夹存放是所有外设的驱动程序源码,Inc(Include 的简写)文件夹存放的是对应源码的头文件。Release_Notes.html 是 HAL 库的版本更新信息。最后四个是库的用户手册,方便我们查阅对应库函数的使用。
打开 Src 和 Inc 文件夹,大家会发现基本都是 stm32f1xx_hal_ 和 stm32f1xx_ll_ 开头的 .c 和 .h 文件。stm32f1xx_hal_ 开头的是 HAL 库,stm32f1xx_ll_ 开头的文件是 LL 库。
文件 | 描述 |
---|---|
sm32f1xx_hal.c stm32f1xx_hal.h |
初始化 HAL 库,主要实现 HLA 库的初始化、系统滴答,HAL 库延时函数、IO 重映射和 DBGMCU 功能。 |
stm32f1xx_hal_conf.h | HAL 的用户配置文件,用来裁剪 HAL 库、配置晶振参数等。 |
stm32f1xx_hal_def.h | 通用 HAL 库资源定义,包含 HAL 的通用数据类型定义,声明、枚举,结构体和宏定义。 |
stm32f1xx_hal_cortex.h stm32f1xx_hal_cortex.c |
Cortex 内核通用函数声明和定义,如 NVIC、MPU、系统软复位、Systick 等,其实主要是对 core_cm3.h 文件的相关函数再次封装。 |
stm32f1xx_hal_ppp.h stm32f1xx_hal_ppp.c |
某任外设驱动源代码,ppp 表示任意外设。 |
stm32f1xx_hal_ppp_ex.h stm32f1xx_hal_ppp_ex.c |
主要是存放外设的扩展(特殊)功能的驱动源码,ppp 表示任意外设。 |
stm32f1xx_II_ppp.h stm32f1xx_II_ppp.c |
LL 库驱动源码,在部分 stm32f1xx_hal_ppp.c 或 stm32f1xx_hal_ppp_ex.c 中会被调用。 |
以上是 HAL 库最常见的文件的列表,在 Src/Inc 下面还有 Legacy 文件夹,用于特殊外设的补充说明。
stm32f1xx_hal_ppp (c/h) 中的函数和变量命名也严格按照命名规则
文件名 | stm32f1xx_hal_ppp (c/h) | stm32f1xx_hal_ppp_ex (c/h) |
---|---|---|
函数名 | HAL_PPP_Function HAL_PPP_FeatureFuntion_MOD |
HAL_PPPEx_Function HAL_PPPEx_FeatureFunction_MODE |
外设句柄 | PPP_HandleTypedef | |
外设工作参数初始化参数结构体 | PPP_InitTypeDef | PPP_InitTypeDef |
配置参数结构体 | PPP_YyyyConfTypeDef | PPP_YyyyConfTypeDef |
枚举类型 | HAL_PPP_StructnameTypeDef |
对于 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_PPP_ENABLE_IT(__HANDLE__, __INTERRUPT__) |
使能外设中断 |
__HAL_PPP_DISABLE_IT(__HANDLE__,__INTERRUPT__) |
禁用外设中断 |
__HAL_PPP_GET_IT (__HANDLE__, __ INTERRUPT __) |
获取外设某一中断源 |
__HAL_PPP_CLEAR_IT (__HANDLE__, __ INTERRUPT __) |
清除外设中断 |
__HAL_PPP_GET_FLAG (__HANDLE__, __FLAG__) |
获取外设的状态标记 |
__HAL_PPP_CLEAR_FLAG (__HANDLE__, __FLAG__) |
清除外设的状态标记 |
__HAL_PPP_ENABLE(__HANDLE__) |
使能某一外设 |
__HAL_PPP_DISABLE(__HANDLE__) |
禁用某一外设 |
_HAL_PPP_XXXX (__HANDLE__, __PARAM__) |
针对外设的特殊操作 |
__HAL_PPP_GET_ IT_SOURCE (__HANDLE__, __INTERRUPT __) |
检查外设的中断源 |
HAL 库的回调函数,这部分允许用户重定义,并在其中实现用户自定义的功能,也是我们使用 HAL 库的最常用的接口之一。
回调函数 | 举例 |
---|---|
HAL_PPP_MspInit() | 由 HAL_PPP_Init() 这个 API 调用,主要在这个函数中实现外设对应的 GPIO、时钟、DMA 和中断开启的配置和操作。 |
HAL_PPP_MspDeInit() | 返初始化函数 |
HAL_PPP_ProcessCpltCallback() | 由外设中断或 DMA 中断调用,调用时 API 内部已经实现中断标记的清除的操作,用户只需要专注于自己的软件功能实现即可。 |
HAL_PPP_ErroCallback() | 外设或 DMA 中断中发生的错误,用于作错误处理。 |
五、HAL库的用户配置文件
stm32f1xx_hal_conf.h 用于裁剪 HAL 库和定义一些变量,官方没有直接提供这个文件,但在 STM32Cube_FW_F1_V1.8.4\Drivers\STM32F1xx_HAL_Driver\Inc 这个路径下提供了一个模版文件《stm32f1xx_hal_conf_template.h》,我们可以直接复制这个文件重命名为 stm32f1xx_hal_conf.h,做一些简单的修改即可,也可以从在官方的例程中直接复制过来。这里使用的开发板使用的芯片是 STM32F103 的 E 系列,所以也可以从下面的路径获取这个配置文件:STM32Cube_FW_F1_V1.8.4\Projects\STM3210E_EVAL\Templates\Inc。
5.1、配置外部高速晶振的频率
HSE_VALUE 这个参数表示我们的外部高速晶振的频率。这个参数请务必根据我们板子外部焊接的晶振频率来修改,源码在 76 行左右,官方默认是 25M。一般我们使用的 STM32F103 开发板外部高速晶振的频率是 8MHZ。我们没有在代码的其它地方定义过 HSE_VALUE 这个值,所以编译器最终会引用这里的值 8MHZ 作为外部调整晶振的频率值。
/* ########################## Oscillator Values adaptation ####################*/
/**
* @brief Adjust the value of External High Speed oscillator (HSE) used in your application.
* This value is used by the RCC HAL module to compute the system frequency
* (when HSE is used as system clock source, directly or through the PLL).
*/
#if !defined (HSE_VALUE)
#if defined(USE_STM3210C_EVAL)
#define HSE_VALUE 25000000U /*!< Value of the External oscillator in Hz */
#else
#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */
#endif
#endif /* HSE_VALUE */
#if !defined (HSE_STARTUP_TIMEOUT)
#define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */
#endif /* HSE_STARTUP_TIMEOUT */
使用官方的开发板需要定义 USE_STM3210C_EVAL 这个宏
5.2、配置外部低速晶振的频率
外部低速晶振频率,这个用于 RTC 时钟,这个官方默认是 32.768KHZ,一般我们使用的开发板的低速晶振也是这个频率,所以不用修改,源码在 110 行左右。
/**
* @brief External Low Speed oscillator (LSE) value.
* This value is used by the UART, RTC HAL module to compute the system frequency
*/
#if !defined (LSE_VALUE)
#define LSE_VALUE 32768U /*!< Value of the External oscillator in Hz*/
#endif /* LSE_VALUE */
5.3、使能外设
用户配置文件可以用来选择使能何种外设,源码配置在 31 行到 68 行。
/* ########################## Module Selection ############################## */
/**
* @brief This is the list of modules to be used in the HAL driver
*/
#define HAL_MODULE_ENABLED
#define HAL_ADC_MODULE_ENABLED
#define HAL_CAN_MODULE_ENABLED
/* #define HAL_CAN_LEGACY_MODULE_ENABLED */
#define HAL_CEC_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
#define HAL_CRC_MODULE_ENABLED
#define HAL_DAC_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
#define HAL_ETH_MODULE_ENABLED
#define HAL_EXTI_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
#define HAL_GPIO_MODULE_ENABLED
#define HAL_HCD_MODULE_ENABLED
#define HAL_I2C_MODULE_ENABLED
#define HAL_I2S_MODULE_ENABLED
#define HAL_IRDA_MODULE_ENABLED
#define HAL_IWDG_MODULE_ENABLED
#define HAL_NAND_MODULE_ENABLED
#define HAL_NOR_MODULE_ENABLED
#define HAL_PCCARD_MODULE_ENABLED
#define HAL_PCD_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_RTC_MODULE_ENABLED
#define HAL_SD_MODULE_ENABLED
#define HAL_SMARTCARD_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
#define HAL_SRAM_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED
#define HAL_USART_MODULE_ENABLED
#define HAL_WWDG_MODULE_ENABLED
#define HAL_MMC_MODULE_ENABLED
如果我们要不使用哪个外设的功能,只需要在源码相应位置把这个宏注释掉即可;
5.4、滴答定时器的优先级
stm32f1xx_hal_conf.h 文件的 126 行左右的宏定义 TICK_INT_PRIORITY 是滴答定时器的优先级。
#define TICK_INT_PRIORITY 0x0FU /*!< tick interrupt priority */
这个优先级很重要,因为如果其它的外设驱动程序的延时是通过滴答定时器提供的时间基准,来实现延时的话,又由于实现方式是滴答定时器对寄存器进行计数,所以当我们在其它中断服务程序里调用基于此时间基准的延迟函数 HAL_Delay,那么假如该中断的优先级高于滴答定时器的优先级,就会导致滴答定时器中断服务函数一直得不到运行,从而程序卡死在这里。所以滴答定时器的中断优先级一定要比这些中断高。
这个时间基准可以是滴答定时器提供,也可以是其他的定时器,默认是用滴答定时器。
5.5、断言
断言这个功能用来判断 HAL 函数的形参是否有效,并在参数错误时启用这个断言功能,告诉开发者代码错误的位置,断言功能由用户自己决定。这个功能的使能开关代码是一个宏,在源码的 159 行左右,默认是关闭的。
/* ########################## Assert Selection ############################## */
/**
* @brief Uncomment the line below to expanse the "assert_param" macro in the
* HAL drivers code
*/
/* #define USE_FULL_ASSERT 1U */
通过宏 USE_FULL_ASSERT 来选择功能,在源码 378 行左右。
/* Exported macro ------------------------------------------------------------*/
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */
当用户自己需要使用断言功能时,首先需要定义宏 USE_FULL_ASSERT 来使能断言功能,即把源码的 159行的宏定义 USE_FULL_ASSERT 的注释去掉即可。然后看到 源码 389 行左右的 assert_failed() 这个函数。根据这个宏定义,我们还需要去定义这个函数的功能,可以按以下格式自定义这个函数,出错后使代码停留在这里:
#ifdef USE_FULL_ASSERT
voidassert_failed(uint8_t*file,uint32_tline)
{
while(1){
}
}
#endif
可以看到这个函数里面没有实现如何功能,就是一个什么不做的死循环,具体功能请根据自己的需求去实现。file
是指向源文件的指针,line
是指向源文件的行数。__FILE__
是表示源文件名,__LINE__
是表示在源文件中的行数。
总的来说断言功能就是,在 HAL 库中,如果定义了 USE_FULL_ASSERT 这个宏,那么所有的 HAL 库函数将会检查函数的形参是否正确。如果错误将会调用 assert_failed() 这个函数,程序就会停留在这里,用户可以定位到出错的函数。这个功能实际上是在芯片上运行的时候的增加错误提示信息的功能,属于调试功能的一部分,实际我们的编译器就可以帮助定位到参数错误的问题并提示信息。
六、stm32f1xx_hal.c文件
这个文件内容比较多,包括 HAL 库的初始化、系统滴答、基准电压配置、IO 补偿、低功耗、EXTI 配置等都集合在这个文件里面。
6.1、HAL_Init()函数
该函数是 HAL 库的初始化函数,原则上在程序中必须优先调用,其主要实现如下功能:
- 使能 Flash 的预取缓冲器
- 根据闪存编程手册,打开预取指令可以提高对 I-Code 总线的访问效率,且 AHB 时钟的预分频系数不为 1 时,必须打开预取缓冲器;
- 设置 NVIC 优先级分组为 4
- 配置滴答定时器每 1ms 产生一个中断
- 在这个阶段,系统时钟还没有配置好,因此系统还是默认使用内部高速时钟源 HSI 在跑程序。
- 调用 HAL_MspInit 函数初始化底层硬件
- HAL_MspInit 函数在 STM32F1xx_hal.c 文件里面做了弱定义
/**
* @brief This function is used to initialize the HAL Library; it must be the first
* instruction to be executed in the main program (before to call any other
* HAL function), it performs the following:
* Configure the Flash prefetch.
* Configures the SysTick to generate an interrupt each 1 millisecond,
* which is clocked by the HSI (at this stage, the clock is not yet
* configured and thus the system is running from the internal HSI at 16 MHz).
* Set NVIC Group Priority to 4.
* Calls the HAL_MspInit() callback function defined in user file
* "stm32f1xx_hal_msp.c" to do the global low level hardware initialization
*
* @note SysTick is used as time base for the HAL_Delay() function, the application
* need to ensure that the SysTick time base is always set to 1 millisecond
* to have correct HAL operation.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_Init(void)
{
/* Configure Flash prefetch */
#if (PREFETCH_ENABLE != 0)
#if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \
defined(STM32F102x6) || defined(STM32F102xB) || \
defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \
defined(STM32F105xC) || defined(STM32F107xC)
/* Prefetch buffer is not available on value line devices */
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
HAL_InitTick(TICK_INT_PRIORITY);
/* Init the low level hardware */
HAL_MspInit();
/* Return function status */
return HAL_OK;
}
6.2、HAL_DeInit()函数
该函数取消初始化 HAL 库的公共部分,并且停止 systick,是一个可选的函数。该函数做了一下的事:
- 复位了 APB1、APB2 的时钟
- 调用 HAL_MspDeInit 函数,对底层硬件初始化进行复位
- HAL_MspDeInit 也在 stm32f1xx_hal.c 文件里面做了弱定义,并且与 HAL_MspInit 函数是一对存在
/**
* @brief This function de-Initializes common part of the HAL and stops the systick.
* of time base.
* @note This function is optional.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_DeInit(void)
{
/* Reset of all peripherals */
__HAL_RCC_APB1_FORCE_RESET();
__HAL_RCC_APB1_RELEASE_RESET();
__HAL_RCC_APB2_FORCE_RESET();
__HAL_RCC_APB2_RELEASE_RESET();
#if defined(STM32F105xC) || defined(STM32F107xC)
__HAL_RCC_AHB_FORCE_RESET();
__HAL_RCC_AHB_RELEASE_RESET();
#endif
/* De-Init the low level hardware */
HAL_MspDeInit();
/* Return function status */
return HAL_OK;
}
HAL_MspInit 函数负责对底层硬件初始化,HAL_MspDeInit 函数则是对底层硬件初始化进行复位。这两个函数都是需要用户根据自己的需求去实现功能,也可以不使用。
6.3、HAL_InitTick()函数
该函数用于初始化滴答定时器的时钟基准,主要功能如下:
- 配置滴答定时器 1ms 产生一次中断
- 配置滴答定时器的中断优先级
该函数是 __weak 修饰的函数,如果在关联工程中的其它地方没有定义 __weak 后面的函数,这里是 HAL_InitTick(),则使用此处的定义,否则就使用其它地方定义好的函数功能。我们可以通过重定义这个函数来选择其它的时钟源(如定时器)作为 HAL 库函数的时基或者通过重定义不开启 Systick 的功能和中断等。
该函数可以通过 HAL_Init() 或者 HAL_RCC_ClockConfig() 重置时钟。在默认情况下,滴答定时器是时间基准的来源。如果其他中断服务函数调用了 HAL_Delay(),必须小心,滴答定时器中断必须具有比调用了 HAL_Delay() 函数的其他中断服务函数的优先级高(数值较低),否则会导致滴答定时器中断服务函数一直得不到执行,从而卡死在这里。
/**
* @brief This function configures the source of the time base.
* The time source is configured to have 1ms time base with a dedicated
* Tick interrupt priority.
* @note This function is called automatically at the beginning of program after
* reset by HAL_Init() or at any time when clock is reconfigured by HAL_RCC_ClockConfig().
* @note In the default implementation, SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals.
* Care must be taken if HAL_Delay() is called from a peripheral ISR process,
* The SysTick interrupt must have higher priority (numerically lower)
* than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
* The function is declared as __weak to be overwritten in case of other
* implementation in user file.
* @param TickPriority Tick interrupt priority.
* @retval HAL status
*/
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
6.4、滴答定时器相关的函数
/**
* @brief This function is called to increment a global variable "uwTick"
* used as application time base.
* @note In the default implementation, this variable is incremented each 1ms
* in SysTick ISR.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
/**
* @brief Provides a tick value in millisecond.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval tick value
*/
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
/**
* @brief This function returns a tick priority.
* @retval tick priority
*/
uint32_t HAL_GetTickPrio(void)
{
return uwTickPrio;
}
/**
* @brief Set new tick Freq.
* @retval status
*/
HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq)
{
HAL_StatusTypeDef status = HAL_OK;
HAL_TickFreqTypeDef prevTickFreq;
assert_param(IS_TICKFREQ(Freq));
if (uwTickFreq != Freq)
{
/* Back up uwTickFreq frequency */
prevTickFreq = uwTickFreq;
/* Update uwTickFreq global variable used by HAL_InitTick() */
uwTickFreq = Freq;
/* Apply the new tick Freq */
status = HAL_InitTick(uwTickPrio);
if (status != HAL_OK)
{
/* Restore previous tick frequency */
uwTickFreq = prevTickFreq;
}
}
return status;
}
/**
* @brief Return tick frequency.
* @retval tick period in Hz
*/
HAL_TickFreqTypeDef HAL_GetTickFreq(void)
{
return uwTickFreq;
}
/**
* @brief This function provides minimum delay (in milliseconds) based
* on variable incremented.
* @note In the default implementation , SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals where uwTick
* is incremented.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @param Delay specifies the delay time length, in milliseconds.
* @retval None
*/
__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)
{
}
}
/**
* @brief Suspend Tick increment.
* @note In the default implementation , SysTick timer is the source of time base. It is
* used to generate interrupts at regular time intervals. Once HAL_SuspendTick()
* is called, the SysTick interrupt will be disabled and so Tick increment
* is suspended.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_SuspendTick(void)
{
/* Disable SysTick Interrupt */
CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
}
/**
* @brief Resume Tick increment.
* @note In the default implementation , SysTick timer is the source of time base. It is
* used to generate interrupts at regular time intervals. Once HAL_ResumeTick()
* is called, the SysTick interrupt will be enabled and so Tick increment
* is resumed.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_ResumeTick(void)
{
/* Enable SysTick Interrupt */
SET_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
}
如果函数被前缀 __weak 定义,则用户可以重新定义该函数。
6.5、HAL库版本相关的函数
uint32_tHAL_GetHalVersion(void); // 获取HAL库驱动程序版本
uint32_tHAL_GetREVID(void); // 获取设备修订标识符
uint32_tHAL_GetDEVID(void); // 获取设备标识符
uint32_tHAL_GetUIDw0(void); // 获取唯一设备标识符的第一个字
uint32_tHAL_GetUIDw1(void); // 获取唯一设备标识符的第二个字
uint32_tHAL_GetUIDw2(void); // 获取唯一设备标识符的第三个字
6.6、调试功能相关函数
void HAL_DBGMCU_EnableDBGSleepMode(void);
void HAL_DBGMCU_DisableDBGSleepMode(void);
void HAL_DBGMCU_EnableDBGStopMode(void);
void HAL_DBGMCU_DisableDBGStopMode(void);
void HAL_DBGMCU_EnableDBGStandbyMode(void);
void HAL_DBGMCU_DisableDBGStandbyMode(void);
这六个函数用于调试功能,默认调试器在睡眠模式下无法调试代码,开发过程中配合这些函数,可以在不同模式下(睡眠模式、停止模式和待机模式),使能或者失能调试器。
七、HAL库中断处理
设置外设的控制句柄结构体 PPP_HandleType 和初始化 PPP_InitType 结构体的参数,然后调用HAL库对应这个驱动的初始化 HAL_PPP_Init(),由于这个 API 中有针对外设初始化细节的接口 Hal_PPP_Mspinit(),我们需要重新实现这个函数并完成外设时钟、IO 等细节差异的设置,完成各细节处理后,使用 HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ() 来使能我们的外设中断;
定义中断处理函数 PPP_IRQHandler,并在中断函数中调用 HAL_ppp_function_IRQHandler() 来判断和处理中断标记;HAL 库中断处理完成后,根据对应中的调用我们需要自定义的中断回调接口 HAL_PPP_ProcessCpltCallback(); 如串口接收函数 HAL_UART_RxCpltCallback(),我们在这个函数中实现我们对串口接收数据想做的处理;
中断响应处理完成后,STM32 芯片继续顺序执行我们定义的主程序功能,按照以上处理的标准流程完成了一次中断响应。
标签:__,03,HAL,函数,STM32,PPP,外设,define From: https://www.cnblogs.com/kurome/p/17566475.html