- /* SysTick滴答定时器
& [/ k2 K/ s: e9 u - 一、功能' h8 \/ N( p7 |- `1 e" G+ t
- SysTick定时器是一个简单的定时器,CM3\CM4内核芯片都具备此定时器。SysTick定时器常用来做延时,采用实时系统时则用来做系统时钟。无论用作延时还是用作系统心跳时钟,不需要太复杂的功能,SysTick即可胜任。
1 H$ Q# v7 K: h7 p& `) i7 V7 ^/ Y* g$ w' X - 二、实现原理# x5 d. D+ S- C! f$ D
- SysTick定时器是一个24位的倒计数,当倒计数为0时,将从RELOAD寄存器中取值作为定时器的初始值,同时可以选择在这个时候产生中断(异常号:15)。
* R9 k; i6 L; S5 f- p, x7 ? - 例如从RELOAD的值为999,那么当倒计数为0时,就会从复位为999继续倒计数。
5 d% ~5 Q% v7 I, g4 ~0 u - 只要不把它在SysTick控制及状态寄存器中的使能位清楚,就永不停息,即使在睡眠模式下也能继续工作。( Q% P% ^6 e5 f: _
- 三、SysTick寄存器(在 core_cm3.h 有定义,凡是 M3 内核的单片机都是一样的) */
, n# u' B8 \( t - #define SysTick ((SysTick_Type *) SysTick_BASE)
: @+ ^1 D! P, k! O - #define SysTick_BASE (SCS_BASE + 0x0010)/ i0 F* k7 ^: K! R# n2 B2 D
- #define SCS_BASE (0xE000E000)
: v3 P0 c4 Y! \; h2 ~. ]2 g - typedef struct N+ K0 L. ?* N% B8 a
- {
4 J& ]5 n+ j( J* H - __IO uint32_t CTRL; // 控制及状态寄存器
% Y: f/ a1 y/ E' K% A: Z$ R2 F - __IO uint32_t LOAD; // 重装载数值寄存器
3 Q" `9 O! ?4 g$ t. g& y - __IO uint32_t VAL; // 当前计数数值寄存器
' W4 z" l# x+ H; L, Q5 ]3 I- ~ - __I uint32_t CALIB; // 校准寄存器4 L. L3 Q% N4 R W: X$ x
- } SysTick_Type;
' `) K$ F; j' B, S; A) {6 _1 d% ? - /*
9 y# |0 ~* ^5 ?$ |. h - SysTick->CTRL: (可通过 SysTick_CLKSourceConfig() 函数设置)
+ F' e0 ]$ M5 @2 W - COUNTFLAG(16)R: 计数标志位
9 D- P8 c1 q& T- G- J1 G, R' P* S - 当SysTick数到0,则该位被硬件置 1,当读取该位时,将被硬件清零
: O3 h/ H" w: G. U q; `' k - / A6 ^0 ?) C4 @2 j
- CLKSOURCE(2)R/W: 时钟源设置
) M* j/ Y. Z3 o7 [6 }2 J* W - 1 = 外部时钟源(STCLK) (AHB总线时钟的1/8(HCLK/8))
- t4 i* Y9 z6 ]2 q( S; p - 0 = 内核时钟(FCLK) (AHB总线时钟的频率(HCLK))
\' n r5 r$ e5 h+ \0 ^ - ! h0 A7 g" U Y) `
- TICKINT(1)R/W: 中断使能位 x" c t+ P0 v! O
- 1 = SysTick 倒数到0时产生 SysTick 异常请求, V0 y7 c4 r: G
- 0 = 数到 0 时无动作
7 U" D: K6 q7 y1 Y
* y: \$ E0 M0 @6 i3 `; _$ f- ENABLE(0)R/W: SysTick 定时器使能位/ V: ?1 u. u4 J; W: a/ G
- (当中断被使能后,需要关注 void SysTick_Handler(void) 函数); ^, I* {' w+ K. V5 b
- SysTick_Type->LOAD: (SysTick_Config() 函数会设置该寄存器)% y! h# t( k Z* W. l
4 s* X6 y) s5 [+ D% b4 X1 @- RELOAD(23:0)R/W: 重装载数值寄存器- Q4 Z. O3 k: l" B
- 当SysTick数到0,将被重装载的值; ^0 k% ~8 F+ ], e: V7 `: r
* o) R( {, Q0 m. M+ p9 P- SysTick_Type->VAL: (SysTick_Config() 函数会设置该寄存器)7 u. c- j! r1 f f6 y* ~1 H
- CURRENT(23:0)R/Wc: 当前计数数值寄存器
! q7 T: z7 K% _ - 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。( @! h7 ]( s6 N# y; d$ w
: u' v* Q) l) x6 i- 四、库函数分析
4 I- c' t3 }- O% ]( s9 r* n - misc.c
/ d# ?6 m: P) o- p# C% `4 | - ---------------------------------------------------------------------------------- */) }$ f( x, |, s: z: J+ }
- #define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
9 W" Y n) Y+ d+ F; [ t - #define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)3 D8 E3 I9 {8 O9 p1 N6 n4 l8 l
- #define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
. t; v4 z4 B7 t - ((SOURCE) == SysTick_CLKSource_HCLK_Div8))
0 ^% y( p i$ Y - void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
) N7 v% J9 V6 Z1 A+ O1 S - {# s/ E7 H; f% J4 Y; p
- /* Check the parameters */2 t' Y4 O5 d T" A) y# l) k" z
- assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
# R4 B* ^* h: _6 r - if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
- B" Z% |! M; e0 F - {% h* r" l8 M5 P4 D, p3 J2 Q' k
- SysTick->CTRL |= SysTick_CLKSource_HCLK; // 设置 CLKSOURCE 为 1. T# V8 H! |6 ?! M
- }
7 X7 }2 Y v* e/ g8 l - else) ?& x) H) i2 D: m! C
- {! P. S& U- V- @. l! ^! n
- SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; // 设置 CLKSOURCE 为 0
+ J# Y1 |" C" ^0 h - }
- t7 B6 g9 J# ?* ~ - }1 ]3 C: R3 @$ y
+ L" n" P! W6 U6 U9 u7 V4 u- core_cm3.c6 p9 j Z& a8 Z9 }+ m* V; M
- ----------------------------------------------------------------------------------
: b" n* v! ]' |" F! b6 m' _ - #define SysTick_LOAD_RELOAD_Pos 0
4 \& o/ S* E2 Z( ~3 z) ` - #define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)
1 `% \3 H1 y' Z: W( y - typedef enum IRQn6 [6 ^$ K i8 p! q% l
- {6 U. |2 R. t6 I$ n2 p0 s$ F7 k: n& |7 b
- //...0 s/ g+ M, K) R( [3 S. v/ H4 p, d0 K
- SysTick_IRQn = -1,
0 u9 H7 }% ?' N4 A8 z/ T - //...
9 ?! z* S) E7 ?( b( N% N$ [ - } IRQn_Type;) j+ M- D2 e7 O* }
- #define __NVIC_PRIO_BITS 4
, R4 D4 Z5 i R, C8 \% b - #define SysTick_CTRL_CLKSOURCE_Pos 2- ~ d$ k" a( P9 E& ~- W
- #define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos)3 x5 n4 \) U8 `; q8 p+ M9 v8 e
1 W' l" z( R# q g+ {6 }/ j- #define SysTick_CTRL_TICKINT_Pos 1
' I; \' y8 ]% R4 [- ^5 O/ F0 P3 B. B - #define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos)
/ K/ F. O3 H8 y* h
3 c; u( y8 Z( j( {- #define SysTick_CTRL_ENABLE_Pos 0
+ E6 x" v ]) c. e. }3 O# }' c - #define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos)
# }# n9 H: o- [8 n4 R4 v
* r7 S3 l2 e+ j6 p; U' ?1 E" u- static __INLINE uint32_t SysTick_Config(uint32_t ticks)
% P ^6 k- _& k! l% ] - {
( a' J; o% ~: n- p - if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
; w ?* b/ Z( X0 J% w( Q+ M7 @ - // 设置计数值为 ticks - 1
: s* L/ N& c& z: J! u5 c& v) s - // 原因1:视频说是执行这些代码需要时间,所以减少一个节拍( A8 }. t7 r- n% M7 `
- // 原因2:我认为是因为 SysTick 的倒计数到 0,例如设置 1000 ,那么范围就应该是 999 ~ 0。
( H. P& v* f" [! H - SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
* P2 O/ G$ O3 [, U. U: S5 q4 R, T - // 设置中断优先级
, N/ I- U) B7 S) H' v& | r: Y/ A - NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);4 j4 z& Y# T- K/ P3 r/ Y& u# _
- SysTick->VAL = 0;
8 n" G9 y- l" z) T: i - // 设置时钟源为外部时钟源,同时开启中断、并使能 SysTick 定时器$ G- A' \5 E) p8 C' n) k3 F
- SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |. `) I. ~3 `: N" u! g
- SysTick_CTRL_TICKINT_Msk |
; B' w! n5 b! S, V5 C+ \ - SysTick_CTRL_ENABLE_Msk;+ T( _+ v; D' p$ d) F
- return (0);
7 L8 }8 s3 P; |2 y* W7 f - }; n6 c5 x) b; D' v. {4 V
- /* 五、延时应用+ s& E" ^4 e* J
- 1、中断方式 */
5 L0 ~; M. x7 |* e5 C7 \, E+ k - static __IO uint32_t TimingDelay;2 G7 `' h) Z. w. P* o+ ^
- void Delay(__IO uint32_t nTime)
- T, h" ]- q7 G - {3 V1 E5 q* d( m: u# E9 J3 B$ ]4 Q
- TimingDelay = nTime;2 M* g+ ~; _& ?" K) ?
- while(TimingDelay != 0);1 ]% L9 i& k! K1 T0 O4 k6 A( H
- }8 J; l9 J! j" a5 ^% i
- /* 中断服务函数 */! _' x t, B; V4 L" p/ g' |2 o; E$ ]
- void SysTick_Handler(void): y' [9 {- x& q; E
- {
- U. u; M2 v* V3 _ - if (TimingDelay != 0x00)
0 N5 j$ m0 C0 m R - {1 c/ i% Q) U; x" |. k
- TimingDelay--;( e1 D$ \3 t1 y; B( k+ l
- }: m- Q& r! h4 i$ z
- }
( J) v" ? A8 I. s& \' } - int main(void)4 y' _" p7 ^% P, Z* c9 n; S
- {
- h [7 f& ^& J1 h1 r - // ..." K2 ?8 R% l5 M1 ^
- if (SysTick_Config(SystemCoreClock / 1000)) // 注意,这里systick时钟为HCLK,中断时间间隔1ms/ c; W. _8 x9 \! e* R
- {
7 ]& y" D: p* O4 A - while (1);
4 D+ T( ]" f, D - }
3 y% A6 `: e" e - while(1)8 h6 P$ U# d: ^' @5 r+ ]
- {
+ O) M$ l! Z9 \, Z$ E - Delay(200);//2ms
& k% ?6 G, p" A1 B; b% w% G" h0 @ - // ...
; j! C( V0 L/ g - }1 u4 T( c/ V4 H- }& y
- }
5 q( ~0 w6 O! b - /* SysTick_Config(SystemCoreClock / 1000): (原代码这里假设是采用时钟源为 HCLK)
/ k& a% J' G1 t: m5 y7 b2 ]3 ? - 这里设置的是 72000000Hz / 1000 = 72000 ticks,也就是说 SysTick 从 (72000-1) 开始倒数。
3 K. c9 _2 }- `& u - 每倒数完 72000 个节拍就触发一次中断。8 r; }! h( @* m( l
- 一个节拍的时间为:72000000 / 72000 = 1000us == 1ms
! T- {: o+ M3 A0 w - SysTick_Config((SystemCoreClock / 8000000) * 1000 * 1):+ U# e7 r; D5 ?, ~' k/ T( l
- SysTick_Config() 会设置时钟源为 HCLK/8 所以实际应用中不能按照上述代码的参数。4 }$ j: L. ^% A m0 P: N
- SystemCoreClock / 8000000: 1us 的节拍数% ]& h0 s: N5 M/ E7 i: y. `* T" I) c
- 1us的节拍数 * 1000: 则为 1ms 的节拍数+ R+ U% y, ?/ t8 i- t
- 1ms 的节拍数 * 1: 设置 1ms 一个SysTick中断,即从 ((SystemCoreClock / 8000000) * 1000 * 1) - 1 开始倒数。
J6 V) N2 j. x+ K - 2、轮询方式 */
/ K% Q/ p/ i# q# c0 } - static u8 fac_us=0; //us延时倍乘数
$ A" m3 n$ K: l2 k - static u16 fac_ms=0; //ms延时倍乘数
$ B3 r4 }& W5 X: U' \$ D. m% a - void delay_init()) p, z! B- }: W$ |! ]9 {) |
- {( d+ y7 Y( x3 v
- SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8# V# |5 P v- I- ]( A$ c$ }
- fac_us = SystemCoreClock/8000000; // 为系统时钟的1/8 1us = 72000000 / 8000000 = 9 个节拍/ I% s& [$ l6 h
- fac_ms = (u16)fac_us*1000; // 1ms 需要 9 * 1000 = 9000 个节拍& a$ R9 G1 ~- ^0 k0 I/ a
- }: a) p9 X$ L% `: o0 r+ s
- //延时 nus 微秒
% G% t) @" h- o" S+ u - void delay_us(u32 nus)2 Q- q) w2 [- [% i7 z
- {- {% m; f6 F/ [( B* d! ~
- u32 temp;
! |, s+ \0 `( D# l7 X5 F0 x8 ` - SysTick->LOAD=nus*fac_us; //时间加载
8 i' \/ K& }" y, E: g6 z- O) t+ \ - SysTick->VAL=0x00; //清空计数器
5 E- a7 v* d4 M! O+ |8 V - SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
2 [. S7 _. P3 ~5 |. ^- _& x# M - do' w2 e% b: _) {# H. t/ r( B
- {
4 K6 d" ? n4 X6 f7 z6 O( c/ G) p - temp=SysTick->CTRL;
/ P" p! J; o: N. d8 B - }
7 g+ z& N5 N- s6 Z; Z y6 m - while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
) `' ^4 [3 E7 @0 ^! G - SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;//关闭计数器, w6 x! W. C, E& Q, \% s' E
- SysTick->VAL =0X00; //清空计数器1 T0 p' v! O' m" O6 }
- }9 V& u6 j( v$ e+ j5 t, a9 z1 E; [
- //延时nms. x4 ~' V. m) X a* q: G4 n
- //注意nms的范围
+ W- t4 c& K) A - //SysTick->LOAD为24位寄存器,所以,最大延时为:1 e+ [) t9 O- ~( l: U, [
- //nms<=0xffffff*8*1000/SYSCLK- B) U2 H0 ~# U8 a# g
- //SYSCLK单位为Hz,nms单位为ms
$ y: x; m8 `7 X" `8 a - //对72M条件下,nms<=1864
, N* U( \6 E# j0 ~! T0 o - void delay_ms(u16 nms)$ h& g, m- J1 r
- {
9 K2 _0 ]8 J# @; s- z - u32 temp;! q/ H% O2 J6 |3 e
- SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)" {6 M2 a) [( E5 }2 j
- SysTick->VAL =0x00; //清空计数器, ~% s2 k$ ~! H! h' o) W6 x. a2 K
- SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
& v3 v; @, @3 X2 ^9 c: M3 e) w - do1 P w3 t5 H1 d4 d
- {% z/ w1 C* G' [+ I- C. C! E
- temp=SysTick->CTRL;7 | ]% `: t" F7 ^- N
- //等待时间到达,这里使用了一个小技巧,通过(temp&0x01)检查 SysTick 的使能位,避免 Systick 定时器被关闭而导致无限循环% x6 n$ U0 e4 [9 ?7 o C. S% i
- }
7 `& D- j X& `/ b6 @ - while((temp&0x01)&&!(temp&(1<<16)));$ Y8 s& N, ]: h- x
- SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器9 v! ~' v/ Z$ y
- SysTick->VAL =0X00; //清空计数器
4 A. ~+ Y1 F4 |# g" K, O - }
复制代码 0 I1 A: @' @1 @6 x5 y$ B0 X! O
' U" R J" ?3 N; ?: p6 z% WSYSTick 定时器* r) J6 {$ I; q( w3 p5 ?' B; u
CM3 内核的处理器,内部包含了一个 SysTick 定时器,(SysTick 的时钟源自HCLK的8分频,8个系统时钟周期systick跳一个,即8*1/72M=1/9 us)SysTick是一个24位的倒计数定时器,当计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。0 \1 A( b. d, S+ j
利用 STM32 的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器。因为在 ucos 下 systic 不能再被随意更改,如果我们还想利用 systick 来做 delay_us 或者delay_ms 的延时,就必须想点办法了,这里我们利用的是时钟摘取法。以 delay_us 为例,比如delay_us (50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 systick 计数次数,这里为 50*9 (假设系统时钟为 72Mhz,那么 systick 每增加 1,就是 1/9us) ,然后我们就一直统计systick的计数变化,直到这个值变化了50*9,一旦检测到变化达到或者超过这个值,就说明延时50us时间到了。———实质上就是不改变systick基本单位时长,以基本单位时长为基本元做多次到达摘取。
$ U0 s) O3 b0 |9 y: i |