16.1 初学者重要提示
0 _: S. O' T8 [% t8 C0 }& _2 ? 对于一些常用的函数,大家一定要熟练的掌握都实现了什么功能,比如HAL_Init,HAL_RCC_OscConfig,HAL_RCC_ClockConfig等。最好的办法是把这些函数的源码读一遍。1 e1 i% [: Q$ D' U- V8 ]8 V
16.2 那些是必备的API
1 X5 C4 W$ k* ~+ ~- r6 B* ~& o这里我们通过一个简单的初始化流程来了解STM32H7的工程模板所必备的库文件和API:3 t$ a5 K2 N) R
2 c$ a$ N5 _! V V7 ~6 W0 y# t
第1步:系统上电复位,进入启动文件startup_stm32h743xx.s,在这个文件里面执行复位中断服务程序。5 J8 A) n0 E; B1 U% q; P* r
* X+ t- d1 v( K. v0 o/ q. M
在复位中断服务程序里面执行函数SystemInit,此函数在文件system_stm32h7xx.c里面。* n( S0 V, x" j3 z2 y+ G% c
之后是调用编译器封装好的函数,比如用于MDK的启动文件是调用__main,最终进入到main函数。
' c# t) u5 }. r# }0 w1 S! i 第2步:进入到main函数就可以开始用户应用程序编程了。在这个函数里面要做几个重要的初始化,依次是:: w5 l1 N# i0 d; y
* W. a- c+ E+ Q2 G, M% @( E5 D
MPU初始化,需要用到库文件stm32h7xx_hal_cortex.c和stm32h7xx_hal_cortex.h。
8 G/ @9 O3 d7 t$ e Cache初始化,需要用到core_cm7.h文件。
# h0 V- r G) q7 C9 V HAL库初始化函数HAL_Init,需要用到文件stm32h7xx_hal.c。. l9 {) [$ k/ @) t. P# L$ i
系统时钟初始化,需要用到库文件stm32h7xx_hal_rcc.c。
7 @" N; [% i) v. }: K4 b( L1 y( p* |0 i# I
9 i# g7 J- c4 v5 a; v' j3 t前面的两步完成后,就可以开始做用户需要的按键、串口等方面的初始化和应用代码的实现了。这里把我们需要学习的几个库文件整理出来,依次有:2 z0 n: l/ n* K% M$ S
startup_stm32h743xx.s5 z1 q' v8 a6 q2 q) ]+ ?' r
system_stm32h7xx.c
# Y7 |+ x7 o, x4 c stm32h7xx_hal.c
J0 [4 v4 J2 l! h! ~6 V5 A stm32h7xx_hal_cortex.c
: q; L+ Y& y% m7 S2 h! n* c, M. }& t stm32h7xx_hal_rcc.c8 G7 u% K) k5 C1 F6 O) o
core_cm7.h
$ t& Y6 ?9 f! f6 C其中startup_stm32h743xx.s和system_stm32h7xx.c已经在第13章为大家讲解过,这里不再赘述。而MPU和Cache涉及到的文件core_cm7.h在第23和24章为大家讲解。本章教程重点为大家讲解文件stm32h7xx_hal.c、stm32h7xx_hal_cortex.c和sm32h7xx_hal_rcc.c。
4 a) D3 j: T! e. z
1 k1 h* g' U+ z( A" t8 _6 U16.3 源文件stm32h7xx_hal.c(重要)9 m- \$ [ S: G$ b" X, S
这个文件比较杂,像基准电压大小配置,EXTI配置,IO补偿配置等都在这个文件里面设置。学习这个文件注意事项:
% L+ Y* q& Q& }" a' d8 ^/ G/ o. X0 P* F* f0 `8 ^% x1 y/ h2 \' c
HAL库中各个外设驱动里面的延迟实现是基于此文件提供的时间基准,而这个时间基准既可以使用滴答定时器实现也可以使用通用的定时器实现,默认情况下是用的滴答定时器。4 w$ H8 F+ S* ^# p& u% t
函数HAL_Init里面会调用时间基准初始化函数HAL_InitTick,而调用函数HAL_RCC_ClockConfig也会调用时间基准初始化函数HAL_InitTick。
# w2 H5 w+ n" D A! u 如果在中断服务程序里面调用延迟函数HAL_Delay要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行,从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。
! q% F- v1 L7 s) B, J0 f T* U V4 ^" U/ d1 S- h
16.3.1 函数HAL_Init
& j2 T$ T0 H3 T! }& T函数原型:$ `' v: J8 c2 m: X/ F8 O
* _0 I" K' s/ K) E( Y
- HAL_StatusTypeDef HAL_Init(void). ~" m: S) k8 e$ Z R1 M& c/ G
- {8 R3 J e9 f0 b. R+ x, ^' g9 A
- /* 设置中断优先级分组 */! g+ c+ R7 q% x+ t2 l
- HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); n& J9 y/ i9 s+ M
0 |* ?0 j( y' u( _5 X- /* 使用滴答定时器做为默认时基,配置为1ms滴答,另外系统上电后默认使用的HIS时钟 */* k& g2 y4 n, ]' Q/ L' W
- if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
* z3 w/ Q0 u- S& z8 a - {: @0 {% F, d4 y9 h" t
- return HAL_ERROR;8 _0 p$ S( M) G
- }$ k# o/ Y4 [' R4 \% L9 X
- " ^ m9 n9 |% V+ a2 g9 k
- /* 初始化底层硬件 */
! B/ I1 A2 K# j - HAL_MspInit();2 @; A, i2 l# ?- R1 ?$ G5 ^* T
8 R1 G' n$ |7 a \- /* 返回函数状态 */
( e& ~! L, _5 b- [$ \- C - return HAL_OK;9 i* ~9 a& u8 J% f( i, v' x
- }
复制代码
! W2 a# n7 @% \' T* f: Z0 Z% I4 _% L0 w, T6 o函数描述:
( O7 K3 y) x% N" M; m$ `( @* C b( l9 W- R1 v
此函数用于初始化HAL库,此函数主要实现如下功能:
- ?, E! e! n. }
9 N9 d7 O6 v& ?7 t 设置NVIC优先级分组是4。4 E2 t& G1 x" s# ~% M
设置滴答定时器的每1ms中断一次。6 R" E' `* S- b, p
HAL库不像之前的标准库,在系统启动函数SystemInit里面做了RCC初始化,HAL库是没有做的,所以进入到main函数后,系统还在用内部高速时钟HSI,对于H7来说,HSI主频是64MHz。. l6 ], Z( g5 ]; q3 @3 R+ Z
函数HAL_Init里面调用的HAL_MspInit一般在文件stm32h7xx_hal_msp.c里面做具体实现,主要用于底层初始化。当前此函数也在文件stm32h7xx_hal.c里面,只是做了弱定义。/ q, u. m5 Z! j4 T
: C; Z! `0 a3 U/ V7 T0 y
函数参数:! U' \/ \1 L! J1 W e) W6 J# S0 i! m
返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
0 w3 t: G. a& F! u3 y
7 p: Z4 @" \, W3 I注意事项:
, O8 P# ~) W3 ^必须在main函数里面优先调用此函数。7 `* D; c7 W% l2 v' h C
用户务必保证每1ms一次滴答中断。
; ~7 c5 e( _9 v) m& }& ^9 l' O关于优先级分组的设置可以看第21章节。: g9 e7 M# p* m' D: _/ j
. a5 [! c1 H$ i3 D* b+ x使用举例:- P2 ?% |, w1 c, L# T! i. e
此函数的使用比较简单,上电后优先调用即可。
) d: _8 d9 S$ R/ k& s5 ?5 A a; B, ^; N" e4 N* n2 R
16.3.2 函数HAL_DeInit& {8 o% ^/ N/ B. z' p8 v
函数原型:, i! n6 @- J: G; d1 |# U
8 _0 n' U' G6 E2 @- HAL_StatusTypeDef HAL_DeInit(void)8 `9 }4 _3 d- r7 H5 ^/ Z
- {7 J) r) @. @: L9 o
- /* 复位所有外设 */__set_PRIMASK3 l; I+ }% v7 }2 E, a D, q
- __HAL_RCC_AHB3_FORCE_RESET();, G; e4 K( K" |3 ~
- __HAL_RCC_AHB3_RELEASE_RESET();% [$ {/ n# ]+ a: M
* h# J* A2 L) a, `) n- D- /* 省略未写 */
7 i% I b4 R6 i# j1 ^% Y
# y( Y! f# F. }3 B7 X- __HAL_RCC_APB4_FORCE_RESET();
' u5 M" M, }; D3 { h - __HAL_RCC_APB4_RELEASE_RESET();
% c$ p" h6 r0 S) d5 T - * W7 K/ c+ v' u0 l" {2 {
- /* 复位底层硬件初始化 */' o) @6 z' A0 P3 _" y f- \, H( i7 s
- HAL_MspDeInit();; N `/ l. `: t2 ?- I! }
- 9 |- z/ |" Z7 O! W6 J( t. t, \
- /* 返回值 */# y/ ^& V- ]! t
- return HAL_OK;- |% i% n5 M: s- k/ l; a; @
- }
复制代码 6 C. m/ ^+ n1 M0 r r
函数描述:
! ~; g: ]4 k/ t; t* f) V
2 [- J: O3 I6 b" J3 y此函数用于复位HAL库和滴答时钟。
# I4 Q$ |+ y; Q7 i# V 复位了AHB1,2,3,4的时钟以及APB1L,APB1H,APB2,3,4的时钟。
+ O# I. X3 T+ c1 {% V/ `8 K" W 函数HAL_DeInit里面调用的HAL_MspDeInit一般在文件stm32h7xx_hal_msp.c里面做具体实现,主要用于底层初始化,跟函数HAL_Init里面调用的HAL_MspInit是一对。当前此函数也在文件stm32h7xx_hal.c里面,只是做了弱定义。
2 H0 B5 u) j j5 m' Q
6 x! d7 s, m/ S5 d& C& E* C4 ]! Q9 m' G/ F b9 a& [
使用举例:2 |9 {! G" F4 m, d9 [, x+ S2 _
此函数的使用比较简单,需要调用的时候直接调用即可。1 c' Q8 C. u; `& K/ ^
; T& f/ I8 }3 z
16.3.3 函数HAL_InitTick
$ ?5 X. g: `0 Y$ C' E, M; v- ~函数原型:
' O q& S) \2 J- ?. s
5 a6 W3 S- v* z. L* G- __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)6 Q. |8 [% e4 y$ l5 c0 k. Z6 g
- {
/ i" f2 `8 W# K! ^ - /* Configure the SysTick to have interrupt in 1ms time basis*/; {: b& d, h) F/ G0 |9 D5 x
- if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)( j+ F6 m9 @3 M$ e' P' z
- {1 n1 k. ?2 |( \5 m
- return HAL_ERROR;
* D+ Z6 i* ^3 @! S' U L - }6 Y$ S/ h/ U# U n
- t) m8 p: P- B* k5 p# P' h- /* Configure the SysTick IRQ priority */) U o$ `" c& b$ _/ x9 z% J& [2 d
- if (TickPriority < (1UL << __NVIC_PRIO_BITS)). e+ H9 T3 W# U1 z
- {" k n0 `5 z' Y C. k8 u
- HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);2 g- l# M) O' K. I
- uwTickPrio = TickPriority;
/ E# I# a" H1 R0 D! I; R% l - }% F/ c1 p* U. ]2 G; e8 F
- else
9 ]0 j( s s8 Y4 f# k% ]. I - {6 X. O6 q" d' v/ p6 D
- return HAL_ERROR;
9 o A9 h6 q& j; z8 _: ^ - }" Z+ J9 E6 W- |8 h6 y( |; @
2 P& w6 E+ g$ V2 Y% B- /* Return function status */4 y f! U6 y; J' W
- return HAL_OK;
5 I, H% g" H3 S" `0 C$ D* ~ - }
复制代码 7 [2 g. s: [9 n! ]
函数描述:
6 n" Q9 R. l% V' p. n' _此函数用于初始化滴答时钟,此函数相关问题如下:* K* U. M& n& V0 q; F" B
此函数有个前缀__weak ,表示弱定义,用户可以重定义。
: V, }7 N/ _# P0 y% b/ J0 r 此函数用于初始化滴答时钟1ms中断一次,并且为滴答中断配置一个用户指定的优先级。
, O$ k' c: D- E0 b& t7 K( T 此函数由HAL_Init调用,或者任何其它地方调用函数HAL_RCC_ClockConfig配置RCC的时候也会调用HAL_InitTick。+ u/ O3 \- L' H7 A" I3 m
调用基于此函数实现的HAL_Delay要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行,从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。7 {4 c& L5 L6 ]: E, d; c
% ^9 P( p- L* x! e+ p
* ]$ K' \. z2 g0 L4 q函数参数:
8 N4 ~1 d5 l. z% T2 H 形参TickPriority用于设置滴答定时器优先级。; p% N' O' _9 g s* A5 U2 j
返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。! h$ a" L+ U2 Z2 e4 q L' n
0 _8 a. S% K9 P5 O. o* j. D
& Y4 f: o" i' W% p% @6 R$ O使用举例:
[3 x1 C7 e' n6 H4 x此函数由HAL_Init调用,无需用户操作,除非需要重定义。' x; D, r* m: g' F' T6 s' \9 l% R$ D
, t1 ~, \8 F: ?0 D: j
16.3.4 Systick的相关函数
$ J1 t1 G: S" q" R' |调用了函数HAL_Init后,Systick相关的函数就可以使用了。这些函数如下:, I* t) O' V6 q. i6 T$ i
3 H" F; }3 @) e- P( j6 q函数原型:+ S8 S# j+ A0 @8 x4 V
5 i9 X4 o$ a! d4 z% u
- __weak void HAL_IncTick(void)
- P# R! g- N+ d( t$ z$ R$ O - __weak uint32_t HAL_GetTick(void)( \6 z4 f0 @9 ]) r
- uint32_t HAL_GetTickPrio(void)
0 L+ |" Z1 z( O" V; q8 p- N - HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq)) N$ `: B, s. Y1 Y1 ^4 i
- HAL_TickFreqTypeDef HAL_GetTickFreq(void)
8 h' t/ x9 E- W2 ?: y - __weak void HAL_Delay(uint32_t Delay)
# P9 q# D0 O) i+ d) n1 w - __weak void HAL_SuspendTick(void)2 t p3 X$ h+ s
- __weak void HAL_ResumeTick(void)
复制代码
" S8 A, K$ f/ A; t( Y% a, J$ e8 i函数描述:
3 `5 d+ z6 @+ X$ K这些函数就比较简单了,下面把这些函数实现的功能做个简单的说明:, r. f4 Z- U5 D* T& t
函数HAL_IncTick在滴答定时器中断里面被调用,实现一个简单的计数功能,因为一般滴答定时器中断都是配置的1ms,所以计数全局变量uwTick每毫秒加1。
7 H; u6 c# i8 L I 函数HAL_GetTick用于获取全局变量uwTick当前的计数。
4 M" i% G. X G$ p 函数HAL_GetTickPrio用于获取滴答时钟优先级。
. D5 p% W. m. {9 | 函数HAL_SetTickFreq和HAL_GetTickFreq是一对,前者用于设置滴答中断频率,后再用于获取滴答中断频率。- Y& [5 y/ d, a1 [2 Y+ L
函数HAL_Delay用于阻塞式延迟,默认单位是ms。% O$ v. y) ^/ C
函数HAL_SuspendTick和HAL_ResumeTick是一对,前者用于挂起滴答定时器,后者用于恢复。' H0 z/ m8 Z, J m( S6 Z. V
! J: ]. N2 |4 D1 i; c5 U8 W9 W( j
注意事项:
+ C4 Y+ f, p# E1 ~- T7 s9 i! K 函数有个前缀__weak ,表示弱定义,用户可以重定义。
1 F* I5 X `- ]3 {' {, n+ s
0 V4 F* }) B, d5 G7 `6 j4 z, i- Y: d; }3 D, S
使用举例:2 U/ A( z1 M( d$ D4 v' W: @
这些函数都比较简单,这里就不举例了。需要的时候,直接调用即可。
3 j! i5 f: I7 l8 l# b s
4 i+ n& ]/ w+ }4 C! [4 v5 @16.3.5 函数HAL_SYSCFG_VREFBUF_VoltageScalingConfig
, p# w% O; I' R1 m0 V函数原型:
/ i0 y* S' z3 S0 A l+ l, U; F$ V
$ i5 l( H( P% P. A, N1 ]+ k& N- void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling)
复制代码 & I" F2 y! u" r- V/ a
函数描述:
- Z5 z7 F9 D) i/ Y- D f8 n' {此函数用于配置STM32H7内部电压基准大小。
6 A" u0 x$ J2 h2 S. ~7 z3 G
) o7 ~% Y9 }( k7 s 当形参VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE0时
( h, @: O% d1 z( \ 输出基准是2.048 V,条件是VDDA >= 2.4V。6 ^' ?# p9 b6 H; [( F, u
+ [- q+ k7 O. F! F5 V
当形参VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE1时
. t; ^0 P9 S( O, u v7 I输出基准是2.5 V,条件是VDDA >= 2.8V。# p" e; J4 K% I' {1 }/ D* U
4 t. E6 h+ i+ J7 k/ P
当形参VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE2时9 T6 w! w% h. F5 P! w+ [0 V7 {
输出基准是1.5 V,条件是VDDA >= 1.8V。
# `& S2 K0 m8 {2 w% [" P" n9 O
) J I1 h9 { q; N+ }2 D 当形参VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE3时6 T% e5 J9 X \
输出基准是1.8 V,条件是VDDA >= 2.1V。7 j" m, |9 A2 {$ Z
5 s0 H- ?% ^/ X% `) z
16.3.6 H7自带电压基准相关函数
7 W+ f. \" i7 v: T) v! u/ F函数原型:
* I8 p6 H* P& E- void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling)( {" C0 h( Q) j! K3 c: }- o/ F
- void HAL_SYSCFG_VREFBUF_TrimmingConfig(uint32_t TrimmingValue)
, u8 z* \( I- D7 g) B0 v - HAL_StatusTypeDef HAL_SYSCFG_EnableVREFBUF(void)" z1 R' g. T* a) [ G2 l
- void HAL_SYSCFG_DisableVREFBUF(void)
复制代码 % E: R4 O$ A$ J# C/ m- |
函数描述:- C. L) _% [ _0 D+ f8 u) i8 [
5 {5 i+ b3 E) E; h, `% }7 v
函数HAL_SYSCFG_VREFBUF_VoltageScalingConfig7 |5 z) a, q* r0 j: C: [; E
此函数用于配置STM32H7内部电压基准是否在芯片内部与VREF+引脚接通。
" U6 g. h) z% D- P* t
+ _! L* W9 C% [4 K 形参为SYSCFG_VREFBUF_HIGH_IMPEDANCE_DISABLE时,表示导通。7 D' p( j8 O, G) E
形参为SYSCFG_VREFBUF_HIGH_IMPEDANCE_ENABLE时,表示高阻,即不导通。( L" U6 }6 ^( o+ t8 i! F
函数HAL_SYSCFG_VREFBUF_TrimmingConfig
4 S6 u* z. e$ c- U. |此函数用于内部电压基准的校准调节。& S T; j/ Y; \: `( w
8 c+ |* y0 b F) B 函数HAL_SYSCFG_EnableVREFBUF和HAL_SYSCFG_DisableVREFBUF是一对,分别用于内部电压参考基准的禁止和使能。
; Y$ i6 c* }! r: i) L9 g* t* o. Z+ z& O Y! v
16.3.7 函数HAL_SYSCFG_AnalogSwitchConfig. E6 W) Z5 c4 Y1 O; n+ v
函数原型:) `6 }0 X% }, T E
- void HAL_SYSCFG_AnalogSwitchConfig(uint32_t SYSCFG_AnalogSwitch , uint32_t SYSCFG_SwitchState )
复制代码 3 R$ R o" y6 o, ?8 X
函数描述:
7 J; _$ z y Z5 P" Z 引脚PA0,PA1,PC2,PC3用于ADC时,还有一组对应的可选引脚PA0_C,PA1_C,PC2_C和PC3_C。此函数的作用就是切换可选引脚。关于这个问题的详情可看此贴:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87707。
! ^2 o* K6 x; V V16.3.8 BOOST的使能和禁止(用于ADC)0 a/ D7 T1 c0 T0 C
3 q* x" V9 }8 {
2 t* \8 }5 f; y+ v( Q函数原型:3 n- r5 l: X1 `5 [
- void HAL_SYSCFG_EnableBOOST(void)3 }9 S/ h1 \2 ^7 V* e& K
- void HAL_SYSCFG_DisableBOOST(void)
复制代码
2 E$ L- C: E2 y' b# W2 G7 x函数描述:
- F0 {' B; g4 g' q4 B" m1 j' J* X 这两个函数用于使能或者禁止Booster。如果使能了booster的话,在供电电压低于2.7V时,可以减少模拟开关总的谐波失真,这样的话,模拟开关的性能跟正常供电电压时的全范围测量一样。
0 C* K+ S9 `; w2 k2 p1 s; G' U
' Q. Q; F/ W5 W# |0 X8 V7 A8 U% x
16.3.9 函数HAL_SYSCFG_CM7BootAddConfig
- j) d2 r0 p; `1 S! ^% B, @函数原型:5 H9 p$ Q) I7 \& e2 J6 n0 y
- void HAL_SYSCFG_CM7BootAddConfig(uint32_t BootRegister, uint32_t BootAddress)
复制代码 / P e6 p" E4 a1 v1 i9 K/ A4 o
函数描述:/ n0 B: n! X2 h5 ^3 P. m( ]1 h
, y' i4 ~8 F' Y" Y; a5 P用于配置BOOT = 0或者BOOT = 1时的启动地址,详情可以看第13章的13.4小节。
) _7 b! K! N8 e" _' ?( _+ I
. }- n# E* P9 |4 ?# ~4 u+ J16.3.10 IO补偿相关函数
* B+ D- q- M, D- \% ]5 s函数原型:
6 G, ~& ~. h; b `, C
" Q4 L: r; D& T6 N1 a! |% z$ o- void HAL_EnableCompensationCell(void)- s/ e |( W! l+ H
- void HAL_DisableCompensationCell(void)
$ ]9 ]7 E# k l1 ~. p$ i - void HAL_SYSCFG_EnableIOSpeedOptimize(void); A! B1 ^" Z @' A
- void HAL_SYSCFG_DisableIOSpeedOptimize(void)! k& Y! }! Z4 ^6 I
- void HAL_SYSCFG_CompensationCodeSelect(uint32_t SYSCFG_CompCode)
2 s( P+ l+ x- M* Z - void HAL_SYSCFG_CompensationCodeConfig(uint32_t SYSCFG_PMOSCode, uint32_t SYSCFG_NMOSCode )
复制代码 ! i& c+ T; g" J6 {# T9 B* n) C
函数描述:8 `6 [1 T" J+ _$ a: l0 Q
: ~7 D5 ^% m( A. A0 E4 m8 T2 r
函数HAL_EnableCompensationCell和HAL_DisableCompensationCell
' I4 t& V, d' H9 L5 i分别用于使能或者禁止IO补偿,只有在供电电压范围是2.4V到3.6V之间时,使用此功能才有意义。
- p# I+ R$ k0 l- h2 S& {
% J' M6 h7 X9 g 函数HAL_SYSCFG_EnableIOSpeedOptimize和HAL_SYSCFG_DisableIOSpeedOptimize
& ~9 s6 P3 v5 v0 B7 a" i分别用于优化IO速度或者禁止优化,不过仅在供电电压低于2.5V时可用,高于2.5V是不可以使用的,另外使用这个功能的前提是用户使能了PRODUCT_BELOW_25V(是可选字节配置选项里面的一个bit)。
/ w' V M$ J% e. h7 @" ]4 }5 w0 d; A' V3 }: g; [& f9 l, ~
函数HAL_SYSCFG_CompensationCodeSelect
b+ K* p- W% Y' vIO补偿单元的选择,函数形参可以是SYSCFG_CELL_CODE,即寄存器SYSCFG_CCVR,也可以是SYSCFG_REGISTER_CODE,即寄存器SYSCFG_CCCR。; i; ~5 M* ]- J, F) r) M3 m9 |& b
& l; K; z3 q K* l 函数HAL_SYSCFG_CompensationCodeConfig
) Y/ Z7 k! P4 j4 w- n* T$ R' a用于设置GPIO内部构造中NMOS和PMOS的补偿值,两个形参的范围都是0-15。根据用户调用函数HAL_SYSCFG_CompensationCodeSelect选择的寄存器,这里仅有一个形参的设置是有效的。
% K4 W" N. N6 V( C6 V3 h: r a7 a# _5 x$ d2 H
16.3.11 低功耗和EXTI相关函数 t8 s& y; \2 y( S# v3 U
低功耗和EXTI相关的函数暂时不做讲解,后面章节用到时候再说明。* m/ d7 H0 j" B- i
4 f1 j% a% e7 k% K" u( N5 B; I16.4 源文件stm32h7xx_hal_rcc.c
8 X4 n9 ~# {, p3 a+ @: z; N, ~ b' f这个文件主要是实现内部和外部时钟(HSE、HSI、LSE、CSI、LSI、HSI48、PLL、CSS、MCO)以及总线时钟(SYSCLK、AHB3、 AHB1、AHB2、AHB4、APB3、APB1L、APB1H、APB2、 APB4)的配置。. g- v, _, c, w: Z
: y$ U* {. f% [$ E$ q5 z6 |7 m; M学习这个文件注意事项:
5 r6 h$ M# t% X: e* y$ j. Y6 ?. m! }! C# m" {+ u# n$ r1 b, v
系统上电复位后,通过内部高速时钟HSI运行(主频64MHz),Flash工作在0等待周期,所有外设除了SRAM、Flash、JTAG 和 PWR,时钟都是关闭的。这里特别注意SRAM,官方手册里面说的太笼统,这个SRAM到底是指的哪个SRAM。
% [6 V- R# H. q5 z) S7 p& G2 b4 B
/ ?2 j( f9 T0 J. hAHB和APB总线无分频,所有挂载这两类总线上的外设都是以HSI频率运行。
% S: L. i% R0 V \ u$ l8 o5 V 所有的GPIO都是模拟模式,除了JTAG相关的几个引脚。
. V$ [+ m0 r: b) a1 I5 ^; k2 | 系统上电复位后,用户需要完成以下工作:
7 l- g7 ?- z% { I( |" `# u& g" H! } 选择用于驱动系统时钟的时钟源。
& O! X1 o' R- r) g# m& G 配置系统时钟频率和Flash设置。
( f& F$ I. X5 Q, r$ w 配置分频器。
/ |) f8 F; O4 d9 [% \6 M 使能外设时钟。& z3 y/ N( A. X! M7 K
配置外设时钟源,部分外设的时钟可以不来自系统时钟,此时通过配置寄存器RCC_D1CCIPR、RCC_D2CCIP1R、RCC_D2CCIP2R 和 RCC_D3CCIPR 实现。
5 m! y+ w3 j: E! i# h" `. r3 t& x! ] O$ P' \6 r
0 R3 M# V' t, F7 l% [( ERCC局限性:! I2 Q! g8 [, X& Y( o
- c4 o+ z! J2 o使能了外设时钟后,不能立即操作对应的寄存器,要加延迟。不同外设延迟不同:) w( f Z1 W% D
0 }1 B h5 ?, }! k/ z# s' | 如果是AHB的外设,使能了时钟后,需要等待2个AHB时钟周期才可以操作这个外设的寄存器。
! R& ~( W. m+ ]7 \; F 如果是APB的外设,使能了时钟后,需要等待2个APB时钟周期才可以操作这个外设的寄存器。
v$ ?/ q8 _( z$ R- R1 s0 {% {1 x+ q4 z6 E
当前HAL库的解决方案是在使能了外设时钟后,再搞一个读操作,算是当做延迟用。
* x, s- t _$ E& M o- e7 L0 N9 G* o( @# }
比如下面使能GPIOA的时钟:
) G, j9 {( K/ s4 U/ ]0 G7 l
5 i; T6 m) K$ Z! K+ v) P- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \- n9 U7 f5 |0 A8 @4 u
- __IO uint32_t tmpreg; \
3 e' ~- `- w/ s. n9 ~2 p7 b - SET_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\
2 R# W& m0 Z. p2 V6 z$ F - /* Delay after an RCC peripheral clock enabling */ \; J' h" R2 y2 U8 E% f! x2 J6 @
- tmpreg = READ_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\" s8 N$ |+ v$ `% f8 W. @
- UNUSED(tmpreg); \
9 f/ t, `! o2 N( |1 R - } while(0)
复制代码
6 T4 P! ?0 x5 w4 _ 关于时钟方面的知识点补充: t$ h+ [* {7 t) _9 ?
1、正确理解HCLK1,2,3,4以及PCLK1,2,3,4对应哪些总线的时钟,下面一张图可以说明问题:
3 ~% n/ Y+ w! k9 I4 f8 o" x( l
8 F; j% }. W/ H! ~7 T/ O; h
# H4 k8 ~& J) \" e$ x8 u3 K" l
" M! v8 E1 T0 W由上图可以得出:
" x ?6 [: H8 t. ^. C+ u; ^0 E& K5 v- \: G0 I4 ^1 X7 e
HCLK1,2,3,4对应的是AHB总线AHB1,AHB2,AHB3和AHB4时钟。
5 \; W- W1 X# ^
( Z0 b, x$ O; jPCLK1、2、3、4对应的是APB总线APB1,APB2,APB3和APB4时钟。3 G# s# m0 J. r
2 B1 E6 ^: v; |1 p, Q" S- [" M
2、内部和外部时钟配置:( l$ o" k5 q( T: K
" g, P0 O6 O3 |
HSI (high-speed internal)
& _5 j5 O1 a9 s- C* ]$ R$ a高速内部RC振荡器,可以直接或者通过PLL倍频后做系统时钟源。缺点是精度差些,即使经过校准。9 p* z0 Z+ }( B. z. q
2 ]% W; L: _0 u3 A
CSI (Low-power internal oscillator)7 D6 U2 Q) \! M m. L
低功耗内部RC振荡器,作用同HSI,不过主要用于低功耗。( w! j$ k, ~4 ]/ g) A& Q
4 {3 E. [+ Z' N! e
LSI (low-speed internal)
5 d: a1 i( O. x2 K7 ~7 W" q3 V% C2 P( e低速内部时钟,主要用于独立看门狗和RTC的时钟源。
3 ~1 Z. s# G% B* A' S( m% ]3 B! |2 V$ v2 m& Y
HSE (high-speed external)
3 d- n! H4 A7 M `! a+ m" G% V$ S高速外部晶振,可接4 - 48MHz的晶振,可以直接或者通过PLL倍频后做系统时钟源,也可以做RTC的是时钟源。
% B$ b8 J/ r7 v9 O8 e/ U; N
5 r8 T/ J' R( u& L1 E LSE (low-speed external)% _: p/ y9 d. @" F
低速外部晶振,主要用于RTC。
( [$ c0 m# _* ~2 e" ]/ u1 C% Q: {* l
CSS (Clock security system)
: P: D3 Y( x+ t时钟安全系统,一旦使能后,如果HSE启动失败(不管是直接作为系统时钟源还是通过PLL输出后做系统时钟源),系统时钟将切换到HSI。如果使能了中断的话,将进入不可屏蔽中断NMI。- i& N- |7 l* H
: d% [* w0 V' q% ^9 y, ]! L MCO1 (micro controller clock output)
2 X+ i+ e4 b) M: m3 E; U可以在PA8引脚输出HSI, LSE, HSE, PLL1(PLL1_Q)或HSI48 时钟。
" K5 d( @8 @& M7 Q X. }! ]" A0 C& m7 I, p
MCO2 (micro controller clock output)
, n" _6 C& j, K% C可以在PC9引脚输出HSE, PLL2(PLL2_P), SYSCLK, LSI, CSI或PLL1(PLL1_P) 时钟。
. _) i! j9 g* X1 A0 V5 Q3 g- C _
PLL锁相环,时钟输入来自HSI , HSE 或者CSI
1 T% d r% z2 Y主锁相环PLL1,用于给CPU和部分外设提供时钟。
! o' p0 Y4 N; X& e+ l
5 e! |. R! v0 A8 ^+ c; c0 G- P专用锁相环PLL2和PLL3,用于给部分外设提供时钟。1 F' ?$ T1 v% I5 e% ^% E I
6 N: ]# l ~& c( B9 r% {
( q6 Q+ u! l ^2 `
4 _( N$ B' E. H6 V) m3、System, AHB 和 APB总线时钟配置:( S' S9 D% h5 W# N7 m- s e
+ @' f! \) B& O0 m2 M j
系统总线时钟源可以来自CSI,HIS,HSE 或 PLL。
; c+ D& g8 m" W3 U6 z3 i AHB总线时钟是通过系统时钟分频产生的,用于给AXI,AHB1,2,3,4和APB1,2,3,4九个总线上的外设提供时钟。另外用户可以通过函数HAL_RCC_GetSysClockFreq获取系统时钟。9 y8 P( E- O; ?6 d% o
所有外设时钟都可以通过系统时钟获取,并且部分外设还支持多个时钟源,比如下面串口1,6) P }' s, m8 n# ~
+ \/ h8 |6 D. ^5 ]7 L
: H* [2 P9 h9 ]3 S( Y8 m5 u( a3 N, o" n4 C1 Z" C
16.4.1 函数HAL_RCC_DeInit
0 x9 f0 y; \2 x- x: @0 j函数原型:& L) B) J, m5 e) T: o% o/ T0 B
; ?6 }. \- Y& C6 J
- void HAL_RCC_DeInit(void)
: M+ B" z1 R7 \2 J - {
" U3 B4 n2 [. [5 g- S( k - /* Set HSION bit */
4 f- O& K1 J9 V* v, `$ a - SET_BIT(RCC->CR, RCC_CR_HSION);
+ i- {: ?( L8 N3 j
. l! ?9 z7 T# Z' \( F( N- /* Reset CFGR register */
+ z. u% F5 `( j4 P# X) }5 ` - CLEAR_REG(RCC->CFGR);
2 z- q2 x! I8 {8 u. a
* z6 Y4 u S6 O c0 }- /* 省略未写 */
$ p5 r# ~, \, W; \; P
* F- g3 \" U2 r2 x! b6 P3 @- /* Reset HSEBYP bit */
* E+ U" D) Q5 B% V) @, N- w - CLEAR_BIT(RCC->CR, RCC_CR_HSEBYP);
6 y6 B; g7 q7 h! e3 \8 A$ }& F B) q
7 v# g% y& N6 y4 p) w1 |; U- /* Disable all interrupts */: O6 K$ _' V" ?0 b8 I. S$ E
- CLEAR_REG(RCC->CICR);
( u8 L* F% u/ Y I( L+ B, B - }
复制代码 f# m! E: B6 J' @* W7 F
函数描述:( u# ]7 ^$ c" V3 i
+ d7 l U) l0 `* p$ a2 @ D6 X
此函数用于RCC复位函数,主要实现如下功能:" T9 O* n/ [0 `. O9 P# Z7 h
HSI 打开作为系统时钟。
5 }. v" e1 n) _; o& k HSE, PLL1, PLL2 和 PLL3 关闭。; d* W; X) d5 t7 v8 ]* ^
AHB, APB 总线无分频 。( d- u3 F5 }8 o3 b7 o
CSS, MCO1 和 MCO2 关闭。
; x) R% r8 `9 t) @ 所有中断关闭。7 j1 p1 s% D: z
% ^+ Q4 U+ n- n& U+ i3 F d6 C7 _. t _
% _/ H7 O6 K2 g注意事项:9 r# ]5 W3 h0 ~5 Z+ i7 \
此函数不会修改外设时钟,LSI、LSE和RTC时钟。) {3 K! ^! p0 i- h: D4 z+ }& `2 E- R
# T# ~- n$ \4 b2 m: m' A: A0 n7 T8 \+ `7 |5 m
使用举例:
$ _5 ]- F) q: G# t5 z此函数的使用比较简单,需要调用的时候直接调用即可。
+ B5 y& U6 x, H2 [- B
% ?/ C% y; n8 e) i0 V7 s7 V2 x) L2 B16.4.2 函数HAL_RCC_OscConfig; K& b2 i# l# |0 Q2 W& X
函数原型:
1 `. m0 J9 }' c( }4 D/ X& F" w
6 `$ B6 m& a0 ^0 l$ J, y. U0 u- o: R$ q1 S, }* V: Z0 s
: Q9 I- F4 w) i函数描述:
9 u0 N4 P& z. @$ E* {
' e0 c( N) b/ F0 ?通过上面函数原型,我们可以一目了然的看出此函数的作用,配置了HSE、HSI、CSI、LSI、HSI48、LSE和PLL。
5 R3 t" m4 h, q; B7 V Z* P9 f+ B( K3 k
函数参数:
+ p) \# L8 E0 y& U2 G
L7 S4 R6 p# a) d 函数的形参是RCC_OscInitTypeDef类型结构体变量,这个结构体的定义如下(主要是HSE、HSI、CSI、LSI、HSI48、LSE和PLL的配置变量):
/ W2 a# Y. v+ P- l" }7 l, i- /**
# e% A+ @* D5 f; v" |! ` t9 h1 L+ Q - * @brief RCC Internal/External Oscillator (HSE, HSI, CSI, LSE and LSI) configuration structure definition: N4 U/ F6 g( j; }* j* i
- */
" }0 V4 Q+ t( @: p - typedef struct* \! M% |2 D; j; H9 Q5 s
- {
" A' l' ^8 z \6 h5 K/ H( i6 X) X0 V - uint32_t OscillatorType; 8 |0 m: C- o) Y4 a8 |
- uint32_t HSEState;
0 ]1 U0 Q/ S( l) g; a2 y - uint32_t LSEState; m p; h" G9 A1 P9 q3 \' P) ?! s6 g
- uint32_t HSIState; : m% N0 b+ F# }! c0 y! Y# l g" K
- uint32_t HSICalibrationValue; 8 h6 M8 P, M7 Q. @
- uint32_t LSIState; . c# b5 b# s- \
- uint32_t HSI48State;
$ E( R- {1 j1 _& ?8 g - uint32_t CSIState;
* i: W v% B$ `! K: ^; | - uint32_t CSICalibrationValue; # M$ _# A" {3 k1 y9 C
- RCC_PLLInitTypeDef PLL; 8 |3 k* G3 V+ j. K, Q; B
- }RCC_OscInitTypeDef;
复制代码 ; X' `* P! g& s
注意事项:9 y: H/ _' v! w8 q
! U& _( l9 K) ]- i
LSE Bypass 切换到 LSE On 或者 LSE On切换到 LSE Bypass都不支持,用需要先关闭LSE,然后才可以切换到LSE Bypass 或者LSE On。
2 _* U5 H' [6 ?% YHSE Bypass 切换到 HSE On 或者 LSE On切换到 LSE Bypass都不支持,用需要先关闭HSE,然后才可以切换到HSE Bypass 或者HSE On。
1 Q3 c& i9 C& w9 {3 l5 [4 F使用举例:
. D- q6 F+ s8 [( y
. ~5 S2 F, q6 q% [( b; h3 G- RCC_OscInitTypeDef RCC_OscInitStruct;
. G0 \8 n4 S9 i* i - HAL_StatusTypeDef ret = HAL_OK;% K: ]9 u1 ?2 n4 z7 l- \/ [
- # r6 l3 D) Q) d" k
- /* 使能HSE,并选择HSE作为PLL时钟源 */
' Q/ a3 L/ p* v - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;3 r* y# ~7 ?- G! {7 @
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;9 B1 p# l, U- y: ^2 U) l
- RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
0 L+ S1 A, v& G6 X$ K, A - RCC_OscInitStruct.CSIState = RCC_CSI_OFF;$ P0 O. U7 x& E( j1 A. L9 U- d6 M
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;3 J8 l; g8 t) L: B) _% p' l7 j9 B/ `
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
8 P9 t5 X" f n9 e8 F* n -
2 A7 ~$ c; E# n! K# G7 b - RCC_OscInitStruct.PLL.PLLM = 5;. n% y8 s' h% e6 o2 v
- RCC_OscInitStruct.PLL.PLLN = 160;5 b- t8 e3 I7 e% P; M
- RCC_OscInitStruct.PLL.PLLP = 2;
4 T# z% r! K! r8 J9 r - RCC_OscInitStruct.PLL.PLLR = 2;* @( o# Q3 X/ l7 k% X+ \
- RCC_OscInitStruct.PLL.PLLQ = 4;
' J, H7 K4 d3 i0 E" ~' c, }; u - 9 n/ g- K5 R3 L* ?# U
- RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;3 g) k4 C5 Q" s$ A( i. R8 _9 g2 ]
- RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
. i4 D0 h6 H8 ]2 i0 o) J - ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);$ U! e" ^6 p- R4 S n
- if(ret != HAL_OK)
% S* b2 X f4 n1 Q - {5 c9 P+ b2 ~6 n% X1 Z
- Error_Handler(__FILE__, __LINE__);* f2 T& t1 q4 I3 J6 g5 Y6 r$ X
- }
复制代码 & ?6 @1 n' q! e$ n" R
16.4.3 函数HAL_RCC_ClockConfig
5 o6 p5 K$ y" G" w' G函数原型:
9 b5 ]$ e# h9 B0 {5 o+ U, Z! j: @) y9 b( W1 y/ i7 K9 y
5 `( @* U. I* p& o+ R. {3 A# V' L8 A. |$ T' O" X: t
函数描述:1 }. n% ^9 s, N
通过上面函数原型,我们可以一目了然的看出此函数的作用,配置了CPU、AHB和APB的所有总线时钟,即HCLK,SYSTICK,D1PCLK1,PCLK1,PCLK2和D3PCLK1时钟。这里的D1PCLK1其实就是PCLK3,而D3PCLK1是PCLK4。
2 L* ?' J+ ~$ ]* `# T' q3 U( i# w, b5 y1 d U
函数参数:. b9 Q! e' g# r; r8 R" g% t0 |' [
第1个参数是RCC_ClkInitTypeDef类型的结构体变量。
3 O/ x5 F& n9 Y, J: d 第2个参数是Flash的延迟设置。
1 \2 @0 v2 G ?5 B/ c7 I. R 返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。! z3 M8 Z& p6 P0 p
7 { ?+ C& i T. z0 r, ], g( `8 _
7 S: X4 C0 F/ {- {& g* g5 M注意事项:; G0 b$ y- q* l
此函数会更新全局变量SystemCoreClock的主频值,并且会再次调用函数HAL_InitTick更新系统滴答时钟,这点要特别注意。
& d! i) @5 G! g系统上电复位或者从停机、待机模式唤醒后,使用的是HSI作为系统时钟。以防使用HSE直接或者通过PLL输出后做系统时钟时失败(如果使能了CSS)。8 X4 x8 I M( \5 O
目标时钟就绪后才可以从当前的时钟源往这个目标时钟源切换,如果目标时钟源没有就绪,就会等待直到时钟源就绪才可以切换。( s! Y9 @2 q3 E/ M# V/ N7 ?
根据设备的供电范围,必须正确设置D1CPRE[3:0]位的范围,防止超过允许的最大频率。
! s; D* L1 q- x) g3 X9 V4 r4 S0 l) n
7 p H1 E' A8 m6 n8 b使用举例:, S* }" K2 \0 `- `6 j
4 m7 N6 q1 b/ L* f$ B- RCC_ClkInitTypeDef RCC_ClkInitStruct;
# k- ]2 _" q1 w! b/ v7 \ - HAL_StatusTypeDef ret = HAL_OK;
" w' m) n8 U; M8 n n9 a - ( `% q1 Q- z* L, u+ F e! r
- /* 2 j6 Z* K* Y1 k2 O( W `6 e: @* P
- 选择PLL的输出作为系统时钟
/ {) M8 e! O* T - 配置RCC_CLOCKTYPE_SYSCLK系统时钟2 \/ G. G& U+ f) Z
- 配置RCC_CLOCKTYPE_HCLK 时钟,对应AHB1,AHB2,AHB3和AHB4总线
. r' e, q" g; }$ S. T - 配置RCC_CLOCKTYPE_PCLK1时钟,对应APB1总线
3 L% @1 ]! S& p% S9 s - 配置RCC_CLOCKTYPE_PCLK2时钟,对应APB2总线: t* Q% g& U0 L- h/ P' `% t+ U1 j; x
- 配置RCC_CLOCKTYPE_D1PCLK1时钟,对应APB3总线
7 a8 C- b7 S6 m* T - 配置RCC_CLOCKTYPE_D3PCLK1时钟,对应APB4总线
. x6 ~/ y1 ~; }8 v6 @' F* N - */
N) x" V- `9 O0 r' ? - RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 |
# f: |# P& r5 t - RCC_CLOCKTYPE_PCLK1 | \& Q$ C6 m! a! v8 Q( h8 v' B7 X
- RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1);- D; v; u- @+ O0 S( b( D6 ~' l( {0 n/ _
5 t/ c% l) m! ^5 h- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;+ O9 J0 a. Z) [$ g' D% \
- RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
. |6 p6 E3 H. |. k - RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;8 y2 }9 h+ y. j: [0 s% l
- RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
% c* q7 H' P* |8 l4 b$ z' R- P; F - RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
; Q( s7 B6 ^! `' c2 n+ [8 ^ - RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2; - v" |7 A/ a) T7 {1 `( p. M
- RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2; ; _, ^2 A7 t ?6 U3 ^0 { {/ }8 w% S
- / q. O* s# T" A
- /* 此函数会更新SystemCoreClock,并重新配置HAL_InitTick */
$ r9 Q' a* g- [4 a! n4 w4 C4 w - ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
7 l1 p" F. S( I; B3 P/ J+ J - if(ret != HAL_OK)$ U. g i* O2 ]4 r0 N7 I
- {) }9 o8 o8 I. b5 V. g
- Error_Handler(__FILE__, __LINE__);3 j$ O9 U$ V& O( B" k/ S+ ^8 U/ c$ X; [* q
- }
复制代码 ; Y% ]+ t* }4 W4 [: U- B4 R
16.4.4 函数HAL_RCC_MCOConfig. @1 c' I0 g+ q" C! j
函数原型:, L7 r4 l& p! K6 P- X0 `
; W5 X! W8 Z% X# \5 A6 |) t4 T- void HAL_RCC_MCOConfig(uint32_t RCC_MCOx, uint32_t RCC_MCOSource, uint32_t RCC_MCODiv)- x! n9 C# e: I! V/ b8 C
- { P# B } x; G0 K1 g- }0 \' R+ \* l7 |
- GPIO_InitTypeDef GPIO_InitStruct;
3 y. w7 I$ M) ?* b8 ?+ N - /* Check the parameters */
! D X: ?' G/ S" M - assert_param(IS_RCC_MCO(RCC_MCOx));
: ] f, F3 t, _* e. k9 `# f - assert_param(IS_RCC_MCODIV(RCC_MCODiv));
* \( \5 l' O5 G% c- F
# g Z, S' d( I; q% f. @5 N- /* RCC_MCO1 */$ u b+ z# C% L+ \) i. R- s. I6 {# w
- if(RCC_MCOx == RCC_MCO1)
8 I9 l6 s- ~% w- x- n - {4 {/ x+ w' A4 N
- /* 省略未写 *// [% Z% r3 h4 a; ]- \) U9 v
- }
" w: j4 [- D% [3 e - else
7 ^5 r, m5 W. K% I. y - {
3 }! O5 _; e; E, a - /* 省略未写 */
0 T0 I& J4 y# t" E* X - }8 k" I1 K6 r# e3 i, c1 |
- }
复制代码 t9 P. ]7 P% U/ }* b
函数描述:6 R) \9 b- G- _& j: W* S
此函数的作用是配置MCO1(PA8引脚)和MCO2(PC9引脚)的时钟输出以及选择的时钟源,通过下面的截图可以很好的说明此函数的作用:: h. z, H9 x$ Y9 t+ {
: p r4 u5 ?& l- M+ a
' N7 h& [2 N! _9 ~! n
; h* N3 G2 |% e9 s" }' P函数参数:
5 X4 Z* A, \2 I$ p+ S" o 第1个形参用于选择输出的引脚,可选择RCC_MCO1(PA8引脚)或者RCC_MCO2(PC9引脚)。4 p% C5 b) I2 ~5 u
第2个形参用于选择输出的时钟源,MCO1可选择的时钟源如下:7 H$ _; h& \& c) j3 [. ^! b
RCC_MCO1SOURCE_HSI' `7 u2 f: _: c. V8 H4 D
RCC_MCO1SOURCE_LSE
, e% l! s2 N/ F0 F0 X* k8 T# @ RCC_MCO1SOURCE_HSE
3 `4 v9 R- K* ^/ L5 c2 P& ~ RCC_MCO1SOURCE_PLL1QCLK
- u6 J7 B0 |2 Z- v7 _ RCC_MCO1SOURCE_HSI48
8 P' [4 e) H2 j0 S
+ ]- q) w( m% y/ p
9 I0 _3 ^! x0 i4 h$ TMCO1可选择的时钟源如下:
2 [2 \, S: M# v3 x! n1 O RCC_MCO2SOURCE_SYSCLK
9 h: E5 q! U( x7 ]# [: n RCC_MCO2SOURCE_PLL2PCLK! W4 S( h. G T3 y+ b7 t
RCC_MCO2SOURCE_HSE+ l( P% L, R' J+ n! w2 U) h
RCC_MCO2SOURCE_PLLCLK, y" ?8 C! v! @5 Z( t0 q+ l ]* X
RCC_MCO2SOURCE_CSICLK
* ~- E A. q& y+ W RCC_MCO2SOURCE_LSICLK
8 F# ~6 \) D$ H0 v
$ D1 [- _' `2 S( ~第3个参数用于设置输出分频,范围从RCC_MCODIV_1到RCC_MCODIV_15。
. [4 P( ?% Z0 T* ^' b: X
; C; ^, T( E( V& m2 z
( w5 D; e2 y: v! W3 V使用举例:. h. k% {2 }/ o8 v& ~4 H0 k4 Q G
此函数的使用比较简单,需要在MCO引脚输出时钟的时候,直接调用即可。
& C0 F, A& O- x) `- o2 B7 A8 F1 p2 H1 @$ d0 I$ }+ I5 I7 U+ H
16.4.5 RCC相关函数3 v& v3 s: V/ r0 L/ _
函数原型:
' [: K, W( i, e* F0 E8 U6 n* F9 N
2 q: b' e, M1 \+ e) ~; I2 z! p9 ^- void HAL_RCC_EnableCSS(void)% h' ]4 e9 k0 e0 \0 `
- uint32_t HAL_RCC_GetSysClockFreq(void)
6 q0 v4 P- d* U# J* n( ~9 Z - uint32_t HAL_RCC_GetHCLKFreq(void). E+ l4 b+ O S% K, Q. u& G; Y9 h; p
- uint32_t HAL_RCC_GetPCLK1Freq(void)
6 |( o$ V. e: A: K: e - uint32_t HAL_RCC_GetPCLK2Freq(void)
2 O# h, D q: F) C - void HAL_RCC_GetOscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
) r& q1 I4 c2 X( H5 r& U$ v - void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t *pFLatency)
复制代码 " j* O# q& P) T$ {; H& T
剩下的这些函数都比较简单了,我们这里就不做讲解了。
+ o2 U5 Z. E* n; i( f
3 A4 k* n# w* q5 H* Z. }! M16.5 源文件stm32h7xx_hal_cortex.c
' o n$ M$ X+ Y* E( A这个库文件主要功能是NVIC,MPU和Systick的配置。此文件有个臃肿的地方,里面的API其实就是将ARM的CMSIS库各种API重新封装了一遍。这么做的好处是保证了HAL的API都是以字母HAL开头。
6 H( \ f& m2 t8 S1 ?
( n5 @ D) q2 Q/ H+ n更多NVIC相关知识需要大家看第21章节。2 Q2 v; N5 |& M6 `" @+ w! }4 Y
6 T+ ]4 r0 C) k2 N6 L- P C更多SysTick相关知识需要大家看第22章节。/ T/ ~( h. u1 n% |
8 W" s* r' q, g! ~
更多MPU相关知识需要大家看第23章节。
2 @' s: G" g F% P
; ?( m0 C3 J8 O( l" ~/ s/ j p N0 f16.5.1 NVIC相关函数
* P7 [; n, B. i, W5 u- D7 Y+ e7 I函数原型:
+ P$ q. o) u) ^$ X- void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
2 C- A3 w, h' i) M: v - void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)! Y0 P9 z. O# d7 P! {! ] n5 K
- void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
0 {9 `& ~) E& L- L& N - void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
$ {* ^& p1 s2 n- e% w, {& L - void HAL_NVIC_SystemReset(void)
0 w j+ O8 q- }: M2 b$ g- M$ d - uint32_t HAL_NVIC_GetPriorityGrouping(void)2 U, x- P; M# s! b
- void HAL_NVIC_GetPriority(IRQn_Type IRQn, uint32_t PriorityGroup, uint32_t *pPreemptPriority, uint32_t *pSubPriority)) _% x0 M( L% |6 B' }9 h
- void HAL_NVIC_SetPendingIRQ(IRQn_Type IRQn); y. X8 i) ]8 x* \: S
- uint32_t HAL_NVIC_GetPendingIRQ(IRQn_Type IRQn)
6 i5 N' I( c! x9 e - void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn)3 D' c7 q. P# r, M
- uint32_t HAL_NVIC_GetActive(IRQn_Type IRQn)
复制代码 : |3 Y0 a2 s7 w E8 ]/ G# f w n b
这些函数的操作就比较容易了,这里就不做讲解了,用到时调用即可。
% d$ ^3 [0 J' o/ Z- U9 m
6 w0 B) C, F% L5 a+ d! @4 Z% \1 T16.5.2 SysTick相关函数
+ m% y4 M9 l: b+ g6 d1 T函数原型:
% w& Z5 O4 Z6 z# |, }, k$ h# C$ z' G- uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)3 n1 v5 g7 A' a3 I8 Q3 f. }( W
- void HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource)
3 K' ?( L9 i/ d3 A; B+ a0 l P, p - void HAL_SYSTICK_IRQHandler(void)9 D/ r, j! j6 ?- l
- __weak void HAL_SYSTICK_Callback(void)
复制代码 # d8 O g5 j2 [! g$ l$ I- K
这些函数的操作就比较容易了,这里就不做讲解了,用到时调用即可。3 k. U7 L* C; q6 L6 h
9 U# ~: [4 W/ g Z N/ b
16.5.3 函数HAL_MPU_ConfigRegion, Z4 a4 j, r) i) k) H
函数原型:5 A* V% C# U) u, W' r! C
+ N5 I: f6 ?( z7 \9 ^
- void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init)
" A) A% H" y8 r t0 O7 t* Z - {4 @: |) u4 y/ c& W) [: e, B9 z
- /* 部分省略未写 */
9 M" r5 m4 \# \/ L0 g4 j4 q
% q. T! I% S# s' i! w- /* Set the Region number */
' m$ B" G3 N& B) F' R - MPU->RNR = MPU_Init->Number;- d$ z& f, l5 t$ t+ D4 \$ A! Y
- 5 B R8 T5 F& a# z O# X
- if ((MPU_Init->Enable) != RESET)
2 _! E7 `5 {% U& l) M' m - {+ ^" w- g7 O0 p+ g/ M
- /* 部分省略未写 *// n( P r) N+ N
- ' o6 }8 u7 T$ z% B* A
- MPU->RBAR = MPU_Init->BaseAddress;8 f$ F; s$ j% o$ d
- MPU->RASR = ((uint32_t)MPU_Init->DisableExec << MPU_RASR_XN_Pos) |
0 O/ p# H5 @2 f/ f - ((uint32_t)MPU_Init->AccessPermission << MPU_RASR_AP_Pos) |* h, J% F& C9 b' I M1 J
- ((uint32_t)MPU_Init->TypeExtField << MPU_RASR_TEX_Pos) |
7 ~1 e o6 O# \2 r" J% Q - ((uint32_t)MPU_Init->IsShareable << MPU_RASR_S_Pos) |2 ~) |" c/ D) s4 Y
- ((uint32_t)MPU_Init->IsCacheable << MPU_RASR_C_Pos) |( }& l, h$ v, `% U0 Q" m5 Z
- ((uint32_t)MPU_Init->IsBufferable << MPU_RASR_B_Pos) |
( k" S, D1 q/ ~; a - ((uint32_t)MPU_Init->SubRegionDisable << MPU_RASR_SRD_Pos) |
, V7 _% f f5 y! K1 W; n: \ - ((uint32_t)MPU_Init->Size << MPU_RASR_SIZE_Pos) |
$ V( b+ {: L+ r, H2 Q4 t( m - ((uint32_t)MPU_Init->Enable << MPU_RASR_ENABLE_Pos);- w0 r) z* U/ ], S7 R) M' Y7 v
- }! A( v* ?& C# E
- else
7 M) ]6 r' ?# y8 _, X+ L2 @ - {
$ b7 ?' u( K; I% I$ I( F - MPU->RBAR = 0x00;! X$ c2 Y9 I9 a" Q+ k& X% ~
- MPU->RASR = 0x00;; ^+ ~5 |6 ?( d, j
- }6 P% @. M( F$ @1 S, D
- }
复制代码
5 c% i) k: a$ l( X) W" B. |此函数在本教程第23章有专门的讲解。3 U, l$ G3 F- j4 T, G, W/ X% q
4 x* Q J! ?2 f; c: k16.6 总结$ e5 e6 Y7 [; u! X# R9 V t
本章节就为大家讲解这么多,对于一些常用的函数,望大家务必要掌握。7 p7 ]% ^& |( D) I
1 j7 s6 A4 y& y8 q7 J! I( _
1 }9 |7 Y6 s! I
" m$ J8 k/ c) d7 b/ A1 C
% |) O/ x3 x& D, A$ ~9 q9 M2 z- A2 f' v
|