文主要来填坑,更正之前文章的错误。也进一步加深了我对SysTick定时器的理解,希望对你有帮助。+ g2 [0 }/ Q/ p
8 X3 u3 E, @4 {) \
3 X9 x/ [% W7 e5 Z01 坑的由来
: X* _/ v2 C. J
; z0 }# D+ k( K% |在之前的推文中《STM32延时的四种方法》介绍了使用查询定时器精确延时,使用的就是systick定时器,具体代码如下
' H: z* ^0 n* @0 k: w- Y, X# K$ F- void delay_us(uint32_t nus)" s+ t1 y' V7 F: p8 y- k P
- {6 b0 |7 r! R1 |! G% y; S
- uint32_t temp;
; B; C, C- Z( @7 f: G - SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;8 S2 H& \% D) [$ Z! w
- SysTick->VAL=0X00;//清空计数器, l, J' y: Z) y8 ^5 w
- SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源" Y+ h# @2 h- m% `; @6 q) s
- do
7 t i5 A v( P% I, o - {
' E$ } W/ M: g& ?& P - temp=SysTick->CTRL;//读取当前倒计数值
0 s( C8 h* M0 O( }6 } - }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达3 f0 U( [3 X8 y& H- A' P
- SysTick->CTRL=0x00; //关闭计数器
$ B4 D: ?3 g3 [6 L4 {7 Y - SysTick->VAL =0X00; //清空计数器1 ^$ B0 D. F& N8 _& t! j3 q3 o
- }; o3 p- n% }7 g7 |
- void delay_ms(uint16_t nms)0 t' K1 c* F( Z* [
- {
7 R2 t; C0 k- P" r - uint32_t temp;/ G7 {* \; B7 u/ Q. j! P y
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;5 `3 V q( L8 w: O% v2 f$ e
- SysTick->VAL=0X00;//清空计数器8 U) M2 r$ X k0 }# Q
- SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
: a' U, U0 n- v( D, v: c - do* W& s& A: M/ h7 f
- {
8 B: w u& M/ E4 C7 ]8 S# V e - temp=SysTick->CTRL;//读取当前倒计数值
+ {, U9 B/ z+ C8 q - }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
3 L% K1 E$ h% B% J3 x7 J - SysTick->CTRL=0x00; //关闭计数器8 [; T. p$ e; ~" z- s$ D
- SysTick->VAL =0X00; //清空计数器1 E" J" [- P1 i& W2 ~% N. h" G
- }
复制代码 对于《STM32延时的四种方法》文中所说的内容如下
. a5 q+ a1 u6 b- U
^7 \/ _2 a! G9 h" }6 E也就是下面代码中/8的原因。1 t) I ?9 D) Q, W, }) m
# U! o3 A$ h9 {( R2 W
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;
复制代码 我对此深信不疑,并在STM32F207参考手册(RM0033)上找到“证据”。! A/ m4 F' [+ E
2 Y6 o5 o' j$ b
上图①处直接是8分频,而不像②出的1/2/4/8分频。所以我确信是SYSTICK的时钟固定为HCLK时钟的1/8。
: F/ q; u6 D. E6 }2 u2 x* E: \6 {- U# n8 B4 H" }
我在学习RTThread的时候,看到配置SysTick定制器代码如下" r/ D0 q/ V1 @. {" a( V Y
3 [7 j" L5 J- {2 A# |
2 k& y1 J; f% `9 A. K; y( J K5 T _
3 e& h( h" Q, K6 `! U2 J5 D% B. c# o我心里一堆问号,STM32官方手册,明明写了SYSTICK的时钟固定为HCLK时钟的1/8。我使用示波器测量,RTThread的配置是没有问题,可以正常延时的。+ @+ J1 Q* J6 ^" l( }
- A( Z9 Q; g' J) o02
( ?- A' G: B0 L" q填坑 , x" t5 A( }; p) K
|% r- U6 m% g6 {3 c
4 P ]3 p9 ]5 K5 [3 J2 w7 o这个坑其实很简单,在《STM32延时的四种方法》也提到了,只是自己没有注意这个细节。- `, r0 n e2 S) m4 @0 ~1 f
* [; E* I* C$ t8 D* S1 U, m* C4 U
位2置1,表示时钟频率为AHB,也就是默认的120000000Hz。
2 O, S+ \9 H# J7 c( u/ l* Z- p. X/ Q3 P3 i# N5 j# {
6 h% o/ ~, n% u- |) s位2清0,表示时钟频率为AHB/8,也就是120000000/8Hz。
* g! a3 F8 u+ e: O) B5 E( ? b# P
: n; V$ G, B- k( z7 eRTThread配置为内部时钟
! W9 w9 A! I8 Z _' W7 B3 q
1 T2 t) z% V+ h) O8 F9 A8 l之前的文章配置为外部时钟源( Z1 r+ f* A8 N" }
! D! s9 Q ~/ g) b6 C) h V
这个细节我没有留意,导致我看RTThread代码时有点懵逼。在这里我更正《STM32延时的四种方法》中的错误描述; Y7 w9 S! S* f8 q
$ z: a8 X# E. w m
准确的描述是:% `: i4 b# w5 ]0 i g# S
8 s, D; w1 Y% b( w& q; D/ {) H
# U) t) F/ H! Y1 W4 MSYSTICK的时钟可以为HCLK时钟的1分频或8分频,在这里我们选用外部时钟源120M,所以SYSTICK的时钟为(120/8)M。2 [. Z; n7 \! }, v$ s
! ]7 a( p, H# Y. s3 a3 b5 L
. m0 Y% K. @8 T7 o3 A: x特此更正。
' j: N2 k7 r$ t, _; h- w" e/ {+ ^
关于这点,STM32的标准外设库提供的SysTick_Config函数,也是使用内部时钟的9 r1 U8 i4 [9 ?, X; D) r
- /** \brief System Tick Configuration7 o9 F: [( ]" `6 c, p/ u# j
- % {4 {' O0 G' S$ O
- This function initialises the system tick timer and its interrupt and start the system tick timer.7 ?* H ^0 o8 E P8 f* ?! a
- Counter is in free running mode to generate periodical interrupts.# E$ K2 {* h- Z1 \) b3 u
! G9 M1 M$ `" D J0 e) s- \param [in] ticks Number of ticks between two interrupts
! {& j8 ~; V% Y, X: h6 T - \return 0 Function succeeded @& `5 T/ u S6 `( D
- \return 1 Function failed
% _+ S" R% y# g' j0 f x& x6 K- B - */
; \. z$ k+ m; f. G: g - static __INLINE uint32_t SysTick_Config(uint32_t ticks). K" N k3 ?1 o; Z( _4 I4 x" D# a
- {
8 u; H. m: ]1 `2 y, V - if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible *// z+ }6 G* K4 ^ j+ A
1 [" O( ~* E9 o- w; S- SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */# D9 r1 u! _3 j# b! I. `* w/ I
- NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */* [7 S5 e9 H+ O
- SysTick->VAL = 0; /* Load the SysTick Counter Value */
7 I! z9 u1 A3 C6 D. @ - SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |* s( Z5 O3 g( O8 A; {0 U7 Y
- SysTick_CTRL_TICKINT_Msk |
, r, L% u# U. b' B6 F) u- U; z - SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */9 C6 R) e% N' e' h' f
- return (0); /* Function successful */6 L6 s& B2 U. K8 g% ?! b
- }
复制代码 调用方法,产生1ms中断调用方法& O' C, J; |8 t% ?* b
! c1 W" [0 X: t# [- SysTick_Config(SystemCoreClock / 1000);
复制代码
' T& `# }- {( L2 X' @0 K$ z+ f- U关于时钟源的选择,除了操作寄存器外,还有库函数可以选择。: n$ Q! A( h2 s% P. `9 o& a
% g# Z4 N# ?8 v Z; z* A+ a- /**
8 m. G q9 @+ C8 X5 n - * @brief Configures the SysTick clock source.) S1 [) F& o% ~6 o! ~0 j3 `
- * @param SysTick_CLKSource: specifies the SysTick clock source.
$ C1 Y0 f2 w% E# f6 H( m - * This parameter can be one of the following values:
# D, k1 ^' B7 A( ` - * @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
! n+ t; H7 u5 `$ A1 P V4 z$ v4 n - * @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.% p' f( _& x# ^- h8 ?: b8 V. V
- * @retval None
9 J: @. l5 K2 I - */ E5 F1 D0 T+ E2 L; R4 J0 O; [
- void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)' W' \. X# Q& l }
- {
c+ @0 n; T4 ]! c* k5 u" @ - /* Check the parameters */5 n% `! f c/ p) H; I( i1 Y
- assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
: b6 p9 d( D' R$ C x% N/ m - if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
( {* i/ d" n3 g, a# N1 I - {
' e0 v6 C. S' k R+ Z; b" b - SysTick->CTRL |= SysTick_CLKSource_HCLK;
$ c" ?; W1 k% ^: g3 k: q - }
/ c" H* s+ B5 v$ l5 e" R - else
) o k- f' u4 ? - {. @5 v# h/ Z; e4 l" T# W, H$ z- I
- SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;: T( u$ f7 j% \' Y t
- }
- V9 S# }3 i4 k3 C, P* L" E - }
复制代码 除上外,我找到了其他证据来说明,SYSTICK的时钟可以为HCLK时钟的1分频或8分频。
6 S. o3 H/ l2 o: O7 q8 I5 }0 _, X/ P. O, ]$ z& b0 m7 L: T
在STM32CubeMx配置软件中,可以选择1分频或8分频。- Q. C1 M& D; U+ ` n
) z- k# A) ^, A; d. a
03 ( L G" A7 e4 \
修改代码验证 6 p7 W( c2 C& Z: R, H
1 C1 E% P3 R3 S( |$ [* j4 v
6 C; `, @' g; X5 s2 K: f5 v. C把《STM32延时的四种方法》文中涉及的代码修改成1分频的。% U( z. t- k- S' i: U) A! s
l' h5 Z5 r7 {2 d; `6 n n- void delay_ms(uint16_t nms)
c! J0 H# z( D! t7 } - {
& [. Y6 \5 G1 K+ W" g9 c - uint32_t temp;; }, V0 G: ]# O0 G \: [& D
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000*nms-1;3 P3 f( E6 ]" Z/ E$ l5 `& ~
- SysTick->VAL=0X00;//清空计数器
5 M" D1 W/ w' d - SysTick->CTRL=0X01;
k2 R( r c9 {' M2 p: d7 ] _ - SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
- \3 O) L" p7 L# v4 V, m - do
& }/ ? A# x, I3 P$ d9 \6 d - {
% e: g; o8 E' z- B9 v- a- x' d: K. R - temp=SysTick->CTRL;//读取当前倒计数值
0 q' m5 Q7 Q2 n+ ^! P* t - }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达% N! z+ q. {( }+ p B& b. X
- SysTick->CTRL=0x00; //关闭计数器
! z- [# n6 n6 p, @4 O" i: W - SysTick->VAL =0X00; //清空计数器6 f" f* K& n1 f3 l4 D3 s' l
- }
复制代码 然后调用4 i+ T5 m( n2 v- A, h3 q9 \9 X
- % v# s( e0 s- V. _: H+ h
- GPIO_SetBits(GPIOE,GPIO_Pin_4); //熄灭LED灯 " K4 d+ f% _" ?% Y3 x, Y; V' b9 s0 {( r" y
- delay_ms(500);//延时500ms
) E' c/ L `7 Y& |4 o - GPIO_ResetBits(GPIOE,GPIO_Pin_4);//点亮LED灯 0 I" @ a' |4 g: w/ }: q+ J# o
- delay_ms(500);//延时500ms
复制代码 就踩到另一个坑,延时不准。
0 {' w# z. O, K
6 R; ?9 v. r) a
/ e6 D$ `4 o5 [0 X/ I1 u+ L$ X原因是:此时SYSTICK时钟频率是120MHz的24位的倒计数定时器,也就是说一个周期,最多定时139.810125ms。不能延时500ms。0 m% \3 f3 q4 Z L8 H
这里再更正之前的一个错误,如下图
0 c6 W' e& M! p: _2 `6 p& @
0 y7 C3 r% j2 E这个计数器的值,我们减去了1,这样才更准确。需要减1的具体原因在定时器讲解的文章中讲解过了,不明白的同学请看《STM32基础定时器讲解》。
- E# m+ U0 @- `7 l/ p: w$ e% Y0 C' C% S( R( G
04
1 [- m C- x% g! p' g$ S# N1 @5 ]& g总结
. o3 W1 {1 Q+ S. J* U( Q, d/ W- J, o1 L
2 g! N8 w2 k) z" ~
总结:STM32官方手册并不一定是准确的,要亲自做实验,自己动手验证。这是个老生常谈的问题,大家都知道,关键还在于实践。
k! {/ u/ R! V& c
# M7 c' M8 Q+ _! Y& @9 n; Q4 h; I8 o* W, V
7 g9 p/ w/ j# q! Z* U: Y0 T
- C0 h% Z, z. |# e" P5 K- I7 C( N9 `
2 ~2 H7 c& l; p1 y: W7 }- }$ E# r1 |2 Z1 D# T7 J
|