#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 函数实现不同功能,而并不需要修改库中已经固定的函数。
#include<stdio.h> #include<softwareLib.h> int Callback(){ // 回调函数 (...) return 0; } int main(){ // 主程序 (...) Library(Callback); (...) return 0; }
下例进一步展示了回调函数的使用:
#include<stdio.h> void Callback_1(int x) { printf("Hello, this is Callback_1: x = %d ", x); } void Callback_2(int x) { printf("Hello, this is Callback_2: x = %d ", x); } void Callback_3(int x) { printf("Hello, this is Callback_3: x = %d ", x); } int Handle(int y, int (*Callback)(int)){ printf("Entering Handle Function. "); Callback(y); printf("Leaving Handle Function. "); } int main(){ int a = 2, b = 4, int c = 6; printf("Entering Main Function. "); Handle(a, Callback_1); Handle(b, Callback_2); Handle(c, Callback_3); printf("Leaving Main Function. "); return 0; } /* 程序输出: * Entering Main Function. * Entering Handle Function. * Hello, this is Callback_1: x = 2 * Leaving Handle Function. * Entering Handle Function. * Hello, this is Callback_2: x = 4 * Leaving Handle Function. * Entering Handle Function. * Hello, this is Callback_3: x = 6 * Leaving Handle Function. * Leaving Main Function. */
3.6 HAL通用API
-
通用 API 提供了适用于所有 STM32 设备的通用功能,分为四组 API:
-
初始化和去初始化函数:
HAL_PPP_Init()
,HAL_PPP_DeInit()
**功能:**用于初始化外设并配置低级资源,主要包括时钟、GPIO、备用功能(AF),以及可能的 DMA 和中断。
HAL_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()
**功能:**在运行时检索外设及数据流状态,并识别发生的错误类型。下面的示例基于 ADC 外设。此通用 API 列表并非详尽无遗,仅作为示例提供。
-
对于某些 外设 / 模块 驱动程序,这些分组可能会根据 外设 / 模块 的实现进行修改。例如,在 TIM 定时器的驱动程序中,API 的分组是基于定时器功能(PWM、OC、IC 等)。
以 ADC 为例看通用接口:
功能组 | 接口函数名称 | 说明 |
---|---|---|
初始化和去初始化函数 | HAL_ADC_Init() | 初始化外设并配置低级资源(时钟、GPIO、备用功能等) |
初始化和去初始化函数 | HAL_ADC_DeInit() | 恢复外设的默认状态,释放低级资源,并消除与硬件的直接依赖 |
输入输出操作函数 | HAL_ADC_Start () | 使用轮询方法时启动 ADC 转换 |
输入输出操作函数 | HAL_ADC_Stop () | 使用轮询方法时停止 ADC 转换 |
输入输出操作函数 | HAL_ADC_PollForConversion() | 使用轮询方法时等待转换结束。用户可以根据应用需求指定超时值 |
输入输出操作函数 | HAL_ADC_Start_IT() | 使用中断方法时启动 ADC 转换 |
输入输出操作函数 | HAL_ADC_Stop_IT() | 使用中断方法时停止 ADC 转换 |
输入输出操作函数 | HAL_ADC_IRQHandler() | ADC 中断处理函数 |
输入输出操作函数 | HAL_ADC_ConvCpltCallback() | 中断服务子例程中的回调函数,用于指示当前处理过程或 DMA 传输已完成 |
输入输出操作函数 | HAL_ADC_ErrorCallback() | 中断服务子例程中的回调函数,用于在发生外设错误或 DMA 传输错误时调用 |
控制函数 | HAL_ADC_ConfigChannel() | 配置所选的 ADC 常规通道、对应的序列器中的等级和采样时间 |
控制函数 | HAL_ADC_AnalogWDGConfig | 配置选定 ADC 的模拟看门狗 |
状态和错误函数 | HAL_ADC_GetState() | 在运行时获取外设及数据流的状态 |
状态和错误函数 | HAL_ADC_GetError() | 在运行时获取发生的错误类型 |
3.7 HAL扩展API
3.7.1 HAL扩展模型概述
- 扩展 API 提供了针对特定系列(或系列内特定部件号)的特定功能或覆盖修改的 API(译注:即可以对通用 API 的功能进行功能扩展或重写)。 扩展模型包含一个额外的文件
stm32f1xx_hal_ppp_ex.c
,该文件包括所有针对特定部件号的特定函数和定义语句(stm32f1xx_hal_ppp_ex.h
)。例如,ADC 的扩展 API 有HAL_ADCEx_CalibrationStart()
,该函数用于启动自动 ADC 校准。
3.7.2 应用案例
- HAL 驱动程序可以通过以下五种不同方式处理特定 IP 功能:
- 添加特定部件号的功能:当需要针对某个设备的新功能时,新的 API 会被添加到
stm32f1xx_hal_ppp_ex.c
扩展文件中。这些 API 被命名为HAL_PPPEx_Function()
。 - 添加系列特定功能:在这种情况下,API 会被添加到扩展驱动程序的 C 文件中,并命名为
HAL_PPPEx_Function()
。 - 添加新的外设(特定于某个系列中的设备):当需要一个仅在特定设备中可用的外设时,相关的 API 将被添加到新的
stm32f1xx_hal_newppp.c
文件中。然而,这个文件的包含需要在stm32f1xx_hal_conf.h
中通过宏来选择。 - 更新现有通用 API:在这种情况下,例程在
stm32f1xx_hal_ppp_ex.c
扩展文件中使用相同的名称进行定义,而通用 API 被定义为弱符号,这样编译器会用新定义的函数覆盖原有的例程。 - 更新现有数据结构:特定设备部件号的数据结构(例如
PPP_InitTypeDef
)可能具有不同的字段。在这种情况下,数据结构会在扩展头文件中进行定义,并通过特定部件号的定义语句进行区分。
- 添加特定部件号的功能:当需要针对某个设备的新功能时,新的 API 会被添加到
3.8 文件组织结构
-
通用 HAL 驱动程序文件的头文件 (
stm32f1xx_hal.h
) 包含了整个 HAL 库的通用配置。用户仅需在源文件和 HAL C 源文件中包含该头文件,便可使用 HAL 资源。 -
每个 PPP 驱动程序都是一个独立的模块。用户必须在配置文件中启用相应的
USE_HAL_PPP_MODULE
定义语句,才能在项目中使用,例如:/********************************************************************* * @file stm32f1xx_hal_conf.h * @author MCD Application Team * @version VX.Y.Z * @date dd-mm-yyyy * @brief 本文件包含要使用的模块 ********************************************************************** (…) #define USE_HAL_USART_MODULE #define USE_HAL_IRDA_MODULE #define USE_HAL_DMA_MODULE #define USE_HAL_RCC_MODULE (…)
3.9 HAL的通用资源
-
包含 通用定义枚举、结构体和宏,定义在
stm32f1xx_hal_def.h
文件中。 -
通用枚举
- HAL 状态枚举
HAL_StatusTypeDef
几乎被所有 HAL API 使用(除了布尔函数和 IRQ 处理程序)。其返回当前 API 操作的状态,具有以下四种可能的值:
Typedef enum { HAL_OK = 0x00, HAL_ERROR = 0x01, HAL_BUSY = 0x02, HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef;
- HAL 锁定枚举
HAL_StatusTypeDef
用于所有 HAL API,以防止意外访问共享资源:
typedef enum { HAL_UNLOCKED = 0x00, /* 资源未锁定 */ HAL_LOCKED = 0x01 /* 资源锁定 */ } HAL_LockTypeDef;
- HAL 状态枚举
-
通用宏
NULL
宏
#ifndef NULL #define NULL 0 #endif
HAL_MAX_DELAY
宏
#define HAL_MAX_DELAY 0xFFFFFFFF
- 将 PPP 外设与 DMA 结构指针关联的宏
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \ do{ \ (__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \ (__DMA_HANDLE_).Parent = (__HANDLE__); \ } while(0)
3.10 HAL 配置
配置文件 stm32f1xx_hal_conf.h
允许为用户应用程序自定义驱动程序。修改该配置并非强制要求,应用程序可以使用默认配置而无需任何修改。要配置这些参数,用户应通过取消注释、注释或修改相关定义语句的值来启用、禁用或修改某些选项,如下表所示:
配置项 | 描述 | 默认值 |
---|---|---|
HSE_VALUE | 外部高速振荡器(HSE)的值(单位:Hz) | STM3210C-EVAL 上为 25,000,000,其余为 8,000,000 |
HSE_STARTUP_TIMEOUT | HSE 启动超时(单位:毫秒) | 5000 |
HSI_VALUE | 定义内部振荡器(HSI)的值(单位:Hz) | 8,000,000 |
LSE_VALUE | 外部低速振荡器(LSE)的值(单位:Hz) | 32,768 |
LSE_STARTUP_TIMEOUT | LSE 启动超时(单位:毫秒) | 5000 |
VDD_VALUE | VDD 值(单位:毫伏) | 3300 |
USE_RTOS | 启用 RTOS 的使用 | FALSE(保留值) |
PREFETCH_ENABLE | 启用预取功能 | TRUE |
注:
stm32f1xx_hal_conf_template.h
文件位于 HAL 驱动程序的 Inc 文件夹中。应将其复制到用户文件夹中,重新命名并按照上述说明进行修改。- 默认情况下,
stm32f1xx_hal_conf_template.h
文件中定义的值与示例和演示中使用的值相同。所有 HAL 包含文件都已启用,以便可以在用户代码中使用而无需修改。
3.11 HAL 系统外设处理
本节概述了 HAL 驱动程序如何处理系统外设。每个外设驱动程序的完整 API 列表在对应的驱动程序描述部分提供。
3.11.1 时钟(Clock)
-
可以使用两个函数来配置系统时钟(主要使用):
-
HAL_RCC_OscConfig (RCC_OscInitTypeDef *RCC_OscInitStruct)
**功能:**配置 / 启用多个时钟源(HSE、HSI、LSE、LSI、PLL)。
-
HAL_RCC_ClockConfig (RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
功能:
- 选择系统时钟源。
- 配置 AHB、APB1 和 APB2 时钟分频器。
- 配置 Flash 存储器的等待状态数。
- 当 HCLK 时钟发生变化时,更新 SysTick 配置。
-
-
一些外设时钟不是由系统时钟派生出来的(如 RTC、USB)。在这种情况下,时钟配置通过在
stm32f1xx_hal_rcc_ex.c
中定义的扩展 API 进行: HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit) -
此外,还提供了一些 RCC HAL 驱动程序函数:
-
时钟去初始化函数 HAL_RCC_DeInit()
功能::将时钟配置恢复到复位状态。
-
获取时钟函数,允许检索各种时钟配置(系统时钟、HCLK、PCLK1、PCLK2 等)。
-
MCO 和 CSS 配置函数。
-
-
在
stm32f1xx_hal_rcc.h
和stm32f1xx_hal_rcc_ex.h
中定义了一组宏,用于在 RCC 模块寄存器上执行基本操作,如外设时钟门控/复位控制:-
__HAL_PPP_CLK_ENABLE / __HAL_PPP_CLK_DISABLE
**功能:**启用/禁用外设时钟。
-
__HAL_PPP_FORCE_RESET / __HAL_PPP_RELEASE_RESET
**功能:**强制/释放外设复位。
-
__HAL_PPP_CLK_SLEEP_ENABLE / __HAL_PPP_CLK_SLEEP_DISABLE
**功能:**在睡眠模式下启用/禁用外设时钟
-
3.11.2 GPIO
-
GPIO HAL API 包括以下函数:
- HAL_GPIO_Init() / HAL_GPIO_DeInit()
- HAL_GPIO_ReadPin() / HAL_GPIO_WritePin()
- HAL_GPIO_TogglePin()
-
除了标准的 GPIO 模式(输入、输出、模拟),引脚模式还可以配置为 EXTI 模式以支持中断或事件生成。当选择 EXTI 模式并启用中断生成时,用户必须在
stm32f1xx_it.c
中调用 HAL_GPIO_EXTI_IRQHandler() 函数,并实现 HAL_GPIO_EXTI_Callback() 回调函数。 -
GPIO_InitTypeDef 结构体有四个字段构成:
-
Pin:指定要配置的 GPIO 引脚。
采用值:
GPIO_PIN_x
,其中 x[0…15]GPIO_PIN_All
-
Mode:指定所选引脚的操作模式(GPIO 模式或 EXTI 模式)。
采用值(GPIO模式):
GPIO_MODE_INPUT
: 输入模式(译注:标准库中的浮空输入、上拉输入、下拉输入均使用该模式,并通过调整对应的上拉 / 下拉电阻实现)GPIO_MODE_ANALOG
: 模拟输入模式GPIO_MODE_OUTPUT_PP
: 推挽输出模式GPIO_MODE_OUTPUT_OD
: 开漏输出模式GPIO_MODE_AF_PP
: 复用推挽输出模式GPIO_MODE_AF_OD
: 复用开漏输出模式
采用值(外部中断模式):
GPIO_MODE_IT_RISING
: 上升沿触发检测GPIO_MODE_IT_FALLING
: 下降沿触发检测GPIO_MODE_IT_RISING_FALLING
: 上升沿 / 下降沿触发检测
采用值(外部事件模式):
GPIO_MODE_EVT_RISING
: 上升沿事件检测GPIO_MODE_EVT_FALLING
: 下降沿事件检测GPIO_MODE_EVT_RISING_FALLING
: 上升沿 / 下降沿事件检测
-
Pull:指定所选引脚的上拉或下拉电阻。
采用值:
GPIO_NOPULL
: 无上下拉电阻GPIO_PULLUP
: 上拉电阻GPIO_PULLDOWN
: 下拉电阻
-
Speed:指定所选引脚的速度。
采用值:
GPIO_SPEED_LOW
: 低速(译注:对应标准库的 GPIO_Speed_2MHz,适合 USART (波特率 115.2k )等低速应用)GPIO_SPEED_MEDIUM
: 中速(译注:对应标准库的 GPIO_Speed_10MHz,适合 I2C (波特率 400k )等中速应用)GPIO_SPEED_HIGH
: 高速(译注:对应标准库的 GPIO_Speed_50MHz,适合 SPI (波特率 9M/18M )等高速应用)
译注:在不同系列的 STM32 芯片中,该采用值不尽相同,上述为 stm32f1xx 系列采用值,而在 stm32f4xx(如 F407)中为 LOW(8MHz)、MEDIUM(50MHz)、HIGH(100MHz)、VERY_HIGH(180MHz),又如在 stm32f7xx (如 F743)中为 LOW(16MHz)、MEDIUM(110MHz)、HIGH(166MHz)、VERY_HIGH(220MHz)。
-
以下是典型的 GPIO 配置示例
-
将 GPIO 配置为推挽输出以驱动外部 LED:
GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
-
将 PA0 配置为外部中断,触发为下降沿:
GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
-
3.11.3 嵌套中断向量控制器(NVIC)和系统滴答定时器(SysTick Timer)
HAL库中的 stm32f1xx_hal_cortex.c
驱动提供了用于处理 NVIC 和 SysTick 的 API。支持的 API 包括:
- NVIC
- HAL_NVIC_SetPriority() / HAL_NVIC_SetPriorityGrouping()
- HAL_NVIC_GetPriority() / HAL_NVIC_GetPriorityGrouping()
- HAL_NVIC_EnableIRQ() / HAL_NVIC_DisableIRQ()
- HAL_NVIC_SystemReset()
- HAL_NVIC_GetPendingIRQ() / HAL_NVIC_SetPendingIRQ() / HAL_NVIC_ClearPendingIRQ()
- HAL_NVIC_GetActive(IRQn)
- SysTick
- HAL_SYSTICK_IRQHandler()
- HAL_SYSTICK_Config()
- HAL_SYSTICK_CLKSourceConfig()
- HAL_SYSTICK_Callback()
3.11.4 电源管理(PWR)
PWR HAL 驱动程序负责电源管理。所有 STM32 系列共享的功能包括:
- PVD 配置、使能/禁用及中断处理
- HAL_PWR_ConfigPVD()
- HAL_PWR_EnablePVD() / HAL_PWR_DisablePVD()
- HAL_PWR_PVD_IRQHandler()
- HAL_PWR_PVDCallback()
- 唤醒引脚配置
- HAL_PWR_EnableWakeUpPin() / HAL_PWR_DisableWakeUpPin()
- 低功耗模式进入
- HAL_PWR_EnterSLEEPMode()
- HAL_PWR_EnterSTOPMode()
- HAL_PWR_EnterSTANDBYMode()
3.11.5 外部中断 / 事件控制器(EXTI)
EXTI 并不是一个独立的外设,而是由其他外设使用的服务,这些外设通过 EXTI HAL API 进行处理。此外,每个外设的 HAL 驱动程序在其头文件中实现了与之关联的 EXTI 配置和功能宏。前 16 条连接到 GPIO 的 EXTI 通道由 GPIO 驱动程序管理。GPIO_InitTypeDef 结构体允许将 I/O 配置为外部中断或外部事件。内部连接到 PVD、RTC、USB 和以太网的 EXTI 通道由这些外设的 HAL 驱动程序通过下表中的宏进行配置。EXTI 的内部连接取决于目标 STM32 微控制器(更多细节请参见产品数据手册)。
宏名称 | 描述 |
---|---|
__HAL_PPP{SUBLOCK}__EXTI_ENABLE_IT() | 使能指定的 EXTI 通道中断,例:__HAL_PWR_PVD_EXTI_ENABLE_IT() |
__HAL_PPP{SUBLOCK}__EXTI_DISABLE_IT() | 禁用指定的 EXTI 通道中断,例:__HAL_PWR_PVD_EXTI_DISABLE_IT() |
__HAL_PPP{SUBLOCK}__EXTI_GET_FLAG() | 获取指定 EXTI 通道中断标志位的状态,例:__HAL_PWR_PVD_EXTI_GET_FLAG() |
__HAL_PPP{SUBLOCK}__EXTI_CLEAR_FLAG() | 清除指定 EXTI 通道中断标志位,例:__HAL_PWR_PVD_EXTI_CLEAR_FLAG() |
__HAL_PPP{SUBLOCK}__EXTI_GENERATE_SWIT() | 为指定的 EXTI 通道生成软件中断,例:__HAL_PWR_PVD_EXTI_GENERATE_SWIT() |
__HAL_PPP{SUBLOCK}__EXTI_ENABLE_EVENT() | 使能指定的 EXTI 通道事件,例:__HAL_RTC_WAKEUP_EXTI_ENABLE_EVENT() |
__HAL_PPP{SUBLOCK}__EXTI_DISABLE_EVENT() | 禁用指定的 EXTI 通道事件,例:__HAL_RTC_WAKEUP_EXTI_DISABLE_EVENT() |
__HAL_PPP{SUBLOCK}__EXTI_ENABLE_RISING_EDGE() | 配置 EXTI 中断或事件为上升沿触发 |
__HAL_PPP{SUBLOCK}__EXTI_ENABLE_FALLING_EDGE() | 配置 EXTI 中断或事件为下降沿触发 |
__HAL_PPP{SUBLOCK}__EXTI_DISABLE_RISING_EDGE() | 禁用 EXTI 中断或事件为上升沿触发 |
__HAL_PPP{SUBLOCK}__EXTI_DISABLE_FALLING_EDGE() | 禁用 EXTI 中断或事件为下降沿触发 |
__HAL_PPP{SUBLOCK}__EXTI_ENABLE_RISING_FALLING_EDGE() | 配置 EXTI 中断或事件为上升/下降沿触发 |
__HAL_PPP{SUBLOCK}__EXTI_DISABLE_RISING_FALLING_EDGE() | 禁用 EXTI 中断或事件为上升/下降沿触发 |
如果选择了 EXTI 中断模式,用户应用程序必须从 stm32f1xx_it.c
文件中调用 HAL_PPP_FUNCTION_IRQHandler()(例如 HAL_PWR_PVD_IRQHandler()),并实现 HAL_PPP_FUNCTIONCallback() 回调函数(例如 HAL_PWR_PVDCallback())。
3.11.6 DMA
DMA HAL 驱动程序允许启用和配置连接到 DMA 通道的外设(内部 SRAM / FLASH 内存除外,这些内存不需要初始化)。有关每个外设对应的 DMA 请求的详细信息,请参阅产品参考手册。
-
对于给定的通道,
HAL_DMA_Init()
API 允许通过以下参数编程所需的配置:- 传输方向
- 源和目标数据格式
- 循环、普通或外设流控制模式
- 通道优先级等级
- 源和目标递增模式
-
有两种操作模式可用:
-
轮询模式 I/O 操作
- 当源地址、目标地址和要传输的数据长度已配置好时,使用
HAL_DMA_Start()
启动 DMA 传输。 - 使用
HAL_DMA_PollForTransfer()
轮询当前传输的结束。在这种情况下,可以根据用户应用程序配置固定的超时时间。
- 当源地址、目标地址和要传输的数据长度已配置好时,使用
-
中断模式 I/O 操作
- 使用
HAL_NVIC_SetPriority()
配置 DMA 中断优先级。 - 使用
HAL_NVIC_EnableIRQ()
启用 DMA IRQ 处理程序。 - 当源地址、目标地址和要传输的数据长度已配置好时,使用
HAL_DMA_Start_IT()
启动 DMA 传输。在这种情况下,配置 DMA 中断。 - 在 DMA 中断服务程序中调用
HAL_DMA_IRQHandler()
。 - 当数据传输完成时,
HAL_DMA_IRQHandler()
函数会被执行,并可以通过自定义XferCpltCallback
和XferErrorCallback
函数指针(即 DMA 句柄结构的成员)调用用户函数。
- 使用
-
-
为了确保 DMA 的高效管理,还提供了额外的函数和宏:
- 使用
HAL_DMA_GetState()
函数返回 DMA 状态,并在检测到错误时使用HAL_DMA_GetError()
。 - 使用
HAL_DMA_Abort()
函数中止当前传输。
- 使用
-
最常用的 DMA HAL 驱动程序宏包括:
__HAL_DMA_ENABLE
:启用指定的 DMA 通道。__HAL_DMA_DISABLE
:禁用指定的 DMA 通道。__HAL_DMA_GET_FLAG
:获取 DMA 通道的挂起标志。__HAL_DMA_CLEAR_FLAG
:清除 DMA 通道的挂起标志。__HAL_DMA_ENABLE_IT
:启用指定的 DMA 通道中断。__HAL_DMA_DISABLE_IT
:禁用指定的 DMA 通道中断。__HAL_DMA_GET_IT_SOURCE
:检查指定的 DMA 通道中断是否已启用。
注:
- 当外设以 DMA 模式使用时,DMA 初始化应在
HAL_PPP_MspInit()
回调中完成。此外,用户应用程序应将 DMA 句柄与 PPP 句柄关联(参见“HAL IO 操作函数”部分)。- DMA 通道回调函数仅在内存到内存传输时需要由用户应用程序初始化。然而,当使用外设到内存传输时,这些回调函数会在调用使用 DMA 的过程 API 函数时自动初始化。
3.12 如何使用 HAL 库
3.12.1 HAL 交互模型
略。
3.12.2 HAL 初始化
3.12.2.1 HAL全局初始化
除外设 初始化 和 去初始化 函数外,HAL 库还提供了一组 API 用于初始化 HAL 核心,这些函数在 stm32f1xx_hal.c
文件中实现。
-
HAL_Init()
调用: 此函数必须在应用启动时调用。
功能:
- 初始化 数据 / 指令缓存 和 预取队列。
- 设置 SysTick 定时器,以每 1 毫秒生成一次中断(基于 HSI 时钟),并设置为最低优先级。
- 调用 HAL_MspInit() 用户回调函数以执行系统级初始化(时钟、GPIO、DMA、中断)。HAL_MspInit() 在 HAL 驱动程序中定义为“弱”空函数。
-
HAL_DeInit()
**调用:**该函数将调用 HAL_MspDeInit() 函数,用于执行系统级去初始化。
**功能:**重置所有外设
-
HAL_GetTick()
**功能:**获取当前的 SysTick 计数值(在 SysTick 中断中递增),用于外设驱动程序处理超时。
-
HAL_Delay()
**功能:**使用 SysTick 定时器实现延迟(以毫秒为单位)。
**注:**使用 HAL_Delay() 时需要注意,此函数提供基于在 SysTick ISR 中递增的变量的精确延迟。这意味着如果
HAL_Delay()
从外设 ISR 中调用,则 SysTick 中断的优先级必须高于(中断配置的数值上较低于)外设中断,否则调用 ISR 将被阻塞。
3.12.2.2 系统时钟初始化
时钟配置在用户代码的开始部分完成。不过,用户可以在自己的代码中修改时钟配置。下面是典型的时钟配置序列:
void SystemClock_Config(void) {
RCC_ClkInitTypeDef clkinitstruct = {0};
RCC_OscInitTypeDef oscinitstruct = {0};
/* 配置 PLL------------------------------------------------------*/
/* PLL2 配置:PLL2CLK = (HSE / HSEPrediv2Value) * PLL2MUL = (25 / 5) * 8 = 40 MHz */
/* PREDIV1 配置:PREDIV1CLK = PLL2CLK / HSEPredivValue = 40 / 5 = 8 MHz */
/* PLL 配置:PLLCLK = PREDIV1CLK * PLLMUL = 8 * 9 = 72 MHz */
/* 启用 HSE 振荡器并以 HSE 作为源激活 PLL */
oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
oscinitstruct.HSEState = RCC_HSE_ON;
oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV5;
oscinitstruct.Prediv1Source = RCC_PREDIV1_SOURCE_PLL2;
oscinitstruct.PLL.PLLState = RCC_PLL_ON;
oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9;
oscinitstruct.PLL2.PLL2State = RCC_PLL2_ON;
oscinitstruct.PLL2.PLL2MUL = RCC_PLL2_MUL8;
oscinitstruct.PLL2.HSEPrediv2Value = RCC_HSE_PREDIV2_DIV5;
if (HAL_RCC_OscConfig(&oscinitstruct) != HAL_OK) { /* 初始化错误 */
while (1);
}
/* 选择 PLL 作为系统时钟源,并配置 HCLK、PCLK1 和 PCLK2 时钟分频器 */
clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2) != HAL_OK) { /* 初始化错误 */
while (1);
}
}
3.12.2.3 HAL MSP 初始化过程
外设初始化通过 HAL_PPP_Init() 完成,而外设(PPP)所需的硬件资源初始化是在此过程中通过调用 MSP 回调函数 HAL_PPP_MspInit() 来执行的。MspInit 回调函数执行与不同附加硬件资源相关的低级初始化,包括 RCC、GPIO、NVIC 和 DMA。
所有带有句柄的 HAL 驱动程序都包括两个 MSP 回调函数用于初始化和去初始化:HAL_MspInit() 和 HAL_MspDeInit()。这些 MSP 回调函数在每个外设驱动程序中被声明为空的弱函数。用户可以使用这些函数来设置低级初始化代码,或省略它们并使用自己的初始化例程。
/**
* @brief 初始化 PPP MSP。
* @param hppp: PPP 句柄
* @retval 无 */
void __weak HAL_PPP_MspInit(PPP_HandleTypeDef *hppp) {
/* 注意:此函数不应被修改。如果需要回调,可以在用户文件中实现 HAL_PPP_MspInit */
}
/**
* @brief 反初始化 PPP MSP。
* @param hppp: PPP 句柄
* @retval 无 */
void __weak HAL_PPP_MspDeInit(PPP_HandleTypeDef *hppp) {
/* 注意:此函数不应被修改。如果需要回调,可以在用户文件中实现 HAL_PPP_MspDeInit */
}
(译注:弱函数,是C语言中一种特殊的函数声明方式,允许在链接时被另一个具有相同名称的强函数所覆盖,如下片段,如果用户提供了一个与 HAL_PPP_MspInit 同名的函数,编译器会优先使用用户提供的实现,而忽略默认的弱函数实现)
void HAL_PPP_MspInit(PPP_HandleTypeDef *hppp) {
// 用户提供的实现
}
HAL MSP 回调函数在用户文件夹中的 stm32f1xx_hal_msp.c
文件中实现。stm32f1xx_hal_msp.c
文件模板位于 HAL 文件夹中,应复制到用户文件夹。该文件可以通过 STM32CubeMX 工具自动生成,并进一步修改。所有例程都声明为弱函数,可以被覆盖或删除以使用用户的低级初始化代码。stm32f1xx_hal_msp.c
文件包含以下函数:
函数 | 描述 |
---|---|
void HAL_MspInit() | 全局 MSP 初始化例程 |
void HAL_MspDeInit() | 全局 MSP 去初始化例程 |
void HAL_PPP_MspInit() | PPP MSP 初始化例程 |
void HAL_PPP_MspDeInit() | PPP MSP 去初始化例程 |
默认情况下,如果程序执行期间没有外设需要去初始化,则所有 MSP 初始化都在 HAL_MspInit() 中完成,MSP 去初始化则在 HAL_MspDeInit() 中完成。在这种情况下,HAL_PPP_MspInit() 和 HAL_PPP_MspDeInit() 不需要实现。
当一个或多个外设需要在运行时去初始化,并且某个外设的低级资源需要释放并由另一个外设使用时,应为相关外设实现 HAL_PPP_MspDeInit() 和 HAL_PPP_MspInit(),而其他外设的初始化和去初始化则保留在全局的 HAL_MspInit() 和 HAL_MspDeInit() 中。如果全局的HAL_MspInit() 和 HAL_MspDeInit() 不需要进行初始化,这两个例程可以直接省略。
3.12.3 HAL 的 I/O 操作过程
HAL 函数在进行内部数据处理(如传输、接收、写入和读取)时,通常提供以下三种数据处理模式:轮询模式(Polling mode)、中断模式(Interrupt mode)、DMA 模式(DMA mode)。
3.12.3.1 轮询模式
在轮询模式下,当数据处理完成时,HAL 函数会返回处理状态。操作在函数返回 HAL_OK
状态时视为完成,否则会返回错误状态。用户可以通过 HAL_PPP_GetState() 函数获取更多信息。数据处理在内部通过循环方式进行。为了防止处理挂起,会使用超时机制(以毫秒为单位)。下面的示例展示了典型的轮询模式处理流程:
HAL_StatusTypeDef HAL_PPP_Transmit ( PPP_HandleTypeDef * phandle, uint8_t pData, int16_t Size,uint32_t Timeout){
if((pData == NULL ) || (Size == 0)){
return HAL_ERROR;
}
(...)
while (data processing is running){
if( timeout reached ){
return HAL_TIMEOUT;
}
}
(...)
return HAL_OK;
}
3.12.3.2 中断模式
在中断模式下,HAL 函数在启动数据处理并启用相应的中断后会返回处理状态。操作结束通过一个声明为弱函数的回调函数来指示。**用户可以自定义该回调函数,以实时获取处理完成的信息。**用户还可以通过 HAL_PPP_GetState() 函数获取处理状态。
在中断模式下,驱动程序中声明了以下四个函数:
-
HAL_PPP_Process_IT()
**功能:**启动处理。
-
HAL_PPP_IRQHandler()
**功能:**全局 PPP 外设中断
-
__weak HAL_PPP_ProcessCpltCallback()
**功能:**处理完成的回调函数
-
__weak HAL_PPP_ProcessErrorCallback()
**功能:**处理错误的回调函数
要在中断模式下使用处理,用户文件中需要调用 HAL_PPP_Process_IT(),其中断处理程序 HAL_PPP_IRQHandler() 在 stm32f1xx_it.c
中实现。HAL_PPP_ProcessCpltCallback() 函数在驱动程序中声明为弱函数,意味着用户可以在应用程序中重新声明该函数,而驱动程序中的函数不会被修改。
以下是使用示例:
文件:
main.c
UART_HandleTypeDef UartHandle; int main(void){ /* 用户参数设置 */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = USART1; HAL_UART_Init(&UartHandle); HAL_UART_SendIT(&UartHandle, TxBuffer, sizeof(TxBuffer)); while (1); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {} void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {}
文件:
stm32f1xx_it.c
extern UART_HandleTypeDef UartHandle; void USART1_IRQHandler(void){ HAL_UART_IRQHandler(&UartHandle); }
3.12.3.3 DMA 模式
在 DMA 模式下,HAL 函数在通过 DMA 启动数据处理并启用相应的 DMA 中断后,会返回处理状态。操作结束由一个声明为弱函数的回调函数指示,用户可以自定义该回调函数,以实时获得处理完成的信息。用户也可以通过 HAL_PPP_GetState() 函数获取处理状态。
在 DMA 模式下,驱动程序中声明了以下三个函数:
-
HAL_PPP_Process_DMA()
**功能:**启动处理。
-
HAL_PPP_DMA_IRQHandler()
**功能:**PPP 外设使用的 DMA 中断处理函数。
-
__weak HAL_PPP_ProcessCpltCallback()
**功能:**处理完成的回调函数。
-
__weak HAL_PPP_ErrorCpltCallback()
**功能:**处理错误的回调函数。
要在 DMA 模式下使用处理,用户文件中需要调用 HAL_PPP_Process_DMA(),其中断处理程序 HAL_PPP_DMA_IRQHandler() 在 stm32f1xx_it.c
文件中实现。当使用 DMA 模式时,DMA 的初始化在 HAL_PPP_MspInit() 回调函数中完成。用户还需要将 DMA 句柄与 PPP 句柄关联。为此,所有使用 DMA 的外设驱动句柄必须按如下方式声明:
typedef struct {
PPP_TypeDef *Instance; /* 寄存器基地址 */
PPP_InitTypeDef Init; /* PPP 通信参数 */
HAL_StateTypeDef State; /* PPP 通信状态 */
(...)
DMA_HandleTypeDef *hdma; /* 关联的 DMA 句柄 */
} PPP_HandleTypeDef;
初始化过程如下(以 UART 为例):
int main(void){
/* 设置用户参数 */
UartHandle.Init.BaudRate = 9600;
UartHandle.Init.WordLength = UART_DATABITS_8;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.Instance = UART1;
HAL_UART_Init(&UartHandle);
(...)
}
void HAL_USART_MspInit(UART_HandleTypeDef *huart){
static DMA_HandleTypeDef hdma_tx;
static DMA_HandleTypeDef hdma_rx;
(...)
__HAL_LINKDMA(UartHandle, DMA_Handle_tx, hdma_tx);
__HAL_LINKDMA(UartHandle, DMA_Handle_rx, hdma_rx);
(...)
}
HAL_PPP_ProcessCpltCallback() 函数在驱动程序中声明为弱函数,这意味着用户可以在应用程序代码中重新声明该函数。驱动程序中的函数不应被修改。例程如下:
文件:
main.c
UART_HandleTypeDef UartHandle; int main(void){ /* 设置用户参数 */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = USART1; HAL_UART_Init(&UartHandle); HAL_UART_Send_DMA(&UartHandle, TxBuffer, sizeof(TxBuffer)); while (1); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *phuart){ // 传输完成的回调处理 } void HAL_UART_TxErrorCallback(UART_HandleTypeDef *phuart){ // 传输错误的回调处理 }
文件:
stm32f1xx_it.c
extern UART_HandleTypeDef UartHandle; void DMAx_IRQHandler(void){ HAL_DMA_IRQHandler(&UartHandle.DMA_Handle_tx); }
HAL_USART_TxCpltCallback() 和 HAL_USART_ErrorCallback() 应在 HAL_PPP_Process_DMA() 函数中与 DMA 传输完成回调和 DMA 传输错误回调进行关联,代码如下:
HAL_PPP_Process_DMA(PPP_HandleTypeDef *hppp, Params….){
(…)
hppp->DMA_Handle->XferCpltCallback = HAL_UART_TxCpltCallback;
hppp->DMA_Handle->XferErrorCallback = HAL_UART_ErrorCallback;
(…)
}
3.12.4 超时与错误管理
3.12.4.1 超时管理
超时常用于以轮询模式操作的 API。它定义了一个阻塞过程应等待的时间,直到返回错误。例如:
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout);
其超时的可能取值如下:
- 0:不轮询,立即检查过程并退出。
- 1 … (HAL_MAX_DELAY -1):超时时间(单位:毫秒)。
- HAL_MAX_DELAY:无限期轮询直到过程成功(该宏在
stm32f1xx_hal_def.h
中定义为0xFFFFFFFF
)。
在某些情况下,系统外设或内部 HAL 驱动程序进程会使用固定超时。这些情况下,超时的含义相同,使用方式也相同,只是它在驱动程序中被本地定义,不能在用户应用中作为参数进行修改或引入。例如:
#define LOCAL_PROCESS_TIMEOUT 100
HAL_StatusTypeDef HAL_PPP_Process(PPP_HandleTypeDef *hppp){
(...)
timeout = HAL_GetTick() + LOCAL_PROCESS_TIMEOUT;
(...)
while(ProcessOngoing) {
(...)
if(HAL_GetTick() >= timeout) {
/* 进程解锁 */
__HAL_UNLOCK(hppp);
hppp->State = HAL_PPP_STATE_TIMEOUT;
return HAL_PPP_STATE_TIMEOUT;
}
}
(...)
}
以下示例展示了如何在轮询函数中使用超时:
HAL_PPP_StateTypeDef HAL_PPP_Poll (PPP_HandleTypeDef *hppp, uint32_t Timeout){
(...)
timeout = HAL_GetTick() + Timeout;
(...)
while(ProcessOngoing) {
(...)
if(Timeout != HAL_MAX_DELAY) {
if(HAL_GetTick() >= timeout) {
/* 进程解锁 */
__HAL_UNLOCK(hppp);
hppp->State = HAL_PPP_STATE_TIMEOUT;
return hppp->State;
}
}
(...)
}
}
3.12.4.2 错误管理
HAL驱动程序对以下项进行检查:
-
有效参数:对于某些过程,所使用的参数应为有效且已定义的,否则系统可能会崩溃或进入未定义状态。这些关键参数在使用前会进行检查。例如:
HAL_StatusTypeDef HAL_PPP_Process(PPP_HandleTypeDef* hppp, uint32_t *pdata, uint32 Size) { if ((pdata == NULL) || (Size == 0)) { return HAL_ERROR; } }
-
有效句柄:PPP 外设句柄是最重要的参数,因为它保持了 PPP 驱动程序的重要参数。它总是在 HAL_PPP_Init() 函数的开始处进行检查。例如:
HAL_StatusTypeDef HAL_PPP_Init(PPP_HandleTypeDef* hppp) { if (hppp == NULL) { // 句柄应已实例化 return HAL_ERROR; } }
-
超时错误:在发生超时错误时,例如:
while (Process ongoing) { timeout = HAL_GetTick() + Timeout; while (data processing is running) { if (HAL_GetTick() > timeout) { return HAL_TIMEOUT; } } }
当外设处理过程中发生错误时,HAL_PPP_Process() 会返回 HAL_ERROR
状态。HAL PPP 驱动程序实现了 HAL_PPP_GetError() 以便检索错误的来源:
HAL_PPP_ErrorTypeDef HAL_PPP_GetError (PPP_HandleTypeDef *hppp);
在所有外设句柄中,HAL库定义并使用 HAL_PPP_ErrorTypeDef 来存储最后的错误代码:
typedef struct {
PPP_TypeDef *Instance; /* PPP寄存器基地址 */
PPP_InitTypeDef Init; /* PPP初始化参数 */
HAL_LockTypeDef Lock; /* PPP锁定对象 */
__IO HAL_PPP_StateTypeDef State; /* PPP状态 */
__IO HAL_PPP_ErrorTypeDef ErrorCode; /* PPP错误代码 */
(...)
/* PPP特定参数 */
} PPP_HandleTypeDef;
在返回错误之前,错误状态和外设全局状态总是会更新:
PPP->State = HAL_PPP_READY; /* 设置外设为准备好状态 */
PP->ErrorCode = HAL_ERRORCODE; /* 设置错误代码 */
_HAL_UNLOCK(PPP); /* 解锁PPP资源 */
return HAL_ERROR; /* 返回HAL错误 */
在错误回调函数中,必须使用 HAL_PPP_GetError() :
void HAL_PPP_ProcessCpltCallback(PPP_HandleTypeDef *hppp) {
ErrorCode = HAL_PPP_GetError(hppp); /* 检索错误代码 */
}
3.12.4.3 运行时检查
HAL 通过检查所有 HAL 驱动函数的输入值来实现运行时故障检测。运行时检查通过使用 assert_param
宏来实现。该宏用于所有具有输入参数的 HAL 驱动函数中,用于验证输入值是否在允许的参数范围内。要启用运行时检查,请使用 assert_param
宏,并确保在 stm32f1xx_hal_conf.h
文件中未注释掉 USE_FULL_ASSERT
定义。例如:
void HAL_UART_Init(UART_HandleTypeDef *huart) {
// 检查参数
assert_param(IS_UART_INSTANCE(huart->Instance));
assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate));
assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
assert_param(IS_UART_STOPBITS(huart->Init.StopBits));
assert_param(IS_UART_PARITY(huart->Init.Parity));
assert_param(IS_UART_MODE(huart->Init.Mode));
assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));
// 其他代码
(...)
}
宏定义 assert_param
用于函数参数检查。如果传递给 assert_param
宏的表达式为假,则会调用 assert_failed
函数,该函数返回源文件名称和失败调用的行号。如果表达式为真,则不会返回任何值。宏 assert_param
在 stm32f1xx_hal_conf.h
中实现:
#ifdef USE_FULL_ASSERT
#define assert_param(expr) ((expr)?(void)0:assert_failed((uint8_t *)__FILE__, __LINE__))
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr)((void)0)
#endif /* USE_FULL_ASSERT */
assert_failed
函数在 main.c
文件或其他用户 C 文件中实现:
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line) {
// 用户可以添加自己的实现以报告文件名和行号
// 例如: printf("Wrong parameters value: file %s on line %d\r\n", file, line)
while (1);
}
#endif
**注:**由于运行时检查会增加开销,建议在应用程序代码开发和调试期间使用,并在最终应用程序中移除,以减少代码的大小并提升执行速度。