你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于stm32f103滴答定时器经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-16 18:53
滴答定时器:5 X/ t3 I0 A' }- Y+ j
        定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。
0 b. r% }5 z- k- m0 S7 M* {  _6 ^; d4 N( u; `1 |' \
滴答定时器systick是一个内核外设(即:内核自带的)
5 k. ~% p4 X/ N; M) |! v
7 R9 w4 b' h) T) I 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册
' P/ H! |, L3 _4 M7 w《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》
2 Y" {" I1 B2 J% b2 W! S8 O
1 C' f; @, ]& v: H        Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。' Y3 Z# z1 s& J+ o8 t
# U/ i2 ]& e4 Z+ b
Systick的作用
6 I+ R8 Q4 ~$ L- B0 c5 S1.产生一个精准的定时. n; L; t2 o) V( z
2.FreeRTOS时基由Systick提供9 r- H. e$ d0 e
8 ?6 E# t( w4 x& Y. I* N5 q
MJ5}R@G1S4I9X34C]7ZVQJT.png
: X3 z/ G( j9 _
+ \5 X! o: i2 z4 d0 m3 |% P配置滴答定时器:2 |* }9 x/ q" ]* ?6 a: i  g; T
# ~6 G4 Z& O9 V3 ~# H
2e4242cbd0334a8dbcc333256a6d9fcb.png 8 D1 E* }: T( l2 ~- T9 ?4 @0 ]

; z4 _8 i9 {$ x: r: x; g  r        红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz
& {0 v) \! o, Y0 P# _
$ z. `# I8 q& Y) L$ l/ F# q7 }配置滴答定时器我们需要下面几个寄存器:
* v2 U3 m3 H. T+ Y* l2 |0 v- u) J6 h; `- E  ?+ w5 L- V5 i5 x; C
fb7ea8d208f14b14975bb8c2c2b8daac.png & u1 e) x4 z% F7 d" [0 k% b
4 r% g4 o8 Q- [# o; ?5 L
注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。: S: T2 l& J4 B, z( ?( S7 ~& e

/ t9 Z4 `% J* T1 t
439efd5325344681aa687218ae27eeac.png
0 ~4 f( z! V. Y
! g) |  N' T  f# C& d& k6 ?( W 注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF   
, W, M& V6 l. K: }+ @* f) e/ }( H2 F  w; c, ~( f6 M
SNCE360KPH_{K{XBYE)3WIW.png
" J9 V/ r, p3 `
7 X8 r$ x: k2 j
d1b8265cf617451caef6d6a879b01593.png
" ?( O2 q6 ]0 D( S6 `. V+ r- c( \. I, e# R1 j  p* o
编程) L3 F  `. s! p  S% ]
产生Nms定时编程步骤:5 Z: ]2 a1 X5 [+ H
        1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
& H: h4 _( Z) m  j        2》设置计数值---N*90001 G3 x0 {; R( \, _7 T' x2 s
        3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0% v* x( r' `$ o6 ]0 |2 e
        4》打开定时器) b$ v7 q6 V) R5 N0 W: z* p# q7 E
        5》等待定时器结束+ f6 @; z9 n6 o' g& {
        6》关闭定时器0 k7 m, l: w8 ]0 f2 L
: E, a9 ^0 S# c8 O
代码:! _7 A! \( ?) d- k' Z4 R8 G& r
uint32_t fu_ms;  // 1ms需要计的次数
: j; c' s1 C/ M0 Puint32_t fu_us;  // 1us需要计的次数
) _0 H7 g2 B9 M! r7 G, }3 _. @& ~" P! {) V& Z4 H
2 |6 `* Q" l. z9 r( O
void Systick_Config(uint32_t Sysclk)        //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz/ I" t' S) ]6 J6 ^; g! r
{
/ @1 q$ ^. o; L4 E. M    // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
9 V6 v3 i' M9 a. F0 C    SysTick->CTRL  &=~0x05;        //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置+ r: O/ Q2 g3 l) {  T4 @* l

% _* D( q; c4 D9 N# E    fu_us  =Sysclk/8;                // 72/8 = 9,即:表示9Mhz' u! S  l: m. y& C
    fu_ms  =fu_us*1000;        //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms. d6 G% J8 g" y% X) u- `4 b3 o
}' k! d9 O2 @' R& @! B( g4 @4 K+ H

& `% a. o" S6 A0 E5 c/ [5 O) e: Y$ g/ G3 P) r, z! h
// 定时Nms程序,ms级延时) _2 s. S9 Z$ r# l1 R5 ^% [
void Systick_NmsDelay(uint32_t Nms)
+ d( I3 Z& U8 M1 C: Q% C{
# G; Q* L- f1 @* e    uint32_t temp;" W0 Z, ]: W$ p2 {# D, r
    // 2》设置计数值---N*9000
' L7 [# |/ |, d1 j  L    SysTick->LOAD =Nms * fu_ms;. {6 J0 K" L$ A* W

4 M! g% `2 r2 G    // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清03 ]  B& h: k7 _, B; S* g/ p; [
    SysTick->VAL =0;        //清除标志位' S8 J7 V& T% q% M& M0 C
  {9 S+ K7 `1 S2 A# T
    // 4》打开定时器
2 w0 r- N! w' ?7 ?: A' L    SysTick->CTRL |=0x01;; Z( M- s9 O5 o8 Y
% ]) P: z) U) L. J$ }0 n
    // 5》等待定时器结束
* M5 ^+ Q$ q4 ^& r! z    do{7 k1 s% }! K6 E2 v
        temp=SysTick->CTRL;        & |/ L; y9 d6 r; n* `7 V9 `
    }while(!(temp&(1<<16)));        //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成
. d6 B" ^6 A$ U* q- W- l
& `( u; {3 u6 D# f    // 6》关闭定时器; a: y0 j, w$ k3 e7 H5 h
    SysTick->CTRL &=~0x01;
( j% S/ M: L7 ~  z5 ~7 U  A* _}
2 k: I! Y1 J+ Z% a1 i6 _
2 m/ r4 p3 S+ l) r& x) E' g- k$ Q解析:
7 X( k* k4 o& z/ j, @2 x& s/ v) {8 Q; b0 b7 \/ x0 N  l
1.我们先来看程序,定时器程序为什么被分成  Systick_Config(uint32_t Sysclk)  、Systick_NmsDelay(uint32_t Nms)两部分来写?
0 j: k' G1 e/ G/ N5 K9 u/ t7 Z
- `% }/ ^" l6 E% x: G3 B( [2 E. ^        因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。
" i1 I# i! s, r: U8 @7 t6 H( N
. u# d. h$ G) [% X! r9 q1 K6 t2.为什么程序中的这部分需要用do.....while而不用while()?0 W' ?- z: \: |# x* z
) e! `1 v# v. w! Y$ a
8df6add7dbcb4ca6b71a02a7ec75dea9.png ( s5 L8 @9 b5 |5 w5 L6 W

- [# w8 j" m# u+ o- @8 g! Z/ _        这里需要用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 `- o1 D9 u+ o- a% z/ j4 E$ b4 x  i) |0 w+ _  D1 c& H. m
3.  由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?
3 f5 H3 V$ P. p6 M* h* F7 z! \8 J! @1 O7 }) z! B
        由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即:  =  1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。% `% h( E: P: y! h

+ _5 B! _: E9 |" M7 Z2 m  }, P        的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。
" b4 w: A4 v% y1 G; |* b, d, ?" j- P
! }. J5 A/ Y, y! b所以,我们改下一下程序:  Y6 [. i7 k: P1 I: N4 o
// 定时Nms程序" {1 h) D3 \" e& j( q0 l
void Systick_NmsDelay(uint32_t Nms)
1 J- L4 x; g/ S6 E- A# y! @{
. M1 l. j# U* t; Q8 }! @    uint8_t  systick_flag=0;
/ u* `0 B4 u1 I    uint32_t temp;
) F* l4 z5 S5 c& ?4 b
; n' B) N2 z( t# s. b
$ o" T; ^  L1 g$ b1 |    while(0==systick_flag){7 i$ y% V4 b1 [  v- g% H3 I2 e
7 v+ m. I& S. O

* u3 s1 [# W' w/ u! p3 N) H5 `        if(Nms>1864){( ~# o: x6 B- Q
            SysTick->LOAD =1864 * fu_ms;
* ?% o" k# n& h. K: u  `* g            Nms =Nms -1864;
2 h) s1 o+ ]1 s1 }' l2 k        }else{1 F1 j% ^- ]* H; U; }! K
            // 2》设置计数值---N*9000
; S+ G# F5 u- R- d/ }, M            SysTick->LOAD =Nms * fu_ms;
* F& B1 G0 z7 }! p8 m            systick_flag=1;2 z4 g" A3 D& r6 i* I9 ^# }) N
         }
& z- [4 p$ C; c3 R- [( [( R! x# c, Z) @4 m& Q2 }
8 K$ [* i& `: f
        // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清04 o0 Q, W- e, i+ L2 r% R# ^/ A- d
        SysTick->VAL =0;- [# N5 X0 M" P- w4 L

+ }+ f  S7 N8 t, P  P: }        // 4》打开定时器4 P6 A& H# W1 I3 t
        SysTick->CTRL |=0x01;
# N& U& z; M1 |. Y( }5 O% z2 S6 q" u  ^  T( l
        // 5》等待定时器结束1 o' ?2 o  w6 D2 g1 q7 p( |# [/ r/ ~
        do{
; t$ i2 |8 a- y$ N# h9 R5 s$ ]: x' ~0 i            temp=SysTick->CTRL;* P3 e! ?/ E' }- |
        }while(!(temp&(1<<16)));
* i2 O1 Y: f8 _/ x) L* h1 u: B/ M* j: J2 j7 }6 ~$ s
        // 6》关闭定时器
( n7 m2 A2 f8 R        SysTick->CTRL &=~0x01;
6 c! E5 s+ |1 B3 e% F
  S  }8 p1 x: T/ j, W2 T     }
1 S/ ?( y0 ~. x# ]- _) e" U) W: W! u+ |! E: {8 i% a/ U/ T
  b( r) z% I* F$ R9 ?
}
8 l( f$ F, _% V+ I1 B) `9 b6 ^' k2 ]5 T! G5 B! F& i6 n: |: ^
这样的话,就解决了1864问题了。
% ?+ k: m. ^, W% W; Z6 [# ]
8 a. J9 |5 c( k& z$ A/ @2 b$ D+ t6 r4 ?! W
4.us级的延时程序是什么?- m+ X1 D/ L) D, }, |$ J, O- s

0 E9 u9 M! t) @0 Q' X( ?// 定时Nus程序) w2 \/ N5 \4 f5 I
void Systick_NusDelay(uint32_t Nus), k* ?& T" K  i3 r
{" r; N; Q1 B) n0 a7 U7 S0 i7 c" U
    uint32_t temp;& k" p: n/ C# X0 l, P3 I
    // 2》设置计数值---N*9
9 W3 o1 ^2 F. `* }) E    SysTick->LOAD =Nus * fu_us;        //us与ms的程序就只有这里改变了" V% K( F. [! G9 v2 \* h  A  ]

- [6 j* P8 i9 N    // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
" u  t3 a4 k* c" @    SysTick->VAL =0;
  S' x+ Z) }* E& U: X2 l: N
( c$ a! ]6 C0 L& H    // 4》打开定时器
$ q6 i( Q' o6 k- j* v' N; {% Z    SysTick->CTRL |=0x01;- l9 g; _: R2 A
  @: o5 z: l5 [& L4 B6 J
    // 5》等待定时器结束
3 @0 z3 q* X# o  Q. |) @; t    do{
' C% T3 `4 ?  I. H6 l+ O0 h8 j        temp=SysTick->CTRL;
  s5 d8 J1 x9 O/ s5 H- r+ z- J    }while(!(temp&(1<<16)));' F. G4 b! S7 E, Q2 O
% a. [( W" q  D+ o0 ?- [. m
    // 6》关闭定时器
/ }6 I2 K2 T) r% P    SysTick->CTRL &=~0x01;
$ @7 i0 o  a/ Y1 y}
) e& I& C% M+ m2 A) p
! C2 W2 K3 W5 H1 ~9 j. N注意:+ g$ _, M6 m! U9 E$ l) V7 ?
        在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?. I# k3 k1 z* G, J! b# n4 i9 |. |

# ~5 Z2 ?: i6 p" m( b4 s8 o        FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:
" M4 V; {0 D% @4 b9 p  x0 u( e  Q( O; N& b5 P- M  @
//初始化延迟函数
1 V2 Q9 D6 E# I5 h9 i) q//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
4 _9 B; o9 \: H3 D$ p+ p5 l4 }$ u//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!* Z4 O- {9 Q7 H5 n+ |5 D
//SYSCLK:系统时钟频率
& B2 d6 ^  V) P+ Avoid delay_init(u8 SYSCLK)  l) w. V" S. k( d+ b
{
9 |! ^/ t7 J7 U7 I, W5 \, ]; D    u32 reload;7 c! n( {% L3 \4 d
     SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); , Y7 n$ t) L& c7 I1 h
    fac_us=SYSCLK;                            //不论是否使用OS,fac_us都需要使用
! o* C$ v+ R" s    reload=SYSCLK;                            //每秒钟的计数次数 单位为M       & X- g& c3 U: I! U
    reload*=1000000/configTICK_RATE_HZ;        //根据configTICK_RATE_HZ设定溢出时间
' I9 m2 Z, m8 D$ F* d, R$ F                                            //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右    ( ^0 j2 Z  {" i" X! Y4 Q( r( ]
    fac_ms=1000/configTICK_RATE_HZ;            //代表OS可以延时的最少单位       - p) S  I. _/ R5 ?/ I
    SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
! u* w8 A; W; J; c% O  t% ^) ^    SysTick->LOAD=reload;                     //每1/configTICK_RATE_HZ断一次   
( L+ f% Y/ h! i; ^; E( y8 G; ^    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK     
+ [) u( q6 X3 R6 o- g}             ' r: r, I3 a9 M7 Z! O# i

+ L5 r7 ^, q1 p6 H7 v. u& X/ i6 I# [! k# ?+ s% o6 k# x, @4 s
4 F6 f! v# l, q9 b' Y  V0 w5 \! n
//延时nus
8 _& e$ V& r- Q( n//nus:要延时的us数.    # g" [$ M+ @0 a! Z6 g' C
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)                                           7 D2 L$ ]* m8 W6 N
void delay_us(u32 nus)+ R# l+ S  m4 c0 _' M# w
{        ) G% Z5 Q8 p+ E" Q
    u32 ticks;
+ y$ M) o, M0 c    u32 told,tnow,tcnt=0;
- M- g# w( n, d    u32 reload=SysTick->LOAD;                //LOAD的值            
5 o+ `7 \" W) j4 X+ l: S    ticks=nus*fac_us;                         //需要的节拍数
* D( L7 `; d5 k% A) K7 u    told=SysTick->VAL;                        //刚进入时的计数器值5 R4 a7 g: k! c, r: A2 E" V
    while(1)
! A+ D6 o. l  Q) P    {
/ h: p* b  Y6 A3 U* U. ~, z        tnow=SysTick->VAL;    6 m& M% _$ M: [3 U; I' l
        if(tnow!=told)4 i9 t% n2 w( o& C
        {        
* N( x7 e8 z: g8 {, s            if(tnow<told)tcnt+=told-tnow;    //这里注意一下SYSTICK是一个递减的计数器就可以了./ Z1 H) h4 G2 [) E( K6 W) c
            else tcnt+=reload-tnow+told;        : u6 p, P. Y: G' Q5 y
            told=tnow;# h5 B. v; T: h, H( p/ o# F7 b5 V
            if(tcnt>=ticks)break;            //时间超过/等于要延迟的时间,则退出./ h: u- ?4 x6 n' v9 Z3 `6 I
        }  ( O2 r$ L- Q, }/ B1 M
    };                                            ! u# C/ s. T( P, w" H
}  
+ V  l8 \6 f( V! ?1 Y
$ t! T+ c5 O: E& [1 F( d% b
# x; M+ G: u2 e& @6 F5 G: r1 L//延时nms
" H, }: O( i8 ]$ D& n  l7 u//nms:要延时的ms数
& _: L1 q8 x  u  I" T, }! E/ B) Y//nms:0~65535
" M/ a4 E7 U5 u6 xvoid delay_ms(u32 nms)% m+ n; Y1 T; h
{   
  V; Q- d5 J, y) j    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行) T; @5 Q: ]( E: `; C
    {        
4 J/ l6 P" g+ Q        if(nms>=fac_ms)                        //延时的时间大于OS的最少时间周期
3 a* A( p0 `( ]; b9 s* B        {
$ k# @; |& d4 g  ^! g               vTaskDelay(nms/fac_ms);             //FreeRTOS延时4 r/ W  E( Z% Q. j# i5 c
        }
$ w* u! |1 A  {. Z5 N5 _        nms%=fac_ms;                        //OS已经无法提供这么小的延时了,采用普通方式延时    / h% W# p4 m# t1 ^
    }8 v  m% M; f' T5 }8 q: E) [" C
    delay_us((u32)(nms*1000));                //普通方式延时
3 _1 |# ?; @0 u- c8 g2 e}2 J, C4 p6 j9 r- V* [1 W) l+ p

3 M5 x0 {. u# K' G' z1 y! [6 _//延时nms,不会引起任务调度  m4 E* M5 r" L" T! [: x
//nms:要延时的ms数3 B4 J( u+ U! E. e( n  K3 ?# P
void delay_xms(u32 nms)- d$ W5 w0 B, I: O8 @/ _7 i
{6 Y+ @5 {  `, K7 P! R; j7 t' w: C. U4 H
    u32 i;) I* i0 h9 z1 {) M+ W
    for(i=0;i<nms;i++) delay_us(1000);
, L. e1 d/ H2 Q5 q}  g, h9 ~8 A, z: U/ n  a
————————————————3 M- v+ I. k6 E+ Y( d% l
版权声明:无敌小小雷/ V; t- C4 x0 h0 V- j! X

# G$ Z9 I3 |  ]
0 n! a2 O! ^4 Y% Z
6 Z2 B: q5 L& V0 c" _3 K
赞 收藏 评论0 发布时间:2023-4-16 18:53

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版