#C0107
——《沧海拾昧集》@CuPhoenix
本章目录
3 HAL库概述
3.1 HAL库和用户程序文件
略。
3.2 HAL库的数据结构
每个 HAL 驱动程序可能包含以下数据结构:
外设句柄结构体
初始化/配置结构体
特定进程的结构体
3.2.1 外设句柄结构体(译注:是HAL库的重要特点)
HAL 库的 API 具有模块化的通用多实例架构,允许同时操作多个 IP 实例。PPP_HandleTypeDef *handle
是 HAL 驱动程序中实现的主要结构(称为句柄)。句柄处理 外设 / 模块 的配置和寄存器,并包含所有该外设设备流所需的结构和变量。
外设句柄用于以下目的:
- 多实例支持:每个 外设 / 模块实例都有其自己的句柄,因此实例资源是独立的。
- 外设进程间通信:句柄用于管理进程例程之间的共享数据资源。(如:全局指针、DMA 句柄、状态机等)
- 存储:句柄也能用于管理给定 HAL 驱动程序中的全局变量。
句柄示例(USART的句柄):
typedef struct {
USART_TypeDef *Instance; /* USART 寄存器的基地址 */
USART_InitTypeDef Init; /* USART 通信参数 */
uint8_t *pTxBuffPtr; /* USART 发送数据缓冲区的指针 */
uint16_t TxXferSize; /* USART 发送传输数据大小 */
__IO uint16_t TxXferCount; /* USART 发送传输计数器 */
uint8_t *pRxBuffPtr; /* USART 接收数据缓冲区的指针 */
uint16_t RxXferSize; /* USART 接收传输数据大小 */
__IO uint16_t RxXferCount; /* USART 接收传输计数器 */
DMA_HandleTypeDef *hdmatx; /* USART 发送 DMA 句柄参数 */
DMA_HandleTypeDef *hdmarx; /* USART 接收 DMA 句柄参数 */
HAL_LockTypeDef Lock; /* 锁定对象 */
__IO HAL_USART_StateTypeDef State; /* USART 通信状态 */
__IO HAL_USART_ErrorTypeDef ErrorCode; /* USART 错误码 */
} USART_HandleTypeDef;
注:
- 多实例支持表明程序中使用的所有 API 都是可重入的(译注:可重入函数 reentrant 指可被递归调用的函数),因此必须避免在这些 API 中使用全局变量(否则若递归调用时依赖的某个全局变量值被更改,程序将无法保持可重入性)。因此,需遵守以下规则:
- 可重入代码中不含任何静态(或全局)非常量数据:可重入函数可以使用全局数据。例如,可重入中断服务例程可以获取一个硬件状态(如串口读缓冲区),该状态不仅是全局的,而且是易变的。然而,通常不建议使用静态变量和全局数据,即这些变量应仅使用原子读-改写-写指令。(译注:原子操作,即一种不可被中断的操作,不应被任何调度机制打断执行)执行原子操作期间,不能发生中断或信号。
- 可重入代码不得修改其自身的代码。
- 当外设能够通过 DMA 同时管理多个进程时(全双工情况),每个进程的 DMA 接口句柄会被添加到
PPP_HandleTypeDef
中。- 对于可共享的系统外设,不使用句柄或实例对象。包括: GPIO、SYSTICK、NVIC、PWR、RCC、FLASH。
3.2.2 初始化/配置结构体
结构体在通用驱动程序头文件中定义,适用于所有型号。此外,对于独有的功能,结构体会在每个型号的扩展头文件中定义。例如:
typedef struct {
uint32_t BaudRate; /* 配置 UART 通信的波特率 */
uint32_t WordLength; /* 指定帧中传输或接收的数据位数 */
uint32_t StopBits; /* 指定传输的停止位数 */
uint32_t Parity; /* 指定校验模式 */
uint32_t Mode; /* 指定接收模式或发送模式是否启用 */
uint32_t HwFlowCtl; /* 指定硬件流控制模式是否启用 */
uint32_t OverSampling; /* 指定是否启用超采样8,以实现更高的速度(最高可达fPCLK/8) */
} UART_InitTypeDef;
通过初始化结构体进行配置,例如:
HAL_ADC_ConfigChannel (ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig)
3.2.3 特定进程的结构体
特定进程的结构体用于处理特定的进程。这些结构在通用驱动程序头文件中定义(通用 API)。例如:
HAL_PPP_Process (PPP_HandleTypeDef* hadc,PPP_ProcessConfig* sConfig)
3.3 API类型
HAL 库中的 API 被分为以下三类:
- 通用 API: 适用于所有 STM32 设备的通用 API,存在于所有 STM32 微控制器的通用 HAL 驱动程序文件中。例如:
HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_DeInit(ADC_HandleTypeDef *hadc);
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
void HAL_ADC_IRQHandler(ADC_HandleTypeDef* hadc);
-
扩展 API
- 系列特定 API:适用于某一特定系列的 API。这些 API 位于扩展 HAL 驱动程序文件中,例如:
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc, uint32_t SingleDiff); uint32_t HAL_ADCEx_Calibration_GetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);
- 器件编号特定 API:这些 API 实现在扩展文件中,并由特定的定义语句与特定器件编号相关联,例如:
#if defined (STM32F101xG) || defined (STM32F103x6)|| defined (STM32F103xB) || defined (STM32F105xC) || defined (STM32F107xC) || defined (STM32F103xE) || defined (STM32F103xG) /* ADC 并行模式(multimode) */HAL_StatusTypeDef HAL_ADCEx_MultiModeStart_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_tLength); HAL_StatusTypeDef HAL_ADCEx_MultiModeStop_DMA(ADC_HandleTypeDef *hadc); #endif /* STM32F101xG || defined STM32F103x6 || defined STM32F103xB || defined STM32F105xC || defined STM32F107xC || defined STM32F103xE || defined STM32F103xG */
3.4 HAL库支持的设备
略。
3.5 HAL库通用规则
3.5.1 HAL库的命名规则
HAL 库中使用以下命名规则:
通用 | 系列特定 | 器件特定 | |
---|---|---|---|
文件名 | stm32f1xx_hal_ppp (c/h) | stm32f1xx_hal_ppp_ex (c/h) | stm32f1xx_ hal_ppp_ex (c/h) |
模块名 | HAL_PPP_ MODULE | HAL_PPP_ MODULE | HAL_PPP_ MODULE |
函数名 | HAL_PPP_Function,HAL_PPP_FeatureFunction_MODE | HAL_PPPEx_Function,HAL_PPPEx_FeatureFunction_MODE | HAL_PPPEx_Function,HAL_PPPEx_FeatureFunction_MODE |
句柄名 | PPP_HandleTypedef | NA | NA |
初始化结构体名 | PPP_InitTypeDef | NA | PPP_InitTypeDef |
枚举名 | HAL_PPP_StructnameTypeDef | NA | NA |
- PPP 前缀(译注:在本手册中 PPP 是一种占位符,就像 xxx 一样)指的是外设功能模式,而不是外设本身。例如,USART 外设的 PPP 可以是 USART、IRDA、UART 或 SMARTCARD,具体取决于外设模式。
- 一个文件中使用的常量定义在该文件内。若一个常量在多个文件中使用,则定义在头文件中。所有常量均采用大写字母,外设驱动函数参数除外。
- typedef 变量名应以 _TypeDef 结尾。
- 寄存器被视为常量,通常使用大写字母,并采用 STM32F1 参考手册中的相同缩写。
- 外设寄存器在 CMSIS 头文件中的 PPP_TypeDef 结构体中声明(例如,ADC_TypeDef):stm32f1xxx.h 对应 stm32f100xb.h、stm32f100xe.h、stm32f101x6.h、stm32f101xb.h、stm32f101xe.h、stm32f101xg.h、stm32f102x6.h、stm32f102xb.h、stm32f103x6.h、stm32f103xb.h、stm32f103xe.h、stm32f103xg.h、stm32f105xc.h 和 stm32f107xc.h。
- **外设函数名以 HAL_ 为前缀,后接对应的外设缩写(大写),然后是一个下划线。每个单词的首字母大写(例如 HAL_UART_Transmit())。**函数名中只允许一个下划线,用于分隔外设缩写和函数名的其他部分。
- 包含 PPP 外设初始化参数的结构体名为 PPP_InitTypeDef(例如 ADC_InitTypeDef)。
- 包含 PPP 外设特定配置参数的结构体名为 PPP_xxxxConfTypeDef(例如 ADC_ChannelConfTypeDef)。
- 外设句柄结构体名为 PPP_HandleTypedef(例如 DMA_HandleTypeDef)。
- 用于根据 PPP_InitTypeDef 中指定的参数初始化 PPP 外设的函数名为 HAL_PPP_Init(例如 HAL_TIM_Init())。
- 用于将 PPP 外设寄存器重置为默认值的函数名为 HAL_PPP_DeInit(例如 HAL_TIM_DeInit())。
- MODE 后缀指的是处理模式,可以是轮询、中断或 DMA。例如,当使用 DMA 作为额外资源时,函数应命名为:HAL_PPP_Function_DMA()。
- Feature 前缀应指代新功能。
3.5.2 HAL通用命名规则
对于共享和系统外设,不使用句柄或实例对象。此规则适用于 GPIO、SYSTICK、NVIC、RCC、FLASH。例如,HAL_GPIO_Init() 仅需要 GPIO 地址和其配置参数:
HAL_StatusTypeDef HAL_GPIO_Init (GPIO_TypeDef* GPIOx, GPIO_InitTypeDef *Init) {
/*GPIO 初始化内容 */
}
处理中断和特定时钟配置的宏在每个外设/模块驱动程序中定义。这些宏在外设驱动程序的头文件中导出,以便扩展文件可以使用。下表列出了这些宏:
宏定义 | 说明 |
---|---|
_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_, __INTERRUPT__) | 获取特定外设的标志状态 |
__HAL_PPP_CLEAR_FLAG(__HANDLE_, __INTERRUPT__) | 清除特定外设的标志状态 |
__HAL_PPP_ENABLE(__HANDLE_) | 启用外设 |
__HAL_PPP_DISABLE(__HANDLE_) | 禁用外设 |
__HAL_PPP_XXXX(__HANDLE_, __PARAM__) | 特定 PPP HAL 驱动程序宏 |
__HAL_PPP_GET_IT_SOURCE(__HANDLE_, __INTERRUPT__) | 检查指定中断的来源 |
注:
NVIC 和 SYSTICK 是 Arm Cortex 的核心功能,相关的 API 位于 stm32f1xx_hal_cortex.c 文件中。
当从寄存器中读取状态位或标志时,其值由位移值组成,这取决于读取值的数量和大小。返回的状态宽度为 32 位。如:
STATUS = XX | (YY << 16)
或STATUS = XX | (YY << 8) | (YY << 16) | (YY << 24)
。在使用 HAL_PPP_Init() API 之前,PPP 句柄必须有效。初始化函数在修改句柄字段之前会执行检查。
以下宏定义被使用:
条件宏:
#define ABS(x) (((x) > 0) ? (x) : -(x))
伪代码宏(多条指令宏)
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \ do{ \ (__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \ (__DMA_HANDLE_).Parent = (__HANDLE__); \ } while(0)
3.5.3 HAL中断处理程序和回调函数
-
HAL 外设驱动程序还包括:
HAL_PPP_IRQHandler()
外设中断处理程序,应从stm32f1xx_it.c
文件中调用。- 用户回调函数。这些回调函数被定义为空函数,并具有“weak”(弱)属性。用户需要在其代码中定义这些函数。
-
用户回调函数有三种类型:
-
外设系统级 初始化 / 去初始化 回调:
HAL_PPP_MspInit()
和HAL_PPP_MspDeInit()
例如:
HAL_USART_MspInit()
,从HAL_PPP_Init()
API 函数中调用,用于执行外设系统级初始化(如 GPIO、时钟、DMA、中断)。 -
处理完成回调:
HAL_PPP_ProcessCpltCallback
例如:
HAL_USART_TxCpltCallback
,当处理完成时,由外设或 DMA 中断处理程序调用。 -
错误回调:
HAL_PPP_ErrorCallback
例如:
HAL_USART_ErrorCallback
,当发生错误时,由外设或 DMA 中断处理程序调用。
-
-
译注:回调函数,是一个被作为参数传递的函数。例如,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。使用回调函数而不是普通函数的具体意义在于解耦。如下代码片段,对于库中封装的 Library 函数,通过对 Callback 函数的修改与传入,可以使 Library 函数实现不同功能,而并不需要修改库中已经固定的函数。
#i