HAL库的外设设计_句柄/外设初始化

  • 在介绍新的外设之前,我们先介绍HAL库是如何设计类似TIM、UART这样更复杂外设的:

    在HAL操作GPIO、HAL的中断处理这些小节中我们简单探讨了HAL库在处理这些简单外设时的设计,但在对应TIM、UART这类更复杂的外设时,就需要引入新的设计方法,具体如下

外设句柄设计

句柄概述

  • 初始化结构体的局限

    在GPIO一章中,我们提到为了简化配置操作,HAL库使用一个用于初始化GPIO引脚的初始化数据类型GPIO_InitTypeDef,这个结构体中包含了外设的各项属性变量

    这种做法对GPIO、外部中断这样简单的外设适用,但对TIM定时器、UART等工作流程更为复杂的外设,它们不仅需要在使用前进行一次性的初始化,还需要在外设工作时对所需的各项参数进行配置,这时仅使用初始化数据类型的做法就不够用了

    例如在通信时不仅要对时钟硬件进行配置来确定收发频率,还要在工作时动态标识当前通信状态的变量,以防通信正进行时被新的通信打断,破坏原先信息收发过程

  • 句柄的原理

    为解决初始化数据类型的局限,设计了统一的外设句柄数据类型PPP_HandleTypeDef(PPP表示外设名)

    句柄外设的一个标识符,其本质是一个结构体的指针,通过这个指针可以访问外设相应的资源(储存外设各项参数的结构体)

    句柄的结构体成员为外设的各项工作参数,可见句柄的原理类似于初始化所用的数据类型InitTypeDef

  • 句柄与初始化结构体的区别

    可以说句柄是初始化结构体的一个超集,其包含了初始化硬件所需的参数,且额外多出其他外设工作时所需的参数

    • 初始化结构体仅仅用于初始化阶段的配置,在初始化后便销毁;它就像选车时的配置单,决定一辆车用什么引擎,油箱有多大,买完车之后这配置单也就没用了
    • 句柄则用于外设的运行时管理,初始化后外设的各项操作都由句柄完成,其生命周期覆盖整个程序运行期;它就像车的行车电脑,管理引擎用什么马力,当前的排量有多大

句柄的实现

  • 句柄的结构体成员

    显然,与InitTypeDef中封装的各个外设属性相比,句柄中的参数会更为复杂,包括:

    • 外设实例:表示具体的外设,是指向对应外设寄存器组起始地址的指针
    • 初始化配置参数:定义外设初始化数据类型,也即InitTypeDef被包含在句柄之中了
    • I/O缓冲区:定义数据传输时接收缓冲区和发生缓冲区的起始地址和数据传输个数
    • 外设状态:忙状态busy;就绪状态ready;错误状态error
    • DMA通道句柄:定义外设使用DMA方式

    以下一章介绍的TIM为例,其结构体TIM_HandleTypeDef声明如下:

    typedef struct
    {
    	TIM_TypeDef *Instance;//外设实例
    	TIM_Base_InitTypeDef Init;//时基单元初始化数据类型
    	HAL_TIM_ActiveChannel Channel;//捕获/比较单元
    	DMA_HanddleTypeDef *hdam[7];//DMA通道句柄
    	HAL_LockTypeDef Lock;//保护锁
    	__IO HAL_TIM_StateTypeDef State;//定时器工作状态
    }TIM_HandleTypeDef;
    
  • 句柄的使用

    句柄的本质是一个结构体指针:在对某一个外设编程时,要先定义该外设对应句柄的变量,定义了具体的外设句柄变量后,就可用外设句柄变量的指针去访问该外设的所有资源

    例如,要访问定时器TIM2资源,可以使用句柄指针htim2,以启动定时器2为例:

    TIM_HandleTypeDef htim2;
    //这一部分工作已经由软件自动帮我们做好了,我们在使用接口函数时调用即可,如下示例
    HAL_TIM_Base_Start_IT(&htim6);
    
    //其他外设同理,如UART串口的句柄,ADC的句柄:
    UART_HandleTypeDef *huart2;
    ADC_HandleTypeDef *hadc1;
    

外设通用接口函数

外设通用接口函数的分类

  • 设计了句柄后,就可以通过操纵句柄来访问、操纵对应的外设资源,将各种对外设的操作进行封装后就得到了外设通用接口函数,这些函数可以分为四类:

  • 初始化函数

    对外设写入配置参数,如HAL_GPIO_Init()

  • I/O操作函数

    用于外设的数据传输,分三种外设编程模型,后文介绍

  • 控制函数

    动态配置外设的参数

  • 状态函数

    获取外设的运行状态,如HAL_TIM_Base_GetState()

外设编程模型

  • 根据外设和MPU的数据传输方式,HAL库设计了三种编程模型用于不同的外设数据传输方式,与中断一章提及的三种数据传输方式(轮询、中断、DMA)对应

    以启动定时器的定时功能为例,可见三种类型以其后缀作区分(在调用对应功能时,根据需求不同修改不同后缀即可),各个函数的第一个入口参数都是指向外设句柄的指针:

  • 轮询方式
    HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim);
    
  • 中断方式
    HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
    
  • DMA方式
    HAL_StatusTypeDef HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length);
    //DMA数据传输需要告知传输地址与数据长度,因此需要更多参数
    

外设初始化过程

  • 在有了句柄访问外设的资源与外设通用接口函数配置外设参数后,对外设的初始化被进一步简化了,初始化外设句柄后进行的处理总共分两步:

  • 直接使用CubeMX进行初始化将自动生成这些以下函数,后文将不再单独介绍

抽象

  • 外设通用初始化

    抽象是指与MCU无关的初始化设置,例如通信中的字符格式这类参数无论使用STM32还是51都需要进行设置

  • 外设初始化函数HAL_PPP_Init

    抽象所用的函数是HAL_PPP_Init(),其中PPP指外设名,例如对GPIO初始化就使用HAL_GPIO_Init

承载

  • MCU硬件初始化

    承载是指与MCU有关的初始化设置,将上一步抽象的设置在具体的MCU上实现,这种设计的好处就是把与硬件相关的操作剥离除了,以提高代码的可移植性

  • 硬件初始化函数HAL_PPP_MspInit

    承载所用的函数是HAL_PPP_MspInit(),其中Msp是MCU specific package的缩写,表示MCU级别的初始化设置

    此函数被定义为weak属性,以回调函数的形式提供给用户,在初始化函数的内部就会调用硬件初始化函数,因此除非有特定的硬件操作一般不进行修改

posted on 2025-05-20 12:59  无术师  阅读(207)  评论(0)    收藏  举报