滴答定时器:
@5 W! N5 P$ W8 @: l | 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。$ O X9 W: Z7 l \
6 x/ d, e& \3 z/ z0 s& k3 y* X& R滴答定时器systick是一个内核外设(即:内核自带的)
/ {8 Z, |* q. `& z; ~% k% y3 E, S3 M: C$ J
Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。% [' s4 M) u0 X: _3 a
/ \* ]0 Q. U3 j% CSystick的作用
4 J2 M" U2 o. K: V0 M+ o* ~% \1.产生一个精准的定时
% f# {9 H7 A$ h6 T. K2.FreeRTOS时基由Systick提供- R& C5 z# @) J2 `# J* t
3 b4 c& t: k* _在说定时器前,我们先来看一看如何计数?
: `( G5 v; x: W* D. E$ D ]) p
配置滴答定时器:
# L/ V, u. f B$ x7 U) h% |* U( S+ W2 }" I3 D% c9 O
0 p+ m9 Q8 l3 d! N- r) q7 t; w
. J# J$ h+ p, M0 d+ {
红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz( m1 D0 C) e8 ^, A& {3 ~; d- v+ A
, A0 B4 [- Y. l3 d$ Y* n, g配置滴答定时器我们需要下面几个寄存器:
6 T% X' l% d- O. h- S/ N* G
( W- y5 u7 }' D# b" r0 g% c3 p' _' K) n& n
; q$ E9 j' V G
' c$ D8 A1 |" n5 m9 { 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。! [% n! ?; G0 i9 q/ b4 }8 N& U) T+ c
7 o9 \0 F, {5 {2 Q; |, D
& ^4 G+ U. s) ]' n
# k4 S, s' z0 s* q
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF
$ s. a. c9 S" T, |) @
- Z; y; U, Y( x: n) _+ U5 p即:1~ 也就是:1 ~ 16,777,215,如果填入的定值不是在0x000001 ~ 0xFFFFFF这个范围,则无法定时计数。" c6 P1 I* I" f5 R5 {3 |
% q, a6 K% x) u
' Z0 {' c8 k! ^9 L
2 D8 U3 w( [9 V4 o& {* d编程
; Q; g2 t0 q2 a4 a产生Nms定时编程步骤:
8 V4 g" [9 c- O6 D* Y! _+ E( h 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)3 z! I5 A/ Z/ y1 U
2》设置计数值---N*9000/ C1 }6 i' i- h# X7 `$ o; l/ P
3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0
; S4 J% C9 Y: X+ W& h) R' m/ P6 D2 y8 R 4》打开定时器: `" B* [6 A5 r
5》等待定时器结束
3 o: B* \5 q$ B) z' \/ f P8 [ 6》关闭定时器, h# |# E2 A5 s: [. u: @
% P( a3 k+ J7 m% G
代码:
: V" ]9 d D0 b) ^- uint32_t fu_ms; // 1ms需要计的次数0 I1 S. r& Z" o( l4 _3 b
- uint32_t fu_us; // 1us需要计的次数
% r% k% N% s$ E! G L4 [ - ) l; O5 r7 x8 o& J# ^9 W
- $ v, F) p! V/ [& {! `
- void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz( l; A5 \9 N* [0 C
- {# N) Q* |/ K; w. ^5 O, Y& U* I
- // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
# F' w* j, m3 R' e- C# F. P+ Z - SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置, Q9 k# J$ H) n- m# p3 ] f5 O, l. d
- 8 S2 N* V, b: v5 r
- fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz$ y* ~' h. g1 B
- fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms
" O3 l/ N4 Y$ T& u$ G - }' v/ d, O, E" ~: _) ^( p
- % `1 J1 ~9 D8 P1 V/ E& a$ a$ F
- ! p! H* D& v+ N5 I- E
- // 定时Nms程序,ms级延时" c1 d4 w$ _3 D1 w% \
- void Systick_NmsDelay(uint32_t Nms)
0 d; t. ~: {8 I6 S9 q# A% x - {
& [" I2 }8 [- |# | - uint32_t temp;
+ l( C( k, E3 v+ x2 j - // 2》设置计数值---N*9000
/ j% s& @! o' I2 a7 N6 K- W2 C - SysTick->LOAD =Nms * fu_ms;: e4 J4 Z# x' o" ~7 L/ D5 d) S
2 z9 N4 V' ]# v% w' I% ~- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0
0 @9 D7 b w; }/ G - SysTick->VAL =0; //清除标志位( Z' t# o/ O: r' H! f
& ^7 t( ], s% @0 Y. y- // 4》打开定时器/ n3 t. v4 k7 k" _8 _
- SysTick->CTRL |=0x01;+ Q; [8 ^ P" R. Y4 _# v
+ t1 e. P+ U+ p5 `/ z0 j- // 5》等待定时器结束9 p7 i9 L5 z$ W: M
- do{2 `" d+ v% W' r& D
- temp=SysTick->CTRL;
& ^ M/ F8 j( P4 F4 @& y - }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成
* ~# ^' o$ W$ E) [9 i5 y - / S% m* D* f5 g: x1 M$ v& i
- // 6》关闭定时器
4 K5 n4 x5 C+ K# H' n- K. {: U - SysTick->CTRL &=~0x01;. H. S7 I4 W, _: l/ g1 q
- }
. c9 U1 D, Q( D" e% r& ^$ F - 6 Y: D- K7 n& e" W$ z
复制代码
W$ ^; Y, P. l7 L9 p解析:. P/ f" X! H) |8 ]6 [+ G% C
1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写?
% _/ R7 R' J- F3 Z, t1 P 因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。
0 ]: T6 G& `' F
8 K9 p1 Y+ P, c5 R4 v& G" K2.为什么程序中的这部分需要用do.....while而不用while()?) n! }' R+ v& m' X
5 s' _! ]9 S: N+ `$ v: d6 g+ t5 A
6 W5 M$ A5 D. h$ o
5 P: S5 k8 i ] 这里需要用do....while而不用while,是因为需要先执行temp = SysTick->CTRL; 将SysTick->CTRL寄存器中的值赋值给变量temp,然后再对temp进行真假判断,即:!(temp&(1<<16))。temp&(1<<16)为读取SysTick->CTRL中的第16位是1还是0,如果第16位是1,则为真,但是由于非 "!" 的存在,所以 ! (temp & (1<<16))就为假,所以此时do.....while循环不再循环下去。如果第16位是0,则为假,但是由于非 "!" 的存在,所以 ! (temp & (1<<16))为真,所以此时do.....while循环将继续循环下去。等到第16位变为1,也就是定时完成标志位置1时,如上所述,while循环将不再循环下去,此时计数器计数完成。. |- A. o' A& u, ^
4 u3 f, {8 j1 t$ h' ?2 G
3 ]5 c* ?6 r/ L# H+ v" q3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?
8 \" o# `* X4 F2 [
4 ~5 \$ s% C4 \* |" n+ o0 O) S 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。
]( x% m: C; Y+ }* U/ b: B6 g# V3 {. C" Z( N& G P
的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。
, `4 s r/ ~9 A6 l; j! a# H$ M
' G. O6 b! k F) u
! Y, t: q' j, }所以,我们改下一下程序:8 g$ X4 `+ ?5 Q( u6 H
- // 定时Nms程序* h6 z% z- w, e0 K+ ~3 s
- void Systick_NmsDelay(uint32_t Nms)
* x0 ]1 ]9 S% O: ?# `7 e" x1 ^" p2 [ - {$ R4 c" ^: @* W$ A
- uint8_t systick_flag=0; s' v6 f# o; S- q3 {, p
- uint32_t temp;
" o/ w6 Q. F; J5 [ G$ S/ X - " N- Q; Y4 J a: R
1 E9 [, e3 |- M0 T- B- while(0==systick_flag){- I# P& A1 G& W- J% p/ W
- ; P6 e' Q" q- \! L) Y
7 T) i c# Z/ N- if(Nms>1864){
g% q/ C8 ^( O" p7 c( o; k$ R% J - SysTick->LOAD =1864 * fu_ms;
$ l s) }+ I# t2 W8 k0 ?/ u/ T1 J$ m - Nms =Nms -1864;8 v/ }& `* r5 J" c* v- q. \
- }else{
: i9 J7 J1 V2 z4 q* p* B - // 2》设置计数值---N*9000) D4 n( i. K' r) N2 W! Z* I
- SysTick->LOAD =Nms * fu_ms;
9 T! @2 f+ J9 b) x9 W2 J% \ - systick_flag=1;2 }0 q9 }; O0 z1 j+ j) w
- }
- V: p/ _1 m! n/ ?
+ Y2 n$ D+ }! U
' T: ]" o2 s1 N \( }, l( E3 y- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
X" q$ n1 ^) A* x" |$ k - SysTick->VAL =0;. y! [) X- P% x7 M, z$ @
% k* u! w2 c D" ~+ |- // 4》打开定时器
: S% e& I- a$ O, \# K- Q* ?4 @ - SysTick->CTRL |=0x01;
! z) i6 B2 E# }( |* ~' X
# d7 u/ W0 r3 ?" y) t+ @# U- // 5》等待定时器结束
! j7 Z! M" m! }3 D7 `/ m - do{
/ }9 ?5 a1 q% Q - temp=SysTick->CTRL;
, Z1 w9 I# E9 a0 \) _$ S - }while(!(temp&(1<<16)));4 b, H; }2 ~5 n& N0 l1 I Z
& T' X7 _4 n! _1 N3 J- // 6》关闭定时器1 c6 ?! P+ i# E7 [1 F) t: G& o
- SysTick->CTRL &=~0x01;
& R2 v0 X' @4 J! }
& ~" d" L$ d$ k) p* Q- }
5 K+ B. J* y# {, @( b8 n( v5 t - & n- m2 `/ j! f: R: L4 C e1 O
) W3 r2 B6 G$ e" U+ [, P- }6 @ P l- k5 X$ m' V8 } E
- 1 U3 Y+ V% _ Q1 F
复制代码 6 ^1 E5 s/ B& Y' h+ E1 v
这样的话,就解决了1864问题了。
0 P% h4 k! l( O$ Z# w" m: [3 H& s% ~
% |9 ^6 i! j* ^& Z2 C& w1 c0 d4.us级的延时程序是什么?6 P3 G& y' p, R' u
- // 定时Nus程序
, Q3 E! Z, L+ f1 q* u8 L Z - void Systick_NusDelay(uint32_t Nus)
( }$ c# i# U; d0 t% A+ L9 h - {1 m0 E) e9 O4 f/ l3 c: \4 p
- uint32_t temp;$ a' a% ^2 M$ l' p' {. x
- // 2》设置计数值---N*9" J/ Y% w! H W" [) ?) P/ r4 f
- SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了& }- Q$ ^ N. I
- % Y% h( p! u) i- x* @0 Q
- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清04 r5 F ^* {! s
- SysTick->VAL =0;4 k8 O: V" @9 N- W! c% r! J, A) f
) \; W4 N, O7 o' S; D- // 4》打开定时器
/ A" K" @% K0 r - SysTick->CTRL |=0x01;
. k$ d( ]* l- F$ L; A) b$ K
6 K/ g" k; t- K- // 5》等待定时器结束
* i- a3 d! e6 O: L0 h - do{% q5 v" h: g$ b( a8 a
- temp=SysTick->CTRL;
7 S6 o! x' M+ u, p: w6 C* D - }while(!(temp&(1<<16)));
$ k) H9 z/ R7 r
/ ]: N! K# a, h. Q0 j1 R! a- // 6》关闭定时器$ O5 w0 }$ ?8 x; I- R
- SysTick->CTRL &=~0x01;
& q0 G# L1 I! c; F! g m/ n - }0 x: b, r+ k4 X- I* O0 |7 K5 L
- / k9 i" r3 V: R% Z# v$ @5 p$ x, [
复制代码 & E4 W: H, Q Q j7 q @4 T
注意:
, ^+ s$ |" b; j+ p 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?
( _4 `" R2 d& R {" T+ b% l9 K) `9 c
FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:
& y/ V% Y) f, d' a; T) x+ g) G# p5 s2 i; J6 }( |# B+ [
- //初始化延迟函数
! n0 _5 O A+ C% l& Y2 N# j - //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
4 z4 B# m y' t; q - //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!" m6 x$ O0 z+ a+ ^8 J. p7 S
- //SYSCLK:系统时钟频率$ B) f3 K$ w# @3 @6 E( L
- void delay_init(u8 SYSCLK)/ E& @" a9 _9 F$ c% z! u; c }
- {2 E* I( n% B# N6 Q0 |" G
- u32 reload;8 w8 v- V6 `" ]1 \
- SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
5 ?6 ~7 T. s) h6 ~ - fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用
4 X& a; z6 C: a1 r T) S, Z C. e - reload=SYSCLK; //每秒钟的计数次数 单位为M " x0 Z% c1 U" W: z5 ]( B) ~1 V4 Y8 W
- reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间
1 |1 w6 G8 Z" t, i* V* x6 w7 X. E - //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 " |( A8 B" G$ c t2 M
- fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 3 B& b6 d9 Q r7 L$ u/ Y l
- SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断5 {, M1 @8 N) `
- SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 & z' F- Q, ]( A# U
- SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK . y; W& o$ N8 o
- }
, T# j3 P: s# m, L+ Z
& }, x n8 L6 U4 X7 a+ r-
1 X. S) f. G4 W0 y$ c# z
1 a9 ^/ O4 J5 _; _- //延时nus
- g; F5 Y" O9 t+ v - //nus:要延时的us数.
* K" ~& o! j. C - //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) / ^: V" V2 J$ M% r6 P) Q+ P* d
- void delay_us(u32 nus)* ]4 w# Z2 C% A/ [4 s
- { 8 K( R) H& |/ x# P6 Y3 x) W* Z
- u32 ticks;
8 G, w" G. N; |0 p f - u32 told,tnow,tcnt=0;
2 M+ ^4 x9 |% M* }: x - u32 reload=SysTick->LOAD; //LOAD的值 ) c; }4 ]/ h3 \3 p$ T7 P: d
- ticks=nus*fac_us; //需要的节拍数
/ _0 b6 v ~6 h! C8 n0 v - told=SysTick->VAL; //刚进入时的计数器值$ v9 U0 A' Y2 C* G7 p
- while(1)
2 G. e7 n, `* j( Q; c. k - {
- }5 T. [8 I1 s4 } - tnow=SysTick->VAL;
' J5 E t$ Y: w& n* b - if(tnow!=told)
0 i1 @" H6 V( Y8 M4 y# X Y5 G - { " X4 v' |5 A' @/ P) R. l9 I2 Z9 L5 a
- if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.# B) X" e4 e7 L4 O" N
- else tcnt+=reload-tnow+told; ; K) A! y0 G$ b- b- T! k
- told=tnow;( [' ^. Z0 H/ O/ |
- if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. ^1 f) B8 A; I) D* U: u. R# \& Y
- } 8 w X8 h. g& M- [
- }; ' R: m4 d+ e; _" ^, |8 Z7 a9 w2 I
- }
3 D" h9 k% Q: }
4 |& i- G7 W4 G
& c4 p& @% T; d4 |; V7 e4 U9 ^- //延时nms
% B8 Q" \3 l8 ]7 f' v) B" N! I( W - //nms:要延时的ms数: v5 i& ~/ r2 e Q1 q
- //nms:0~65535& h, \) a# `0 U: v2 W1 h
- void delay_ms(u32 nms)
3 f) ^6 P2 J: J$ ] - {
& f5 p( ^, j2 _- S1 k: h - if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
2 m0 K5 I1 |9 X2 s4 ]/ t- ^ - { 6 x( \1 P% _) c! C3 K
- if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
3 ?3 u2 M" g6 z5 ?/ U4 N& P - { _! a$ c6 C/ ~$ w2 [
- vTaskDelay(nms/fac_ms); //FreeRTOS延时- U1 N7 h# [' n% q
- }
8 O) _8 x& U7 @1 l3 H9 T% c - nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 9 A4 t& |/ Y) ]3 X2 `, X
- }3 O1 d% N" P4 d& ?
- delay_us((u32)(nms*1000)); //普通方式延时
: N% I) ?- d) g8 i2 ~' A' m/ N - }
3 t2 Z p0 u$ l/ Y - 8 o' _4 j, H& S
- //延时nms,不会引起任务调度" m0 l0 C4 t2 ?' U- V
- //nms:要延时的ms数
. M; O! m+ V. C) N - void delay_xms(u32 nms)
" V$ x$ x; J4 j+ } - {
/ [8 N1 `6 E! K! H - u32 i;
) i2 Z! |: {' ^0 f - for(i=0;i<nms;i++) delay_us(1000);& w" Z, Y$ j# I* ?
- }% k4 I$ r. g8 |) Q, Y+ \0 O5 x
- ————————————————
8 l0 f! Z. W$ I3 G, V: C - 版权声明:本文为CSDN博主「无敌小小雷」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。, D: h/ x e$ X* ?" t" w; A) }& G
- 原文链接:https://blog.csdn.net/qq_39577221/article/details/125153214
复制代码
: \. h I _2 d" \# j————————————————$ \- G! Z& P) w( Q
版权声明:无敌小小雷
; Z/ V$ M: p% a% h/ A5 {4 `2 K: H如有侵权请联系删除: e6 r* d0 o( S$ ]( B! K
6 G& O; b: M; r. T& Y" u |