0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32的HAL库与标准库的区别

科技绿洲 来源:STM32嵌入式开发 作者:STM32嵌入式开发 2023-06-22 11:43 次阅读

新手在入门 STM32 的时候,一般大多数都会选用标准库和 HAL 库,而极少部分人会通过直接配置寄存器进行开发。

对于刚入门的朋友,可能没法直观了解这些不同开发发方式之间的区别,本文试图以一种非常直白的方式,用自己的理解去将这些东西表述出来。

配置寄存器

不少先学了 51单片机的朋友可能会知道,会有一小部分人或教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了 STM32 就变得不太容易行得通了。

因为 STM32 的寄存器数量是 51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。也有人喜欢去直接操作寄存器,因为这样更接近原理,代码更少,知其然也知其所以然。

标准库

上面也提到了,STM32 有非常多的寄存器,而导致了开发困难,所以为此 ST 公司就为每款芯片都编写了一份库文件,也就是工程文件里 stm32F1xx..... 之类的。在这些 .c 与 .h 文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如 GPIO、时钟等。

所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习 STM32 接触最多的一种开发方式,我也就不多阐述了。

HAL库

HAL 库是 ST 公司目前主推的开发方式,全称就是 Hardware Abstraction Layer(抽象印象层),简单来说就是弱化了开发者硬件底层知识的依赖。

同样的功能,标准库可能要用几句话,HAL 库只需用一句话就够了。并且 HAL 库也很好地解决了程序移植的问题。不同型号的 STM32 芯片它的标准库是不一样的,例如在F4 上开发的程序移植到 F3 上是不能通用的,而使用 HAL 库,只要使用的是相同的外设,程序基本可以完全复制粘贴。注意是相同外设,意思也就是不能无中生有。例如 F7 比 F3 要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。

而且使用 ST 公司研发的 STMcube 软件,可以通过图形化的配置功能,直接生成整个适用于HAL库的工程文件,可以说是方便至极。但是方便的同时也造成了它执行效率偏低。

综合上面说的,其实笔者还是强烈推荐 HAL 库的,理由:

  • ST 公司已经停止更新标准库,公司主打 HAL 库的目的已经非常明显了;
  • 模块化的 HAL 库是趋势,低效的短板会被硬件的增强所弥补。

当然底层的基本原理是必须要懂的,HAL 库也不是万能的,结合对底层的理解相信一定会让你的开发水准大大提高。

HAL库与标准库的区别

1 句柄

在STM32的标准库中,假设我们要初始化一个外设(这里以 USART 为例) 我们首先要初始化他们的各个寄存器。

在标准库中,这些操作都是利用固件库结构体变量+固件库 Init 函数实现的:

USART_InitTypeDef USART_InitStructure;


USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式


USART_Init(USART3, &USART_InitStructure); //初始化串口1

可以看到,要初始化一个串口,需要对六个位置进行赋值,然后引用 Init 函数,并且USART_InitStructure 并不是一个全局结构体变量,而是只在函数内部的局部变量,初始化完成之后,USART_InitStructure 就失去了作用。

而在HAL库中,同样是 USART 初始化结构体变量,我们要定义为全局变量。

UART_HandleTypeDef UART1_Handler;

结构体成员:

typedef struct
{
  USART_TypeDef                 *Instance;        /*!< UART registers base address        */
  UART_InitTypeDef             Init;             /*!< UART communication parameters      */
  uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */
  uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */
  uint16_t                      TxXferCount;      /*!< UART Tx Transfer Counter           */
  uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */
  uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
  uint16_t                      RxXferCount;      /*!< UART Rx Transfer Counter           */
  DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */
  DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */
  HAL_LockTypeDef               Lock;             /*!< Locking object                     */
  __IO HAL_UART_StateTypeDef    State;            /*!< UART communication state           */
  __IO uint32_t                ErrorCode;        /*!< UART Error code                    */
}UART_HandleTypeDef;

我们发现,与标准库不同的是,该成员不仅包含了之前标准库就有的六个成员(波特率,数据格式等),还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等,要在整个项目流程中都要设置的各个成员。

该UART1_Handler就被称为串口的句柄 它被贯穿整个 USART 收发的流程,比如开启中断:

HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

比如后面要讲到的 MSP 与 Callback 回调函数:

void HAL_UART_MspInit(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这些函数中,只需要调用初始化时定义的句柄 UART1_Handler 就好。

2 MSP函数

MCU Specific Package 单片机的具体方案。

MSP 是指和 MCU 相关的初始化,引用一下正点原子的解释,个人觉得说地很明白:

“我们要初始化一个串口,首先要设置和 MCU 无关的东西,例如波特率,奇偶校验,停止位等,这些参数设置和 MCU 没有任何关系,可以使用 STM32F1,也可以是 STM32F2/F3/F4/F7 上的串口。而一个串口设备它需要一个 MCU 来承载,例如用 STM32F4 来做承载,PA9 做为发送,PA10 做为接收,MSP 就是要初始化 STM32F4 的 PA9,PA10,配置这两个引脚。所以 HAL驱动方式的初始化流程就是:HAL_USART_Init()—>HAL_USART_MspInit(),先初始化与 MCU无关的串口协议,再初始化与 MCU 相关的串口引脚。在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,被 HAL_PPP_Init() 函数所调用。当我们需要移植程序到 STM32F1 平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。

在 HAL 库中,几乎每初始化一个外设就需要设置该外设与单片机之间的联系,比如IO口,是否复用等等。可见,HAL 库相对于标准库多了MSP函数之后,移植性非常强,但与此同时却增加了代码量和代码的嵌套层级。可以说各有利弊。

同样,MSP 函数又可以配合句柄,达到非常强的移植性:

void HAL_UART_MspInit(UART_HandleTypeDef *huart);

3 Callback函数

类似于 MSP 函数,个人认为 Callback 函数主要帮助用户应用层的代码编写。还是以 USART 为例,在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理,这样如果我们在一个中断函数中写这么多代码,就会显得很混乱:

void USART3_IRQHandler(void) //串口1中断服务程序
{
  u8 Res;
  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
  if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
  {
    //读取接收到的数据
    Res =USART_ReceiveData(USART3); 


    /*数据处理区*/
  } 
}

而在HAL库中,进入串口中断后,直接由 HAL 库中断函数进行托管:

void USART1_IRQHandler(void)                 
{
  //调用HAL库中断处理公用函数
  HAL_UART_IRQHandler(&UART1_Handler); 


  /***************省略无关代码****************/ 
}

HAL_UART_IRQHandler 这个函数完成了判断是哪个中断(接收?发送?或者其他?),然后读出数据,保存至缓存区,顺便清除中断标志位等等操作。比如我提前设置了,串口每接收五个字节,我就要对这五个字节进行处理。在一开始我定义了一个串口接收缓存区:

/*HAL库使用的串口接收缓冲,处理逻辑由HAL库控制,
接收完这个数组就会调用HAL_UART_RxCpltCallback进行处理这个数组*/
/*RXBUFFERSIZE=5*/
u8 aRxBuffer[RXBUFFERSIZE];

在初始化中,我在句柄里设置好了缓存区的地址,缓存大小(五个字节)

/* 该代码在HAL_UART_Receive_IT函数中,初始化时会引用 */
huart- >pRxBuffPtr = pData; //aRxBuffer
huart- >RxXferSize = Size; //RXBUFFERSIZE
huart- >RxXferCount = Size; //RXBUFFERSIZE

则在接收数据中,每接收完五个字节,HAL_UART_IRQHandler 才会执行一次Callback 函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这个Callback回调函数中,我们只需要对这接收到的五个字节(保存在 aRxBuffer[] 中)进行处理就好了,完全不用再去手动清除标志位等操作。

所以说 Callback 函数是一个应用层代码的函数,我们在一开始只设置句柄里面的各个参数,然后就等着 HAL 库把自己安排好的代码送到手中就可以了~

综上 ,就是HAL库的三个与标准库不同的地方之个人见解。

个人觉得从这三个小点就可以看出 HAL 库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也更有逻辑性。但由此带来的是复杂的代码量,极慢的编译速度,略微低下的效率。看怎么取舍了。

HAL库的结构

说到 STM32 的 HAL库,就不得不提 STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。另外 STM32CubeIDE 集成了STM32CubeMX 的功能,是一个集配置与编译于一体的软件,可以尝试一下。软件更新频率很高,持续优化某些 bug 及性能问题。

图片

上面两个开发软件是以 HAL 库为基础的,且目前仅支持 HAL 库及LL库!

首先看一下,官方给出的HAL库的文件结构:

图片

下图是STM32库文件结构。

图片

stm32f2xx.h 主要包含 STM32 同系列芯片的不同具体型号的定义,是否使用 HAL 库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:

#if defined(STM32F205xx)
#include "stm32f205xx.h"
#elif defined(STM32F215xx)
#include "stm32f215xx.h"
#elif defined(STM32F207xx)
#include "stm32f207xx.h"
#elif defined(STM32F217xx)
#include "stm32f217xx.h"
#else
#error "Please select first the target STM32F2xx device used in your application (in stm32f2xx.h file)"
#endif

紧接着,其会包含 stm32f2xx_hal.h。

  • stm32f2xx_hal.h:stm32f2xx_hal.c/h 主要实现 HAL 库的初始化、系统滴答时钟相关的函数、及 CPU 的调试模式配置
  • stm32f2xx_hal_conf.h :该文件是一个用户级别的配置文件,用来实现对 HAL 库的裁剪,其位于用户文件目录,不要放在库目录中。

接下来对于HAL库的源码文件进行一下说明,HAL 库文件名均以 stm32f2xx_hal 开头,后面加上_外设或者模块名(如:stm32f2xx_hal_adc.c):

库文件:
 stm32f2xx_hal_ppp.c/.h   // 主要的外设或者模块的驱动源文件,包含了该外设的通用API
 stm32f2xx_hal_ppp_ex.c/.h  // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。
 stm32f2xx_hal.c/.h    // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API
 其他库文件
用户级别文件:
 stm32f2xx_hal_msp_template.c // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。
 stm32f2xx_hal_conf_template.h // 用户级别的库配置文件模板。使用者复制到自己目录下使用
 system_stm32f2xx.c    // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。**它不在启动时配置系统时钟(与标准库相反)**。时钟的配置在用户文件中使用HAL API来完成。
 startup_stm32f2xx.s    // 芯片启动文件,主要包含堆栈定义,终端向量表等
 stm32f2xx_it.c/.h    // 中断处理函数的相关实现
 main.c/.h

根据 HAL 库的命名规则,其 API 可以分为以下三大类:

  • 初始化/反初始化函数:HAL_PPP_Init(), HAL_PPP_DeInit()
  • IO 操作函数:HAL_PPP_Read(), HAL_PPP_Write(),HAL_PPP_Transmit(), HAL_PPP_Receive()
  • 控制函数:HAL_PPP_Set (), HAL_PPP_Get ().
  • 状态和错误:HAL_PPP_GetState (), HAL_PPP_GetError ().

“注意:目前 LL 库是和 HAL 库捆绑发布的,所以在 HAL 库源码中,还有一些名为 stm32f2xx_ll_ppp 的源码文件,这些文件就是新增的LL库文件。使用 CubeMX 生产项目时,可以选择LL库。

HAL 库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:

  • 处理外设句柄,实现用户功能
  • 处理MSP
  • 处理各种回调函数,外设句柄定义

HAL 库在结构上,对每个外设抽象成了一个称为 ppp_HandleTypeDef 的结构体,其中 ppp 就是每个外设的名字。所有的函数都是工作在 ppp_HandleTypeDef 指针之下。

每个外设/模块实例都有自己的句柄。因此,实例资源是独立的。

外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。

下面,以ADC为例,

/** 
 * @brief  ADC handle Structure definition
 */
typedef struct
{
  ADC_TypeDef                   *Instance;                   /*!< Register base address */
  ADC_InitTypeDef               Init;                        /*!< ADC required parameters */
  __IO uint32_t                 NbrOfCurrentConversionRank;  /*!< ADC number of current conversion rank */
  DMA_HandleTypeDef             *DMA_Handle;                 /*!< Pointer DMA Handler */
  HAL_LockTypeDef               Lock;                        /*!< ADC locking object */
  __IO uint32_t                 State;                       /*!< ADC communication state */
  __IO uint32_t                ErrorCode;                   /*!< ADC Error code */
}ADC_HandleTypeDef;

从上面的定义可以看出,ADC_HandleTypeDef 中包含了 ADC 可能出现的所有定义,对于用户想要使用 ADC 只要定义一个 ADC_HandleTypeDef 的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。

当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。例如以下外设:

  • GPIO
  • SYSTICK
  • NVIC
  • RCC
  • FLASH

以 GPIO 为例,对于 HAL_GPIO_Init() 函数,其只需要 GPIO 地址以及其初始化参数即可。

1 三种编程方式

HAL 库对所有的函数模型也进行了统一。在HAL 库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):

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);


HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);

其中,带 _IT 的表示工作在中断模式下;带 _DMA 的工作在 DMA 模式下(注意:DMA 模式下也是可以开启中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。

此外,新的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 __) 检查中断源

2 三大回调函数

在 HAL 库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:

图片

有些则没有被实现,例如:

__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
/* Prevent unused argument(s) compilation warning */
  UNUSED(hspi);
/* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file
  */
}

所有带有 __weak 关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带 __weak 关键字,那么连接器就会采用外部实现的同名函数。通常来说,HAL 库负责整个处理和 MCU 外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。

HAL 库包含如下三种用户级别回调函数(PPP为外设名):

  1. 外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于 MSP 的处理):HAL_PPP_MspInit()HAL_PPP_MspDeInit 例如:__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)。在 HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)。
  2. 处理完成回调函数:HAL_PPP_ProcessCpltCallback(Process指具体某种处理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)。当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者 DMA 的中断处理函数中被调用。
  3. 错误处理回调函数:HAL_PPP_ErrorCallback例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)。当外设或者 DMA 出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用。
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    30

    文章

    5028

    浏览量

    117719
  • STM32
    +关注

    关注

    2239

    文章

    10671

    浏览量

    348738
  • HAL
    HAL
    +关注

    关注

    2

    文章

    68

    浏览量

    12471
收藏 人收藏

    评论

    相关推荐

    STM32HAL标准外设、LLSTM32 Embedded Software)

    STM32 Embedded Software  工作以来一直使用ST的STM32系列芯片,ST为开发者提供了非常方便的开发。到目前为止,有标准外设
    发表于 07-03 15:02

    stm32hal标准有什么区别

    stm32hal标准有什么区别
    发表于 07-20 08:20

    HAL标准的原理区别

    HAL的学习HAL标准的原理区别串口通信实验
    发表于 07-29 08:48

    标准HAL有啥区别

    “健康守护者”——STM32标准HAL的比较使用STM32芯片的时候呢,大家可能都喜欢用
    发表于 08-04 08:22

    HAL和lib区别和联系

    的开发产品需求进行裁剪选择做出专用的程序包,这个程序包通过STM32CubeMux工具配置后自动生成。2.HAL和lib区别和联系(1
    发表于 08-12 06:41

    STM32标准HAL有哪些不同

    STM32标准HAL有哪些不同?标准
    发表于 10-21 07:44

    STM32 HAL标准区别有哪些

    STM32的开发方式有哪几种?怎样去安装HAL固件呢?STM32 HAL
    发表于 10-25 07:49

    HAL标准有哪些区别

    什么是HAL标准又是什么?HAL标准
    发表于 10-26 06:54

    请问STM32 HAL标准区别是什么?

    请问STM32 HAL标准区别是什么?
    发表于 11-25 08:43

    标准外设HAL的相关资料分享

    C 语言使用 HAL 开发ST公司先后推出了两套主要标准外设(Standard Peripherals Library)和
    发表于 01-18 07:39

    STM32 HAL标准区别在哪

    最近笔者开始学习STM32HAL,由于以前一直用标准进行开发,于是发现了HAL
    发表于 02-15 07:03

    请问标准外设HAL区别是什么?

    标准外设HAL区别是什么?
    发表于 10-23 08:01

    STM32 HAL库与标准库的区别_浅谈句柄、MSP函数、Callback函数

    最近笔者开始学习STM32HAL库,由于以前一直用标准库进行开发,于是发现了HAL库几点好玩的地方,在此分享。1.句柄在STM32
    发表于 12-16 16:57 30次下载
    <b class='flag-5'>STM32</b> <b class='flag-5'>HAL</b>库与<b class='flag-5'>标准</b>库的<b class='flag-5'>区别</b>_浅谈句柄、MSP函数、Callback函数

    STM32 HAL库与标准库的区别

      相比较早几年使用标准库开发来讲,最近几年HAL库的使用是越来越多,那么我们开发应当使用哪一种呢,本文着重介绍常用的几种开发方式及相互之间的区别,白猫也好、黑猫也好,抓到耗子就是好猫。
    的头像 发表于 03-22 09:13 1.3w次阅读

    STM32 HAL库的功能和使用方法 STM32 HAL库和标准库的区别

    和可维护性,从而节省开发成本和时间。STM32 HAL库是STM32系列芯片的HAL库,由ST公司提供和维护。下面将介绍STM32
    发表于 08-08 18:21 2248次阅读