STM32标准库和HAL库有什么不同 我们怎么用

发布时间:2023-10-20  

摘要:通常新手在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。

网上关于标准库、HAL库的描述相信是数不胜数。可是一个对于很多刚入门的朋友还是没法很直观的去真正了解这些不同开发发方式彼此之间的区别,所以笔者想以一种非常直白的方式,用自己的理解去将这些东西表述出来,如果有描述的不对的地方或者是不同意见的也可以大家提出。


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



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


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


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


三、HAL库HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。


库如其名,很抽象,一眼看上去不太容易知道他的作用是什么。它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。


也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有。


例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。


四、总结综合上面说的,其实笔者还是强烈推荐HAL库的,理由有二:


第一、 F7系列开始 ST公司就已近开始停止更新标准库,也就是F7开始包括F7已经不能用标准库了,公司对于主打HAL库的目的已经非常明显了。


二、追求更方便、追求模块化向来是世界的潮流,更方便的HAL库一定会迅速发展,低效的短板迟早会被硬件高度集成化所弥补。


当然啦,不能只学习HAL库,底层的原理必需是要懂的,这是每个学有所成的人都公认的事实,HAL库也不是万能的,结合对底层的理解相信一定会让你的开发水准大大提高。


五、STM32 HAL库与标准库的区别1.句柄在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例) 我们首先要初始化他们的各个寄存器。


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


USART_InitTypeDefUSART_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_HandleTypeDefUART1_Handler;

右键查看结构体成员:


typedefstruct

{

USART_TypeDef*Instance;/*!< UART registers base address        */

UART_InitTypeDefInit;/*!< UART communication parameters      */

uint8_t*pTxBuffPtr;/*!< Pointer to UART Tx transfer Buffer */

uint16_tTxXferSize;/*!< UART Tx Transfer size              */

uint16_tTxXferCount;/*!< UART Tx Transfer Counter           */

uint8_t*pRxBuffPtr;/*!< Pointer to UART Rx transfer Buffer */

uint16_tRxXferSize;/*!< UART Rx Transfer size              */

uint16_tRxXferCount;/*!< UART Rx Transfer Counter           */

DMA_HandleTypeDef*hdmatx;/*!< UART Tx DMA Handle parameters      */

DMA_HandleTypeDef*hdmarx;/*!< UART Rx DMA Handle parameters      */

HAL_LockTypeDefLock;/*!< Locking object                     */

__IOHAL_UART_StateTypeDefState;/*!< UART communication state           */

__IOuint32_tErrorCode;/*!< UART Error code                    */

}UART_HandleTypeDef;


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


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


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

比如后面要讲到的

MSP

Callback

回调函数:


voidHAL_UART_MspInit(UART_HandleTypeDef*huart);

voidHAL_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函数又可以配合句柄,达到非常强的移植性:


voidHAL_UART_MspInit(UART_HandleTypeDef*huart);

3.Callback函数

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


voidUSART3_IRQHandler(void)//串口1中断服务程序

{

u8Res;

if(USART_GetITStatus(USART3,USART_IT_RXNE)!=RESET)//接收中断(接收到的数据必须是0x0d0x0a结尾)

{

Res=USART_ReceiveData(USART3);//读取接收到的数据

/*数据处理区*/

}

}

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


voidUSART1_IRQHandler(void)

{

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

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

}

HAL_UART_IRQHandler

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


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

/*RXBUFFERSIZE=5*/

u8aRxBuffer[RXBUFFERSIZE];

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


/*该代码在HAL_UART_Receive_IT函数中,初始化时会引用*/

huart->pRxBuffPtr=pData;//aRxBuffer

huart->RxXferSize=Size;//RXBUFFERSIZE

huart->RxXferCount=Size;//RXBUFFERSIZE

则在接收数据中,每接收完五个字节,

HAL_UART_IRQHandler

才会执行一次Callback函数:


voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart);

在这个

Callback

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


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


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


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


五、HAL库结构

说到STM32 的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。


STM32CubeMX 就是以 HAL 库为基础的,且目前仅支持 HAL 库及 LL 库!首先看一下,官方给出的 HAL 库的包含结构:

76286ca0-37c0-11ec-82a8-dac502259ad0.png

7672a518-37c0-11ec-82a8-dac502259ad0.png


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


#ifdefined(STM32F205xx)

#include"stm32f205xx.h"

#elifdefined(STM32F215xx)

#include"stm32f215xx.h"

#elifdefined(STM32F207xx)

#include"stm32f207xx.h"

#elifdefined(STM32F217xx)

#include"stm32f217xx.h"

#else

#error"PleaseselectfirstthetargetSTM32F2xxdeviceusedinyourapplication(instm32f2xx.hfile)"

#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为例


/**

*@briefADChandleStructuredefinition

*/

typedefstruct

{

ADC_TypeDef*Instance;/*!< Register base address */

ADC_InitTypeDefInit;/*!< ADC required parameters */

__IOuint32_tNbrOfCurrentConversionRank;/*!< ADC number of current conversion rank */

DMA_HandleTypeDef*DMA_Handle;/*!< Pointer DMA Handler */

HAL_LockTypeDefLock;/*!< ADC locking object */

__IOuint32_tState;/*!< ADC communication state */

__IOuint32_tErrorCode;/*!< 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_StatusTypeDefHAL_ADC_Start(ADC_HandleTypeDef*hadc);

HAL_StatusTypeDefHAL_ADC_Stop(ADC_HandleTypeDef*hadc);


HAL_StatusTypeDefHAL_ADC_Start_IT(ADC_HandleTypeDef*hadc);

HAL_StatusTypeDefHAL_ADC_Stop_IT(ADC_HandleTypeDef*hadc);


HAL_StatusTypeDefHAL_ADC_Start_DMA(ADC_HandleTypeDef*hadc,uint32_t*pData,uint32_tLength);

HAL_StatusTypeDefHAL_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_GETIT_SOURCE (HANDLE, __ INTERRUPT __)检查中断源

2. 三大回调函数

在 HAL 库的源码中,到处可见一些以

__weak

开头的函数,而且这些函数,有些已经被实现了,比如:


__weakHAL_StatusTypeDefHAL_InitTick(uint32_tTickPriority)

{

/*ConfiguretheSysTicktohaveinterruptin1mstimebasis*/

HAL_SYSTICK_Config(SystemCoreClock/1000U);

/*ConfiguretheSysTickIRQpriority*/

HAL_NVIC_SetPriority(SysTick_IRQn,TickPriority,0U);

/*Returnfunctionstatus*/

returnHAL_OK;

}

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


__weakvoidHAL_SPI_TxCpltCallback(SPI_HandleTypeDef*hspi)

{

/*Preventunusedargument(s)compilationwarning*/

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    测可能才推出不久,下载链接还没有更新过来。 4 官网中文版和英文版的细节差异 之前我说过,建议浏览官网原版网页(英文),但很多朋友说英文不好,所以使用中文页面。 英文版网页: https......
    还是英文的问题,电子界争论已久。 EEWorld曾在论坛发起投票,调研工程对于中文版和英文版的Datasheet偏好情况,结果选择英文版的占44.07%(182票),选择中文版的占55.93%(231......
    Simco-Ion推出全新中文版 IQ Power 智能静电控制台;Simco-Ion,全球领先的静电防控解决方案供应商,宣布推出中文版 IQ Power Control Station智能......
    官方发布的专门针对STM32片内DFSDM外设的应用笔记。除了英文版外,还可以在www.stmcu.com.cn中文网搜索到中文版。 AN4990里面的内容主要包括下面这些,内容通用性强。 *使用DFSDM的......
    人工智能进入人类专属的活动领域时会发生什么?日本经济新闻(中文版:日经中文网)的采访了担任“机器人能考上东大吗”项目负责人的日本国立情报学研究所教授新井纪子。          日本......
    -usbx 。 进入官网后,通过左下角的“Download PDF”将在线文档导出来。 1.3.3 ThreadX USBX用户手册在线中文版 ThreadX USBX中文版......
    蓝牙技术联盟发布最新环境物联网市场研究报告;该环境物联网研究报告预测了物联网的发展演变和市场增长趋势 负责监管蓝牙技术的行业协会蓝牙技术联盟(SIG)近日发布了中文版市场研究报告《环境......
    蓝牙技术联盟发布最新环境物联网市场研究报告;该环境物联网研究报告预测了物联网的发展演变和市场增长趋势 北京,2024年3月6日——负责监管蓝牙技术的行业协会蓝牙技术联盟(SIG)近日发布了中文版......
    微软:战争机器4等大批中文游戏袭来;在本届E3游戏展上,一大批新作的出现让玩家们大呼过瘾,但是相信国内玩家更加关心的是这些大作会不会推出中文版。现在微软亚太区Xbox业务经理Rennie......
    ,包含GUI、电机控制、USB-C PD等,而“开发人员资源” 选项为新手和资深开发人员提供相关的技术文档。工程师在这里能够快速找到合适的开发板和软件工具,迅速迈出设计的第一步。 开发者社区中文版......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>