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

【经验分享】STM32实例-定时器中断实验

[复制链接]
STMCU小助手 发布时间:2022-6-29 17:00
   上次我们介绍了 STM32F1的外部中断,本文我们来学**定时器中断。STM32F1 的定时器功能非常强大,其包含 2 个基本定时器(TIM6、TIM7)、4 个通用定时器(TIM2-TIM5)和 2 个高级定时器(TIM1、TIM8),共计8 个,与 51单片机定时器的功能和数量相比优势非常明显。本章以通用定时器为例进行讲解,让大家学会 STM32F1 定时器的使用。本章要实现的功能是:通过 TIM4 中断控制 D2指示灯闪烁, 主函数控制 D1 指示灯闪烁。

5 z& q# v) U. [* g! s+ B3 T
定时器介绍
    STM32F1 的定时器非常多,由 2 个基本定时器(TIM6、TIM7)、4 个通用定时器(TIM2-TIM5)和 2 个高级定时器(TIM1、TIM8)组成。基本定时器的功能最为简单,类似于 51 单片机内定时器。通用定时器是在基本定时器的基础上扩展而来,增加了输入捕获与输出比较等功能。高级定时器又是在通用定时器基础上扩展而来,增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能主要针对工业电机控制方面。本章对高级定时器不做过多介绍,主要针对常用的通用定时器进行讲解,如需对定时器深入了解,可以参考《STM32F10x 中文参考手册》-13、14、15 定时器章节,这么大篇幅对定时器的介绍,足见STM32F10x 定时器之重要性。
    如果学会了使用通用定时器,那么你也学会了基本定时器用法,至于高级定时器,只是比通用定时器多了那几个功能,你只要把对应的那几个功能对照中文参考手册看下即可。
8 v1 T' H( E: n  n5 x
通用定时器简介
    STM32F1 的通用定时器包含一个 16 位自动重载计数器(CNT),该计数器由可编程预分频器(PSC)驱动。STM32F1 的通用定时器可用于多种用途,包括测量输入信号的脉冲宽度(输入捕获)或者生成输出波形(输出比较和 PWM)等。
    使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32F1 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
    STM32F1 的通用定时器TIMx (TIM2-TIM5 )具有如下功能:
(1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
(2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。
(3)4 个独立通道(TIMx_CH1-4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C. PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
(4)可使用外部信号(TIMx_ETR)控制定时器,且可实现多个定时器互连
(可以用 1 个定时器控制另外一个定时器)的同步电路。
(5)发生如下事件时产生中断/DMA 请求:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
(6)支持针对定位的增量(正交)编码器和霍尔传感器电路
(7)触发输入作为外部时钟或者按周期的电流管理

9 Y% ]' B. E, o5 A' i/ K8 h
通用定时器结构框图
    其实上一节介绍的那些通用定时器功能,是由通用定时器内部结构所致。其内部结构框图如下图所示。

6 {2 }  b7 n4 |0 W& P
微信图片_20220628170713.jpg
! C5 S1 T( R6 \$ `% X  C
    在通用定时器结构框图中我们可以看到有两种奇怪的箭头,它们的意义如下:

# I- M8 A* _; z! P- W
微信图片_20220628170707.png

& {& A- K8 ?4 L; Z6 l6 e% b
    我们把通用定时器结构框图分成 5 个子模块,按照顺序依次进行简单介绍。
(1)标号 1:时钟源
    通用定时器的时钟来源有4种可选:
A.内部时钟(CK_INT)
B.外部时钟模式 1:外部输入引脚 TIx(x=1,2,3,4)
C.外部时钟模式 2:外部触发输入 ETR
D.内部触发输入(ITRx(x=0,1,2,3))
    通用定时器时钟来源这么多,具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置,定时器相关寄存器的介绍可以参考《STM32F10x中文参考手册》-通用定时器-寄存器章节详细了解。这里的 CK_INT 时钟是从 APB1 倍频得来的, 除非 APB1 的时钟分频数设置为 1 (一般都不会是 1) , 否则通用定时器TIMx的时钟是 APB1 时钟的 2 倍,当 APB1 的时钟不分频的时候,通用定时器 TIMx的时钟就等于 APB1 的时钟。这里还要注意的就是高级定时器的时钟不是来自APB1,而是来自 APB2,这个在库文件 stm32f10x_rcc.h 也可以查找到,如下:

- e8 q- }' k2 `/ e, W6 S
微信图片_20220628170657.jpg

0 R9 P7 r$ ^6 F% z& O* C
    通常我们都是将内部时钟(CK_INT)作为通用定时器的时钟来源,而且通用定时器的时钟是 APB1 时钟的2倍,即 APB1 的时钟分频数不为 1。所以通用定时器的时钟频率是 72MHz。
(2)标号 2:控制器
    通用定时器控制器部分包括触发控制器、从模式控制器以及编码器接口。触发控制器用来针对片内外设输出触发信号,比如为其它定时器提供时钟和触发DAC/ADC 转换。从模式控制器可以控制计数器复位、启动、递增/递减、计数。编码器接口专门针对编码器计数而设计。
(3)标号 3:时基单元
    通用定时器时基单元包括3个寄存器,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)。高级定时器中还有一个重复计数寄存器(TIMx_RCR),通用和基本定时器是没有的。通用定时器这三个寄存器都是 16 位有效。而高级定时器的 TIMx_RCR 寄存器是 8 位有效。
    在这个时基单元中,有个预分频器寄存器(TIMx_PSC),用于对计数器时钟频率进行分频,通过寄存器内的相应位设置,分频系数值可在 1 到 65536 之间。
    由于从模式控制寄存器具有缓冲功能,因此预分频器可实现实时更改,而新的预分频比将在下一更新事件发生时被采用。
    在时基单元中,还有个计数寄存器(TIMx_CNT),通用定时器计数方式有向上计数、向下计数、向上向下计数(中心对齐计数)。下面分别来介绍下这几种计数方式:
1.向上计数
    在向上(递增)计数模式下,计数器从 0 开始计数,每来一个 CK_CNT脉冲计数器就加 1,直到等于自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0开始计数并生成计数器上溢事件。每次发生计数器上溢时会生成更新事件 (UEV) ,或将 TIMx_EGR 寄存器中的 UG 位置 1(通过软件或使用从模式控制器)也可以生成更新事件。通过软件将 TIMx_CR1 寄存器中的 UDIS 位置 1 可禁止 UEV 事件。这可避免向预装载寄存器写入新值时更新影子寄存器。在 UDIS 位写入 0 之前不会产生任何更新事件。不过,计数器和预分频器计数器都会重新从 0 开始计数(而预分频比保持不变)。此外,如果设置 TIMx_CR1 寄存器中相应的中断位置 1,也会产生中断事件。
2.向下计数
    在向下(递减)计数模式下,计数器从自动重载值( TIMx_ARR 寄存器的内容) 开始递减计数到 0, 然后重新从自动重载值开始计数并生成计数器下溢事件。
    每次发生计数器下溢时会生成更新事件, 或将 TIMx_EGR 寄存器中的 UG 位置 1(通过软件或使用从模式控制器)也可以生成更新事件。通过软件将 TIMx_CR1寄存器中的 UDIS 位置 1 可禁止 UEV 更新事件。这可避免向预装载寄存器写入新值时更新影子寄存器。在 UDIS 位写入 0 之前不会产生任何更新事件。不过,计数器会重新从当前自动重载值开始计数,而预分频器计数器则重新从 0 开始计数(但预分频比保持不变)。此外,如果设置 TIMx_CR1 寄存器中相应的中断位置 1,也会产生中断事件。
3.向上向下计数(中心对齐计数)
    在中心对齐模式下,计数器从 0 开始计数到自动重载值( TIMx_ARR 寄存器的内容)-1,生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重新计数,如此循环执行。每次发生计数器上溢和下溢事件都会生成更新事件。
    在时基单元中,还有个自动重载寄存器(TIMx_ARR),该寄存器是用来放与
CNT 计数器比较的值。自动重载寄存器(TIMx_ARR)的控制受 TIMx_CR1 寄存器中ARPE 位决定,当 ARPE=0 时,自动重载寄存器(TIMx_ARR)不进行缓冲,寄存器内容直接传送到影子寄存器。当 APRE=1 时,在每一次更新事件( UEV)时,才把预装载寄存器(ARR)的内容传送到影子寄存器。
(4)标号 4:输入捕获
    输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,通常用于测量输入信号的脉宽、测量 PWM 输入信号的频率及占空比。
    在输入捕获模式下,当相应的 ICx 信号检测到跳变沿后,将使用捕获/比较
寄存器(TIMx_CCRx)来锁存计数器的值。发生捕获事件时,会将相应的 CCxIF 标志( TIMx_SR 寄存器)置 1,并可发送中断或 DMA 请求(如果已使能)。如果发生捕获事件时 CCxIF 标志已处于高位, 则会将重复捕获标志 CCxOF ( TIMx_SR寄存器)置 1。可通过软件向 CCxIF 写入 0 来给 CCxIF 清零,或读取存储在TIMx_CCRx 寄存器中的已捕获数据。向 CCxOF 写入 0 后会将其清零。
    输入捕获单元由输入通道、输入滤波器和边沿检测器、输入捕获通道、预分频器、捕获/比较寄存器等组成。
    从框图中可以看到,通用定时器的输入通道有 4 个 TIMx_CH1/2/3/4。通常也把这个 4 个通道称为 TI1/2/3/4,如果后面出现此类称呼就表明是通用定时器的 4个输入通道。这些通道都对应到 STM32F1 引脚上,可以把被检测信号输入到这 4个通道中进行捕获。当输入的信号存在高频干扰时,可以使用输入滤波器进行滤波,具体滤波原理可以参考《STM32F10x 中文参考手册》通用定时器捕获/比较通道章节。边沿检测器是用来设置信号在捕获时哪种边沿有效,可以为上升沿、下降沿、双边沿,具体是由TIMx_CCER 寄存器相应位设置。输入捕获通道就是框图中的 IC1/2/3/4, 每个捕获通道都有对应的捕获寄存器 TIMx_CCR1/2/3/4。
    如果发生捕获的时候,CNT计数器的值就会被锁存到捕获寄存器中。这里我们要搞清楚输入通道和捕获通道的区别,输入通道是用来输入信号的,捕获通道是用来捕获输入信号的通道,一个输入通道的信号可以同时输入给两个捕获通道。比如输入通道 TI1 的信号经过滤波边沿检测器之后的 TI1FP1和 TI1FP2 可以进入到捕获通道 IC1 和 IC2,在前面的框图中也可以看到信号箭头的流向。
    ICx 的输出信号会经过一个预分频器,用于决定产生多少个事件时进行一次捕获。具体由寄存器 TIMx_CCMRx 的位 ICxPSC 配置,如果希望捕获信号的每一个边沿,则把预分频器系数设置为 1。经过预分频器的信号 ICxPS 是最终被捕获的信号,当发生第一次捕获时,计数器 CNT 的值会被锁存到捕获/比较寄存器CCR 中 (此时使用捕获寄存器功能) , 还会产生 CCxI 中断, 相应的中断位 CCxIF(在 SR 寄存器中)会被置位,通过软件或者读取 CCR 中的值可以将 CCxIF 清0。如果发生第二次捕获 (即重复捕获:CCR 寄存器中已捕获到计数器值且 CCxIF标志已置 1) , 则捕获溢出标志位 CCxOF (在 SR 寄存器中) 会被置位, CCxOF 只能通过软件清零。
(5)输出比较
    输出比较就是通过定时器的外部引脚对外输出控制信号,可以输出有效电
平、无效电平、翻转、强制变为无效电平、强制变为有效电平、 PWM1 和 PWM2等模式,具体使用哪种模式由寄存器 CCMRx 的位 OCxM[2:0]配置。其中 PWM 模式是输出比较中的特例,使用的也最多。
    从框图中可以看到, 输出比较单元与输入捕获单元共用了捕获/比较寄存器,只不过在输出比较的时候使用的是比较寄存器功能。
    当计数器 CNT 的值与比较寄存器 CCR 的值相等的时候,输出参考信号OCxREF 的信号的极性就会改变,并且会产生比较中断 CCxI,相应的标志位CCxIF(SR 寄存器中)会置位。然后 OCxREF 再经过一系列的控制之后就成为真正的输出信号 OC1/2/3/4,最终输出到对应的管脚 TIMx_CH1/2/3/4。
    由于篇幅限制,本章并没有对定时器寄存器进行介绍,大家可以参考
《STM32F10x 中文参考手册》通用定时器章节,里面有详细的讲解。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。

/ B, e6 U1 x2 B% @
通用定时器配置步骤
    接下来我们介绍下如何使用库函数对通用定时器进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(定时器相关库函数在 stm32f10x_tim.c和 stm32f10x_tim.h 文件中)
(1)使能定时器时钟
    本章定时器实验,我们使用的是通用定时器 TIM4,我们知道 TIM4 是挂接在APB1总线上的,所以可以使用 APB1 总线时钟使能函数来使能 TIM4,调用的库函数如下:
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能TIM4 时钟
复制代码

  q# Z( C5 U, M( c, }
(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等' @+ {6 ^5 ]/ |
    要使用定时器功能,必须对定时器内相关参数初始化,其库函数如下:
  1. voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);
复制代码

+ L0 F! C7 M* h# Y2 K
    函数中第一个参数是用来确定哪个定时器,例如 TIM4;第二个参数是一个结构体指针变量,结构体类型是 TIM_TimeBaseInitTypeDef,其内包含了定时器初始化的成员变量。下面我们就来看下这个结构体:
  1. typedef struct
    ' s6 J' M1 v1 k+ h
  2. {
    / g- L8 b# ^. H; [; G- f, t
  3. uint16_t TIM_Prescaler; //定时器预分频器
    1 U7 c& X$ V3 B- j) I9 d8 \
  4. uint16_t TIM_CounterMode; //计数模式% _6 I  A- s: h2 y
  5. uint32_t TIM_Period; //定时器周期! L; i! T  W$ A  k$ W
  6. uint16_t TIM_ClockDivision; //时钟分频. h9 `6 v( K# Y# _8 t+ u
  7. uint8_t TIM_RepetitionCounter; //重复计数器( _6 t! n1 B0 q. q) Z1 A
  8. } TIM_TimeBaseInitTypeDef;
复制代码

/ O3 d, B# |2 f, O; v2 l+ y& `
    TIM_TimeBaseInitTypeDef 结构体内含有 5 个成员变量,前 4 个在通用定时器中会使用到,最后一个是在高级定时器中才会用到。下面就来简单介绍下每个成员变量的功能:
    TIM_Prescaler:定时器的预分频器系数,时钟源经过该预分频器后输出的
才是定时器时钟,设置值范围:0-65535,分频系数由于是除数,分母不能为0,所以会自动加 1,最后实现1-65536 分频。
    TIM_CounterMode:定时器计数方式,前面讲解过,可以设置为向上、向下、中心对齐计数方式。比较常用的是向上计数模式(TIM_CounterMode_Up)和向下计数模式(TIM_CounterMode_Down)。
    TIM_Period:设置定时器自动重载计数周期值,在事件产生时更新到影子寄存器。可设置范围为 0 至 65535。
    TIM_ClockDivision:时钟分频因子,设置定时器时钟 CK_INT 频率与数字
滤波器采样时钟频率分频比。
    TIM_RepetitionCounter:重复计数器,通过此参数可以非常简单的控制
PWM 输出个数。此成员只针对于高级定时器配置,基本定时器与通用定时器不用设置。
    了解结构体成员功能后,就可以进行配置,例如:
  1. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    1 h" n2 q2 Q0 t5 u  C0 a# D" ]3 l: G
  2. TIM_TimeBaseInitStructure.TIM_Period=1000; //自动装载值
    , h6 ^. B- M5 Y. F8 @1 n
  3. TIM_TimeBaseInitStructure.TIM_Prescaler=35999; //分频系数
    ( N+ @8 Q! l& P8 O, `2 t
  4. TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    6 o5 m4 O# s* k. r0 J. X3 s" h
  5. TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;5 }2 j/ S# ]+ d/ D
  6. //设置向上计数模式* L0 z- M* E! u
  7. TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
复制代码

1 z0 f* x0 @* d% m1 q1 f- b$ j
(3)设置定时器中断类型,并使能
    对定时器中断类型和使能设置的函数如下:
  1. void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT,FunctionalState NewState);
复制代码

. N. z: \( j1 l
    第一个参数用来选择哪个定时器,例如 TIM4。
0 m3 E; g7 N4 S5 I3 U, g% ]; P
    第二个参数用来设置定时器中断类型,定时器的中断类型非常多,包括更新中断 TIM_IT_Update、触发中断TIM_IT_Trigger、输入捕获中断等等。
第三个参数用来使能或者失能定时器中断类型,可以为 ENABLE 和 DISABLE。
    例如我们要使能定时器TIM4 更新中断,调用函数如下:
  1. TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //开启定时器中断
复制代码

4 A) B8 a4 c5 X0 U* N- X+ L
(4)设置定时器中断优先级,使能定时器中断通道
    在上一步我们已经使能了定时器的更新中断,只要使用到中断,就必需对
NVIC 初始化,NVIC 初始化库函数是 NVIC_Init(),这个在前面讲解 STM32 中断时就已经介绍过,不清楚的可以回过头看下。
(5)开启定时器
    前面几个步骤已经将定时器配置好,但还不能正常使用,只有开启定时器了才能让它正常工作,开启定时器的库函数如下:
  1. void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码
" C7 {2 `* d/ ^
    第一个参数是用来选择定时器。
; }/ U2 H" Q. ^. X; T6 V/ s- i
    第二个参数是用来使能或者失能定时器,也就是开启或者关闭定时器功能。同样可以选择 ENABLE 和 DISABLE。例如我们要开启 TIM4,那么调用此函数如下:
  1. TIM_Cmd(TIM4,ENABLE); //开启定时器
复制代码
2 |2 O  s( j/ S. ~
(6)编写定时器中断服务函数
( p+ R3 i% ~1 D3 W
    最后我们还需要编写一个定时器中断服务函数, 通过中断函数处理定时器产生的相关中断。定时器中断服务函数名在 STM32F1 启动文件内就有,TIM4 中断函数名如下:
TIM4_IRQHandler
    因为定时器的中断类型有很多,所以进入中断后,我们需要在中断服务函数开头处通过状态寄存器的值判断此次中断是哪种类型,然后做出相应的控制。库函数中用来读取定时器中断状态标志位的函数如下:
  1. ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
复制代码

( s4 w: D9 F3 v1 c2 [
    此函数功能是判断 TIMx的中断类型 TIM_IT 是否产生中断, 例如我们要判断TIM4的更新(溢出)中断是否产生,可以调用此函数:
  1. if(TIM_GetITStatus(TIM4,TIM_IT_Update))4 b! ~9 [% @5 y- H: `  W0 t/ }* m; s
  2. {! O! `& L* H4 _0 X9 x) z
  3. ...//执行 TIM4更新中断内控制
    * k. u. n; A" A! W: v. I( }
  4. }
复制代码

/ i) x+ _, X( d2 G
    如果产生更新中断,那么调用 TIM_GetITStatus 函数后返回值为 1,就会进入到 if函数内执行中断控制功能程序。否则就不会进入中断处理程序。
    在编写定时器中断服务函数时,最后都会调用一个清除中断标志位的函数,如下:
  1. void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
复制代码
" Q& Q& L- n3 g' h1 e
    里面的两个参数功能和前面读取定时器中断状态标识位函数一样, 例如我们要清除TIM4 的更新中断标志位,调用函数如下:
  1. TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
复制代码
4 K2 g% D/ x* `1 L: {0 f0 n8 [
    固件库中还有两个函数是用来读取状态标志位以及清除中断标志位, 函数分别为 TIM_GetFlagStatus和TIM_ClearFlag。
    这两个函数作用与前面两个函数类似, 只不过 TIM_GetITStatus 函数会先判断中断是否使能,使能了才会读取中断状态标志位,而 TIM_GetFlagStatus函数是直接判断状态标志位。
    将以上几步全部配置好后,我们就可以正常使用定时器中断了。

' x, @  W  E0 `
硬件设计
    本章硬件电路非常简单,只使用到开发板上的 LED(D1 和 D2),至于TIM4它属于STM32F1 芯片内部的资源。开发板上 LED 模块电路在前面已经介绍,这里不多说。
    所要实现的功能是:通过 TIM4 的更新中断控制 D2 指示灯间隔 500ms
0 _- x3 G: G3 Q
秒状态取反,主函数控制D1指示灯不断闪烁。程序框架如下:
(1)初始化 TIM4,并使能更新中断等
(2)编写 TIM4 中断函数
(3)编写主函数
    在前面介绍定时器配置步骤时,就已经讲解如何初始化定时器。下面我们打开“定时器中断实验”工程,在 APP 工程组中 添 加 time.c 文 件 , 在 StdPeriph_Driver 工 程 组 中 添 加 stm32f10x_tim.c 库文件。定时器操作的库函数都放在 stm32f10x_tim.c 和stm32f10x_tim.h文件中, 所以使用到定时器就必须加入stm32f10x_tim.c文件,同时还要包含对应的头文件路径。
: A4 h# e. E, G0 ]9 S+ \3 x2 X$ u: \
TIM4 初始化函数
要使用定时器中断,我们必须先对它进行配置。TIM4 初始化代码如下:
  1. /****************************************************************& H6 C, @' \) @* Q2 |  t. Z$ Q# D7 H
  2. * 函 数 名 : TIM4_Init' E) J) o) O. W' }
  3. * 函数功能 : TIM4 初始化函数5 O. m2 o. D* s1 h1 y; G$ Z( k: H8 _# Q9 v
  4. * 输 入 : per:重装载值psc:分频系数7 \( T! N9 k: E+ }
  5. * 输 出 : 无
    / d0 N3 \3 N# r/ o. u8 j
  6. *****************************************************************/
    ) b) }' X) Y( _" p- p' J; x
  7. void TIM4_Init(u16 per,u16 psc), _% _* @* J7 b% I
  8. {
      q4 _7 m" S7 t1 k" H% J0 ?, m) D! [
  9. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;( E" z9 L0 S$ n9 n; {: k2 e
  10. NVIC_InitTypeDef NVIC_InitStructure;* n3 o2 d, k  {3 t# X8 n
  11. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能TIM4时钟
    3 a: i0 W3 _4 I
  12. TIM_TimeBaseInitStructure.TIM_Period=per; //自动装载值
    # Z8 c) P! W5 l' {# L- Y
  13. TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数1 Y9 ?* h. H/ v. a0 f
  14. TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    $ N0 ~' L2 G6 {1 T6 r- L
  15. TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
    9 H/ J8 D; F( ~1 t9 i
  16. //设置向上计数模式
    , O2 X/ V0 E& G, i1 Y! j
  17. TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
    - w& p1 {5 b% P& _
  18. TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //开启定时器中断) @/ G% g6 o6 Z$ h6 f, b8 r. ]
  19. TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
    4 b0 v& c- e+ u6 ?6 D+ d' G2 T6 H1 J
  20. NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;//定时器中断通道
    5 h! t7 _$ m  A* X3 a! \+ m
  21. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢 占优先级
    * i5 X; N' d! x! u! i) s
  22. NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
    0 n4 [  S" E9 J& E; f; F& a
  23. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能; U8 l: e" D- E9 N
  24. NVIC_Init(&NVIC_InitStructure);% z0 W+ F( I: b( m
  25. TIM_Cmd(TIM4,ENABLE); //使能定时器; ^4 W" E; O% @, j' S1 z$ k% i$ X
  26. }
复制代码

% s, `. j; C& M+ k
   在 TIM4_Init()函数中,首先使能 TIM4 时钟,其次配置定时器结构体TIM_TimeBaseInitStructure,使能 TIM4 的更新中断,为了防止定时器中断状态标志位默认值的影响,我们清除下 TIM4 的更新中断标志,然后配置相应的NVIC并使能对应中断通道, 我们将 TIM4 的抢占优先级设置为 2, 响应优先级设置为3。
    最后就是对开启 TIM4。这一过程在前面步骤介绍中已经提了。
    TIM4_Init()函数有两个参数,用来设置定时器的自动装载值和分频系数,方便大家修改定时时间。
    其实如果你会使用通用定时器 TIM4,那么其他通用定时器都一样,如果要
使用基本定时器,那就更简单,因为通用定时器已经包含了基本定时器的功能。
    至于高级定时器大家可以参考中文参考手册了解, 配置过程只是增加一个复位计数寄存器的配置。
TIM4 中断函数
    初始化 TIM4 后, 中断就已经开启了, 当 TIM4 内计数器CNT 发生更新 (溢出)事件,就会产生一次中断,具体代码如下:
  1. /****************************************************************
    7 R' ]0 m/ _/ I6 [& s& s$ t+ J% i
  2. * 函 数 名 : TIM4_IRQHandler5 G5 G0 c+ ]5 C7 A5 f  k
  3. * 函数功能 : TIM4 中断函数3 z" M* f0 ^% @  l  C. y8 q0 @6 ^" G) b
  4. * 输 入 : 无
    % q8 x8 F, u- @0 P% D
  5. * 输 出 : 无
    " ]) Z) Q2 F; z/ K8 j& Z2 C
  6. *****************************************************************/7 W* v2 M4 `6 h5 T  I" [
  7. void TIM4_IRQHandler(void)
    % d( E- u% l7 N
  8. {
    4 M# F# ~. X* O+ P" m5 Y' E( J
  9. if(TIM_GetITStatus(TIM4,TIM_IT_Update))
    / M) s$ d% |% l) u" v5 I
  10. {
    . W4 w! ~1 ]2 Q3 L% i/ D( {
  11. led2=!led2;
    8 i, w3 ^( a; T2 i, m* p
  12. }6 |  \  E2 T" D' W1 e4 ~& s
  13. TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
    : o; p3 T. ^' f* V- E& e
  14. }
复制代码
/ C, N& o1 A+ |2 A* f# e$ s) I
    为了确认定时器是否发生更新中断, 调用了读取定时器中断状态标志位函数TIM_GetITStatus(TIM4,TIM_IT_Update),如果确实产生更新中断,那么就会执行if 内的语句,从而控制 D2 指示灯状态取反。最后要记得清除下定时器中断状态标志位。
" D# L: U- [7 [
, [' u- u) v% _6 a
主函数
    编写好定时器初始化和中断服务函数后,接下来就可以编写主函数了,代码如下:
  1. /****************************************************************
    " s1 x0 P& w9 G6 _0 Z2 Y: O' P; u
  2. * 函 数 名 : main
    , |7 j, j& E7 o! X/ [9 p" y
  3. * 函数功能 : 主函数
    2 B4 A0 x8 E/ |' e3 K1 g, r
  4. * 输 入 : 无0 A5 k# Y* D. S  y1 s7 c  \
  5. * 输 出 : 无
    ; z) ?5 R) m9 i- R/ h- F
  6. *****************************************************************/
    - Q( q5 Y2 b4 S- D( |
  7. #include "system.h"
    ' u6 \2 |  M5 C7 c1 L! Q
  8. #include "SysTick.h"
    6 j- o4 Z. N3 {$ Y  n9 |
  9. #include "led.h"
    5 ~6 c) h% i& F6 K0 }' j
  10. #include "time.h"
    ) e* a& E2 P' v$ ?. Z
  11. int main()  L! ^) B* ?/ H6 P) u9 |
  12. {
    8 k7 a& p& j% L: m& }
  13. u8 i;
    7 Y" }6 m, u1 r' Q& ~9 }+ b
  14. SysTick_Init(72);8 a, d* \! U! k4 r# i% F
  15. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
    ' U. x% t) k$ @4 N
  16. LED_Init();
    $ ]0 `# |+ ~; r# Y8 R% q  I
  17. TIM4_Init(1000,36000-1); //定时 500ms
    - v" J* T8 W( l- u7 h
  18. while(1)
    9 P* ?# O% l& g" A% p' B
  19. {
    # ?7 {' }2 q1 S! P' B
  20. i++;2 l; m) s; |, |5 x8 h9 B
  21. if(i%20==0)  W5 u+ R& A0 j( O( U
  22. {
    4 Z# e+ Y# J* E# T  l" H5 y
  23. led1=!led1;  [2 c$ V( i. \$ J
  24. }! ^- K# Z1 S* W* [
  25. delay_ms(10);( s. D  r& l) ]
  26. }
    ' s" l! ]: T: Q  \1 h' r# H9 x
  27. }
复制代码

" Q* s4 Y( _: T( N9 C
    主函数实现的功能很简单,首先初始化对应的硬件端口时钟和 IO 口,然后4 X2 W: r2 M+ R' j$ S
调用我们前面编写的 TIM4的初始化函数,这里我们设定定时器自动重装载值为1000,预分频系数为 36000-1,这里减 1 是因为定时器预分频器内部会自动加1,所以如果要进行 36000 分频的话,就传递 35999。最后进入while 循环语句,不断让 D1指示间隔 200ms闪烁。
    初始化后,定时器开始工作,计数器 CNT 从 0 开始计数,每来一个定时器时钟,计数器值就会累加一次,当计数到 1000 也就是累积计数 1000 次,这个时候定时器就发生溢出并产生更新中断,每产生一次更新中断时间是 500ms。
    有的朋友就会问,这个定时 500ms 是怎么计算的?很简单,我们知道 TIM4 是挂接在 APB1 总线上的,而 APB1 的时钟是36MHz,前面介绍定时器就说过,如果 APB1 时钟分频系数为 1,TIM2-7 的时钟即为 APB1总线的时钟,否则就是 APB1 总线时钟的 2 倍,即 72MHz。再根据刚才设计的自
动重装载值和预分频系数就可以计算出定时时间,计算公式如下:
  1. Tout= ((per)*(psc+1))/Tclk;
复制代码

: P% h2 c& A2 P3 y/ k5 p
    Tclk 是定时器的时钟频率值,72M,per 和 psc 是我们传递的参数值,Tout是定时器产生中断的时间,单位是 us。将数据代入即可得到产生定时更新中断时间是500ms。
    将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。D2 指示灯每隔500ms 状态取反一次,实现 1 秒钟闪烁一次。
. M0 o) S6 t  F% R& L0 R* ~
收藏 评论0 发布时间:2022-6-29 17:00

举报

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