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

【经验分享】STM32---普通定时器应用

[复制链接]
STMCU小助手 发布时间:2022-5-24 10:15
一、STM32通用定时器原理                        & f* z9 C! ^6 F& x* t2 ?

- x- n( I$ x, Q: j6 Q1 p( t4 Q    STM32 系列的CPU,有多达8个定时器,其中TIM1和TIM8是能够产生三对PWM互补输出的高级定时器,常用于三相电机的驱动,它们的时钟由APB2的输出产生。其它6个为普通定时器,时钟由APB1的输出产生。4 z7 |9 X  [: m" s
5 i- d3 A) ]2 X+ f
下图是STM32参考手册上时钟分配图中,有关定时器时钟部分的截图:
  d) V; g7 U5 L/ t0 @1 i/ l" t$ s) m6 I7 n. E
1345217050_8286.jpg % s0 {$ t9 w8 g- u% D

! M1 o. i1 i8 F0 s从图中可以看出,定时器的时钟不是直接来自APB1或APB2,而是来自于输入为APB1或APB2的一个倍频器,图中的蓝色部分。2 M! l, i7 u( h- E+ ?# q
$ T* D% Y/ i5 R) G) \
下面以通用定时器2的时钟说明这个倍频器的作用:当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍。
  H- {/ L& [6 {9 e, g2 T
; v* y* w5 p! @% d4 Z可能有同学还是有点不理解,OK,我们举一个例子说明。假定AHB=36MHz,因为APB1允许的最大频率为36MHz,所以APB1的预分频系数可以取任意数值;
- O9 `0 d; I: b8 \3 c0 I( t( u: L5 ^7 f/ v, N4 H
当预分频系数=1时,APB1=36MHz,TIM2~7的时钟频率=36MHz(倍频器不起作用);; H5 ^3 V8 J- Q. }$ U* O; Y( ~

, h% d6 @- y1 {' N3 _* c6 D当预分频系数=2时,APB1=18MHz,在倍频器的作用下,TIM2~7的时钟频率=36MHz。4 q$ W$ V+ f9 i  h9 g4 G2 U+ p
, ]+ T! X: S( r  p8 v& O& l4 X
有人会问,既然需要TIM2~7的时钟频率=36MHz,为什么不直接取APB1的预分频系数=1?答案是:APB1不但要为TIM2~7提供时钟,而且还要为其它外设提供时钟;设置这个倍频器可以在保证其它外设使用较低时钟频率时。
  z# F" v* D$ k, b# a+ I0 P) H+ a) j
Stm32外设用户手册,如图:
$ W! c& g. k0 h: p$ x5 J+ b+ @
1345217152_6400.jpg
7 t3 f: j* l( \3 Q' T4 h2 N9 J' Y+ E& M, N
再举个例子:当AHB=72MHz时,APB1的预分频系数必须大于2,因为APB1的最大频率只能为36MHz。如果APB1的预分频系数=2,则因为这个倍频器,TIM2~7仍然能够得到72MHz的时钟频率。能够使用更高的时钟频率,无疑提高了定时器的分辨率,这也正是设计这个倍频器的初衷。* f: H2 X3 B# h3 J3 i4 q

4 V6 U. J5 ]2 L- m. k$ p* Y. A
; N+ j* L$ d# z$ D5 v4 z: `" Z. X1 v% ?
二、STM32通用定时器编程! L# p7 Y2 P% o' Y4 z" u1 Z

  ?" F0 z; _* u6 K  ^    定时器编程,就是中断的编程。因为使用定时器必定要使用到中断。
2 M. N. j6 K6 M9 J2 c1 @8 q/ S# ~
6 Z8 x5 R7 E0 K7 i" T! |/ p
! B6 ^/ \2 E( [& |2 R; v9 S9 @
/ n$ u* O. ], p8 ~1 \" g6 X步骤一:RCC_Configuration();//设置系统时钟,包括时钟RCC的配置,倍频到72MHZ。0 ~' C& [2 i4 O  P0 X9 x- O) ~' u

  h( x9 A, a9 Q+ q, \! x. `: S
' s# k! ^1 V( Y  S& |7 [/ h; z
# w# K" L8 a7 r步骤二: GPIO的配置,使用函数为GPIO_cfg();,该函数的实现如下:* J/ [4 T$ P0 R  c$ d% [
$ G5 S' H- d( ^) I
  1. void GPIO_cfg()* T3 O- j6 t( ?. I

  2. 0 u/ y5 D# Z5 D6 Q, F
  3. {. y* W- {! _' T6 g

  4. ! b! a2 P8 E4 g0 A1 v( ?) Z% @( O
  5.        GPIO_InitTypeDef GPIO_InitStructure;
    7 m) t; k: V4 ^; D+ X- D
  6. & |; V8 A9 ^6 |3 J+ W
  7.       RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD , ENABLE);
    : c7 l% r2 F3 k: p1 V7 m
  8. % k$ @8 C7 ]# k

  9. , h% c& v, B8 F! U7 g

  10. % `; ~8 A2 v' k2 R
  11.        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;                 //选择引脚61 j4 C/ x( L- P: c& N0 R8 ^

  12.   \7 e: u; o0 @& R
  13.        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出频率最大50MHz
    : @. T* N8 }7 X9 i: n

  14.   M$ k1 g0 z, A. J$ j
  15.       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //带上拉电阻输出
      h7 y' y- {: L

  16. 7 ?" X+ q. y2 A9 E' }/ S
  17.        GPIO_Init(GPIOC,&GPIO_InitStructure);
    6 Q9 g, V  E) y0 }/ z1 A! T
  18. . J- U, e4 r! _' L) a/ j) P
  19. }
复制代码
& r, P1 `* ?: X* i4 m) c/ |. b9 K* A
实际上定时器的讲解,不需要配置GPIO的引脚,只是我们在定时器实验中,9 L! H' X, f" O
3 a9 J* w! {: Z/ @5 u
使用每隔一秒点亮一次LED灯来做实验,所以需要配置对应GPIO的引脚。
. h. |& a' [0 T3 A6 P: |& s7 g0 ^! \0 S
7 ]  y% E' X. ?& B" ?- J, o- I
9 `6 a% l% ^) r- M  k
步骤三:嵌套中断控制器的配置,我们照样使用函数NVIC_Config();只是初始化的过程略有不同。这里我们也把函数实现列出来:4 P4 h6 J4 Q  x
# W5 p5 O+ l! W% h1 j, `+ Q0 v. r
     从以上函数实现来看,实际上只是改动了结构体成员NVIC_IRQChannel的值,现在需要的通道是TIM2的通道,因此初始化值为TIM2_IRQChannel。从这里也可以看出,这个函数实际上可以看做一个模型,可以拿去别的程序中改动后直接使用。
; X& q! Q* S6 o9 Y( K6 z4 s9 W3 A! U: E* R! Q% @8 p

; l. ?6 O' ^/ |8 O5 d( d8 U! S- N/ m! b. I, w  L6 g( e2 ~, r
  1. void NVIC_cfg()
    ( _, s1 I# q" L

  2. 2 h! ^% ?/ N7 `3 X/ x
  3. {" q3 S4 C$ A- O% D( H# z
  4. - E5 q# n: p2 V$ p
  5.        NVIC_InitTypeDef NVIC_InitStructure;
    7 w( D. B  J- y1 X
  6. ) v9 N  ^! n, V& _* H
  7.         //选择中断分组1; h: d+ b+ p$ ?$ E
  8. . r# P7 g0 V" k
  9.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    4 P8 X2 x6 u. ^- H

  10. 7 q1 c8 m3 F  J* P' y

  11. - g2 j% J' q% o2 b1 K3 ^5 [, u
  12. 9 |4 z' o6 m3 @% X0 c
  13.         //选择TIM2的中断通道
    % {) b0 ^  P& ?; G

  14. 2 `/ x3 X9 {# d3 i+ K
  15.         NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;     0 G5 l# x/ @& O* ?" m! A
  16. 5 G3 t' {' K1 C7 u) g* b
  17.         //抢占式中断优先级设置为0) s$ l5 g6 u6 R

  18. 4 B+ e( l7 \+ f" F
  19.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    7 N% j- s6 [: \( {1 c8 V

  20. , ^/ Z- F$ L( B7 Z  h% T
  21.        //响应式中断优先级设置为0
    0 D( f7 s$ {" K! |: v

  22. 7 [, @+ y- }4 {1 E) e
  23.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    ! j- ^9 ^# m; U5 Q2 F7 `

  24. 7 r  o/ n4 Z6 f, |
  25.         //使能中断
    - s) r  J5 {# o, s4 Y6 i

  26. $ o5 `1 S$ }7 R! i2 i
  27.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    % e9 H3 @* t& h% M
  28. 9 E% n3 k8 H, D
  29.         NVIC_Init(&NVIC_InitStructure);2 z6 V. G% t2 W$ ^2 G! K
  30. 1 [, t. h+ \4 z* J4 N" _
  31. }
复制代码

2 N, w! [, R. T1 I  O5 _7 x0 b0 B) Y/ g. i2 x( }; T, x
步骤四:定时器的初始化配置,使用Timer_Config();。OK,关键部分出来了。
# t4 W5 p2 A- _/ l% @- \. }# }/ i* l3 d3 J& R4 }: K
我们来看下实现过程:
/ y, Z  Q% a1 ^/ ]8 Y
4 m! _& z7 v. _" o6 _* K5 @
  1. TIMER_cfg(); //定时器的配置
    & A& {* l9 _: Y2 R) `& o) u
  2. ( S+ _! }7 C! a% G6 q4 R$ ~  r- p
  3.        //开启定时器2- i# e3 ]+ |5 u& F

  4. ' X  o9 d/ v; ^2 L5 [# D# W3 w' z
  5. TIM_Cmd(TIM2,ENABLE);
    1 c1 R! i3 M9 R: X) o3 A8 N' t, r) |8 G
  6. % X. K8 z3 d4 O6 e
  7. voidTimer_Config(void)9 K. e. p9 q+ T" R
  8. {
    . w) @  S* h3 a$ @, \) |

  9. 8 J7 ~: v. j! {# e$ R- J% K
  10.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); 8 ^! J- g6 l' m; l7 i, ^9 u

  11. 8 U2 j) I3 i" Y# z: F5 J! J; Q5 A) [, `
  12.     TIM_DeInit(TIM2);
    , l0 {" n* x0 M- S4 C- n

  13. ( H7 m1 A" j  {$ l5 h) H
  14.     TIM_TimeBaseStructure.TIM_Period=2000-1;  //自动重装载寄存器的值
    ! f' D: r1 Y! L7 e6 P5 ?- k
  15. # {1 R, ^2 w4 N/ a4 X* z. E. H& ^- Q
  16.     TIM_TimeBaseStructure.TIM_Prescaler=(36000-1);         //时钟预分频数
    * z$ X0 x  z5 A. e
  17. 9 C# K- N6 |3 q% b1 Y- a
  18.     TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;  //采样分频
    6 K2 f1 F+ D1 A) F# S3 W% l
  19. 5 s& e" j/ B! D! W9 V
  20.      TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    ) c- T: m5 P4 h6 b% J
  21.     TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);# H$ [& G- E, m9 w
  22. 4 ^3 S! `) ]* y3 r4 }8 C2 g
  23.     TIM_ClearFlag(TIM2,TIM_FLAG_Update);               //清除溢出中断标志$ m9 t1 Q& d: z; l' w
  24. ! d! {# S, `$ h# ]  j0 f4 w  C
  25.     TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);+ E: V1 f  W/ {& M  v* G

  26. / C. v) e0 p6 A. ~; Q
  27.      TIM_Cmd(TIM2,ENABLE);                              /开启时钟/ v# h& j1 l; E, L  p2 L& x6 v

  28. 1 H2 d  f: B$ i$ v+ H% e3 Z
  29. }
复制代码
" u/ ], F) Y& o# v# v
    我们每个语句都来解释一下。首先我们想使用定时器,就必须使能定时器的时钟,这就是函数 RCC_APB1PeriphClockCmd();,通过它开启 RCC_APB1Periph_TIM2。! K9 z; x. _  U

( x1 t. A) c! JTIM_DeInit(TIM2);该函数主要用于复位TIM2定时器,使之进入初始状态。9 y/ M- _: |5 J
& h! N7 N$ T% D; F% F" Y( i
然后我们对自动重装载寄存器赋值,TIM_Period的大小实际上表示的是需要经过TIM_Period次计数后才会发生一次更新或中断。接下来需要设置时钟预分频数TIM_Prescaler,这里有一个公式,我们举例来说明:例如时钟频率=72MHZ/(时钟预分频+1)。说明当前设置的这个TIM_Prescaler,直接决定定时器的时钟频率。通俗点说,就是一秒钟能计数多少次。比如算出来的时钟频率是2000,也就是
5 x; x% S$ _1 Q: g
4 S( M( z) n/ C% t2 `一秒钟会计数2000次,而此时如果TIM_Period设置为4000,即4000次计数后就会中断一次。由于时钟频率是一秒钟计数2000次,因此只要2秒钟,就会中断一次。" G+ I2 U# a7 f% l1 X( u

6 H3 N* N; I# b再往后的代码,还有一个需要注意的,TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;就是我们一般采用向上计数模式,即每次计数就会加1,直到寄存器溢出发生中断为止。最后别忘了,需要使能定时器!!
; M; u0 t5 j# M* c- v' s. h# x$ f! x# r3 D+ M& W) q9 X
发生中断时间=(TIM_Prescaler+1)* (TIM_Period+1)/FLK
0 y1 i; |% G4 l% |
4 f4 }' z6 @+ M, e4 t3 B  q. ] 用上述公式可算出:发生中断时间 (2000-1+1)*(36000-1+1)/72000000=1 秒
- y5 f1 b8 I+ I- ^' {6 x9 [7 y& Z& H. y, Q% T( T
步骤五:编写中断服务程序。同样需要注意的,一进入中断服务程序,第一步要做的,就是清除掉中断标志位。由于我们使用的是向上溢出模式,因此使用  T: Y' h7 _2 U4 e8 r" {6 S
4 ]0 Q) y% x( T* A9 e1 R$ v) A
的函数应该是:TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);。+ X$ n0 a% u1 X. G. U5 u

  o: @7 X4 l" Q, W: ISTM32开发板实现的中断服务程序如下:
9 j. ]# {, H( H0 A$ c6 M% i8 J# n+ d1 k) f) M2 H( Q
每隔一秒,发生中断时,进入此中断函数执行程序,让LED闪一下,此中断程序所在文件stm32f10x_it.c
; Q5 P  B* ]* Z6 S4 {3 m7 H1 |% x# d$ @$ r, y9 S
  1. /*******************************************************************************
    & O8 y6 v3 t* d% w/ l1 S

  2. & U( n- x( R8 z% S2 q1 C
  3. * Function Name  : TIM2_IRQHandler
    % }; F4 f6 C4 n: @/ b. F- p  \, r

  4. , \, [- P4 f$ Y% j! l$ H0 K
  5. * Description    : This function handles TIM2 global interrupt request.4 c0 r5 ?% r8 T( m

  6. 1 z* B- A& X" l" y. s' U9 [6 D  A
  7. * Input          : None
    ) r# L, m; Y1 c+ m* G

  8. ( {8 \, w! Q2 Q
  9. * Output         : None) {, N& ~- @$ y5 l; |
  10. , j) g, q) c& p' A8 Y
  11. * Return         : None
    9 e5 u3 C# c6 H+ }' m! j, C& ?* K

  12. - C; h/ A" I9 u: y
  13. ******************************************************************************/. H1 J  c; t" t# b
  14. / E6 l' U0 E3 E% U

  15. 3 Z' S6 {0 ?+ H2 G

  16. : k9 l' }( ~  V8 Q4 o4 \$ E1 n
  17. void TIM2_IRQHandler(void)
    ) u: I1 E6 Z9 t" `; Y. x) _
  18. 2 K6 c1 }; x/ K3 C) S" G
  19. {) _% M' a* C! ~

  20. 5 Z6 |4 u) Z. }. E/ c7 g0 `4 u
  21.    u8 ReadValue;
      H: F  V& w$ x

  22. 3 y$ }- k6 r9 d/ ^# \8 f

  23. - \' x% F, O. a# z4 d! o0 C+ I  a
  24. - C9 t' ^" U# w: R9 a
  25.    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)    //检测是否发生溢出更新事件% C+ @( _4 J, n/ P; s) h$ k
  26. , N7 A4 C  R9 i' |3 M4 l
  27.      {5 h% M, d+ K7 \6 E& \$ m
  28. 1 _8 C, L% X+ L! V# h
  29.         <span style="color:#ff0000;">TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);//清除TIM2的中断待处理位
    8 N$ e* z9 w2 {/ ]
  30. - Y2 t2 J6 e6 B% t8 ]6 ]3 |+ ?
  31. </span>              //UART2_TX485_Puts("123450");
    ' G- Q. A5 u0 g0 J" `
  32. " Y7 x! @1 A% W: j$ f6 l& _+ c

  33. ! w  n1 }3 O9 B- [
  34. 8 D7 g, |& @( H! ^5 S! L
  35.               /*调试定时器测试*/* o: g, e7 S" e' P- n" `  H4 Y1 R
  36. 1 [8 {9 y6 ~1 k. y. m
  37.               ReadValue = GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_6);
    . X7 p  F: M+ P* c% q  M7 ~

  38. + b7 w5 Z% _: a4 S
  39.         if(ReadValue == 0)                   //若该端口当前为低电平,
    2 _+ ?7 f/ \+ H: B! C) i
  40. 3 d2 f. D9 g+ b% B& @5 l8 S: {
  41.          {
    , a' `7 S6 k4 {- J, k/ |* J7 m+ `
  42. 8 `* a& u9 i, Y' q6 z' t
  43.             GPIO_SetBits(GPIOC,GPIO_Pin_6);//将其改为高电平输出 ;$ Y* B# C2 M% m, g- d$ Z: s% b& O
  44. # t' F4 V1 B1 ]& P
  45.          }   
    - M5 K) H# ~& }; }1 |
  46. 4 n. u  y2 X8 l, B/ X7 C
  47. 1 k9 x; N9 \$ f' Y1 D  V

  48. : S  L% d" i% \( K' T( I
  49.          else                            //若该端口当前为高电平,6 v, m2 ], g3 Y' C

  50. & L" E; u* @3 r( T1 G
  51.          {
    : H5 v# n4 o# v' D: {- C" _  ]9 S2 \

  52. 3 B  q  |0 G' x- O( ?4 U
  53.             GPIO_ResetBits(GPIOC,GPIO_Pin_6);   //将其改为低电平输出;7 D' u4 Y/ c4 m+ ~
  54. $ ]5 d' r! X1 A: q; q% M% W
  55.          }& c4 d) }( f0 u  _

  56. ! Z) a! G9 E6 o& h

  57. 9 p: ^9 |# \) g
  58. # Y6 ^% G$ a4 T: `6 H9 f9 e6 _3 ?- N! }
  59.      }        
    2 y) n1 a6 q* \
  60. , P+ _7 a; F+ M9 b: |, r) N& B
  61. }
复制代码
2 V, f$ Y! g1 V# v/ t% @0 l
4 l' z' j1 Y+ \
普通定时器工作原理图' O0 O$ b5 ]' f0 O

3 `+ t3 h- H+ l; y6 v4 ` 1345217262_1135.jpg 9 t5 s7 E# `4 S6 |) [' q* _

2 G+ Q$ u, t) U) p- C% ~5 m& T4 q
( a$ I% M+ |/ R' V8 x" |
赞 收藏 评论0 发布时间:2022-5-24 10:15

举报

0个回答

所属标签

相似技术帖

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