库里面的时钟配置程序。$ C" v% [* j- w9 b
startup 里面有个调用SystemInit()函数的过程:8 ~9 ]3 j1 P3 q0 h9 Y% B. w o
- Reset handler
5 D, }0 @6 ?, R, [% n* `3 x - Reset_Handler PROC
; y/ B/ w P) i4 @ - EXPORT Reset_Handler [WEAK]4 C0 n" S) N1 t4 _% p( T
- IMPORT __main
- J/ g( Z. ^4 R$ F, K: | - IMPORT SystemInit
# Z7 j* t6 c# k+ \: L% e6 M - LDR R0, =SystemInit
6 h" d6 e6 R3 S7 }' t! u - BLX R0
* Z! Y2 [6 ?0 `- j4 y - LDR R0, =__main
- ]5 W- u& W3 D5 Z0 Z2 c/ d - BX R0
6 K/ Q4 e; a7 Y# D - ENDP
复制代码 ! x6 H( O3 i8 `, K/ P0 K
这个SystemInit在哪里定义呢?在system_stm32f10x.c中定义。
1 Q( i- Z8 Q0 g4 l$ iSystemInit()快结束时调用了SetSysClock()函数,时钟配置就由该函数具体实施。2 ?2 p+ w9 b7 J3 _# @$ I9 Q. |
------------------------------------------------------
8 e* w' r: f4 e) I: \9 xSetSysClock()也在system_stm32f10x.c中定义。
$ F$ Y4 X$ g \其实该函数很短,它根据不同的宏定义来调用不同的SetSysClockToxx()函数,比如SetSysClockTo71()就是当有宏定义SYSCLK_FREQ_72MHz时就调用该函数来具体配置时钟的。这些不同的SetSysClockToxx()调用是有优先级的,频率低的大于频率高的,而直接采用HSE具有最高优先级。
! ^' H0 W5 L1 K' ^& _9 ~! Q来看看这些宏定义和调用函数之间的关系:
) K- t: W. _4 c6 [$ l' K4 q3 \首先是定义宏:
D- [4 d8 b: U& h: U0 Q \- #if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL); w: O m: p: Y' J r
- /* #define SYSCLK_FREQ_HSE HSE_VALUE */
|& H/ ]; N- w* P - #define SYSCLK_FREQ_24MHz 24000000
5 Q( U% B; A4 h) Y( s - #else
. q5 y1 n' h9 E. Y+ y% ~7 t - /* #define SYSCLK_FREQ_HSE HSE_VALUE */
f# t- Q' d* ^) _ \0 [$ D) r- Q - /* #define SYSCLK_FREQ_24MHz 24000000 */
5 F% L. H4 W1 e* K# @3 h# k - /* #define SYSCLK_FREQ_36MHz 36000000 */
3 F$ ?1 h7 }' W7 o6 Z - /* #define SYSCLK_FREQ_48MHz 48000000 */' N6 b& o9 u) v" A6 G5 H
- /* #define SYSCLK_FREQ_56MHz 56000000 */
. j/ b' f& p8 J' K: H" g$ ~! |( } - #define SYSCLK_FREQ_72MHz 72000000% [( v+ u( J( g0 |
- #endif
复制代码 0 N" @5 u4 L- ^) I7 j
看来需要根据项目需求来,不能同时使用多个宏,不用的宏需要注释掉。
4 E, q: N7 c2 u" @+ N然后根据定义的宏来声明所需的函数:2 w2 J0 ^: x% W6 [3 s- b3 j+ X# X$ g
- [mw_shl_code=c,true]#ifdef SYSCLK_FREQ_HSE! w1 l6 s/ O" p8 _' W R" U* `
- static void SetSysClockToHSE(void);/ V$ Q" `( j3 K8 D4 I* A" V
- #elif defined SYSCLK_FREQ_24MHz
. P7 A1 R3 _% i2 Z6 E - static void SetSysClockTo24(void);1 n' _2 w- ^ C9 _2 _% F6 D
- #elif defined SYSCLK_FREQ_36MHz# D/ m4 _+ m5 X6 Y5 {8 G- D
- static void SetSysClockTo36(void);
) y* x/ N8 P2 z+ P - #elif defined SYSCLK_FREQ_48MHz
! u& p F% J+ M; F - static void SetSysClockTo48(void);3 d k, S) b3 ~# @/ L' y; X
- #elif defined SYSCLK_FREQ_56MHz
4 |; m3 y" H, p3 E# j9 ` - static void SetSysClockTo56(void);
0 v- o2 }: z' R% M! T - #elif defined SYSCLK_FREQ_72MHz
, p: l8 C: b& s2 Y2 }2 \1 ] - static void SetSysClockTo72(void);; r) I# N4 Y' l
- #endif+ q! g- Q/ v# m
- [/mw_shl_code]
复制代码
/ Y4 d4 |3 t. G) ~: P如果定义了多个宏会屏蔽掉低优先级的函数。在SetSysClock()里面再根据宏来调用用时钟配置函数:8 P5 m! h* r% }2 s
- [mw_shl_code=c,true]static void SetSysClock(void)
4 N9 @0 t# x, _) Y4 n3 S - {) i" E- o' {: j0 Q T0 J
- #ifdef SYSCLK_FREQ_HSE
& f2 U" t3 X5 Q - SetSysClockToHSE();
+ @& o/ Z, S! Z6 o - #elif defined SYSCLK_FREQ_24MHz
+ J1 Y8 @, F0 \ - SetSysClockTo24();7 \8 B+ c. I, b- l
- #elif defined SYSCLK_FREQ_36MHz* [1 l6 W5 u. ]# a& T
- SetSysClockTo36();
' z. f" W$ U0 ? - #elif defined SYSCLK_FREQ_48MHz+ ~9 ?' [, R8 L5 t; l
- SetSysClockTo48();
2 v4 J. t, p. E0 E- r$ ` - #elif defined SYSCLK_FREQ_56MHz( L! I; s& ? k4 @3 d `+ D9 H* W
- SetSysClockTo56();
, e: C7 h! o* V( z( @0 z$ S - #elif defined SYSCLK_FREQ_72MHz
6 V9 ^9 Z E$ ]3 {8 q4 {* v2 f - SetSysClockTo72();8 n6 P. k' H: x, f) z
- #endif# J2 O3 C) w) V7 \
- ) t( R4 i! W; S2 e
- 2 G9 `* F/ x8 r. Y" d+ B
- /* If none of the define above is enabled, the HSI is used as System clock
5 h4 x3 T, g f7 @9 x - source (default after reset) */% G. N; S6 m% M* V2 w& T
- }; ?& q# }* U% F! W8 H! g
- [/mw_shl_code]
复制代码
+ m! H% ~1 H& ^如果没有宏定义,或者说让SystemInit()函数为空函数,则系统不会配置时钟而直接采用内部高速时钟HSI作为系统时钟使用。) E0 v% z) C, H/ }& P0 w
system_stm32f10x.c里面其他的代码就是关于这些SetSysClockToxx()的实现。也就是如何使用寄存器来设置倍频、分频数等等,需要了解了寄存器后才能明白。查看这些函数,有个直观的感受是里面的代码是直接使用寄存器来设置数据的,不是使用库函数。通观整个过程,如果只是学习的话,根据硬件只需要把待设置成的频率宏去掉注释而把不需要的宏注释掉就可以了,编译时会自动加入需要的时钟设置函数的。$ j8 h; h( h3 n G$ d
2 S* m7 C$ I- j7 C5 j配置好时钟后就开始执行用户程序了,在用户程序里面要使用外设首先必须让外设挂载的总线上的该设备进行时钟使能,让时钟驱动外设工作。6 Z r. d, c* ~5 w: G
这是通过寄存器设置来实现的。' l+ }7 D9 D# c/ A/ [% k% ]
RCC一共有10个寄存器,互联型更多。启动时配置时钟当然要是通过这些寄存器来设置的,不过进入main后使能外设时钟就只用到了三个寄存器:RCC_AHBENR、RCC_APB2ENR、RCC_APB1ENR。寄存器同样是封装在结构体里面的:RCC_TypeDef,里面的寄存器名称去掉了RCC_前缀。既然是使用库函数,我们也就不必太关心这些寄存器封装是如何使用的了,只需关心库提供了哪些函数去操作这些寄存器实现所需的功能了。7 [' P f; ~4 ^$ D; E1 T0 W3 J2 l
RCC库函数在stm32f10x_rcc.h和.c中实现。
" a$ P/ h# f7 Q% f, X& C( |RCC地址宏定义,在stm32f10x.h中定义:4 b6 M4 K1 z8 C- i7 @
- [mw_shl_code=c,true]#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
2 Z2 g& \2 K$ V - #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)- J5 [1 h8 B5 x$ [% \' E0 M6 C4 j
- #define RCC_BASE (AHBPERIPH_BASE + 0x1000)#define RCC ((RCC_TypeDef *) RCC_BASE)
& V c- g4 [0 T$ C( R - #define RCC ((RCC_TypeDef *) RCC_BASE)
& \, z, l& C. U/ i - [/mw_shl_code]
复制代码 ! X$ v8 A3 o! l0 s/ o
RCC寄存器原来是在AHB总线地址空间上的,这个AHB总线有些不平常。AHB、APB1、APB2总线上外设使能的函数分别是:5 p, o( a! H: l
- [mw_shl_code=c,true]void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
& A) ~0 M* P$ m' F; j - void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);$ E+ j2 t' C# }; s
- void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);[/mw_shl_code]
复制代码 ; g0 h6 ]$ H) D+ n1 \" t7 P8 E
要使某个外设时钟使能,其实就是使RCC_AHBENR、RCC_APB2ENR、RCC_APB1ENR三个寄存器中对应位置1,那么这是如何在函数中实现的呢?首先是定义了这三个寄存器各个位代表的外设的宏,也就是当置1时寄存器的值。以APB2ENR寄存器各个位代表的外设为例说明:
" t! k8 K" P, y- [mw_shl_code=c,true]#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001); H$ Y+ E3 p0 G i
- #define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
( H, v# \+ X2 I% K1 y - #define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
7 W: q' L2 }8 Y$ E' H - #define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
( h9 f, s0 h! ^ D6 W - #define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
# l8 B" m _' h- D - #define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
4 Z6 w2 K# y+ S. H - #define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080)5 Q/ {, d) \$ e) \# N# o4 @
- #define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100)& [8 g- E: }+ O9 d Q; B: G# b
- #define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200)
7 m. Q6 t* N/ L Y _ - #define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400). ^0 F# ?6 `- A* m- Q5 a# `
- #define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800)' b" V% n7 Y- g1 f
- #define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000)
6 U2 w* z! e# g3 [/ [1 ]; T - #define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000)
& L4 r/ ?9 F3 G/ ^- a; a6 F - #define RCC_APB2Periph_USART1 ((uint32_t)0x00004000)
, n2 m( c5 Q* I$ v+ \) K6 J% O' \ - #define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000)
( y- _2 t, d" { - #define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000)# {( U9 o3 p9 k
- #define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000)+ W1 ]0 X5 B+ }& V
- #define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000)
5 v! A! f; ^. q5 @- ` - #define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000)4 y( v$ U5 i* A% O- T
- #define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000)( z( c+ t; D- f) c8 R7 @
- #define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000)+ f& w/ i4 j0 t5 V
- [/mw_shl_code]
复制代码
; @, d( S6 {* |. d' T" F7 @( o7 T4 E' M; H7 u
均是当外设位为1而其他位为0时寄存器的值。stm32中文参考手册V10.0中关于APB2ENR寄存器只有低16位有定义,高16位保留,可是,库函数里面却有高16位的几个定义,这是为什么?
3 s9 D5 {5 S6 H0 F0 S6 I而如果需要使能就使APB2ENR寄存器“或”上该值,关闭则是“与”上该值的取反值。. P3 e& ^/ C$ e/ b& ]; I
- [mw_shl_code=c,true]void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
/ N- w8 K2 I& ~ - {
" d( G. S6 p, f4 Z5 Y2 {% E* O1 s+ C - /* Check the parameters */5 N H& d4 y) ]% T% ~# h- i# A. E
- assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
6 A! X4 H5 r; `* x' F - assert_param(IS_FUNCTIONAL_STATE(NewState));" q) S4 d' i0 ^% R! n. ?8 _: V" @2 D' c
- if (NewState != DISABLE)4 _9 ~& V, l0 N8 A b/ q
- {
% F0 X( x6 w( t: a - RCC->APB2ENR |= RCC_APB2Periph;
. _" d0 @' B( M$ i! M6 t2 G5 l - }
* P. Q/ Z; |& c' Q - else5 P0 x f2 q: j, z) c
- {
, i3 U# R" x- }2 I. | - RCC->APB2ENR &= ~RCC_APB2Periph;) u7 j' T3 b5 n7 {" ]0 d. \
- }
. |9 @: |) V) N, {- S - }6 }" e$ q( l( J: l1 V" }% x$ [. @8 z
- [/mw_shl_code]
复制代码
2 c9 X6 v& @2 p+ L6 {------------------------------------------------
% S2 ?9 ]2 q. N: ^$ B断言assert_param():
2 Z) w7 }6 E1 J: g/ z! R- t% L看似是一个函数,其实是宏,在stm32f10x_conf.h中定义:
6 Q; Y3 l- h+ N3 V- @0 ?% A/ y- [mw_shl_code=c,true]#ifdef USE_FULL_ASSERT
* G( ?/ Y# K( m: A: f6 y. S7 f - #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
# E2 N; k) |$ F0 [- T
! ?( x* W9 m, c! M/ `) r7 P7 T
& H, d! j5 r9 {: u- @& q+ Q. `3 ~- void assert_failed(uint8_t* file, uint32_t line);" R# m' F6 S0 z, d( F, `
- #else2 Z, e4 U. |6 q8 {1 m
- #define assert_param(expr) ((void)0)/ Y4 u- n' z% B, ~; \
- #endif /* USE_FULL_ASSERT */
8 t S1 S" Z9 P4 f - [/mw_shl_code]
复制代码
, M6 G% F a( D! @9 W; h1 }断言用于参数检查,如果说传入的参数符合要求那么宏值为(void)0,否则执行assert_failed()函数,这个函数没有实现。如果需要可以实现assert_failed()函数,指出哪个文件的哪行出了错误。/ L) O7 z2 e% f: W
断言一般用在开发阶段,必须定义USE_FULL_ASSERT宏,否则只是得到(void)0,对于C来说就相当于空语句。开发调试结束取消USE_FULL_ASSERT宏。这里需要注意:宏定义不仅仅用于预编译阶段,它甚至可以用于运行阶段。比如在运行阶段才能决定断言里面的值倒地是true还是false,从而决定断言值是(void)0还是执行assert_failed()函数。断言和错误捕捉显然不同。
/ H. w' I, h5 J---------------------------------
& D* Q' t5 Y1 P9 f6 a来看看都做了什么检查:
3 o$ \. b3 Y" J3 `; j, G - assert_param(IS_FUNCTIONAL_STATE(NewState));# ^, n# n: Z9 o+ p
- [mw_shl_code=c,true]#define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE)), G3 A# k4 _4 G P8 K+ c$ x& S% E
- [/mw_shl_code]很清楚了,如果说参数STATE为DISABLE或ENABLE则宏值为true,否则为false。
* q) ^5 S8 z V - 那么 assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph))呢?[mw_shl_code=c,true]#define IS_RCC_APB2_PERIPH(PERIPH) ((((PERIPH) & 0xFFC00002) == 0x00) && ((PERIPH) != 0x00))
. p4 }0 ?7 {: n$ _ g( y - [/mw_shl_code]
复制代码
0 c) Y6 B: e) E6 i6 E# e5 D D这个比较诡异!!* z, J8 H0 w- r) h. P& {/ r
---------------------------------------$ [; @, U9 P, }5 T+ u! u
总结,void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);中的RCC_APB2Periph参数必须是预定义的宏才能保证是有效值。
. I* m1 I- g7 M/ \0 P* \% [/ p比如:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
7 D& q& V# {4 o! B7 E1 J如果需要同时使能多个同总线上的外设就采用“或”.1 s3 v& A+ E+ a+ x, k1 }3 G
如何记忆这个函数:RCC_APB2PeriphClockCmd就是“关于RCC时钟的”挂载在APB2总线上的外设时钟使能函数,参数开头也必须是RCC_APB2Periph_开头。' R0 N% z& g. \+ `$ z `
————————————————
$ Y- g$ ]/ t% O版权声明:被大佬糊弄的只会点灯的小菜鸡
Q* N$ Z! j+ ^ {* L2 M6 `+ v如有侵权请联系删除( f1 T7 j' ~$ r6 z/ P# _2 @
4 h( z9 Q2 z$ N8 R5 D6 p f( G+ F l
, Z: x$ n. h( U0 U
|