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

STM32的HAL库知识总结

[复制链接]
STMCU小助手 发布时间:2022-9-17 17:12
ST 为开发者提供了非常方便的开发库:有标准外设库(SPL库)、HAL 库(Hardware Abstraction Layer,硬件抽象层库)、LL 库(Low-Layer,底层库)三种。前者是ST的老库已经停更了,后两者是ST现在主推的开发库。

/ ]3 g5 g9 U9 f: H; s
相比标准外设库,STM32Cube HAL库表现出更高的抽象整合水平,HAL API集中关注各外设的公共函数功能,这样便于定义一套通用的用户友好的API函数接口,从而可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。HAL库是ST未来主推的库,ST新出的芯片已经没有STD库了,比如F7系列。目前,HAL库已经支持STM32全线产品。
6 s& N" E9 d" b+ _' `, `, ~
通过文字描述可以知道HAL库的几个点:1.最大可移植性。2.提供了一整套一致的中间件组件,如RTOS,USB,TCP / IP和图形等。3.通用的用户友好的API函数接口。4.ST新出的芯片已经没有标准库。5.HAL库已经支持STM32全线产品。, B+ m: C* u: [( M
网友认为,“HAL我觉得是极好的,就是SPI接收时速度实在太慢,不用DMA的话,吃不消。“
% [+ e( z2 d, {- S
通常新手在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。网上关于标准库、HAL库的描述相信是数不胜数。可是一个对于很多刚入门的朋友还是没法很直观的去真正了解这些不同开发方式彼此之间的区别,所以笔者想以一种非常直白的方式,用自己的理解去将这些东西表述出来,如果有描述的不对的地方或者是不同意见的也可以大家提出。

; S0 P' ~3 `: N" y9 R. r; P6 ~
01、直接配置寄存器
6 A4 o% Z3 h7 ~
不少先学了51的朋友可能会知道,会有一小部分人或是教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了,因为STM32的寄存器数量是51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。但还是会有很小一部分人,喜欢去直接操作寄存器,因为这样更接近原理,知其然也知其所以然。

& G4 k$ a! q5 Q; F  `3 m( m$ J3 f; b. d0 l0 |- a, ]
02、标准库
4 \( b- b8 ]2 U; {/ E9 w5 k
上面也提到了,STM32有非常多的寄存器,而导致了开发困难,所以为此ST公司就为每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的。在这些 .c .h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO口时钟等。所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式,我也就不多阐述了。

. |4 b( A6 ~+ N! y" Z8 M2 ?2 _0 y# g* m4 E  }' o6 \
03、HAL库
& x/ t' z2 L4 n0 o% y0 W* L
HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。库如其名,很抽象,一眼看上去不太容易知道他的作用是什么。它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有,例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。
  ?% `3 k  ~, Q. O( r% B  s

$ u! z; W# ]. T8 ]3 a' F# I
HAL库和标准固件库区别. @4 }4 Q' {( Y$ Y7 d
STM32的开发中,我们可以操作寄存器:( t9 `7 [) R' a& t% S
GPIOF->BSRR=0x00000001;//这里是针对STM32F1系列
1 W& S4 x+ `1 ~, f1 f0 [; X
这种方法当然可以,但是这种方法的劣势是你需要去掌握每个寄存器的用法,你才能正确使用STM32,而对于STM32这种级别的MCU,数百个寄存器记起来又是谈何容易。于是ST(意法半导体)推出了官方标准固件库,标准固件库将这些寄存器底层操作都封装起来,提供一整套接口(API)供开发者调用,大多数场合下,你不需要去知道操作的是哪个寄存器,你只需要知道调用哪些函数即可。
0 I! T3 d, S9 o& u+ G& o
比如上面的控制 BRR 寄存器实现电平控制,官方库封装了一个函数:

( F  u8 l) ^& ^, a3 {5 C
  1. void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
    3 j. ^7 X" A0 U3 @
  2. {1 U0 Z7 m$ y! k: X+ W9 K
  3. GPIOx->BRR= GPIO_Pin;' k* p7 J& r* x3 l4 r# A- b
  4. }
复制代码
/ g& V. A/ `( R4 L
这个时候你不需要再直接去操作 BRR 寄存器了,你只需要知道怎么使用 GPIO_ResetBits()这个函数就可以了。在你对外设的工作原理有一定的了解之后,你再去看标准库函数,基本上函数名字能告诉你这个函数的功能是什么,该怎么使用,这样开发就方便很多。" f; o0 i3 ]$ Z" W# k' Y6 s% \
标准固件库自推出以来受到广大工程师推崇,现在很多工程师和公司还在使用标准库函数开发。不过,ST官方已经不再更新STM32标准固件库,而是力推新的固件库:HAL库。$ C8 x) ^4 Z" D5 ~* L1 Q1 `
比如上面的控制BSRRL 寄存器实现电平控制,官方 HAL 库封装了一个函数:
2 a( z" z  r1 ^- y
  1. voidHAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,
    ( W. X8 f. L$ E7 r6 O  R- L
  2. GPIO_PinStatePinState), R. ?7 ], u& s9 X8 ]
  3. {- I# I- F  m4 K9 d' g2 C% e: ?
  4. assert_param(IS_GPIO_PIN(GPIO_Pin));
    / t/ N' o  g- ~) p- m) V8 V
  5. assert_param(IS_GPIO_PIN_ACTION(PinState));9 E& ^0 [8 L& }& o4 Y$ s- r' g3 f. [
  6. if(PinState!= GPIO_PIN_RESET)
    8 i' A% s. r$ A8 y( G4 u5 f# R! \
  7. {+ h# p& x- p9 S! s' L) X
  8. GPIOx->BSRR= GPIO_Pin;3 r" q# A4 E5 c( q7 s- Q
  9. }
    9 L0 X& f6 D; I9 L$ C  S
  10. else  q5 L- a- g% M2 n' {
  11. {' j6 H; F+ |& J& X& W& {# K; g
  12. GPIOx->BSRR= (uint32_t)GPIO_Pin << 16;
    6 J( n8 Y) U! K5 I
  13. }
    4 J. B- A# g& T" p, ^, r
  14. }
复制代码

, d1 t/ a; V# R" O
这个时候你不需要再直接去操作BSRRL 寄存器了,你只需要知道怎么使用HAL_GPIO_WritePin这个函数就可以了。
  y  h7 E- `5 w8 w
标准固件库和HAL库一样都是固件库函数,由ST官方硬件抽象层而设计的软件函数包,由程序、数据结构和宏组成,包括了STM32所有外设的性能特征。  这些固件库为开发者底层硬件提供了中间API,通过使用固件库,无需掌握底层细节,开发者就可以轻松应用每一个外设。
/ n& U4 c7 K& }" V/ m6 i  F
HAL 库和标准库本质上是一样的,都是提供底层硬件操作 API,而且在使用上也是大同小异。有过标准库基础的同学对 HAL 库的使用也很容易入手。ST 官方之所以这几年大力推广 HAL 库,是因为 HAL 的结构更加容易整合 STM32Cube,而 STM32CubeMX 是 ST 这几年极力推荐的程序生成开发工具。所以这几年新出的 STM32 芯片,ST 直接只提供 HAL 库。% Q7 B. t$ f  l! |) H4 d
在ST的官方声明中,HAL库是大势所趋。ST最新开发的芯片中,只有HAL库没有标准库。标准库和HAL库虽然都是对外设进行操作的函数,但由于标准库官方已经停止更新,而且标准库在STM32创建工程和初始化时,不能由CubMX软件代码生成使用,也就是说CubMX软件在生产代码时,工程项目和初始化代码就自动生成,这个工程项目和初始化代码里面使用的库都是基于HAL库的。STM32CubeMX是一个图形化的工具,也是配置和初始化C代码生成器 ,与STM32CubeMX配合使用的是HAL库(硬件抽象层软件库)。
& ^6 m( h; v- E" r+ }

; _* r1 X- z# z/ f( C* v" A& [
基本配置9 Q0 I( M; L. u2 `& f/ h8 f
5 y# O7 T! T  u/ e- e/ }
工程创建

9 ]+ ?8 z( E3 h2 P- F, }# y6 j- _( \
通过内核芯片的选择,创建相应的工程文件。1 E2 n& ]  e9 e8 l/ w

# T; Y8 T/ P( U" B9 l/ P: K5 ^
640 (13).jpg
3 z8 Z" w6 \' f. o) y) ?, u
2 e2 @# d1 s6 I" ~0 B

. G  U0 V* g  n( h3 n4 R 640 (12).jpg : o" T" D1 Z+ _" Z

. L/ Z5 F) a7 ~- u: ]对时钟系统进行配置,对引脚及基本功能进行配置。
- J2 v. X# ^- e6 L! ]
配置时钟系统我们首要思考的是:我们需要怎样的时钟系统,而不是如何配置时钟系统。
  L6 y! H0 i( J8 D: `/ f. @, e) k6 G
640 (11).jpg
) a: A* K' j4 C+ j2 \  W* w5 @7 ?& q, d* l* _! c. t9 }
配置SWD程序烧录接口,使用ST-Link进行烧录下载。
7 s( n% ?! L$ U2 u4 e8 x

9 X% f1 J2 C( E6 M3 l
640 (10).jpg
5 x* G4 R5 n( l  P4 N
$ h' L+ B$ i/ q$ I* B; e工程管理设置推荐图中所示配置,实现更快编译和更简洁的文件系统。
. l7 r8 t* e" G  P
点击图中“GENERATE CODE”生成Keil工程文件4 S- ]7 E- I  v
640 (9).jpg # T9 |, R% h! O) p* \
& K3 D% c( ?) N0 {  g' J
640 (8).jpg
. V: R0 P! l  W! C0 [5 Q5 l7 m9 H, u+ u  J  d  T& ], H
如果你已经安装了编译环境MDK了,可点击直接打开工程。
( v2 z% [8 o5 V2 Q  O6 P

  G; f$ Z8 B/ b7 F+ o+ i: U* I* P
GPIO使用) N9 h5 m3 g3 D0 d  N
开发环境搭建好以后,可以依开始STM32的开发,下面是使用GPIO的例程,让LED灯每隔400ms闪烁一次- T8 {+ g$ O3 C$ g) h
打开STM32CubeMX新建工程,选择STM32F103ZET6芯片。选择外部高速晶振(HSE)。$ g; `- D) F) @. R1 J" K. _
9 |) O5 K- k! {  e4 K! M9 z& P
640 (7).jpg
8 J" @/ ^- u6 o. z/ \: J# C
5 H! \' A# S, @7 X; t 640 (6).jpg ; x# ]+ g: K7 x# d7 S+ T

! |6 r* P5 q+ U$ t
2 _1 G5 M8 u+ K7 H$ [% i( Z+ m
根据下位机主控引脚分配图及原理图,选择LED引脚+ Z  d) z" O3 s6 P" o% v9 T
640 (13).png 640 (11).png
* Q$ `4 [2 }: |+ D
4 `8 s& C6 u5 w0 I
0 q$ S, I* s3 v% E
PD7为LED1输出控制管脚,选择GPIO_OUTPUT模式。
640 (10).png
* d$ _6 @4 H+ P
点击Clock Configuration配置系统时钟为72M最高速度。  F; x7 g' R: {3 @, j
: L/ \* b9 l1 O6 h1 V- r
640 (5).jpg
! b7 W/ z4 |1 D! y6 V+ u$ P& P, Y5 T! A& U  S1 q7 f; `- n

1 W! @4 {, @$ h' N  D, p* ]点击Configuration->GPIO配置管脚。LED管脚配置为低速推挽输出模式,既不上拉也不下拉(即默认的模式不用配置)。2 j& {. M3 }# ^) `. ~0 G
& P# U: R9 Q+ u

) v2 }4 J" r5 v$ ^( M) I. w 640 (4).jpg ( V" x9 g4 n& E

& p, K- k  Y! o
8 G3 _% j% o6 F9 H: q! e
点击 640 (14).png 生成报告,软件会提示新建工程,输入工程名,选择工程保存路径。IDE选择MDK-ARM V5。
9 q8 _* G+ D. o5 [/ B$ V8 ~4 e& s, [" K
640 (3).jpg $ s9 n. ]  U& h5 A+ h/ r' b9 U

) e5 M% `* c- E在Code Generator中找到Generated files框,勾选Generated periphera initialization as a pair of '.c/.h'files per IP。外设初始化为独立的C文件和头文件。& R( u. N' z8 {) ~  Q' Y* m5 C
640 (2).jpg
( t8 B* R1 y2 X" x" U) P
点击 640 (15).png 生成代码。点击Open Project打开工程。到这里我们就配置好工程外设初始化。( ~" l1 ^+ z# Z- B8 M5 I9 G
640 (9).png
( o& \- o6 E* [/ L0 ?6 |1 |) W点击Build按钮,然后等一会,Build Optput信息框会输出没有错误没有警告。在main函数里添加如下代码4 Z1 F# v/ s: d. q$ o
640 (1).jpg
' Z. o9 C# ^4 s3 i& o9 w
延时电平翻转函数,这样LED灯就能开始闪烁。% m) H2 q& a7 P$ N* {) X
再点击Build按钮,然后等一会,Build Optput信息框会输出没有错误没有警告。代码烧写。% n2 R; s# l9 |* T
现在开始烧写程序,烧写程序有两种,一种是使用ST-LINK工具烧写,一种是直接用与上位机通讯的串口1烧写。烧写工具使用mcuisp。& g4 d7 p5 W9 L( ~
640 (16).png ,软件可自行网上搜索,下载配置如下。: P( k& H8 c9 B2 l
640.jpg
. c. y5 ?% B; i3 W; s' t/ ]' o1 w
选择好串口端口后,可以开始下载。6 ?3 x) P! f: `* L7 L$ g' _. _
# P/ l# {: i4 R1 H; a0 w5 ^3 r
HAL库固件库安装与用户手册

% `" M* H' C# b% b1 s( C! M

1.首先设置让Cube可以自动联网下载相关固件库选择updater Settings
) |" D3 v$ F  @' ^2 S+ l
640 (8).png

* x3 {2 f6 D# M$ o! E

% _% B/ V0 f; x

设置如下
640 (7).png


; c5 h) U/ f' T1 u$ Z  ?5 L1 X

2.根据芯片选择所需固件版本是向下兼容的,可以直接选择最新版。但如果觉得最新版太大,可以阅读下面的Main Changes.能够支持你目前的芯片就好。

; I; L0 \! N( c& l5 _+ J& ^0 U9 F
640 (6).png


6 h) {9 T# b5 B$ y( `

选好了,点击Install Now就行,过程可能有点长。建议直接官网下载到本地,再安装7 E. u$ j5 \. U4 X9 J
文件会被下载到如下位置,建议更改此目录,不要选在C盘!!!
% k; l( F: d2 ]' c
640 (5).png

  s3 I+ B1 S* K, c7 C- ?  D
查找帮助手册
$ M/ D. M: I1 D% P7 V' [! b
3.寻找用户帮助手册进入固件所在文件夹,里面包含很多内容。
, b. m8 N/ _% v. e' w% h- L
640 (4).png

" C" `2 \% a* D. R5 ^/ P4 |2 I, t

比如说 官方提供的开发板程序( g' R3 D5 M. Q
640 (3).png 3 i+ z. _/ z: e0 ~6 R

$ A$ D3 B& E& [) P
每个型号下面都有对应功能的实现# M4 }( l- e3 W' o" g4 e8 w
9 l  F7 C# a" j0 E6 \* N8 N
640 (2).png . A, h$ f9 }( H6 L8 y
4 r* C7 F# K/ y  A2 ~
用户手册就在Drivers文件夹下面。
* h9 o: c& L* g9 p0 b. p* W* \
640 (1).png

% r8 [3 g1 |) f' Q& x9 o! X- c. y


/ m/ u; H4 S: g: d; P5 b


& x0 t6 b) A9 v1 I5 _$ \8 M4 ^8 z

STM32 HAL库与标准库的区别——浅谈句柄、MSP函数、Callback函数! h9 j3 W) Z3 e

& J/ Y$ N( _7 A% F

01句柄
& n2 m7 @$ P" d+ ]9 u+ K
句柄(handle),有多种意义,其中第一种是指程序设计,第二种是指Windows编程。现在大部分都是指程序设计/程序开发这类。
) {* t5 t7 m, m' `# B" v

& h* x- Z( H7 Y
· 第一种解释:句柄是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。· 第二种解释:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。

% g/ c; {' E) `% s( H0 W6 O
STM32的标准库中,句柄是一种特殊的指针,通常指向结构体!

% H: D5 H+ d0 {+ }  d, l

在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例),我们首先要初始化他们的各个寄存器。在标准库中,这些操作都是利用固件库结构体变量+固件库Init函数实现的:(向右滑动哦)
1 y1 F2 t  R  R( a0 {& e
  1. USART_InitTypeDef USART_InitStructure;
    ; Z6 a& i! z. _" q+ q. g6 J3 m
  2. 5 z" _5 t. T+ `+ q# A
  3. USART_InitStructure.USART_BaudRate = bound;//串口波特率8 O$ @% f* R' q! I3 ?! k  r
  4. USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    6 ~  K" s. l. r- V
  5. USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位# W6 y5 c, W+ }% ?, R( [
  6. USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    5 D* D, V- T) ~8 k0 w3 ]8 Z
  7. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制8 A: x7 t' T+ i5 V
  8. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式5 p8 z# R5 d" L

  9. : s2 v2 @2 O" l- y: @- g$ n
  10. USART_Init(USART3, &USART_InitStructure); //初始化串口1
复制代码
5 d/ j- d6 w" M/ [* t/ t2 _
可以看到,要初始化一个串口,需要:* G. |, y" y* Y& K( W3 Q' g. Y
· 1、对六个位置进行赋值,
· 2、然后引用Init函数,

7 s7 |. a( W: [3 o. X
USART_InitStructure并不是一个全局结构体变量,而是只在函数内部的局部变量初始化完成之后,USART_InitStructure就失去了作用
7 }# y( y: Z1 V7 t. x
而在HAL库中,同样是USART初始化结构体变量,我们要定义为全局变量5 N& _1 A0 V  r. H
  1. UART_HandleTypeDef UART1_Handler;
复制代码
右键查看结构体成员
  1. typedef struct
    - x# j+ r" _+ `$ a' Y: e
  2. {
    , ?! V! t7 ^, V  t; q
  3.   USART_TypeDef                 *Instance;        /*!< UART registers base address        */4 X& {' S( M# _$ P" `; k' |
  4.   UART_InitTypeDef              Init;             /*!< UART communication parameters      */
    2 L  y! }8 S, ^, Y; I
  5. uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */; h2 k- E! P% q* F( F, L
  6. uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */1 b! Q$ X$ i$ u2 i- P
  7. uint16_t                      TxXferCount;      /*!< UART Tx Transfer Counter           */# T$ l7 C& o1 t* G  C; c! X2 n. _
  8. uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */6 x! Y/ q9 b5 B# }+ K7 H
  9. uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
    : }( Z0 W! x  W5 F0 }
  10. uint16_t                      RxXferCount;      /*!< UART Rx Transfer Counter           */
    $ E: Q; d5 {% G6 ?% o0 ]0 Y
  11.   DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */
    ' B. E! z: A1 q$ z8 I  d" f7 p8 ]
  12.   DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */
    # Z. Y  V3 \4 H* `- T
  13.   HAL_LockTypeDef               Lock;             /*!< Locking object                     */; @* C4 z5 {& c
  14.   __IO HAL_UART_StateTypeDef    State;            /*!< UART communication state           *// \+ R, @$ h5 Z  ?% c$ F& I
  15.   __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */
      y1 _; Z7 Y# A8 Q5 D8 \! w
  16. }UART_HandleTypeDef;
复制代码


0 b8 o. V# Z/ K  H/ ~) x9 q

我们发现,与标准库不同的是,该成员不仅:
· 1、包含了之前标准库就有的六个成员(波特率,数据格式等),· 2、还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等要在整个项目流程中都要设置的各个成员。该UART1_Handler就被称为串口的句柄,它被贯穿整个USART收发的流程,比如开启中断:
  1. HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);
复制代码
; }! \0 v5 v9 r' t( H* p

' E' I/ T) P* J% {% N: J( D3 E

5 {8 z) J$ L3 M, Z7 M% s9 P2 j比如后面要讲到的MSP与Callback回调函数:
8 k# y. k* f  i! G( R
  1. void HAL_UART_MspInit(UART_HandleTypeDef *huart);  G$ k" T; W) H5 N5 V+ z6 t1 U3 f
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
复制代码
在这些函数中,只需要调用初始化时定义的句柄UART1_Handler就好。
+ s9 j$ t* T, @9 ]
02MSP函数

+ ^7 B* \. F# r
MSP: MCU Specific Package 单片机的具体方案

7 T  r' ^% A; ]8 i. s! x' X
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 相关的串口引脚
  O2 e( _" a5 [4 L+ \5 E$ ]# n( I
在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。在HAL库中,几乎每初始化一个外设就需要设置该外设与单片机之间的联系,比如IO口,是否复用等等,可见,HAL库相对于标准库多了MSP函数之后,移植性非常强,但与此同时却增加了代码量和代码的嵌套层级。可以说各有利弊。
同样,MSP函数又可以配合句柄,达到非常强的移植性:
  1. void HAL_UART_MspInit(UART_HandleTypeDef *huart);
复制代码
3 L* z, M+ z. \( m

入口参数仅仅需要一个串口句柄,这样就能看出句柄的方便。


" w$ _+ o2 S' ^7 H8 p
03Callback函数

3 Z- j! ~  x0 L, i( T( f7 C
类似于MSP函数,个人认为Callback函数主要帮助用户应用层的代码编写。
还是以USART为例,在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理,这样如果我们在一个中断函数中写这么多代码,就会显得很混乱:
  1. void USART3_IRQHandler(void)                //串口1中断服务程序
    5 j  R6 O" w! Q$ M. _
  2. {6 |4 }, O( [% L; [" k! m* l
  3. u8 Res;# P* o1 N5 _; V: W  a- R9 P
  4. if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
    * M6 g8 L/ M2 P6 f0 M: }2 b
  5. {
    % n% R+ g) b' b7 h$ I
  6. Res =USART_ReceiveData(USART3);//读取接收到的数据
    ! V8 v; f0 ~7 x4 z1 e
  7. /*数据处理区*/- S" ~9 c6 `+ b. ^. |% R
  8. }    # z" G  D. X' _/ R! {
  9.      }
    9 C$ Q8 w# j* g' u
  10. }
复制代码

' w0 W% P! h5 g) B. S
而在HAL库中,进入串口中断后,直接由HAL库中断函数进行托管:
  1. void USART1_IRQHandler(void)               
    9 ^2 C) X  P$ X" d
  2. {
    6 f. |3 E& l4 R3 l
  3. HAL_UART_IRQHandler(&UART1_Handler);//调用HAL库中断处理公用函数
    # w7 \% P+ L- r8 C
  4. /***************省略无关代码****************/
    , L/ ?# d  L9 g) }% s
  5. }
复制代码
, u. ^( j- R# \0 F  x7 {6 N
HAL_UART_IRQHandler这个函数完成了判断是哪个中断(接收?发送?或者其他?),然后读出数据,保存至缓存区,顺便清除中断标志位等等操作。比如我提前设置了,串口每接收五个字节,我就要对这五个字节进行处理。在一开始我定义了一个串口接收缓存区:
  1. /*HAL库使用的串口接收缓冲,处理逻辑由HAL库控制,接收完这个数组就会调用HAL_UART_RxCpltCallback进行处理这个数组*/
    2 L7 G, D$ k  b' y2 f) k
  2. /*RXBUFFERSIZE=5*/
    7 A/ P! O$ @1 X$ p% u, W
  3. u8 aRxBuffer[RXBUFFERSIZE];
复制代码


1 |1 F  m, [8 T4 A: H

在初始化中,我在句柄里设置好了缓存区的地址,缓存大小(五个字节)
  1. /*该代码在HAL_UART_Receive_IT函数中,初始化时会引用*/
    3 v+ N: j6 t0 Z* |
  2. huart->pRxBuffPtr = pData;//aRxBuffer
    ( ~7 ]" b2 D" u) c# U) e
  3.     huart->RxXferSize = Size;//RXBUFFERSIZE
    7 ~  O  U! P! A# j% Z" P; r
  4.     huart->RxXferCount = Size;//RXBUFFERSIZE
复制代码
则在接收数据中,每接收完五个字节,HAL_UART_IRQHandler才会执行一次Callback函数:
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
复制代码

  y$ q4 l1 B0 O9 N- {
在这个Callback回调函数中,我们只需要对这接收到的五个字节(保存在aRxBuffer[]中)进行处理就好了,完全不用再去手动清除标志位等操作。所以说Callback函数是一个应用层代码的函数,我们在一开始只设置句柄里面的各个参数,然后就等着HAL库把自己安排好的代码送到手中就可以了~

; ^; w" l9 }2 j3 Q9 `( @
综上,就是HAL库的三个与标准库不同的地方之个人见解。个人觉得从这三个小点就可以看出HAL库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也更有逻辑性。但与此带来的是复杂的代码量,极慢的编译速度,略微低下的效率。看怎么取舍了。
: X" `3 {& u/ _6 X  ~4 u" K
04STM32 HAL库结构
0 m' C+ F6 m$ t2 @0 S. S$ u4 |


+ ~, Y8 J4 I0 g

说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!首先看一下,官方给出的HAL库的包含结构:
2 {1 u+ v6 _* U
640.png
$ }: w% N9 F: _; C; {( T3 X
· 4.1 stm32f4xx.h主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:
  1. #if defined(STM32F405xx). d# G0 Q/ b+ G6 k4 e& R3 w9 a
  2.   #include "stm32f405xx.h"5 r' ]5 [$ @+ N7 c- s
  3. #elif defined(STM32F415xx)4 s$ t8 p# ?4 a" s( t0 Q
  4.   #include "stm32f415xx.h": w4 g! D* |/ _8 H9 {2 p4 x, a2 [
  5. #elif defined(STM32F407xx)9 d' o8 w0 w( |# a* o
  6.   #include "stm32f407xx.h"
    " G+ l5 r' V1 G
  7. #elif defined(STM32F417xx). _2 j6 X2 z& b. j) J: f
  8.   #include "stm32f417xx.h"# n* V& A5 k8 {9 K: t4 v& U3 D
  9. #else, T& H, ]' C: |1 E5 V  S
  10. #error "Please select first the target STM32F4xx device used in your application (in stm32f2xx.h file)"% m- X! C: s& i/ `3 c$ F
  11. #endif
复制代码
5 E7 R: ?& p% t/ o# C" D; F: c; q
紧接着,其会包含stm32f4xx_hal.h。
· 4.2 stm32f4xx_hal.h:stm32f4xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置
5 s% k: @- ?  ?0 {; n# h
· 4.3 stm32f4xx_hal_conf.h:该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。

) s3 V$ v! Z9 h/ K) J
接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f4xx_hal开头,后面加上_外设或者模块名(如:stm32f4xx_hal_adc.c):
· 4.4 库文件
stm32f4xx_hal_ppp.c/.h // 主要的外设或者模块的驱动源文件,包含了该外设的通用API
stm32f4xx_hal_ppp_ex.c/.h // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API
stm32f4xx_hal.c/.h // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API

# ^% ~1 l! [- {( ?
· 4.5 其他库文件
用户级别文件:
stm32f4xx_hal_msp_template.c // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。
stm32f4xx_hal_conf_template.h // 用户级别的库配置文件模板。使用者复制到自己目录下使用
system_stm32f4xx.c // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。 它不在启动时配置系统时钟(与标准库相反)。 时钟的配置在用户文件中使用HAL API来完成。
startup_stm32f4xx.s // 芯片启动文件,主要包含堆栈定义,终端向量表等stm32f4xx_it.c/.h // 中断处理函数的相关实现

  |* @8 M0 l9 g: R+ u$ I
· 4.6 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 ().
% Z7 d3 H& s& Y' i6 f* M7 O
注意:目前LL库是和HAL库捆绑发布的,所以在HAL库源码中,还有一些名为 stm32f2xx_ll_ppp的源码文件,这些文件就是新增的LL库文件
; D# I6 t6 L7 T$ N- L# d5 ^- h
使用CubeMX生产项目时,可以选择LL库

' X4 Z6 v* p+ X# @8 T+ t
HAL库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:
· 处理外设句柄(实现用户功能)
· 处理MSP
· 处理各种回调函数

2 }7 z4 _6 y+ x
相关知识如下:
· (1) 外设句柄定义  用户代码的第一大部分:对于外设句柄的处理。HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。  
1. 多实例支持:每个外设/模块实例都有自己的句柄。因此,实例资源是独立的 
2. 外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。下面,以ADC为例
  1. /**
    8 b- s, c7 o, _, v
  2. * @brief  ADC handle Structure definition: n% b, v" a, A! i, Q: n' Q
  3. */   h+ z% k0 r( n; Q* t: t) e9 s
  4. typedef struct
    3 B8 \: v8 ~! z
  5. {4 M; r" s" N( _9 r. z% f( }. O' _
  6.   ADC_TypeDef                   *Instance;                   /*!< Register base address */3 {0 @% a( c' C/ o1 d: x4 z& \) e
  7.   ADC_InitTypeDef               Init;                        /*!< ADC required parameters */% V( f7 A$ t9 z# f3 k
  8.   __IO uint32_t                 NbrOfCurrentConversionRank;  /*!< ADC number of current conversion rank */
      t2 ~( X* M- Q# \/ T7 C# }
  9.   DMA_HandleTypeDef             *DMA_Handle;                 /*!< Pointer DMA Handler */
    3 P3 Z' h$ f* a4 V" X
  10.   HAL_LockTypeDef               Lock;                        /*!< ADC locking object */9 q) i9 K/ |, P) L9 n
  11.   __IO uint32_t                 State;                       /*!< ADC communication state */  `, E6 \0 R# w8 i$ V! B
  12.   __IO uint32_t                 ErrorCode;                   /*!< ADC Error code */$ H: B/ s( Q# S  R9 V
  13. }ADC_HandleTypeDef;
复制代码
' e/ x; ]8 o, h6 n7 I3 d9 R$ y
从上面的定义可以看出,ADC_HandleTypeDef中包含了ADC可能出现的所有定义,对于用户想要使用ADC只要定义一个ADC_HandleTypeDef的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。  当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。例如以下外设:- GPIO- SYSTICK- NVIC- RCC- FLASH以GPIO为例,对于HAL_GPIO_Init() 函数,其只需要GPIO 地址以及其初始化参数即可。
6 ?( ]) |% x. Z0 X1 g* N4 ?. {
· (2) 三种编程方式  HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):

- l0 n& ]4 j2 H. z
  1. HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
    : Z* p) q) m3 a: g4 {; v6 X2 R
  2. HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);+ z( l9 B* l4 i9 M  q4 c7 ]

  3. 6 Q. V  S) i3 ?% f6 e7 k8 H+ f
  4. HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);: B, [+ m" M5 _# g
  5. HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
    3 J) `" c( ~; t# R7 r

  6. 0 V  T# h( ?2 f0 I
  7. HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);. k3 [: P, g  Q
  8. HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
复制代码
& \; M8 ^# i3 i& ~0 K
其中,带_IT的表示工作在中断模式下;带_DMA的工作在DMA模式下(注意:DMA模式下也是开中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。  此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设主要由以下宏:
  1. __HAL_PPP_ENABLE_IT(HANDLE, INTERRUPT): 使能一个指定的外设中断
    9 M* d: x$ s) f6 G* j" ^
  2. __HAL_PPP_DISABLE_IT(HANDLE, INTERRUPT):失能一个指定的外设中断# H$ M6 C6 Y/ y% @) h* d
  3. __HAL_PPP_GET_IT (HANDLE, __ INTERRUPT __):获得一个指定的外设中断状态& @+ k2 |! ?' n' B
  4. __HAL_PPP_CLEAR_IT (HANDLE, __ INTERRUPT __):清除一个指定的外设的中断状态
    # Z  z7 o1 R/ j5 d* x/ _
  5. __HAL_PPP_GET_FLAG (HANDLE, FLAG):获取一个指定的外设的标志状态0 n* \# X, o8 _. c/ f3 S. m
  6. __HAL_PPP_CLEAR_FLAG (HANDLE, FLAG):清除一个指定的外设的标志状态7 ~& e) q* T! f1 b, N
  7. __HAL_PPP_ENABLE(HANDLE) :使能外设__HAL_PPP_DISABLE(HANDLE) :失能外设# f$ }4 o8 R9 M1 g( _. c! e
  8. __HAL_PPP_XXXX (HANDLE, PARAM) :指定外设的宏定义1 n1 q4 R# z% i3 k! {
  9. _HAL_PPP_GET IT_SOURCE (HANDLE, __ INTERRUPT __):检查中断源
复制代码
# P9 Y2 \. ?( k: F8 o! x  |0 m
· (3)三大回调函数  在HAL库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:
# A. ~1 Y% y) @, d' d# h3 R0 q/ ~3 n4 a
  1. __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
    ' ?+ c+ |+ P& y9 `/ t0 }  @
  2. {+ H3 |5 E8 R# F0 |
  3.   /*Configure the SysTick to have interrupt in 1ms time basis*/  L. }8 B& x9 `# f( D+ E/ `) R
  4.   HAL_SYSTICK_Config(SystemCoreClock/1000U);4 y7 v' Y3 D, I5 O$ [
  5.   /*Configure the SysTick IRQ priority */0 k  }! w5 ?2 N  c
  6.   HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
    4 N7 Q+ O7 x/ H9 B' t4 V% O5 W! R% n
  7.   /* Return function status */2 x) p" w( ]+ H+ {2 [
  8.   return HAL_OK;7 p% `& m" {: ?9 {% k
  9. }
复制代码
有些则没有被实现,例如:
  1. __weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)) g  f, n3 t0 E4 f. Y: a
  2. {
    / v3 g+ P/ q- c- E2 R
  3.   /* Prevent unused argument(s) compilation warning */. X6 h# Z* I& c& H
  4.   UNUSED(hspi);4 p! O2 U  i; W" G
  5.   /* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file7 l8 ?7 o( R0 [- y: p
  6.   */
    ' P( V9 U% T6 T& G( y- t4 a
  7. }
复制代码
4 H0 H: f- o3 D) ]8 c7 [
7 N# U% {% X# ?/ J* H$ ~; c
所有带有__weak关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。通常来说,HAL库负责整个处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。HAL库包含如下三种用户级别回调函数(PPP为外设名):

9 s- Q! c% H+ E! f4 X
1. 外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):HAL_PPP_MspInit()和 HAL_PPP_MspDeInit** 例如:__weak voidHAL_SPI_MspInit(SPI_HandleTypeDef *hspi)。在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)7 h" W/ _$ P- K- F+ X. h6 E
2.处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)。当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
7 C2 y( @' t/ G. \( U" K5 [
3.错误处理回调函数:HAL_PPP_ErrorCallback例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)**。当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
& Q4 w" H8 Q, i+ \4 `

$ g* r! V: J# E; @
绝大多数用户代码均在以上三大回调函数中实现
2 o4 C1 u. a# s( m8 s
HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。某些外设多次初始化时不调用返回会导致初始化失败。完成回调函数有多种,例如串口的完成回调函数有HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback等(用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题

) `8 y: r. v% n6 b- u. W
2 E; r' W0 f# C2 k( h
05HAL库移植使用/ Q0 m) C- L% S
基本步骤
, c$ u" a* P) |3 I( \- f1 p' m, G
1.复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。5 X5 O+ D4 \+ d. [
2.复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。2 O  F/ K& o! i# x5 C3 W9 M
3.在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void))1 S, `; {/ C* w' B8 i) c
4.HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中)" z* W0 k5 I* u* {) q* H
5.关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。
7 }8 r( |9 d' U8 y: Q2 k% h8 L0 p( O3 G
6.对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。例如:Uart中,HAL提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:

3 U/ g$ n# U( L  _7 y$ A
  1. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
    & z" ^: S: W$ w! X1 y2 c
  2. void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);8 l3 [! @: F0 D9 m
  3. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
    ) N8 K/ L4 p0 \* `; U/ Q0 g
  4. void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);9 [; I, H5 G* U. B
  5. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
复制代码

9 r9 M) l/ B' K- ~使用了哪种就用哪个回调函数即可!3 W0 F9 ]: L$ C& J/ ~* T) t% q/ s

5 a8 w- \1 @' B! d. t$ L

0 r5 H2 `9 P' V7 l" o基本结构" N% k* r$ I# H* Q% t

, ^& ^# L$ W, d6 x
3 O5 g- u7 Z* l. ^- Z
综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下:
: ^% C: S: r: o- i" n
1.配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;,接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart))
2 @9 ?5 P$ t8 A. A9 k. @/ T
2.编写Msp例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
+ x$ q* n9 q; I
3.实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数, Q. c$ O$ H5 o* `$ l
6 M9 ^7 A9 b$ x9 l2 x& _
收藏 评论0 发布时间:2022-9-17 17:12

举报

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