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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-16 18:53
滴答定时器:5 |- K6 q$ x7 v& G% A1 x* |: Z
        定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。
. r9 \" W5 T( p( u
: v7 }/ j4 J. Y6 \& p9 P4 j- K$ k8 x滴答定时器systick是一个内核外设(即:内核自带的)+ F9 _( L, S: o  j, D9 c

: q/ w' b/ |* Z7 [ 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册
8 C0 _& ^' t) a  m) X% k# b( R/ O《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》
  U& N+ \# G( C" C- p: o! o/ f
) B2 D8 Y% F/ H# h0 O- p* Q9 h- Z        Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。" Y( K+ V6 ~  k9 z

- E& y5 E# r& q$ {# p" M7 U  aSystick的作用
& Y2 o$ }* h; A9 W- _1.产生一个精准的定时, E$ l2 b7 i% d# {+ ?3 m$ j( A, P
2.FreeRTOS时基由Systick提供
% w  [- I0 c! k  B9 B* B$ P* d% j
MJ5}R@G1S4I9X34C]7ZVQJT.png
; P1 o* w5 k* A/ n/ |
: L2 Z2 Y8 f$ H. F. |: o0 ^配置滴答定时器:: A5 f( M- K8 B, {
4 t; g2 _8 h9 T: A0 X+ R
2e4242cbd0334a8dbcc333256a6d9fcb.png ) d" O9 T+ Y4 Y% ~4 Q+ y) U2 K

+ v) s* M8 {! I  t5 y        红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz2 x' J" U2 ]; _: f7 }9 v
0 j2 v( i" i& n* c
配置滴答定时器我们需要下面几个寄存器:
2 d# V: z" n8 c4 N: g' Y% R0 E
* C" C* T8 ]  N0 r. Q
fb7ea8d208f14b14975bb8c2c2b8daac.png
1 G  Y" L6 s& o/ J, [- X" v; k5 U/ B
注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。' D  r: H8 j4 Q1 e2 p- X/ l. s) H

1 r% }3 r& T" H
439efd5325344681aa687218ae27eeac.png 3 D* q7 k- \. [' ?% @  U$ h8 k* M- F

- m$ [' q: t' m+ _8 K& {. ~ 注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF   ; d8 m8 G4 b& Q
7 h- C4 f8 W6 g1 |" b0 o
SNCE360KPH_{K{XBYE)3WIW.png
2 O! s- R& D0 F3 Y+ |/ X( q; w
3 T3 Q/ c5 p2 o* G
d1b8265cf617451caef6d6a879b01593.png
0 f) `! M) i2 U5 b
$ H: `) Q/ b7 g, ~. ~& ~编程; p) J* E* O+ |) W/ R5 Z" Y) I
产生Nms定时编程步骤:+ @0 L4 M; M0 ?* ~7 k6 [3 k1 }8 o& N
        1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前). H6 B3 X6 y. c  t7 \1 c: n5 Z
        2》设置计数值---N*9000
- F- C( r: ~1 {. D        3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0
2 y; i! N7 u( ]: y) T- u8 v9 }        4》打开定时器: z& d" Q3 h; L/ A4 H9 S8 _
        5》等待定时器结束
* w% ^( x( t3 h5 q        6》关闭定时器  K1 ^$ }, d. W2 _/ t, U; d
9 [2 Z3 a! a0 A/ L* J. l5 h; n2 T
代码:. ~1 F' A+ B4 o# k! G6 x( X
uint32_t fu_ms;  // 1ms需要计的次数2 x( E+ X$ e+ ]
uint32_t fu_us;  // 1us需要计的次数
* u0 p* V% O4 w$ Y* a1 g* D( e- x/ S7 v  i( B
! {8 W% E: x3 {8 g1 H! J
void Systick_Config(uint32_t Sysclk)        //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz
8 K) M$ s  L/ _/ \  Z& l* \{
4 v* G+ J3 e- w; B1 Y1 |    // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)
: `; F' ?; \  Y0 s6 a$ a    SysTick->CTRL  &=~0x05;        //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置
, {8 w/ Y& L/ S; r+ [7 E' y3 Z# L; C
    fu_us  =Sysclk/8;                // 72/8 = 9,即:表示9Mhz
+ w) P! F/ y3 W, e. t    fu_ms  =fu_us*1000;        //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms
3 v) Z# w5 n1 b. L# i}
) a# ~2 d2 Z8 @$ w4 b7 O( r# E& f1 o( F

3 A3 p  w. {1 N" L' u// 定时Nms程序,ms级延时
6 G2 W. T8 s2 K' m4 x( H3 }$ Avoid Systick_NmsDelay(uint32_t Nms)
. i) ?7 m- L. Z& ?4 E& L$ g% {{# v% ^6 D* W1 H0 s$ U; w6 N  i( o
    uint32_t temp;
6 k) E. T0 ?- h3 H    // 2》设置计数值---N*9000, R  r0 O* l  ]" a; g4 l) [
    SysTick->LOAD =Nms * fu_ms;8 j0 e/ A; J! K. X  b& r
) J1 U3 H" C, s, [8 f/ F* S4 U9 o
    // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0
6 D5 X" Z& G0 D; i$ z    SysTick->VAL =0;        //清除标志位1 I- X0 T( ?0 H# E2 i# M

" I. L! Q# g7 {% }: G0 X0 |2 x5 s    // 4》打开定时器- `9 t& N: C3 p: n" f
    SysTick->CTRL |=0x01;
; S% I6 w+ i& z2 u2 {  P/ j8 X8 R7 k# ~
    // 5》等待定时器结束9 s1 T% [6 z" v
    do{4 }% M9 ~  l" `
        temp=SysTick->CTRL;        2 X# s$ E) b- v1 H* `9 S2 L
    }while(!(temp&(1<<16)));        //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成
/ p. j( Y) L( Q$ _: T* T
# K. |; r+ `% {0 R8 K* S    // 6》关闭定时器
) @# |# j5 a' c. p* E    SysTick->CTRL &=~0x01;
% f# V. `# J! d$ o' ^1 ]1 }( C5 S}. ~6 D5 M8 q/ v/ Q  \& k
6 \1 c& ]4 J# y# D, w
解析:
. X* D. e9 U. C3 m' D
1 s/ \$ d+ @& y1.我们先来看程序,定时器程序为什么被分成  Systick_Config(uint32_t Sysclk)  、Systick_NmsDelay(uint32_t Nms)两部分来写?$ w/ G% j/ i# ~% g3 h- u0 S' o
8 h  _0 k) ~- Q4 s; y
        因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。
8 g! h# B2 Y$ N: a
  G" K' ?( N" }' S( h2.为什么程序中的这部分需要用do.....while而不用while()?# r' r! a0 f5 I

8 o' u$ u- H2 L9 \! t% F
8df6add7dbcb4ca6b71a02a7ec75dea9.png
) y8 i' D: U" g# b/ e- ~# F% U' v8 [4 Y# D' u. l0 C) Y9 O
        这里需要用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循环将不再循环下去,此时计数器计数完成。
- Y- J1 g: P9 _6 O8 z2 g! k; T* Q& A; A1 I8 k2 _
3.  由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?( ^, S$ }4 a9 r6 G

! m2 p0 l) F! Y% x2 ?2 r6 Q        由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即:  =  1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。
+ z& Y/ P2 f( I& f- A. L! [
( i5 x4 s6 v2 Z        的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。% ?- a7 z6 y. T5 [7 j

7 f, F2 E5 L; _所以,我们改下一下程序:
5 r* P7 E) E" a7 _// 定时Nms程序
" g9 P9 T& b3 Q5 u( g' _void Systick_NmsDelay(uint32_t Nms)
1 X/ L5 }1 @* a  j* \{
' J! b8 B8 R+ C- \/ l    uint8_t  systick_flag=0;
9 I2 O$ U( v- n    uint32_t temp;
' n+ [& ~( i; w' g2 y, ]' f& t( d3 [2 m: o
+ I0 t& A5 ?  W% a: i, g/ P7 @4 u
    while(0==systick_flag){2 f2 I/ e: ~( `8 }3 I' y& a/ I! @
) [) l$ _& H) t8 d
: t" @/ M" M7 \
        if(Nms>1864){; u$ C" H9 ?* T% {' n  K9 ^
            SysTick->LOAD =1864 * fu_ms;
" ?( J& d/ Y) x! R            Nms =Nms -1864;
9 J5 z6 ]) P3 Q: a; i        }else{
( R! T1 _# H- M- w/ j; x+ ?            // 2》设置计数值---N*9000
, l/ o& L# C: ?; {            SysTick->LOAD =Nms * fu_ms;
0 Q, I0 @0 S/ w& s8 O4 L5 u3 x            systick_flag=1;
5 h! Y2 d1 P! n5 x6 w& r+ ?         }% n8 O) G' r/ i9 \2 }
9 y4 S& ~+ N/ E
) ~9 m3 U1 O  X/ F8 U  C- U0 a, m6 R
        // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0# r! l3 z, _( e$ M
        SysTick->VAL =0;) {; L. o7 @' N$ r5 B3 p6 k

% C4 k6 H7 O- R  o; R        // 4》打开定时器
4 I: C( }* n4 _# ~: A        SysTick->CTRL |=0x01;
: P9 Y3 r& x* G# x0 \' O7 T
3 v. h" T; ~+ N# N- Q        // 5》等待定时器结束
0 ?1 u# t; e! M6 m! u/ V  o        do{  F3 {9 B- H- u
            temp=SysTick->CTRL;
+ I* z# A! W- [. ~* |        }while(!(temp&(1<<16)));
/ R' r% U+ a5 u- d% {2 [
, u. a. l( m4 ?5 ^7 ~% t        // 6》关闭定时器, L! C' g. Q- V; W
        SysTick->CTRL &=~0x01;. u% \! W) H6 T
1 N2 j& L9 G& g& g* n  f
     }
/ Y9 c9 `& X& x! L6 G  ]7 J) ^# \/ l0 L
  @$ u, r$ |% @$ q9 H
}
3 r5 s$ C4 @6 G$ Q) Q
7 V/ R: P7 Q3 g这样的话,就解决了1864问题了。
- S) ^5 R5 V$ M* _% M1 i! r2 Z( P# i

' E5 A8 J) j# A. k! k4 I& u3 D4.us级的延时程序是什么?
; i2 k: x; C+ Q) n
. `  ~6 q9 ]) f7 A3 N0 p9 p// 定时Nus程序
6 p- j: F6 [6 j9 F! G8 Xvoid Systick_NusDelay(uint32_t Nus)) r  O# A! V' `
{% S, ~) q  f& t9 x& Q& M
    uint32_t temp;/ d! }4 F( M/ y$ I
    // 2》设置计数值---N*9
# m8 E6 j$ _8 y/ i, a    SysTick->LOAD =Nus * fu_us;        //us与ms的程序就只有这里改变了5 a' X$ @) }3 ~- t

1 I4 _2 S- L# ]' ]  G- N    // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0
1 W+ v9 Q7 Y( k: M* Q    SysTick->VAL =0;6 P! t% x+ |2 S0 }9 X

2 \( P/ L$ @& f3 t2 n2 P    // 4》打开定时器5 d0 f" L  ~, y; C6 G
    SysTick->CTRL |=0x01;
( o4 F; ~4 g, C# z, q8 x0 |' T) o, A- ^
    // 5》等待定时器结束
  w: H0 W6 n  v$ Z, M    do{
8 R* c4 H& F+ \        temp=SysTick->CTRL;
% T" @+ }0 z3 o) k2 k4 U    }while(!(temp&(1<<16)));
) j6 ]# Q$ y/ N* s4 {
( z5 _' b- f' M/ L$ [    // 6》关闭定时器
, u- F$ ^4 Z3 K4 v    SysTick->CTRL &=~0x01;2 }- D2 ^/ C5 o% E" i
}7 h4 c; L  Y$ M$ g% n* R
- r% T; B5 X- O2 w" k$ c$ `
注意:$ B6 O& L6 X* j! ?6 }
        在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?
$ {( U, O, d  R, U( e9 n4 b" l
0 Y* \9 ^7 y0 I3 w. F9 F5 A        FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:& \3 U- U  \8 N2 g

$ h9 o; x& i" f7 ]9 K1 e" h& ]//初始化延迟函数
8 l: {. U' A) n5 _6 w3 m3 D- b//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/86 ?( |9 K: C& ^- x8 z3 T+ i2 i' R
//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!
- ?/ Y& X: \6 G3 w5 R" D7 {; O//SYSCLK:系统时钟频率& e7 Q$ P! u% x+ V0 i2 {' u& g
void delay_init(u8 SYSCLK)$ R5 l5 T' E; J1 m# B% D9 h
{( k. J7 S1 R) g4 x$ A
    u32 reload;8 i, D4 \+ P1 q  q; {3 g0 A
     SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); / W/ }1 b4 {- ~* Z9 V
    fac_us=SYSCLK;                            //不论是否使用OS,fac_us都需要使用+ ?4 d( w  e5 q$ k1 D! I
    reload=SYSCLK;                            //每秒钟的计数次数 单位为M       ) B+ h( d# g) W8 n  J
    reload*=1000000/configTICK_RATE_HZ;        //根据configTICK_RATE_HZ设定溢出时间& x9 J5 {% z. }1 s
                                            //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右    $ V. a9 @+ _: L& G, T
    fac_ms=1000/configTICK_RATE_HZ;            //代表OS可以延时的最少单位       * S+ u' O4 h3 H' M# F9 [
    SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
" r% C0 n3 A& x2 b$ y- M, [# v    SysTick->LOAD=reload;                     //每1/configTICK_RATE_HZ断一次    8 n8 `5 P8 z+ c9 \0 H7 [
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK     
* x2 U3 u; R5 Q2 {}             2 C; ?) {! h- z/ b* c2 Q3 e
" n5 X$ S. Y# B4 d1 c3 r0 u
5 Q* r9 j# i) G% w! q" e

, E: `" ?+ L4 B8 D3 A//延时nus
: m8 N5 B9 r- r, |4 C//nus:要延时的us数.   
* G) B3 Q+ [) `* a8 ^; Q: ~/ J1 [//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)                                           7 b! }3 O! q# N- h8 Z8 I
void delay_us(u32 nus)& l6 E8 q( N$ ~9 e6 w4 g
{        . U9 D+ g9 r7 t0 }5 L
    u32 ticks;
% a: z' e5 u" R    u32 told,tnow,tcnt=0;
* L, q' p$ |: G( g* x    u32 reload=SysTick->LOAD;                //LOAD的值             % v7 o/ a: Z% K  w
    ticks=nus*fac_us;                         //需要的节拍数 2 l' t/ m& |! _
    told=SysTick->VAL;                        //刚进入时的计数器值
) \3 V. k, S' T3 i! k( S6 l  e    while(1)
. |1 W$ q, Y" S: W4 `8 V+ ~    {  R( |# H/ J6 y
        tnow=SysTick->VAL;   
* f7 _* e6 l$ \+ L        if(tnow!=told)
$ ^/ Q. A- M8 h/ y7 U. ]' e1 R        {        
; u" ?! D: N- y2 Y& f9 b            if(tnow<told)tcnt+=told-tnow;    //这里注意一下SYSTICK是一个递减的计数器就可以了.
0 h$ m& `2 ~+ X7 U; f- A' l) U            else tcnt+=reload-tnow+told;        
) P# y6 O9 e6 _/ z6 ^" k2 _            told=tnow;' b% ^: Y; T- ^/ M) D
            if(tcnt>=ticks)break;            //时间超过/等于要延迟的时间,则退出.
9 T1 e# t- k6 j7 H* A! }& a        }  
3 @* S, ~) H6 c( J6 R) Z7 o    };                                            1 ~4 `) n$ }, o7 h: v
}  4 e7 v6 k3 k! i  ^* a3 e3 y

6 L* B) {1 S! k
* L, y) f8 m- x8 \$ R- O//延时nms
& Y0 f0 W1 h: F0 h//nms:要延时的ms数
" n6 r0 e7 T* ?& w1 ~7 z//nms:0~655352 l, M) R, a7 A& H
void delay_ms(u32 nms)
9 `! ~' x* }) B# g% I{    2 o# j& _) a1 T$ ~" j5 p
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
0 R9 z' o. G8 L0 ~# {; r: Q    {        / R7 ]  O& T6 t5 m, x9 _0 C
        if(nms>=fac_ms)                        //延时的时间大于OS的最少时间周期
  h0 f8 R8 H+ M! [$ z6 u        { * ^1 h% N( z% I$ p, C
               vTaskDelay(nms/fac_ms);             //FreeRTOS延时* L" e8 {9 D& z
        }
( v2 S3 A, L( m8 K! X# T        nms%=fac_ms;                        //OS已经无法提供这么小的延时了,采用普通方式延时   
9 I, x; r/ o0 Q" f8 L4 K) ]6 Q) q    }
$ ~9 M) B; ?) I    delay_us((u32)(nms*1000));                //普通方式延时! ~+ `1 P% V/ ]5 y- `. T. @. b
}
* _: `$ I7 y3 {- c+ |" G
. u2 e- r( v8 E//延时nms,不会引起任务调度. P  D; S1 q& n# c3 l% V( _2 h
//nms:要延时的ms数
6 ]5 T6 `4 W" B2 ?void delay_xms(u32 nms), a! b2 ^  [% Y6 L9 ~! A: `3 l
{! a9 j* K5 [6 c- ?' H
    u32 i;* b+ D2 T4 C! M9 N" A, f5 M7 E
    for(i=0;i<nms;i++) delay_us(1000);
7 ~' z# ?1 L: J9 q7 @+ a6 u}
* g. i( p0 Y: @1 F5 x! @————————————————
/ Q8 v/ ]# |5 e$ g; q9 q( Z版权声明:无敌小小雷
$ C/ M7 R% B1 I) m0 G6 y- t: p
" C0 c& W$ S; |9 S& L

+ T, G: s. r3 K% j% `7 [% m1 T! F0 n9 F1 d. {, b: X
收藏 评论0 发布时间:2023-4-16 18:53

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版