你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 之二 HAL库详解 及 手动移植

[复制链接]
STMCU小助手 发布时间:2021-12-3 16:00
HAL库结构
5 a4 ~: Y6 ?/ {" M7 ]2 _4 Y  说到 STM32 的 HAL 库,就不得不提 STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX 就是以 HAL 库为基础的,且目前仅支持 HAL 库及 LL 库!首先看一下,官方给出的 HAL 库的包含结构:
/ \9 [4 L+ G& p6 `, c( m
. ?' |) e, C& H' A
20170220193126892.jpg
3 c3 [, X0 S/ f

# ^3 t) `. K2 |stm32f2xx.h 主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:
4 g" w8 T8 {2 {! L
  1. #if defined(STM32F205xx)
    : v0 s3 [- i* m- G4 t7 \
  2.   #include "stm32f205xx.h"" i6 ?9 ?. X* C4 c* l) J% v
  3. #elif defined(STM32F215xx)" N6 m9 y/ J1 `% D
  4.   #include "stm32f215xx.h"
    9 G' Z6 H* j% r9 j  B* _
  5. #elif defined(STM32F207xx); N8 I5 U5 J( J. t" t* u7 b4 M
  6.   #include "stm32f207xx.h"
    ( q5 X# a! v# s9 D- l4 K* s) v
  7. #elif defined(STM32F217xx)/ x, F3 a- f+ x0 l- L
  8.   #include "stm32f217xx.h"
    4 J. w# d2 O: F! T
  9. #else
    + K% @' l! r" ]. U
  10. #error "Please select first the target STM32F2xx device used in your application (in stm32f2xx.h file)"
    * {) o* O6 s) \) F
  11. #endif
复制代码
% a2 l( x4 r3 z0 y5 W
紧接着,其会包含 stm32f2xx_hal.h。; P, ?/ s! z7 T
9 w: x, d, G4 I8 L
stm32f2xx_hal.h:stm32f2xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置% D( v  {$ F# H0 G- K5 t
stm32f2xx_hal_conf.h :该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。" @! L# o: V8 q1 @  ]$ [& Y
  接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f2xx_hal开头,后面加上_外设或者模块名(如:stm32f2xx_hal_adc.c):) F3 J8 m( u' N  f4 U# V0 d+ ?

, O* ]* d2 N& T+ O) p3 a
  1. 库文件:
    0 c8 B( ]: G2 [
  2.         stm32f2xx_hal_ppp.c/.h                        // 主要的外设或者模块的驱动源文件,包含了该外设的通用API
    9 N% a; f+ a" ^
  3.         stm32f2xx_hal_ppp_ex.c/.h                // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。
    ) l- R9 n' H( H" t7 ?: Y! _
  4.         stm32f2xx_hal.c/.h                                // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API
    / R9 p( q" C. _* B. y( D/ ]
  5.         其他库文件
    0 D5 y2 B  L8 H
  6. 用户级别文件:+ Z- I8 E1 d" a: W
  7.         stm32f2xx_hal_msp_template.c        // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。* E6 {  ]1 l+ c# E9 o/ K
  8.         stm32f2xx_hal_conf_template.h        // 用户级别的库配置文件模板。使用者复制到自己目录下使用& c3 x. ^  |0 U8 o8 w% k! ]5 J' C
  9.         system_stm32f2xx.c                                // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。 **它不在启动时配置系统时钟(与标准库相反)**。 时钟的配置在用户文件中使用HAL API来完成。
    ( `9 X2 O" g" R- c/ h
  10.         startup_stm32f2xx.s                                // 芯片启动文件,主要包含堆栈定义,终端向量表等
    & v2 }( c+ L, i# H
  11.         stm32f2xx_it.c/.h                                // 中断处理函数的相关实现
    2 U$ H0 P4 A/ i" R5 ]* T
  12.         main.c/.h                                                //
复制代码
& x9 }' p6 k( ~& O7 w6 [1 w1 N9 D, _
  根据HAL库的命名规则,其API可以分为以下三大类:+ b5 z( h2 [- [# @  n

6 e7 n* y( B$ P! R" B5 G5 E初始化/反初始化函数: HAL_PPP_Init(), HAL_PPP_DeInit()
# S! [' N6 O9 a- I' o$ tIO 操作函数: HAL_PPP_Read(), HAL_PPP_Write(),HAL_PPP_Transmit(), HAL_PPP_Receive()9 d7 ?) q7 z5 h) y; h( R; Z1 C( K9 U
控制函数: HAL_PPP_Set (), HAL_PPP_Get ().! Y2 V3 N5 p0 n, e# _0 {+ n$ |
状态和错误: HAL_PPP_GetState (), HAL_PPP_GetError ().0 {/ P1 W% j* W: h/ X8 W8 e

6 C: N4 S! r2 ?" [/ \5 I7 m注意:
; ?2 a4 D4 W; ]8 X1 Y& m% y* s目前 LL 库是和 HAL 库捆绑发布的,所以在 HAL 库源码中,还有一些名为 stm32f2xx_ll_ppp 的源码文件,这些文件就是新增的LL库文件。; q1 D) V* U& C* z
使用 CubeMX 生产项目时,可以选择LL库& C9 |" l1 u  c$ y% D- R
HAL 库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:/ L1 D: l$ ~2 a  S0 L
- w' C/ b' o, t( W' |. S
处理外设句柄(实现用户功能)- a  R1 t6 C2 }7 W% Y  x
处理MSP1 G# s& t0 E- b, I& y  [0 m8 I
处理各种回调函数" \, f* v6 [! J/ H1 n
外设句柄定义% ~1 z; ~+ W( N. R
  用户代码的第一大部分:对于外设句柄的处理。 HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。
/ j( R7 _( Z' y' b  v2 I& p  k. j  1. 多实例支持:每个外设/模块实例都有自己的句柄。 因此,实例资源是独立的9 a' g& B' Y. P. R% |
  2. 外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。
/ k! X+ ^; e8 O: u& i  F: U2 E$ e下面,以 ADC 为例
$ k. D/ F+ p7 q; }1 ^  I
  1. /** 9 N6 p# p: X! ~& s7 G
  2. * @brief  ADC handle Structure definition' k( J) a1 Q" X& o
  3. */ * }# Q  E  R) P
  4. typedef struct
    ; d# l2 f  H' a0 G$ k
  5. {5 x" I. _3 i' x- g7 U) [
  6.         ADC_TypeDef                   *Instance;                   /*!< Register base address */
    ! _1 _9 P! b+ J* F! _
  7.         ADC_InitTypeDef               Init;                        /*!< ADC required parameters */
    $ n0 \& }  R$ n; t5 a
  8.   __IO uint32_t                 NbrOfCurrentConversionRank;  /*!< ADC number of current conversion rank */6 Q. ?; I6 j' a6 _1 z* H
  9.         DMA_HandleTypeDef             *DMA_Handle;                 /*!< Pointer DMA Handler */
    ( Z% g- c2 ^6 b4 C1 g5 [1 Z
  10.         HAL_LockTypeDef               Lock;                        /*!< ADC locking object */- R  \# u; G  T6 C$ _, C
  11.         __IO uint32_t                 State;                       /*!< ADC communication state */9 `  j; D$ n& i- Z8 Q
  12.         __IO uint32_t                 ErrorCode;                   /*!< ADC Error code */
    1 E1 i$ l$ P6 C) z
  13. }ADC_HandleTypeDef;
复制代码

+ B; A( c( E2 E) h  从上面的定义可以看出,ADC_HandleTypeDef中包含了ADC可能出现的所有定义,对于用户想要使用ADC只要定义一个ADC_HandleTypeDef的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。& o4 P2 X: a, U) o$ u' W. s" ^
  当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。 例如以下外设:
7 V* b" ^2 W, `5 m1 P2 V  - GPIO) z  f( o: a4 B
  - SYSTICK% D! A; i* T: t, h
  - NVIC
$ }9 f; Z, G, F7 t7 G  - RCC1 ?6 @8 j! y. n( I; F8 x7 {) d
  - FLASH
- X* w/ h; C0 i; l, `以 GPIO 为例,对于 HAL_GPIO_Init() 函数,其只需要 GPIO 地址以及其初始化参数即可。) T5 B9 V8 h7 L; q9 w7 S: K* i: i
8 W+ _# z% y7 p. z2 W4 `+ N
三种编程方式0 r7 H* B' j! c% b( o1 h
  HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):
8 B, F9 I) m+ E( \0 F0 W
  1. HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
    $ u2 f. B" L8 L' e& P+ l
  2. HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);& N- j- a4 Z& c1 {
  3. ' Z1 g! {' U9 E; f8 i
  4. HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);  L! q+ A% e+ f; e( S( F
  5. HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
    ! Y* R( W: x* b( V. _+ t/ n, d

  6. ) H. I  E$ D1 r
  7. HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);) R3 Z" V3 X- b6 \
  8. HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
复制代码
* q3 ]5 U9 |" K3 w

" r4 o# l+ Z1 M! C7 Q' U. L: F  其中,带_IT的表示工作在中断模式下;带_DMA的工作在DMA模式下(注意:DMA模式下也是开中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。
" u# s5 A' b9 Y( F! N+ p* n9 M+ i  此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设主要由以下宏:  b' K- p; n# t

( d+ A) Z3 x/ |) g1 [7 S__HAL_PPP_ENABLE_IT(__HANDLE__, __INTERRUPT__): 使能一个指定的外设中断
; G8 G4 X2 z$ d5 m& R__HAL_PPP_DISABLE_IT(__HANDLE__, __INTERRUPT__):失能一个指定的外设中断" n8 e9 C+ i! o# D6 M4 n
__HAL_PPP_GET_IT (__HANDLE__, __ INTERRUPT __):获得一个指定的外设中断状态, u1 y2 Z$ X3 A
__HAL_PPP_CLEAR_IT (__HANDLE__, __ INTERRUPT __):清除一个指定的外设的中断状态+ M  E. T( P' @" A! i, c" j' A  B
__HAL_PPP_GET_FLAG (__HANDLE__, __FLAG__):获取一个指定的外设的标志状态
4 e' {3 a) x7 T1 p/ u& ___HAL_PPP_CLEAR_FLAG (__HANDLE__, __FLAG__):清除一个指定的外设的标志状态' K/ x& L4 i/ Y7 N' O8 g
__HAL_PPP_ENABLE(__HANDLE__) :使能外设% y$ Z0 w2 t2 g: O3 |6 w
__HAL_PPP_DISABLE(__HANDLE__) :失能外设; x6 H: }. N* [" b
__HAL_PPP_XXXX (__HANDLE__, __PARAM__) :指定外设的宏定义
. j3 u2 r$ y# b& I+ b__HAL_PPP_GET_ IT_SOURCE (__HANDLE__, __ INTERRUPT __):检查中断源' U0 P3 e; V7 b& Y/ m3 s* |7 W
! Q2 ]/ x* r6 J- Y" r8 q

" H5 o" q5 a5 @4 z7 W/ L8 ~. s三大回调函数' P3 e5 n5 l% N7 w$ j9 b
  在 HAL 库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:
9 k8 J* p* F9 t% i+ X
8 b' W$ Q3 I8 n$ Q2 A% }$ ?7 E  Z
  1. __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)- I, c( p$ e* r' A
  2. {5 V/ ^. X$ s9 p- t% {6 S4 Z
  3.         /*Configure the SysTick to have interrupt in 1ms time basis*/
    : ~. j2 @: e  a, ~5 G3 f
  4.         HAL_SYSTICK_Config(SystemCoreClock/1000U);$ D/ Y% v. l, K$ A1 @' w: J1 E  D6 Q
  5.         /*Configure the SysTick IRQ priority */
    6 `  ]/ O7 i% D; P
  6.         HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);1 R: \3 C+ l5 t) G5 r( d
  7.         /* Return function status */9 i: p+ ?2 X# ?! f7 |! F
  8.         return HAL_OK;7 g7 k& y4 u/ O. u- z. Y
  9. }
复制代码

+ d3 h) E; n- _0 F$ K' F( B; k$ x有些则没有被实现,例如:
' n4 n/ H( i6 Y7 ^
  1. __weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)6 Q0 V3 F; {7 ^5 T5 y
  2. {' t. j1 e4 p* }% A
  3.   /* Prevent unused argument(s) compilation warning */
    3 R9 ~  R) Z1 e6 P7 o: f
  4.   UNUSED(hspi);
    & `1 N6 G0 S* i) s, u
  5.   /* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file. |; `: B# B8 m4 Z9 @
  6.   */+ C3 o3 x6 g* p' \, _" \
  7. }
复制代码
) L3 f+ S9 O2 \7 g
所有带有__weak关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。通常来说,HAL库负责整个处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。 HAL 库包含如下三种用户级别回调函数(PPP为外设名):
! m* i2 c; @4 r  A  1. 外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):HAL_PPP_MspInit()和 HAL_PPP_MspDeInit** 例如:__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)。在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)' T: D2 I: N: i  c' X2 o! r* @

' p- H* \; Z6 {3 n, F  2. 处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)。当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
1 |; H5 V% o) j1 C" m- @3 d+ t+ z8 Y, N7 Z0 l6 i: Y" w! A
  3. 错误处理回调函数:HAL_PPP_ErrorCallback例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)**。当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用8 S6 N. O2 E  A& ^9 [6 C% B& M4 w
  d! A* J4 c% j2 _& r1 E. `1 u
绝大多数用户代码均在以上三大回调函数中实现。
: j  E: F' @4 t4 L8 CHAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。某些外设多次初始化时不调用返回会导致初始化失败。' S4 S0 l/ k' d9 b# u4 F2 t* ~  j
完成回调函数有多中,例如串口的完成回调函数有HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback等
' G, j' a2 a6 y. U% A1 r3 O4 A3 l& `(用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)9 I, C0 @5 j) l, Z0 }" z- M
在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题4 V8 W5 q0 r- C

' x8 _- e- i7 S) g* cHAL库移植使用
- S5 K4 {9 H; v9 J7 M6 w基本步骤+ `" \2 E% n; r+ e3 ~& k, l
复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。# H# D8 T5 @8 {' V1 H
复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。
) I% G% e2 O% o! s3 B在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void))" L1 b$ M( _; C* X
HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中)2 V+ ~* N$ z+ H1 w
关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。
" c9 k% W& E  a1 T9 H0 C. }对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。例如:Uart中,HAL提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:
$ \  f7 |+ O  E+ C6 h
  1. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
    0 C% B7 ^' N2 t6 w
  2. void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
    " g, W: C' p4 b% p4 A
  3. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
    2 w$ i8 u% S$ w, f0 s+ C
  4. void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);% Y2 [5 E0 E4 z) F
  5. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
复制代码
7 U' Q. W) p# B9 s, r' o
使用了哪种就用哪个回调函数即可!& `8 r* G( O6 {/ m0 i0 c
, Y  e3 M1 l6 s- e9 K+ M7 r
基本结构
' c+ w' B4 e/ C/ K% o0 L  综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下:, d# M$ O9 o( b* J+ w) O- y
3 }/ `* I; d: z1 [5 k
配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;,接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart))
: R6 ?. l+ a" G: W& c编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef* huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart); Q" R2 |* e2 @0 c! X- h
实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数
, b5 O9 H4 i! N* M/ k至此,HAL库的总体结构就介绍完了!具体的每个文件的详细说明,官方源码注释很详细!
, v  c  ~7 M7 G: v) I
/ D1 f3 W( _% W: ]! M3 P% x# e" v3 Z/ P& T; {) F/ R7 \. l
. q3 c3 l( C) z9 M3 h3 b7 s
收藏 评论0 发布时间:2021-12-3 16:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版