【沧海拾昧】第三章_HAL库概述(STM32F1_HAL库官方手册中文版翻译)

#C0107


沧海茫茫千钟粟,且拾吾昧一微尘

——《沧海拾昧集》@CuPhoenix


【阅前敬告】
沧海拾昧集仅做个人学习笔记之用,所述内容不专业不严谨不成体系
【如有问题必是本集记录有谬,切勿深究】

(本文翻译自 STM32F1_HAL 库与低层驱动器说明手册,笔者水平有限,不对正确性进行保证,仅供参考)


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_ MODULEHAL_PPP_ MODULEHAL_PPP_ MODULE
函数名HAL_PPP_FunctionHAL_PPP_FeatureFunction_MODEHAL_PPPEx_FunctionHAL_PPPEx_FeatureFunction_MODEHAL_PPPEx_FunctionHAL_PPPEx_FeatureFunction_MODE
句柄名PPP_HandleTypedefNANA
初始化结构体名PPP_InitTypeDefNAPPP_InitTypeDef
枚举名HAL_PPP_StructnameTypeDefNANA
  • 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)可能具有不同的字段。在这种情况下,数据结构会在扩展头文件中进行定义,并通过特定部件号的定义语句进行区分。

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;
    
  • 通用宏

    • 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_TIMEOUTHSE 启动超时(单位:毫秒)5000
HSI_VALUE定义内部振荡器(HSI)的值(单位:Hz)8,000,000
LSE_VALUE外部低速振荡器(LSE)的值(单位:Hz)32,768
LSE_STARTUP_TIMEOUTLSE 启动超时(单位:毫秒)5000
VDD_VALUEVDD 值(单位:毫伏)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.hstm32f1xx_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 操作

      1. 使用 HAL_NVIC_SetPriority() 配置 DMA 中断优先级。
      2. 使用 HAL_NVIC_EnableIRQ() 启用 DMA IRQ 处理程序。
      3. 当源地址、目标地址和要传输的数据长度已配置好时,使用 HAL_DMA_Start_IT() 启动 DMA 传输。在这种情况下,配置 DMA 中断。
      4. 在 DMA 中断服务程序中调用 HAL_DMA_IRQHandler()
      5. 当数据传输完成时,HAL_DMA_IRQHandler() 函数会被执行,并可以通过自定义 XferCpltCallbackXferErrorCallback 函数指针(即 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_paramstm32f1xx_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

**注:**由于运行时检查会增加开销,建议在应用程序代码开发和调试期间使用,并在最终应用程序中移除,以减少代码的大小并提升执行速度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值