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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-16 18:53
滴答定时器:' g" g, F0 \3 b3 Q& Y
        定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。6 o2 G7 e" Z9 a

9 O" C0 J- y1 ^' c: O滴答定时器systick是一个内核外设(即:内核自带的)
8 F# N8 W! q  a2 Y
4 ^) b) |' z! V. o" a/ d 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册( z4 s, b: U5 h% ?
《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》5 E: y( \9 J) @) v
* S! ?# ?3 C% T. c  t
        Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。
( F. `; l2 y0 M2 Q& {
5 Z) h3 |+ K9 _+ `1 v8 VSystick的作用, {  D* v2 ~7 D  l& o% J
1.产生一个精准的定时: a+ K* J. @+ F
2.FreeRTOS时基由Systick提供$ w1 `5 w* W, i1 ]5 ^/ K: t) u

, Y, Y3 c3 P. b
MJ5}R@G1S4I9X34C]7ZVQJT.png
, z9 _( X1 x" ^0 a  Z$ X  H4 ~* ?3 v
配置滴答定时器:6 L+ c! G% O# M! k/ G- ?  R1 i

' _. }: d+ _6 @9 y& _
2e4242cbd0334a8dbcc333256a6d9fcb.png
' R. q( ]  W0 C* M, e: _: J
/ ~  ^5 m. b; @2 i8 u2 _1 M        红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz
  s4 N  X2 @+ @4 u- c& W2 e/ i0 D7 J8 Z
配置滴答定时器我们需要下面几个寄存器:
2 `; X) e6 ^: t7 b. D7 y1 U( W6 U4 ~
fb7ea8d208f14b14975bb8c2c2b8daac.png ( K' ~, k% b5 M3 X+ ]! P7 w4 g

9 P" y) B( k5 ?5 d. t 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。1 C; |5 D% H5 W# k( N9 y6 f
1 N& D" @- H& o: ^
439efd5325344681aa687218ae27eeac.png 6 [/ |/ J9 R6 u5 f0 ~) w

, m% \  \  R- D 注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF   1 t+ ~+ s) Q7 Q1 [; n% w% G# a

4 X! ?. E, h3 a( N( p, n
SNCE360KPH_{K{XBYE)3WIW.png * ^% m' w1 a6 ^
( I* w4 S6 b) y$ H% q" n) G: l6 z
d1b8265cf617451caef6d6a879b01593.png
. Y3 b+ N; R. z' `6 I/ ]4 @5 C  u  @* a* m9 X! l5 b1 m: J1 g, [0 n) B1 _
编程, B5 p2 p9 ~/ s
产生Nms定时编程步骤:
* w6 D% f% A* ]# J7 ?        1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
/ F$ N% _) `3 j6 ]: V3 m) |1 j9 l        2》设置计数值---N*9000
5 I* R; A6 r8 r2 E        3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0
8 j0 J& ?  F7 C4 y+ _2 w        4》打开定时器) G0 ]% N4 J: ~& c' q
        5》等待定时器结束% A, Y- I3 g% P2 \/ o+ v$ a, E" t
        6》关闭定时器* S. R& S) p* M% ?. E
0 u" v" J$ d  w1 J* T) s+ q
代码:
# u, b7 f& x! ^2 fuint32_t fu_ms;  // 1ms需要计的次数/ v  q6 ]4 j9 L8 I" W
uint32_t fu_us;  // 1us需要计的次数) m( `: z4 J* T& R9 h
& j" s6 O! z. k2 o8 v- b
8 S$ J1 Q' a# i6 x( w- p! D
void Systick_Config(uint32_t Sysclk)        //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz
* a( ^: p5 V% T$ r: O{
" l# {- R, g2 z& _    // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
0 I) B: O* `0 C% s; N6 U    SysTick->CTRL  &=~0x05;        //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置
1 l' I: r0 f* D% X0 E. n7 o; t( t' F/ S8 i1 ~1 t
    fu_us  =Sysclk/8;                // 72/8 = 9,即:表示9Mhz/ ^8 s$ c$ O- r( x
    fu_ms  =fu_us*1000;        //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms' H) J+ B1 G5 `3 h3 o/ Y8 P
}
- v3 i/ S0 V' t8 k9 @& F) W% w! j' n# N( I: A- Z+ t1 U
( Y; C. B6 x: @9 ?3 l4 U
// 定时Nms程序,ms级延时
  Y- h; v( Y, b* P7 j, Zvoid Systick_NmsDelay(uint32_t Nms)' j2 L$ H" m. A0 g% o$ ^! Z
{
, f0 b7 ], P2 y: h- N# W5 S    uint32_t temp;
( w& h8 U$ n$ G; c0 m% m    // 2》设置计数值---N*9000: x2 J  y/ r3 N$ o' K; t- m
    SysTick->LOAD =Nms * fu_ms;. o5 p, U3 r! y$ Y4 m, ~6 B

; {" |+ Q$ }  D  t. G1 Z  z    // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0
* c- l- \- a5 K, Z! H# k: C5 Y" f0 o4 [    SysTick->VAL =0;        //清除标志位) O9 Y) ~. E" J

% T3 ]4 b: S. d7 t+ j% [, K    // 4》打开定时器
6 P0 K) ~7 e2 u/ T- E    SysTick->CTRL |=0x01;
' X4 W& b1 L5 w/ A# j2 C! [5 R
% L; L" A1 M* p3 \8 G4 @; `    // 5》等待定时器结束
& B$ K2 S# }  O8 v    do{6 u8 F0 ?/ V8 `. i: S( U
        temp=SysTick->CTRL;        
4 I" A0 B; D$ W8 p# b, {* [& m    }while(!(temp&(1<<16)));        //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成" g6 Q6 O( r- i" X
7 v5 g: g, F# w8 l6 E* R. A- i
    // 6》关闭定时器, \, d" Y& M+ `
    SysTick->CTRL &=~0x01;
+ I. `: |# f" b  X; g}! b7 A5 ?. \9 S: G4 s0 R6 _* w% w5 F

3 t; l" K( X; f! O; b: K2 ^解析:% x- k% W) X' F4 p% p

5 a# @# W) R* l) J" B" v1 k6 r; q1.我们先来看程序,定时器程序为什么被分成  Systick_Config(uint32_t Sysclk)  、Systick_NmsDelay(uint32_t Nms)两部分来写?+ F- b0 O: B5 G; N# u7 ?3 f( P

. m6 u7 b: C8 l7 ]1 F  h        因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。
# j7 |1 v( Q' x5 ?+ p6 N2 z
. ~. K# u, H0 }5 y3 e; s2.为什么程序中的这部分需要用do.....while而不用while()?
, ^3 u& `0 Q  k2 \
2 \# l! h% o( h/ X2 j2 c3 W
8df6add7dbcb4ca6b71a02a7ec75dea9.png ; V$ f& b0 D6 h. `: _

& i2 Z: w" i9 @! N) |( r+ m        这里需要用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循环将不再循环下去,此时计数器计数完成。: X5 Y/ k- O7 e  W- l7 n
$ K: W: Q/ r( N" M3 {2 c0 q9 T
3.  由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?
& [' g/ U- \1 ]% g
0 b! h4 [3 K+ E& Z, p% p# k        由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即:  =  1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。, W9 Q; D5 @0 a' f7 z. N

; a* `* g) Y# s) d. \# x        的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。5 f. }! x5 ^9 X' X3 T# d
# [. L2 ~4 w1 F  I
所以,我们改下一下程序:
3 B( N# w5 G0 n5 M// 定时Nms程序' i3 O) d. [* h4 i# J. g3 h& n* U
void Systick_NmsDelay(uint32_t Nms)
1 |; m! F, E5 V  R8 h  r{
  N; h* b2 @$ F% \3 P4 q    uint8_t  systick_flag=0;
1 `) Y/ a- \: a, g+ j7 Q( @7 \: m    uint32_t temp;
- R+ q* V9 Y! b9 k2 V
. Y5 c5 l! R7 t1 I: q% Z" s- z0 l
    while(0==systick_flag){
4 O" R) R( J4 ^* _. E# O& Z
) E% \1 o; R6 }1 I' ~. n) i
: ~0 z! ]9 l! m# q        if(Nms>1864){
' P% W( P; u& c% M( L$ l            SysTick->LOAD =1864 * fu_ms;  S' m) X: }& H
            Nms =Nms -1864;2 v2 ~/ m- y6 u
        }else{4 g/ b4 a% P1 j2 ~2 }
            // 2》设置计数值---N*90008 V: ]# A, W- w6 k% S+ ~) x* i) ?
            SysTick->LOAD =Nms * fu_ms;
) D+ m7 [! y6 T1 ~, P            systick_flag=1;3 [7 `3 ?/ B2 V, B# p
         }
( {& M5 S3 d) e
' q8 C" B0 ]6 B  p$ S: [) |1 v: N2 W4 T! e: R
        // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
$ ~2 e) s; U% `        SysTick->VAL =0;
) ?# Q) h: ?" x" G( P1 \3 L. z: b8 N
7 M7 J# ?  _0 j2 m& i4 E        // 4》打开定时器5 z7 d! _! ]' k0 X
        SysTick->CTRL |=0x01;
) U- X, u1 t3 y3 Z4 |: q% P9 L& T; i
        // 5》等待定时器结束
% `; f) Y# e' l" W& g0 Y        do{$ K& e2 U$ ^  W9 ^/ }+ y! q- H
            temp=SysTick->CTRL;
: }1 j- _6 P2 M( ~  q5 u( w0 w3 J        }while(!(temp&(1<<16)));
: i# B" i# S0 ?" `8 W5 |- ^$ T0 M4 i4 u7 z% ^9 w
        // 6》关闭定时器5 |5 x( Y' c* J; q, g
        SysTick->CTRL &=~0x01;' Y, [6 k; U% R$ W2 \) N' Q7 D
. l" u5 @. A2 K, A! c. B% @
     }
6 I, o7 ]* z1 v( h
" \1 m( i: A8 r' u/ C3 Z  U8 l
& E# O" h3 \" n* f}' O  Z6 q9 R7 w* m+ e' A- r# z
. D0 o0 K; X2 q5 u. r; v. _
这样的话,就解决了1864问题了。) \. B  Q1 y2 C* H
3 G$ ~6 `5 H: q. t  ^- S

6 I/ b' G5 \0 v4.us级的延时程序是什么?3 I& s4 h* O' B/ N7 O3 j9 r
( @* P: o* [0 y' S0 {1 Z" L7 G
// 定时Nus程序
' |' X/ a5 W1 b: B# [void Systick_NusDelay(uint32_t Nus)/ t( Q8 q- C. w0 o( |
{: z' H4 z# j" D
    uint32_t temp;8 ?/ E  Q- k% x! v
    // 2》设置计数值---N*9
) e- u0 k5 z( P' Z8 P    SysTick->LOAD =Nus * fu_us;        //us与ms的程序就只有这里改变了
' S' Q" M% w, w- ]) W6 {  [3 P. J. ^% u! F7 T* C4 u& u' @
    // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
& d$ y' L1 {; F. i    SysTick->VAL =0;
+ p/ a1 |6 Q3 }% V5 N6 y$ f9 N! M- y: G
    // 4》打开定时器# @, ?& y; q1 e" N
    SysTick->CTRL |=0x01;' S6 L. ~; H" H% R0 B: p
7 N9 g% H  p# ~0 n" }
    // 5》等待定时器结束0 G1 W' O3 c( t5 `: g
    do{
5 L% E7 K# _% x# r) _  s        temp=SysTick->CTRL;! W8 k, Y$ g' Q7 O* m" }) O
    }while(!(temp&(1<<16)));
" S' q# N9 i. K8 r7 T" z( _
; B! r* |$ s5 Y4 d# n    // 6》关闭定时器9 L" h0 H2 `; K, z1 Z: U
    SysTick->CTRL &=~0x01;; f& O% f2 y8 {% Z3 M! G7 c1 K0 U
}
% {. k8 T# C( X/ H7 b; O8 e, E* [- o
注意:
$ H6 V  I' Q6 X        在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?
& C5 [& l* Z& |- C- A0 E8 X3 b# g
/ b0 y, u: g, J3 R% f        FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:
% x& O9 L8 `7 O* Y
+ g2 U; r- V( Z//初始化延迟函数& r, G8 R' q( ~& i" i
//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
* K) Q" n7 t6 j. G- J& [9 v5 m9 J  f//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!0 \" C7 t5 n/ ]7 [$ W) _
//SYSCLK:系统时钟频率
# z0 U5 e  G& jvoid delay_init(u8 SYSCLK)
' J* M0 F' G) Y- r. E{+ S! U9 j: W$ s$ h. y; S
    u32 reload;
" v! B& J2 B5 V3 \7 g/ W, [     SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
5 `9 A$ Q  {) e+ c    fac_us=SYSCLK;                            //不论是否使用OS,fac_us都需要使用
. x3 I9 e) X! L+ }2 _" Q    reload=SYSCLK;                            //每秒钟的计数次数 单位为M       ) `' k/ A2 R# N
    reload*=1000000/configTICK_RATE_HZ;        //根据configTICK_RATE_HZ设定溢出时间# S8 L9 l' p" O) T
                                            //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右   
  g( W6 w$ J' X- i' C: M    fac_ms=1000/configTICK_RATE_HZ;            //代表OS可以延时的最少单位       8 o7 k, u! J' w; x5 G) c2 N
    SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
1 ^& s' x  p/ X0 m' [    SysTick->LOAD=reload;                     //每1/configTICK_RATE_HZ断一次   
+ W* G6 u5 q' o9 E    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK     
6 P$ u: Z* i4 `9 E, _, Q}            
  {6 K2 O7 p( B. V% e8 Y# J7 A: ?* W

  V# |  w# g* E# x* n7 r1 ~. D. a6 k% \9 A- H6 `4 D
//延时nus
( p- q: ~% G+ W! x5 _# R//nus:要延时的us数.    * G/ W1 ]% w' C, g
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)                                          
* |( q2 M' @8 l8 {7 Tvoid delay_us(u32 nus)
( V/ M/ [# a. z1 Z: r! }{        
) b- e  f0 w. R* |    u32 ticks;
& i# u# k( Y# v# {6 O  w    u32 told,tnow,tcnt=0;
$ q- `7 P' A3 `( H: D    u32 reload=SysTick->LOAD;                //LOAD的值            
7 w0 P  s: G! r" g    ticks=nus*fac_us;                         //需要的节拍数 9 X/ y( T! |3 D; n4 y2 A/ {
    told=SysTick->VAL;                        //刚进入时的计数器值" d" L# V# m" f8 u: V' Y
    while(1)
, V( S$ }$ E  P6 z! B$ Y/ u    {2 U9 t$ \3 @; S( O& F# k- {
        tnow=SysTick->VAL;    ! ^: [5 K  p* d' p1 j7 o+ ?% O
        if(tnow!=told)
" v+ a* d% @1 q0 t4 O+ n! n: }) c        {        
; J# y- y6 x* Q+ I: G& t            if(tnow<told)tcnt+=told-tnow;    //这里注意一下SYSTICK是一个递减的计数器就可以了.
! W! N. D+ b! T1 J            else tcnt+=reload-tnow+told;        
7 R4 B6 L9 ~: ~% B+ q+ l. W            told=tnow;
" Q0 t: k* E- O            if(tcnt>=ticks)break;            //时间超过/等于要延迟的时间,则退出.+ @2 A8 V) `& }
        }  
3 [0 P" D2 z+ F1 U# C    };                                            
7 @, W) G, Y& h* ^}  $ `8 S! C0 e, b1 K4 D( I

9 W; G1 C1 `- p% S  g8 k$ H. l( K& n' x6 K3 @2 i
//延时nms
; f; B" A  i/ k$ f0 _) o//nms:要延时的ms数( {, @0 y  f; q; ~$ p2 S
//nms:0~65535
7 D1 q+ f( A5 N: Y: J3 Rvoid delay_ms(u32 nms)
) F2 w6 G( \7 g% H5 T- `* u3 p) o{   
1 Y0 q) a7 F8 [# ~) d) ]    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行6 K. P- U+ p/ A
    {        
6 f3 q6 K2 ~7 c2 N. a' y& _        if(nms>=fac_ms)                        //延时的时间大于OS的最少时间周期 5 G1 o$ J: j+ C! G4 U. G: m( p
        { 0 z8 D! ^) P# y" v3 {
               vTaskDelay(nms/fac_ms);             //FreeRTOS延时
# J/ ]  {7 L0 i5 C3 H        }' {+ {: V; R) c3 j" L7 {
        nms%=fac_ms;                        //OS已经无法提供这么小的延时了,采用普通方式延时   
3 N- Y+ C/ g* Q2 r" Z    }& r; T9 `6 i# p' t: S
    delay_us((u32)(nms*1000));                //普通方式延时$ ^5 g0 ~+ a) D5 k4 o% o' h
}3 B% n- c/ T9 j$ x5 t& ]

1 a/ f* Z. M* E9 U//延时nms,不会引起任务调度
* e5 e* x' {# i- A2 q. w+ o* a* v//nms:要延时的ms数
4 q7 o) s; n1 @) O  f% o0 Mvoid delay_xms(u32 nms)
# Q- P+ H% _5 Q{$ q  c" W! Q' ]9 ]0 e; ]* w
    u32 i;
9 [' v: M4 J1 j6 w; \6 }    for(i=0;i<nms;i++) delay_us(1000);. ^5 u* P& D0 e& T; x2 c- N2 Y5 h
}9 }: N$ k; ^( Y! [6 q
————————————————
& m1 x$ `% L5 @+ |  w% v版权声明:无敌小小雷
2 T+ ^, X6 ]% Z4 v- }
4 q2 Z! _2 }! T4 i
# ?" l5 U( A* s5 v$ P# M
6 b0 C5 @3 S! N/ T$ L3 i- i
赞 收藏 评论0 发布时间:2023-4-16 18:53

举报

0个回答

所属标签

相似技术帖

官网相关资源

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