滴答定时器:! x# ]5 L5 p9 e2 }3 m$ Y
定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。
/ a% d- s8 w) q* A: n% \8 k' u: _" j0 i1 f* S4 V7 S* |
滴答定时器systick是一个内核外设(即:内核自带的)
$ r. x. T0 n' v* }4 d& @
e. U$ y2 X9 j U7 E3 R' P Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。5 [/ e) I$ G( {# H0 A# D: m$ X
4 y/ x6 g' Q! M. o% i6 y, x/ vSystick的作用
/ Z7 N8 S+ b. |; i& s& Z* C) V1.产生一个精准的定时( j# \8 W. ?* Q6 { C& ?
2.FreeRTOS时基由Systick提供
2 ]: b0 d! \- j' T# D + \ s# V! c4 Z& I r- W
在说定时器前,我们先来看一看如何计数?
B; r0 P) ^. J) g
配置滴答定时器:
. A! l I* P( L; P) I- ]( _ `6 H+ v) {- L. x, L! h
]* }% B* x8 }3 J0 C
0 l% n/ Q, h2 `# R' \, H* U
红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz
+ _! \. E2 Y3 u% F8 b
5 @/ G+ t( _0 J% K2 m5 i配置滴答定时器我们需要下面几个寄存器: H. @# @* _: p6 z& Q& e
0 \. x$ U6 {" s& }: z
( h! w) C4 w, _/ Y. P
* M9 C' u/ g8 D x4 p g
注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。
( |7 u: m1 F% W: P/ u. ]: m, h
5 ^1 U9 F& @) ?6 j1 X
' X0 N6 r A3 d! B7 ]! P+ S
& _* K8 R0 f* S1 |) x% p/ W
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF
$ T1 P5 a- G' ]$ ]9 ^' P2 j6 {* g0 v9 A+ o5 }4 i1 }
即:1~ 也就是:1 ~ 16,777,215,如果填入的定值不是在0x000001 ~ 0xFFFFFF这个范围,则无法定时计数。/ Z4 m. u) ?9 C1 a/ T: b" w0 f" N
- n) ], v) x9 j' S* Z
3 f, D% k1 w5 F# S: g' c5 _
+ d3 N0 C9 c1 _# q7 p编程
1 Z% A- X- ]6 a- z产生Nms定时编程步骤:2 j1 C0 w0 n& X! e6 ?- U
1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)& h, e/ ]2 h. K/ a; H0 T& w
2》设置计数值---N*9000
2 h+ r* ^8 A$ c) _ 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0
/ N* e9 K& O2 g4 I) H2 } 4》打开定时器
: w+ i9 S* u0 x 5》等待定时器结束/ P: E! v2 T# g
6》关闭定时器
1 o1 U+ ?% d6 J) o% t$ _ J; {! O. s8 o# ^- i& U0 R
代码:2 j4 f- k# g) S Q# u
- uint32_t fu_ms; // 1ms需要计的次数6 \5 k' `, H, E+ @& D R! U
- uint32_t fu_us; // 1us需要计的次数
- T( M0 j9 m y1 b. z - 7 N3 K' G7 N1 S5 A) w% I
- 5 K4 d# {" H/ J
- void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz
9 a5 N. g/ |! A' n7 j# O" U - {; y6 Y9 i M6 e5 Z3 }. i
- // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
$ _' b) o5 V; h1 n# i - SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置/ T6 h9 s* k$ e7 V( i! Z
4 C0 B! y) E) n/ m' o- fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz
4 L4 G4 _+ ?( D5 G4 j9 b - fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms
* U( k$ i8 h# ~9 Z) U7 d - }
X' S' v& m) V/ x1 T+ J
$ d# l5 G! q! X( p! B' t
6 y+ P) l4 M; F+ |- a9 D- // 定时Nms程序,ms级延时
6 P" o$ P# v/ O' T, E - void Systick_NmsDelay(uint32_t Nms)
O" G# _- { F+ j& [0 M) h4 P" o - {
3 Y: l0 J, ^) e7 o - uint32_t temp;- R6 r: M% e1 H
- // 2》设置计数值---N*9000
L* y8 w. K: e4 y2 j - SysTick->LOAD =Nms * fu_ms;
J/ |. K$ u' E1 u) F; A
* ~+ Z" B: A( X, h5 b- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0/ T& R/ a3 {1 ~3 C
- SysTick->VAL =0; //清除标志位4 z3 ~% J2 ~$ J1 l- p N
D( `1 j3 y# Q# l9 Y* }/ S) M- // 4》打开定时器$ I# b( V( S$ U7 L0 K
- SysTick->CTRL |=0x01;
* X- q% Q9 K$ \6 L" b+ A3 W7 t6 e - % b0 H! K) o$ d8 k- Q
- // 5》等待定时器结束) d) A0 |+ d# n! Q
- do{" R% {( V0 l6 Q3 x" ]" ], I
- temp=SysTick->CTRL; 4 i4 L; y) R) ]# N6 b5 O/ y
- }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成
; q7 p% K0 c1 r7 V# C - " }+ O0 I7 B' c: z- |4 U6 C
- // 6》关闭定时器+ r3 t; v2 b# r6 x$ `! R
- SysTick->CTRL &=~0x01;+ [9 x t; }- F" Y% e4 w7 @. i
- }
5 I8 d8 V0 o: a3 f5 ?3 _6 r& B* L: e - & p. n7 F8 \3 ?& n5 h
复制代码
& |; G! t, N# @解析:" K% Q! _/ W2 J% G% d8 k8 V: s
1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写?
" W; n! Q' S. f3 d 因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。
3 J: _+ c8 | P# R3 l! M( }
6 ^! U) r- B( h+ j+ ?2.为什么程序中的这部分需要用do.....while而不用while()?
5 \, O0 N* ]" i( k% ^$ z* s0 W) M- R% @0 F
; C0 V$ n4 B( j, G/ r
1 j3 y) y4 B8 Y0 j2 y# s 这里需要用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循环将不再循环下去,此时计数器计数完成。% }8 Y3 R/ V- I5 ]' Y5 S5 v
1 a4 r/ U' P% D3 R* j2 D
& _' V) a3 y; d% k/ g/ @8 u8 O3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?
! ^" R/ o% w% z& y' i$ z9 Y; O
1 H q4 _+ O0 _1 n! @7 q 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。
* O. H$ s0 e$ k, g; f2 A4 E: U6 B& y( G5 |
的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。
6 O g) |4 [# G5 w! j8 p; n# |0 x: X; M/ h6 c- D' \/ p; X1 l
" Y6 \+ a" l+ e5 Y0 w
所以,我们改下一下程序:
8 D, O( S& W+ i- U) N- // 定时Nms程序0 t8 Z. o+ t/ }
- void Systick_NmsDelay(uint32_t Nms)
# M% L- L$ N1 ?# A$ o" { - {5 Y% y7 K- e) h+ E- z
- uint8_t systick_flag=0;8 F7 q; {( u& \! v* ?$ I6 G) m( ?
- uint32_t temp;* P. o/ r0 j+ s. E1 \3 U6 j G! }
$ ?, \) h+ B8 n- 8 v' {- E& g% y! Q& F. h
- while(0==systick_flag){/ p7 g' J' s, n& C( s
- % g( t) ]5 p, a' f' m* w3 a2 b( u
8 ]' f+ R5 e$ g2 A0 i0 z- if(Nms>1864){
3 A5 |( c# j4 O% j - SysTick->LOAD =1864 * fu_ms;
/ k: V# i" c$ B - Nms =Nms -1864;
2 x- I5 ]. R. o0 U! x - }else{
$ j; n! q8 c. s9 _ - // 2》设置计数值---N*9000
: `/ i; H# H* X& K/ ^) K8 [ - SysTick->LOAD =Nms * fu_ms;
~- A* W- M( B - systick_flag=1;
6 b) c+ E+ Q/ R# t" i% o# R - }
2 W9 H5 ~% _3 Z* _5 R - 5 f f2 g3 [# }" z; z9 i# \
$ e4 { p; b& w; ~ ^- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
# ~9 M, f: J3 h6 \ - SysTick->VAL =0;& C$ N6 h' ]( U& ^7 q+ {" e
% e, t- j8 S$ K" e* ~" w- // 4》打开定时器/ d& s- Z7 S! f, e. j" T- s
- SysTick->CTRL |=0x01;
: A$ X9 L5 a$ K# p
6 D; \$ d! K8 e$ [- // 5》等待定时器结束7 I' p; `! \8 x" S
- do{5 G; b" L1 k4 w
- temp=SysTick->CTRL;6 U; A7 m) ]1 K
- }while(!(temp&(1<<16)));
. i% I |4 m" {1 v- j - % T$ S- n7 a9 v! n" q1 _0 O; k/ a
- // 6》关闭定时器+ W: z2 w! \) _9 `) Y5 j6 x9 Z
- SysTick->CTRL &=~0x01;: L7 k5 U6 j1 t
- 3 b0 Q) \0 v$ O
- }2 d) \( L8 f6 D6 L- R3 _
- s+ D! y" ]# I3 p/ v0 q. C
" o$ O3 K6 W& t" N6 M: m- }
. T/ Y n L% z* K - : ~3 X8 k% l, @' f2 [9 D) U6 K0 _
复制代码 : o& s2 N h7 y" e2 g, N4 |
这样的话,就解决了1864问题了。9 Y J+ Z& W/ ]6 S5 m( L1 I
. w& p: {$ z8 K5 J: v7 I7 I
]; ^; ~- p' _3 w3 S* Y; x" h4.us级的延时程序是什么?
2 D+ C5 V' m4 l4 C- V- // 定时Nus程序
+ l# i+ g* _8 A* v. N - void Systick_NusDelay(uint32_t Nus)7 @, G" v) a9 W$ D- c. a
- {5 y9 ^; C# a$ X
- uint32_t temp;
5 A7 c4 B8 y( C) ?" T; ^; e - // 2》设置计数值---N*9
/ [8 Y4 e. @ v% X. a - SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了. ~- U8 q0 A4 T- [* o$ {
- 0 C9 ~' u4 @/ k+ _- J# z0 z4 a3 b6 ^0 l+ E
- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
6 Q9 r% `& O j- Z7 j - SysTick->VAL =0;
4 s1 x) W+ h6 X, ]* ~7 H2 U
$ |% Z$ }3 ]+ t9 b- // 4》打开定时器, X4 F2 b6 M" ?3 m K1 k2 Q
- SysTick->CTRL |=0x01;
1 }* W3 q; @7 O1 i8 g$ I$ N
% F `) D/ x/ W- r5 S& I- c- // 5》等待定时器结束
( x3 u- w. q7 Q% [9 h - do{% B; z3 ~3 C6 ~, k
- temp=SysTick->CTRL;" u/ `" s; _' s, B) \: V, [
- }while(!(temp&(1<<16)));* {- a" U7 Q3 J- i& K8 k
- 2 A3 y3 ~9 |( j" m
- // 6》关闭定时器
( |* y- V0 A6 c y; o; V5 }. ^% _9 E - SysTick->CTRL &=~0x01; c/ W ^; J" ^& b; h
- }
+ m/ J, U: t! |- A% q3 G! F - , y. ~3 o- [+ w( u6 _
复制代码
+ T7 E: U# z# Z- Y$ z: o3 @注意:
: E" w: ~: P; Z+ a2 H! u 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?
+ o6 J# ~4 W# b9 j6 z) f& T) j" F& a0 ?$ L
FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:: \/ ~2 U; _0 U! v I1 L
! p8 C S, u. S2 v" O H6 r- //初始化延迟函数
, C4 A! B2 N7 P3 d - //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/80 i; z4 u! W8 Q1 _
- //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率! @. z5 `* r8 f, W
- //SYSCLK:系统时钟频率
$ F, x1 l3 l( P0 y - void delay_init(u8 SYSCLK)
, |/ `" x/ D& p - {
4 Z3 i$ C' A m: D - u32 reload;1 k( q' M: |$ Z# [
- SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); , h2 H$ Y$ a P
- fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用3 @3 j& Y7 S9 [9 A& ~
- reload=SYSCLK; //每秒钟的计数次数 单位为M # n8 ~3 k9 V8 f* ^+ f2 }: q
- reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间
, {6 P3 Z ]5 _* {* H - //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右
8 n+ a& o( C1 c4 G$ G - fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位
$ u# w# m/ J2 Q9 q' y1 L Z - SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断, c- y1 p# ?, t/ s
- SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次
6 l, a9 V8 Q4 I$ v - SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
/ P& t* J7 c. N5 V - } % W9 ]0 n, A% \8 V* p5 g" L- Q
- `. s5 Z; ` A/ q2 ?7 D- % j5 C3 I3 j6 E. j
@6 D) z9 \& ]4 Q! N7 h- //延时nus
9 D {$ e# c" G - //nus:要延时的us数. 1 u$ j4 Q x. q Y4 u
- //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) 4 w' U/ o6 i# D$ S/ D7 Q9 o" l
- void delay_us(u32 nus)
# z- u* I0 A6 P# R6 V+ F - {
: ?7 y) n4 I/ r - u32 ticks;+ i4 _: Q+ g& I4 {
- u32 told,tnow,tcnt=0;
, K* M0 V* |8 I9 V' d - u32 reload=SysTick->LOAD; //LOAD的值 3 m% N" ^/ a/ f9 b2 ^4 o
- ticks=nus*fac_us; //需要的节拍数
+ k6 u7 M# U! C9 j5 S" d! R, s+ G - told=SysTick->VAL; //刚进入时的计数器值$ w( G/ T: I: ^$ Z; y, j- e
- while(1)
% N: y- |5 C) W6 Z7 Y - {
" F2 L- k) u3 ^4 ~6 f9 k2 n - tnow=SysTick->VAL; ! ^. r2 f: E( ~# l2 O/ L E, I8 x
- if(tnow!=told)
% V5 D( \5 F1 `4 S - {
7 h( {5 ?/ b) a3 k) g- W7 M% ~ - if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
~! M8 I$ @! V2 L% z5 l/ X3 {$ z - else tcnt+=reload-tnow+told; 4 v/ ~, E. K! @6 O) k% \& w
- told=tnow;& V1 U" O, Y& t3 a7 E+ F
- if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
, n" E6 [) K% j - }
, {: L* j5 E6 q# j; ? - }; 9 T" _2 `1 {5 a5 B, Q
- } L0 p, l3 K& L5 c4 A8 E
( r9 _2 h0 H: F* E3 I$ I% e
2 ?- z7 I; j0 e, N* J+ ]- //延时nms
# A9 g1 |4 @& g2 P& U1 E - //nms:要延时的ms数0 J! L! j( E7 X" h# P
- //nms:0~65535* _' h& m1 E! F1 r
- void delay_ms(u32 nms)
8 o, V" e9 j' Y - {
$ v1 L$ Y( `7 J' P& A9 S - if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
! b/ P+ D2 \& r# g - { 4 T# O- P; `3 C$ o/ B
- if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
0 n; t& ^' I) d8 C - { % U2 v O% f* {9 J1 C7 Q) w
- vTaskDelay(nms/fac_ms); //FreeRTOS延时/ a# s3 T2 I; f4 b
- }$ d3 _9 _, ^4 h( `
- nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 ( H5 {. @) J) E4 i! V5 a% V- {
- }; e+ ~, C5 J8 v( k3 @& }; {0 @
- delay_us((u32)(nms*1000)); //普通方式延时
1 G" f$ p' t" p+ S! C+ s: { - }7 U3 y" L* m; l. @
- $ h8 A" v$ Z0 H7 [- P; \0 K5 k# F: y
- //延时nms,不会引起任务调度5 h' p T! P+ O0 J8 s# F
- //nms:要延时的ms数9 G1 [" D0 u2 R! T' |6 K
- void delay_xms(u32 nms)
Q6 q- S3 x4 f$ E - {2 k8 a5 A; t( K" M- t7 ?$ p
- u32 i;) v, R$ S' d) i- Z& ?9 g
- for(i=0;i<nms;i++) delay_us(1000);: l9 a6 I' B u7 O
- }- u1 \5 b; _) l4 g! z- f8 G
- ————————————————
0 Z1 {/ m2 i$ q- r7 q C6 V - 版权声明:本文为CSDN博主「无敌小小雷」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
. O9 ~: p+ @0 q% U- I - 原文链接:https://blog.csdn.net/qq_39577221/article/details/125153214
复制代码 $ ~' j% i2 G R! G* k
————————————————- B% T+ E* ~- Q
版权声明:无敌小小雷) l/ U/ g+ n1 S/ E3 }3 {9 k! o
如有侵权请联系删除 F, `3 d2 U' w4 A: `
. Z$ \/ X+ M( v/ B/ }8 L+ T( n; n. b |