历史上的今天
返回首页

历史上的今天

今天是:2025年07月30日(星期三)

正在发生

2019年07月30日 | STM32 之二 HAL库详解 及 手动移植

2019-07-30 来源:eefocus

HAL库结构


  说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!首先看一下,官方给出的HAL库的包含结构:


- **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指针之下。


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

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

下面,以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 地址以及其初始化参数即可。


三种编程方式

  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 __):检查中断源

三大回调函数

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


__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)

{

/*Configure the SysTick to have interrupt in 1ms time basis*/

HAL_SYSTICK_Config(SystemCoreClock/1000U);

/*Configure the SysTick IRQ priority */

HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);

/* Return function status */

return HAL_OK;

}


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


__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的中断处理函数中被调用


绝大多数用户代码均在以上三大回调函数中实现。

HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。某些外设多次初始化时不调用返回会导致初始化失败。

完成回调函数有多中,例如串口的完成回调函数有HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback等

(用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)

在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题

HAL库移植使用

基本步骤

复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。


复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。

在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void))

HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中)

关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。


对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。例如:Uart中,HAL提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);


使用了哪种就用哪个回调函数即可!


基本结构

  综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下:


配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;,接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart))

编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef* huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)

实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数

参考文档

ST - Description of STM32F4 HAL and LL drivers.pdf

ST - en.stm32_embedded_software_offering.pdf

  至此,HAL库的总体结构就介绍完了!具体的每个文件的详细说明,官方源码注释很详细!



推荐阅读

史海拾趣

乐鑫(espressif)公司的发展小趣事

乐鑫科技自创立之初,就致力于物联网(IoT)领域的芯片设计和技术创新。公司凭借其独特的芯片设计能力和连接技术,成功开发了一系列具有竞争力的产品,为全球开发者提供了高效、可靠的物联网解决方案。乐鑫科技始终坚持以技术创新为核心,通过自主研发和持续投入,不断提升产品的性能和竞争力。

粤翔(FlyWin)公司的发展小趣事
如硬盘、闪存等存储设备,需要掉电保护电路来确保数据在断电时不会损坏。
BOCA公司的发展小趣事

为了进一步提升竞争力,BOCA公司开始尝试产业链整合。公司不仅加强了对上游原材料供应商的控制,还通过并购和合作的方式,将下游的组装、销售等环节也纳入自己的体系。这一举措使得BOCA能够更好地控制产品质量和交货期,同时也降低了成本,提高了整体运营效率。

CIRCUITCO公司的发展小趣事

CIRCUITCO公司自创立之初,就致力于电子电路技术的研发。在某一时期,公司投入大量资源研发新型高性能电路板。经过数年的努力,团队终于成功开发出一款具有更高集成度、更低能耗的电路板。这一突破不仅提升了公司的技术实力,也使其在市场上获得了显著的竞争优势。随着这款新产品的推出,CIRCUITCO公司的业绩迅速攀升,成为行业内的佼佼者。

DPA Components International公司的发展小趣事

作为一家具有社会责任感的企业,DPA Components International公司始终关注社会公益事业。公司积极参与各种公益活动,如扶贫助学、环保行动等,为社会做出了积极贡献。此外,DPA还关注行业发展和社会进步,积极参与行业交流和合作,推动整个电子行业的健康发展。通过担当社会责任,DPA不仅提升了企业的社会形象,也为社会的进步和发展做出了积极贡献。

Dynawave Incorporated公司的发展小趣事

Dynawave Incorporated的创始人李明,是一位在电子行业有着丰富经验的工程师。他深知随着科技的发展,无线通信技术将成为未来发展的重要方向。因此,李明带领一支技术团队,夜以继日地研发新型无线通信技术。经过数月的努力,他们成功开发出了一种具有高效能、低能耗特点的无线传输技术,这一技术为Dynawave在行业中赢得了初步声誉。

问答坊 | AI 解惑

wince、vc如何给按钮加图片背景?

wince、vc如何给按钮加图片背景? CBitmapButton在vs2005下编译不过呀…

查看全部问答>

刚买的DELL A840 本,安装不上声卡驱动

刚买的DELL A840 本,安装不上声卡驱动,我根据网上提示,下载了BIOS程序,刷了BIOS,请问这样对本有影响吗? …

查看全部问答>

1234

adsfdskjkglksjgdkl;ajd\'s…

查看全部问答>

STM32中断与嵌套NVIC快速入门。

STM32 中断与嵌套NVIC 快速入门。我也是靠看这本书才弄懂的:Cortex-M3 权威指南Joseph Yiu 著宋岩 译其实很简单。//CM3 有 最多240个中断(通常外部中断写作IRQs),就是 软件上说的 IRQ& ...…

查看全部问答>

请教2407PWM周期的问题.

查2407的资料其频率为30M,对吗?我现在设的TxPER为0x1388,也就是5000,那么一个PWM周期为10000个CPU周期,即开关频率为3K,我算的对吗?还请知道的人指点指点!…

查看全部问答>

Zstack禁用串行口硬件握手的方法

zstack提供的例程,默认是启用了硬件握手功能,该功能让数据通讯更可靠。实际使用中,串行电缆、或者USB转232转换器不支持,硬件握手信号,导致通讯失败的情况不少。以SerialApp为例,说明如何禁用硬件握手功能。…

查看全部问答>

msp430WIN7 64位用BSL下载

本想用仿真器下载程序,但是鄙人64位机子太不给了,老是弄不好……无奈只能改用BSL了,不过64位的驱动不好找,我找了一个,分享给大家。…

查看全部问答>

给点建议呗,入门级示波器,你会选哪家?

有木有熟悉高校实验室设备采购、入门级示波器采购的?或者对国产示波器了解的筒子?给点暗示吧,如果你采购基础/入门级示波器(单价在5K以下),你会采购哪家的?(备选项:Rigol DS1000、泰克TBS1000、优利德UTS1000,安捷伦1000,或者其他牌子也 ...…

查看全部问答>

(转)多种电源常用芯片特性简介

7800系列三端稳压器(正输出) 输出电压固定的三端系列稳压器;输出电压有5V、6V、7V、8V、9V、10V、12V、15V、18V、20V、24V输出电流1A;5~18V输出的最大电压为35V、20V、24V输出的电大输入电压为40V;7800工作温度为-55~+150℃,7800C的为0~+ ...…

查看全部问答>

晒WEBENCH设计的过程+3.3V1A电源设计

首先登录网站http://www.ti.com.cn/lsds/ti_zh/analog/webench/overview.page 填写你需要的一些设计条件 1.进入TI主页,输入相关参数 2.点击“开始设计”便进入WEBENC DESIGNER的设计页面,选择了第一项:Module。 3.在优化后的方案中我选择 ...…

查看全部问答>