
HAL库结构! w! T0 o: E+ b, U; P& c1 ] 说到 STM32 的 HAL 库,就不得不提 STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX 就是以 HAL 库为基础的,且目前仅支持 HAL 库及 LL 库!首先看一下,官方给出的 HAL 库的包含结构:* d) m" t6 ^5 Y2 h* K+ p ![]() , P4 }% b/ R% N; ^6 w( I# o stm32f2xx.h 主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:
紧接着,其会包含 stm32f2xx_hal.h。0 ?* h4 i. p* R7 v% d # g- Z% j# z5 Q. m stm32f2xx_hal.h:stm32f2xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置 stm32f2xx_hal_conf.h :该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。 接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f2xx_hal开头,后面加上_外设或者模块名(如:stm32f2xx_hal_adc.c):3 X: p, Y3 J6 ?6 v& ~; e ; p- d4 u5 n3 X* |3 @
根据HAL库的命名规则,其API可以分为以下三大类:9 P$ \- z1 ^' j1 K! K; d 2 F# I+ j; q. D+ y 初始化/反初始化函数: HAL_PPP_Init(), HAL_PPP_DeInit() IO 操作函数: HAL_PPP_Read(), HAL_PPP_Write(),HAL_PPP_Transmit(), HAL_PPP_Receive()& }; P. R* l9 B$ l9 d 控制函数: HAL_PPP_Set (), HAL_PPP_Get (). 状态和错误: HAL_PPP_GetState (), HAL_PPP_GetError (). 3 ~, A+ L6 W; Z 注意: 目前 LL 库是和 HAL 库捆绑发布的,所以在 HAL 库源码中,还有一些名为 stm32f2xx_ll_ppp 的源码文件,这些文件就是新增的LL库文件。1 T4 B$ K6 E. ]6 i7 f" \ 使用 CubeMX 生产项目时,可以选择LL库. U& z* y4 p0 ` a% i. i HAL 库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分: k5 }( v N' Z; q& _9 Q 处理外设句柄(实现用户功能) 处理MSP! m! Z: a' [* b1 W# F6 N# y' V 处理各种回调函数 外设句柄定义6 X, m) Y. F8 a% Q) c* U 用户代码的第一大部分:对于外设句柄的处理。 HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。 1. 多实例支持:每个外设/模块实例都有自己的句柄。 因此,实例资源是独立的& C6 o) {4 k1 D" Q* v4 A3 d4 j 2. 外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。 下面,以 ADC 为例
从上面的定义可以看出,ADC_HandleTypeDef中包含了ADC可能出现的所有定义,对于用户想要使用ADC只要定义一个ADC_HandleTypeDef的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。 当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。 例如以下外设:1 L( y" B0 b- M8 b% `' _7 d5 S - GPIO - SYSTICK( x/ h: _3 J3 S. ]6 ~ - NVIC( m5 |1 u0 I7 ^3 x" ?" Q1 | - RCC - FLASH 以 GPIO 为例,对于 HAL_GPIO_Init() 函数,其只需要 GPIO 地址以及其初始化参数即可。; M; S' K7 o' {. k; i, b1 w P% ] ; M P9 U- C) y k2 }5 K 三种编程方式 HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):
其中,带_IT的表示工作在中断模式下;带_DMA的工作在DMA模式下(注意:DMA模式下也是开中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。4 A; I# c: [+ u* {0 f; F 此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设主要由以下宏:( Z9 u% p$ k1 g, R) K9 P) i9 N; ~ $ O+ c" g9 l& }8 e4 e/ ]% H __HAL_PPP_ENABLE_IT(__HANDLE__, __INTERRUPT__): 使能一个指定的外设中断* W, e! H, f0 x1 D4 B- n __HAL_PPP_DISABLE_IT(__HANDLE__, __INTERRUPT__):失能一个指定的外设中断 __HAL_PPP_GET_IT (__HANDLE__, __ INTERRUPT __):获得一个指定的外设中断状态 __HAL_PPP_CLEAR_IT (__HANDLE__, __ INTERRUPT __):清除一个指定的外设的中断状态: u0 _1 t' }+ K1 r9 q( l, e$ M# O __HAL_PPP_GET_FLAG (__HANDLE__, __FLAG__):获取一个指定的外设的标志状态2 u2 h, ?; P3 q9 N __HAL_PPP_CLEAR_FLAG (__HANDLE__, __FLAG__):清除一个指定的外设的标志状态 __HAL_PPP_ENABLE(__HANDLE__) :使能外设 __HAL_PPP_DISABLE(__HANDLE__) :失能外设' c' T/ k6 a6 \6 K z __HAL_PPP_XXXX (__HANDLE__, __PARAM__) :指定外设的宏定义 __HAL_PPP_GET_ IT_SOURCE (__HANDLE__, __ INTERRUPT __):检查中断源 % K$ Q/ V3 m$ [$ Y5 t " a: b/ N+ `; P: S, M6 q) K! n 三大回调函数 在 HAL 库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:
有些则没有被实现,例如:$ V' v0 f5 H: l8 L* t5 J% C
所有带有__weak关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。通常来说,HAL库负责整个处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。 HAL 库包含如下三种用户级别回调函数(PPP为外设名):0 {' c& v4 x, g' q: z 1. 外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):HAL_PPP_MspInit()和 HAL_PPP_MspDeInit** 例如:__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)。在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)1 p4 g. i) ~ j4 S1 z 2. 处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)。当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用 : V% @) D; X' X* e3 ~: ^ C 3. 错误处理回调函数:HAL_PPP_ErrorCallback例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)**。当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用4 p: U; s' A- l# Y1 W 绝大多数用户代码均在以上三大回调函数中实现。 HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。某些外设多次初始化时不调用返回会导致初始化失败。 完成回调函数有多中,例如串口的完成回调函数有HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback等/ o% p4 Q0 P- @& W1 J B (用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)5 A. \- \* W- N+ ?' I 在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题 ; [$ d2 _& o4 b" K/ B HAL库移植使用 基本步骤- g" n! z# k p: E, P# k 复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。 复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。/ |! l, y3 R% ~/ W$ t9 _# ? 在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void))# @9 a+ ~0 B, G0 P2 u HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中), V% j& b0 `1 u1 S( [% m, Q 关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。* x& p8 l: \) v* C* u! K7 [ 对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。例如:Uart中,HAL提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:
使用了哪种就用哪个回调函数即可!0 _" e X4 k. _+ N8 B 基本结构) C+ I, r% e% Z% F3 L" L 综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下: / ~3 Z9 F" P" q( b. {& n/ | 配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;,接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart))$ M2 Y5 a) f4 X6 a; X: A( }8 g 编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef* huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)4 L. n O* ?0 o0 \ 实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数 至此,HAL库的总体结构就介绍完了!具体的每个文件的详细说明,官方源码注释很详细!6 u" ]0 Q# E) V2 f , H, D/ X$ r& P, K |
stm32f207/stm32f407擦除内部flash讲解
【经验分享】STM32F2 中 DMA 的 FIFO 模式
STM32F2x7 通过以太网实现在应用中编程 (IAP)
STM32F2xx 微控制器中的 EEPROM 模拟
在 STM32 F0、 F2、 F3、 F4 和 L1 系列MCU 中使用硬件实时时钟 (RTC)
STM32F1xx、STM32F2xx、STM32F4xx、STM32L1xx、STM32F30/31/37/38x 定时器概览
使用 STM32F2xx 和 STM32F4xx 微控制器时如何提高 ADC 测量精度
使用 STM32F2 和 STM32F4 DMA 控制器
STM32F2x7 通过以太网实现在应用中编程 (IAP)
STM32F2xx 微控制器中的 EEPROM 模拟