文主要来填坑,更正之前文章的错误。也进一步加深了我对SysTick定时器的理解,希望对你有帮助。6 Y7 b1 c$ [2 o5 w I) `; O
' d( n3 S% r6 [
/ R, s6 B+ ?( O: a# F. z/ w( Z01 坑的由来
3 b$ U1 }, j. c6 v+ R+ b4 ~3 F8 w( b N# a1 s7 v2 S/ ]
在之前的推文中《STM32延时的四种方法》介绍了使用查询定时器精确延时,使用的就是systick定时器,具体代码如下2 P. P3 x+ ]6 B7 v9 z: ^* H
- void delay_us(uint32_t nus)
0 c: I) M3 W9 _) k- }/ U - {
2 ]# R- \2 R* ]4 i8 c' K5 H - uint32_t temp;
6 ]3 o7 y; P/ P7 X3 B1 C* c/ w, P - SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;
" W5 w/ X! _) C- t3 l4 ]4 _9 R - SysTick->VAL=0X00;//清空计数器
# R& F/ X# t0 _8 T8 q& }/ D9 k/ g - SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源. u4 Z, ]& f, b8 h5 |5 J
- do' d# g* r" U6 L( [% X4 t8 d
- {
) h4 `' Y9 O7 F B% g2 x& \2 m - temp=SysTick->CTRL;//读取当前倒计数值9 d' X6 y& J q* N1 T' c
- }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达; V% ?' T! B4 Y3 e7 J
- SysTick->CTRL=0x00; //关闭计数器% F2 j3 Y1 H9 |: u7 }" m
- SysTick->VAL =0X00; //清空计数器
( K! f9 v* m) j- D1 ] - } n& m7 G/ ?, `/ W
- void delay_ms(uint16_t nms)0 S. ^" y$ r9 s9 a9 k, i4 A- Z
- {$ q; u7 p, P( P! i
- uint32_t temp;# S8 Y4 r8 T; A7 h
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;& s9 q' ?: I. v4 G+ @4 t
- SysTick->VAL=0X00;//清空计数器
4 e/ F. E3 }% C. f - SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源 s: ~1 a- l' K1 k
- do
# E7 j0 K6 R6 e6 E% a3 z5 A* O - {2 S, `: a' t* s: Q, M
- temp=SysTick->CTRL;//读取当前倒计数值
' ?) M1 \ `& D* a8 z0 ] - }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达7 i2 _) N- o" `7 N+ Y
- SysTick->CTRL=0x00; //关闭计数器! R S w% Z. l7 M& D/ S6 z
- SysTick->VAL =0X00; //清空计数器
0 `6 |$ F& i3 U [ - }
复制代码 对于《STM32延时的四种方法》文中所说的内容如下# m. j2 X. g! F+ l' B/ k
3 X" K/ ^ S) l7 b( E- Y7 h
也就是下面代码中/8的原因。
$ P4 o( R# g( a* o( [
2 o( b: S- P+ A/ n% o- c) S- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;
复制代码 我对此深信不疑,并在STM32F207参考手册(RM0033)上找到“证据”。9 P4 T8 @6 S& U9 J
- G* x/ z# e& @1 l7 r# j+ N上图①处直接是8分频,而不像②出的1/2/4/8分频。所以我确信是SYSTICK的时钟固定为HCLK时钟的1/8。
; K1 P; X- o0 R6 s' e5 d. N" o- Z7 Z, N. g7 L
我在学习RTThread的时候,看到配置SysTick定制器代码如下
8 f0 I" E! f! M& J6 K! Q% q+ N6 B
! d+ Z* {1 i0 G, ~: ?" i2 u
. G% Y9 O" e. s7 C" B. i$ ?( z3 S! A$ M9 G: F8 j
我心里一堆问号,STM32官方手册,明明写了SYSTICK的时钟固定为HCLK时钟的1/8。我使用示波器测量,RTThread的配置是没有问题,可以正常延时的。
5 t! u3 c8 J8 g6 o0 }( p/ g% C9 `9 C3 \$ J3 Q6 j
02
) }/ r4 I: q5 b+ n- I. ?* B% x1 |) A填坑 4 k# V$ u9 N7 A" z7 e
" U f" v2 q4 x/ F }% }
5 i g3 u$ w2 l9 d7 _9 J* g" d7 n6 k
这个坑其实很简单,在《STM32延时的四种方法》也提到了,只是自己没有注意这个细节。2 d1 Q& ^* H6 k" X
1 _- {8 e1 j' g/ q" w8 J
位2置1,表示时钟频率为AHB,也就是默认的120000000Hz。$ U$ @, _7 M V
8 }- i8 J; Q' O) x, n
9 A! I! R3 U3 U+ g; ]& s& U位2清0,表示时钟频率为AHB/8,也就是120000000/8Hz。' x0 _9 Q0 k6 ]: z
; e+ H1 T& j* q/ M1 g7 JRTThread配置为内部时钟
& j8 f. t3 y& q7 W' Y* E
- }& e6 V" j% |8 ]0 V" b0 ?之前的文章配置为外部时钟源6 N* J3 G( T Q' ?/ Q
, c3 T( Q1 [$ f/ G& a% K这个细节我没有留意,导致我看RTThread代码时有点懵逼。在这里我更正《STM32延时的四种方法》中的错误描述5 Z* d* D" g$ U0 y4 ]( n# N
9 z, P8 p4 N% ]准确的描述是:- M! p. v) i3 U5 b9 r1 h5 v
$ R0 O K& q" f
' F; o- E* ^, r& @$ DSYSTICK的时钟可以为HCLK时钟的1分频或8分频,在这里我们选用外部时钟源120M,所以SYSTICK的时钟为(120/8)M。- {/ [5 l2 p3 C% l" l5 C: o
3 B) h. ?% _& \8 N
4 ?1 x' R2 y4 Z" T) u/ {
特此更正。: F) }" ~, X: ~3 T8 K0 U$ p
! Y+ g; c4 C: x+ }关于这点,STM32的标准外设库提供的SysTick_Config函数,也是使用内部时钟的
. f: m$ ~( T) p# o- /** \brief System Tick Configuration
* N; n% `5 u% s9 M. ^
: a# D1 P" I. U2 Y8 E8 d& S( K- This function initialises the system tick timer and its interrupt and start the system tick timer./ r1 G1 E) v! @3 E
- Counter is in free running mode to generate periodical interrupts.! r8 }, Z% M5 p. g. M' B" a `
; d3 N$ E- d9 `; K7 o- \param [in] ticks Number of ticks between two interrupts3 P5 S7 B6 u. h9 i2 i1 j6 ?
- \return 0 Function succeeded
4 ]5 [$ }) T) G' @3 z, b8 n - \return 1 Function failed. h: L& v. C# ?& F0 J" k
- */$ a k# _8 n$ C
- static __INLINE uint32_t SysTick_Config(uint32_t ticks)( s5 V. u" t" s
- {
# h1 i9 }$ g6 [4 e - if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */0 s! w4 |/ p) C* e
6 p6 g7 T. H. h9 S- V' x& S( o- SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
+ G# b1 w+ S- m# e7 x3 Y4 |5 @! D& W - NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
9 w% h; c" i% ^0 p c - SysTick->VAL = 0; /* Load the SysTick Counter Value */
# b3 J- h, X8 q( z" o' R - SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
) W3 Z/ q. v7 l* ~ - SysTick_CTRL_TICKINT_Msk |
' x5 E, z+ g' G5 m. S - SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */2 p2 ]" z1 S R( f
- return (0); /* Function successful */3 \1 j3 b6 l3 Z' k3 _& g
- }
复制代码 调用方法,产生1ms中断调用方法. Z2 @4 a8 Q4 y6 ^3 m r" |
' v5 d7 |2 E. e8 H1 K
- SysTick_Config(SystemCoreClock / 1000);
复制代码 * k* G7 h) U7 j
关于时钟源的选择,除了操作寄存器外,还有库函数可以选择。
% J5 a% ?- M% K# G1 u; v$ H- 5 h9 ~- F( x; }% i) G
- /**- O( `& ?, Z9 ?' T% X1 t3 c4 H: `
- * @brief Configures the SysTick clock source.1 r$ s/ J' t' U9 R% O6 A" R; O
- * @param SysTick_CLKSource: specifies the SysTick clock source.% s1 @. Q: z& O M3 L
- * This parameter can be one of the following values: ^6 N* } [- i/ {+ Q6 ] f. g
- * @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
8 G0 B: \5 p% C6 ~- M$ n - * @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
8 k5 j: a; E8 ^. ~& \& A: A0 t - * @retval None) b* \, |+ ]9 t ?( v( X
- */
3 a- T, X4 I0 H, m$ F7 J - void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
4 Z' y- r, s: ]; _; q& W - {
2 t! `3 e$ \% i2 s# u - /* Check the parameters */
- X4 g% P/ ?- M/ c% g& W - assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));' ]( R* ^3 l. I) j) d. ~- b
- if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
8 @/ z) G# O- X - {
# T* H! z" a! \! Q$ G* A - SysTick->CTRL |= SysTick_CLKSource_HCLK;2 N) l; J# e! A6 h# E
- }: U( A- ~" ~) ~$ H' d
- else
8 y9 w( N, f1 E; ^, m- W - {
8 g7 g. D/ s! `4 f1 O - SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
# N) h" o9 \. B' x2 X - }0 ^9 t: x1 g% e0 y5 j# g% \
- }
复制代码 除上外,我找到了其他证据来说明,SYSTICK的时钟可以为HCLK时钟的1分频或8分频。
/ ~! J0 j' |. c Y: C0 D: ~5 E$ R. w0 v. d p2 a4 c
在STM32CubeMx配置软件中,可以选择1分频或8分频。4 ?* P3 U& x6 b0 L
# ~; i- W2 ~' M9 M
03
( T! k! y5 k; w; m3 D! x修改代码验证
( `" V, x( N7 t" |3 |( T8 d% ]) J! f% y) ]+ _: ^2 i& V8 @
$ w0 g& O3 r3 h) E把《STM32延时的四种方法》文中涉及的代码修改成1分频的。
y7 h$ G" n! _. v) m t* l- p
3 Y6 u, d7 u1 l5 F8 r- void delay_ms(uint16_t nms)
4 ~3 F* C1 u$ r - {) ~- f& D& a, v3 u$ Z5 r
- uint32_t temp;# E# r3 h, D6 H0 u. z" L
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000*nms-1;
. }6 @5 h3 t4 X# k' {$ | - SysTick->VAL=0X00;//清空计数器
6 q9 ?. a/ t0 Y( r$ i3 g - SysTick->CTRL=0X01;
0 c" r' r, D1 I - SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);) T; g4 l7 q! t" [7 I4 r
- do* S8 E( X) j, N- t; }, }/ b9 q
- {
% |' Y4 Y' v: L2 Q5 O6 D - temp=SysTick->CTRL;//读取当前倒计数值/ D* s3 O: o& q- r5 H
- }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达7 q% a# L l2 B4 x
- SysTick->CTRL=0x00; //关闭计数器
) C4 k/ {# U! [6 a6 T - SysTick->VAL =0X00; //清空计数器
# h$ U4 x) e* o0 s. D: k - }
复制代码 然后调用8 i5 z' N" d+ o) J4 j
- ; p& [# Z/ g8 F2 H8 P
- GPIO_SetBits(GPIOE,GPIO_Pin_4); //熄灭LED灯
: _4 Z) V4 z. x2 _. H( A0 r# P - delay_ms(500);//延时500ms
" y* a' v. V2 v2 R1 {# j - GPIO_ResetBits(GPIOE,GPIO_Pin_4);//点亮LED灯 9 k8 }! B, B/ L/ {) W& |. A
- delay_ms(500);//延时500ms
复制代码 就踩到另一个坑,延时不准。
2 Z8 k, y8 S& n3 l1 V) S8 s
; \$ m% Y( M; t8 l6 d5 _" f' w3 W% n
原因是:此时SYSTICK时钟频率是120MHz的24位的倒计数定时器,也就是说一个周期,最多定时139.810125ms。不能延时500ms。" B/ P6 X* W7 ?! ?% j- {
这里再更正之前的一个错误,如下图
3 a5 f6 [" S+ z: B2 {" o0 p/ U/ L( G% d. ^2 |
这个计数器的值,我们减去了1,这样才更准确。需要减1的具体原因在定时器讲解的文章中讲解过了,不明白的同学请看《STM32基础定时器讲解》。
1 n! w$ G, F" |5 m+ [6 `' V1 F0 v: w, t3 Y4 {; q
04 8 ~" `& w3 V& N" K8 t- Y7 E
总结 - R2 N% d3 A1 N+ m: i( K
Q2 m1 b- o& x7 [. ]) d
5 _8 d/ q. b4 T/ k. O
总结:STM32官方手册并不一定是准确的,要亲自做实验,自己动手验证。这是个老生常谈的问题,大家都知道,关键还在于实践。/ |; T: J7 W4 `) y
& q7 t. b/ @ n4 U: U7 u+ o* T( M3 I' j2 X( x8 G' x
7 l `0 N, L$ y ^6 _+ b" @9 @& e- @* v& m! B9 n* E
2 L7 \7 z8 {3 d% @2 t" K ]( |+ G
- _. M4 o' G9 j$ c |