滴答定时器:
, v* k, A% t7 ^ 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。6 D$ h& u" ^8 e- ^0 o
F/ U( n# p7 k* r+ d" L# Z滴答定时器systick是一个内核外设(即:内核自带的)
- m5 p3 _- `8 o, e. S- N: ~1 p) n, A4 U; c% X6 O
Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。
9 }$ x# [) p" n) B, y9 C& F, y5 P& E; y
Systick的作用
, ]9 }# k; D- N1.产生一个精准的定时
0 H: h4 g5 U% F, r" T, y( @/ I2.FreeRTOS时基由Systick提供3 e* K" E; x( ^- v/ p2 C! C
+ y5 X5 w% t/ @: }
在说定时器前,我们先来看一看如何计数?
8 V s5 t: Y" J3 _配置滴答定时器:
! C/ f& \! z/ S: ~+ f2 P0 d" T
7 I3 V# d6 G" q. u6 F- u
( I6 S% e1 X' T' X2 c3 O) I; F; m
+ f1 f+ k0 b0 E1 C6 v 红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz
B1 c. c: {+ |( z
! D! x e$ C0 ]/ X6 g/ W配置滴答定时器我们需要下面几个寄存器:% E4 b; {0 K8 ?6 E$ ?/ z
4 @" X- x1 v$ h$ y$ ^0 _% }5 }
2 R1 K" ^8 o9 T8 u) L: B
& M7 z4 P; z3 ]$ n3 ~9 S: B 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。5 O7 w* M! D# u
; X+ _4 x9 v: t! x! m8 _$ @6 o$ d
, k9 d, _* T) t8 z4 J" P$ Z4 Q- @
+ R% r! w3 Y6 |; h: l8 r% l- e6 i
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF
- l# }7 n0 f7 h( o. m
0 Z$ B. v2 A! H/ j" d即:1~ 也就是:1 ~ 16,777,215,如果填入的定值不是在0x000001 ~ 0xFFFFFF这个范围,则无法定时计数。
$ _9 J* H) h) H/ @# t# l0 X9 k4 V* m, V3 o% m* S
- ?. A& ?! N3 I' |0 I" K+ c- K! l* V7 C
编程0 p5 H; L" G% z) j1 Q1 ^8 [
产生Nms定时编程步骤:
1 J+ g+ H3 o5 o R/ k1 L0 v 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)# Z, ~) `5 j* s( C+ |: i! ~
2》设置计数值---N*9000
, S |" d$ J: N5 g$ f: B6 ? 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0
+ u! _/ H* [0 `3 O+ h# C4 ]5 q# H: R 4》打开定时器
: f# w; I, x9 d+ _. m/ S0 U 5》等待定时器结束% i5 `* e* N6 \9 L( ?/ ?
6》关闭定时器
4 t# W& J/ q! F
: U, F, ?5 S; O; ^7 {) S代码:
* v, x( t$ Y' g- a- uint32_t fu_ms; // 1ms需要计的次数 u u* H; s+ d( v. `) F
- uint32_t fu_us; // 1us需要计的次数: x6 X6 }( M/ g5 r7 [9 P0 p
' Y' x! N+ m/ N+ d0 Z) _9 K$ C- & L, h- _/ Z, O; Y
- void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz- G" b# W2 F8 b% _' }! r2 i! ]/ ~
- {
/ w. H6 m7 \( Z) f. ~4 O: y - // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
0 k& F6 T, n n, B" p4 B9 { - SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置0 O4 s4 R. T0 r
1 o4 }* B* v: x" |& H+ B+ {6 d8 ?- fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz8 v3 `* _+ s: t$ Z% |1 V$ m$ b
- fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms9 i1 V/ E, @6 i7 M& R
- }6 R: Q2 r8 D+ X; n) `
- / z; |$ h& i- t) ^
- 6 |9 z: ~% O: w
- // 定时Nms程序,ms级延时* h& y% {5 T& Q' R6 k) ~9 O
- void Systick_NmsDelay(uint32_t Nms)- i. _2 i. S$ ?. p5 l5 g" {0 u9 Y& F
- {9 }* ]/ @) O5 y: T: ~& j% \1 g
- uint32_t temp;
2 O5 J! T9 u8 p - // 2》设置计数值---N*90002 n) l8 O% s! U0 Z+ ~! A! M
- SysTick->LOAD =Nms * fu_ms;
6 V5 d# H# B( Z4 j- R9 D0 n - 2 c1 M u4 J3 A0 z2 I
- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0
. c4 n& s+ \5 n# _0 q9 o - SysTick->VAL =0; //清除标志位
% A* l9 k, R* A( u
2 M$ h/ L) _; C) E4 E1 ^4 |$ d. h- // 4》打开定时器6 S$ m+ M, r* l9 x# {3 G
- SysTick->CTRL |=0x01;
8 R7 A# a1 M7 N! h
/ ~/ v# ?& P! W9 ?2 X- // 5》等待定时器结束
& X4 F9 b0 t' Y - do{/ k. ]! Y& u( |! S8 P! V( E
- temp=SysTick->CTRL;
, c& F* E/ Y8 t( k6 v2 q V - }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成- c+ n7 Q/ p" t9 c0 [9 z5 G, n
l9 p1 U1 m5 W* E/ a$ E, D- // 6》关闭定时器
) H& Z! e8 Z6 V3 k - SysTick->CTRL &=~0x01;
! u# u- h: J/ d - }
5 t* `4 M9 c0 Z5 t4 M4 K: T
; a7 y2 p% n8 M. N8 F- v2 ]2 L
复制代码
0 u7 |0 Y' I6 i+ u# H解析:
% e* R7 k3 ?8 w6 p3 {& A2 j1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写?/ [7 H/ k8 |6 R! O2 c1 r! q
因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。3 C- O9 ~) d( ~' g/ U
5 v! j" n9 ]; C2 `7 I# D3 T$ F
2.为什么程序中的这部分需要用do.....while而不用while()?
5 k4 u: V- y% s9 t: O' e
/ i$ t4 d5 m4 e) ]
% H4 F- B' T0 [+ N8 D# q5 P
2 E5 s+ H- c$ x2 t- j4 B* C+ ?2 X 这里需要用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循环将不再循环下去,此时计数器计数完成。
9 S7 A# H% d9 s/ R, O8 x, J$ `7 p. W( C' ` O( }
% C2 i+ g( }3 ]7 D% G3 e8 l2 ?3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?) E) R; {* @- p
# d3 W3 v- m( Q: Z; ]
由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。
: P6 T* [5 Q4 c7 n
( T" R/ X4 B) K$ d ]3 _ 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。
9 j$ w1 ?0 ?* j" l' G- m
4 z! f- ]) T+ r6 W
. @, C+ y$ a4 C2 c6 C$ T5 x所以,我们改下一下程序:- \; u2 M8 b0 d3 e
- // 定时Nms程序
0 A& F! t F$ ? y0 s9 I3 n - void Systick_NmsDelay(uint32_t Nms)
) U' F8 R& p/ s9 p+ j- J - {7 Y) d+ b8 v- Y; q8 U4 W/ y4 v+ d* r, ^
- uint8_t systick_flag=0;
. g& ~& Z" \- G+ a - uint32_t temp;
7 L6 L+ a3 V, x - $ x, s6 D, s) B: k. W( {% ?5 g( H, m
: A3 P. g# ^3 h- while(0==systick_flag){, I1 U/ k, L9 L6 f% E! k1 V9 }
* a" u7 i3 {2 J3 W% Q9 S$ b. E- ; }7 [2 F! s# y! D3 Q; \
- if(Nms>1864){
3 j. V2 |6 y( h6 u( n6 r, v8 S - SysTick->LOAD =1864 * fu_ms;
( r4 l9 p. ^& n* W2 n5 E - Nms =Nms -1864;( E5 w& W I, }; z* o5 X9 ~/ F7 }8 U# C
- }else{
8 A) w& j: U8 D5 Q - // 2》设置计数值---N*90001 E: @* Y6 ]6 v, p7 C! n$ u- z
- SysTick->LOAD =Nms * fu_ms;3 I6 S: Y6 R0 @, h: w; w
- systick_flag=1;
! {% L0 L( j+ e( g) u - }
" ^2 ~$ I7 u Y) ]/ B - $ g! n3 R8 G) d, @& J K
- 1 W* P. @ ? k
- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0, I8 S+ T8 a5 A& S
- SysTick->VAL =0;
2 h$ b# j2 w- t% a$ U
* w1 \7 o ?2 O" F8 K- // 4》打开定时器
' D0 ], h' `) T4 H: r/ J; e4 E - SysTick->CTRL |=0x01;
8 G9 W' c1 B4 v; Z1 o/ `% e7 W - , p1 Y/ I6 ^% x
- // 5》等待定时器结束
7 N4 D+ k3 j% G - do{
0 l3 _6 g; e/ R( I - temp=SysTick->CTRL;
* v$ p& b0 q# |* ~1 c - }while(!(temp&(1<<16)));, | T4 ?# \. a' u
/ X% C; x; B: h% e6 h d$ q- // 6》关闭定时器' q2 i6 O, Z; m; t7 U7 \- H$ S4 P
- SysTick->CTRL &=~0x01;
, R- E# m$ S& [ ~& S% k% _ - 1 J) {1 w9 E) a$ K" X
- } L' N7 [7 \0 @: E0 U/ d: O. L
* M2 c( B4 k, Q; s8 o( `- , Q1 ~8 s2 |6 v1 }7 P
- }* ?& y' e9 X- O ^ ?2 [
3 V: g w" U+ C1 p ]& R
复制代码
) A2 [8 b( I0 ~! h3 ]这样的话,就解决了1864问题了。
* N2 @2 |5 O7 w, ]2 y, J" S" A0 z6 A3 V( u
- D5 I5 S3 Z2 |8 P; E4.us级的延时程序是什么?
9 _' ~" E, K+ f3 {4 x1 b- // 定时Nus程序
4 d- ^8 ]' D3 A! F. a( p - void Systick_NusDelay(uint32_t Nus)
Z3 B8 r/ J0 k# |/ g% R- l - {
* G3 y" a" N- S7 r; j1 v - uint32_t temp;
* H, s; d/ }! D& E( C) g - // 2》设置计数值---N*9
( E& R: j. {$ D, a - SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了# m! l5 z6 ?% [9 K" A+ J5 K
- 6 }2 w" V4 D% Q( y
- // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清00 I; ^- a, E3 q' W; X$ B7 [( o$ U
- SysTick->VAL =0;0 p. k! }) c3 W5 u
- 4 o3 Z) V7 K7 H5 e9 r& o" ]2 p: u
- // 4》打开定时器
3 ~) `0 m4 x8 j$ |$ b5 [, p/ b- G - SysTick->CTRL |=0x01;
4 X. z: ~) a( P. J: k - 2 R7 l* v$ B, [+ D1 _$ R: W7 W
- // 5》等待定时器结束0 c, ~5 h* D7 _: M3 o
- do{
# }1 Q; E7 N' e# c. _+ M7 n4 x - temp=SysTick->CTRL;
) r6 Z* B ~% S; _6 z; b+ M - }while(!(temp&(1<<16)));7 T3 i+ D9 y2 u9 O8 ^( N; S
. N( i# }) A" _- E1 T& P- // 6》关闭定时器
3 \7 `4 p( w8 E! i8 E# M - SysTick->CTRL &=~0x01;2 J# _7 }. g8 L$ E+ m# e+ t1 Z- f# V
- }# h" F! X) ]( e3 m: o
! y/ Q( \0 T! w5 X
复制代码
9 ?/ I! \, L1 U: a' A注意:3 _/ r2 K. `& m; g; X- @
在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?
. Y3 }+ ]7 C2 R, w6 O9 ?
1 B# v( o% J x- n+ Z: D5 u3 }5 O FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:2 o! o8 Q; e3 o
0 i' R2 A' g$ _5 H7 D9 c- //初始化延迟函数
3 s" a. ?; k6 t! n! }6 \1 ` d - //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8( R4 O$ U* y& e' q3 y
- //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!1 K4 v% O; \, A3 k, w0 n% X) B+ w* c
- //SYSCLK:系统时钟频率
4 A& S6 W5 M5 Y, f- _% j* } - void delay_init(u8 SYSCLK)
' h U# v, W) q/ c1 _ - {
4 ]3 i- G$ L; P1 U/ | - u32 reload;
3 T# Q% Z6 C. w - SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
0 N3 G/ l. p+ B5 l- z1 T8 G - fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用1 X* Y) p# p, i: d; F
- reload=SYSCLK; //每秒钟的计数次数 单位为M $ w3 |6 ?5 U" v6 ~8 a# l5 s( b( z
- reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间. `7 T( f+ q- j3 c3 f2 o! s
- //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右
6 r* B: r b: S5 j! v( }* g/ N - fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位
6 M( Y2 J+ U5 p, n& ~4 G& [6 P8 X - SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断5 E$ O& e& i# _3 ]3 p/ U0 P
- SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次
* H9 T$ T) e3 \' d - SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
6 k* [2 C, `9 o# A+ c3 l - } / _ A- }2 W! d2 ^" q; R
- @. z+ M0 F4 M. Q! e, G8 \8 Z
-
8 d8 b* R9 I. r6 c1 T8 Z" ^ - : E) q8 \3 l' s( T7 G0 Q
- //延时nus7 {. h: f( j. B z% ~0 A' r/ b
- //nus:要延时的us数. 0 L; N! {- d; [
- //nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
0 C' R" w: v$ H7 Z9 i9 d - void delay_us(u32 nus)4 E: J* r+ v. _: m3 j- H# W q
- {
1 T j1 ]8 H+ q9 r" | - u32 ticks;
6 ]! e2 u. [7 O! H - u32 told,tnow,tcnt=0;
( ?9 _& u* L9 \- E6 r1 ~ - u32 reload=SysTick->LOAD; //LOAD的值
1 d- H2 ^$ Q6 b N/ o$ ^ - ticks=nus*fac_us; //需要的节拍数
6 ]: \0 J/ f* n& z - told=SysTick->VAL; //刚进入时的计数器值
: f( f) \ `4 L- t - while(1): r4 i# C2 F. c0 o
- {
) q4 `/ q/ ^* N0 ^& E4 \ - tnow=SysTick->VAL;
- \7 X8 V; `- o* Q$ B - if(tnow!=told)' ?* D/ i, T6 V: o7 I% g
- {
' [1 d1 x( U# ~/ c+ v - if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
6 P6 K0 I. v: s0 L- v/ Y - else tcnt+=reload-tnow+told; * z3 W, f M) N$ P, T
- told=tnow;" l6 X9 ?9 |! @7 U+ _
- if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.& C* z1 s$ E* G$ m' [2 r
- } & ]* v8 B* ]2 \) F( l `5 w
- }; ( V$ D Q" o1 h& @3 m5 |
- }
1 d" h% t0 H$ { c7 I - ! I% ?: ^+ R$ ~- Q E# S+ h) o
; r( O6 Z5 W d: r) ]- //延时nms* ?# R+ i# J! L' {: |
- //nms:要延时的ms数( B! p0 B/ v1 K
- //nms:0~65535
/ s. T$ _) c+ A9 ]9 f. e3 A& \1 N - void delay_ms(u32 nms)
" q- }$ ~% L7 L5 E2 _ - {
- ^- ], `! c- d. x7 W# d+ A$ m: t8 h9 M- a - if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行" `# z9 U5 a4 I4 I8 \% h
- {
% ~) }9 U: z' l1 p - if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 : u y& k3 y- w; W2 y9 Q
- { , b& X2 h$ @ r8 A
- vTaskDelay(nms/fac_ms); //FreeRTOS延时. ]% [) k+ L, L
- }
2 H: G$ A9 y* Q8 J' L3 C1 o' d% ^ - nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 0 y( i: ~& I" d
- }5 |) l: ~0 ~' V& K' Q) Z! L0 P/ G6 p
- delay_us((u32)(nms*1000)); //普通方式延时% p$ o4 f2 z6 @" I0 w
- }/ g& i. D" Z4 _: X' f) E+ [
2 s' n0 g; q7 R* `' w- //延时nms,不会引起任务调度1 @0 b( k# Q# t6 Q6 C( Z1 r
- //nms:要延时的ms数
3 N( w/ O3 A; i - void delay_xms(u32 nms)
) J3 C s5 a" g* E& b, m) W- C - {
7 l Y0 V3 b6 T& U+ a# u - u32 i;; F: q; K$ g8 Y8 l, n
- for(i=0;i<nms;i++) delay_us(1000);
% R! J: \' X) ]# B3 a; Y- H2 I3 N - }' ?/ o: d& C/ G9 O6 {9 y9 b
- ————————————————
/ J a0 I2 E# m% ]" h$ v2 S - 版权声明:本文为CSDN博主「无敌小小雷」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。. {+ \4 [) a4 u! ~6 x
- 原文链接:https://blog.csdn.net/qq_39577221/article/details/125153214
复制代码
$ ?) C4 v8 M7 @# L+ A5 S————————————————
/ z2 b1 _ K8 Z( B4 c2 a" z版权声明:无敌小小雷; b2 F8 @2 Q3 z+ ~
如有侵权请联系删除
2 Z4 t$ u2 i( K# F8 N* q8 W& f4 B9 Y$ h$ Q
|