TIM定时器

本篇教程针对STM32F103ZET6,因为C8T6的定时器个数和ZET6可能对不上

TIM定时器-1

STM32当中一共有8个定时器,其中这八个定时器分为基本、通用和高级定时器。

TIM定时器-2

基本定时器只能往上递增计数,从0到1、2、3……。由于基本定时器没有捕获比较通道,所以不能产生PWM波,也不能对外界输入脉冲进行捕获。

其余定时器可以递增计数也可以递减计数

单片机的八个定时器都可以产生DMA

一、基本定时器功能详解

TIM定时器-3

基本定时器的主要功能就是定时,它用来计数和产生PWM波。

定时器定时也是通过计算固定周期的脉冲个数来定时的,像51单片机通过对机器周期计数(如果外部晶振为2M,则一个机器周期为1us),计数1000次脉冲则定时了1000us。

STM32的基本定时器定时方法也是类似的,我们需要先了解基本定时器定时的脉冲来源,基本定时器的脉冲来源于内部时钟(即图中的1号红圈圈所示)。

TIM定时器-4

通过查看外设模块框图可知(中文数据手册),基本/通用定时器的定时脉冲来源于内部的APB1时钟总线上,而高级定时器的脉冲来源于APB2时钟总线上。

TIM定时器-5

由时钟树可知,36MHz信号从APB1出来,没有直接进入定时器,而是经过了预分频。

TIM定时器-6

其中如果APB1的分频系数等于1,那么时钟信号仍然保持36MHz,即不分频。如果该倍频系数不等于1时,则该时钟信号就会被倍频,即36MHz*2 = 72MHz,将72MHz的时钟信号提供给这些定时器。

TIM定时器-7

在程序控制上,是由static void SetSysClockTo72(void);这个函数来控制时钟频率的(system_stm32f10x.c文件中可查)

TIM定时器-8

这里的预分频系数代码已经设置成2了。

TIM定时器-9

当72MHz进入定时器后还需要再经过一个PSC预分频(即红圈圈2),这里的分频系数等于PSC+1,经过这里的预分频后,时钟频率就变成了72MHz/PSC+1

如果不经过该预分频器,每个脉冲的周期时间为1/72MHz,即便将计数器计数65535次,计数的时间依旧是极短的,所以需要将频率降下来,方便计数器计数和计时。

在STM32当中,计数器计数溢出不需要计数到65535计满后溢出,而是可以通过自动重装载计数器(即4号红圈圈部分)来指定计数的最大值,将该值命名为ARR,这个值一样也是16位的。

如果ARR被设置为1000,则计数器的计数范围为0-1000,这时候再来一个脉冲,产生溢出之后又回到0,如此反复。(所以ARR实际上就是计数的上限值)

所以实际上计数周期/次数,一共有ARR+1次

因此定时的时间为单个脉冲的周期时间乘以总的定时的次数,即:定时时间T= ((PSC+1)/72M * (ARR + 1))秒

如果需要将定时时间的单位计算为毫秒,则可以乘以1000,即:定时时间T= ((PSC+1)/72000 * (ARR + 1))毫秒

TIM定时器-10

根据定时时间可以对两个参数进行灵活的取值,这两个参数都是16位的,PSC和ARR可以取在0-65535之间的任意值。

取零问题:

如果PSC取0的话,就相当于预分频器不分频;可是如果在代码中ARR1取0时,代码会一直进入中断(从0开始计数后下一个脉冲又从0开始),所以取0在这里是没有意义的,因此ARR在代码中是不赋值为0的。

二、定时器定时的代码实现

对于F103C8T6单片机而言,一共有4个Timer定时器,其中TIM1为高级定时器,TIM2、3、4为标准定时器。

TIM定时器-11

而F103ZET6单片机的定时器如开头所说,有8个TIM定时器。

TIM定时器-12

TIM定时器-13

1、打开外设时钟

使用TIMER2这个定时器,首先要先打开TIMER的时钟,这里使用RCC库当中的函数: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

2、基本初始化TIM定时器

调用TIM库当中的函数:

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = 1; //再分频的分频系数
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //定时器的计数模式
TIM_TimeBaseInitStruct.TIM_Period = 999; //计数周期(次数)
TIM_TimeBaseInitStruct.TIM_Prescaler = 7199; //预分频的分频系数
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //设置需要定时几次后进入中断

//对TIM2进行基本初始化
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);

其中计数模式一共有三种,分别是向上计数模式(从0开始计数到规定值),向下模式(从规定值计数到0),和中央对齐模式(从0开始计数到规定值,再从规定值计数到0)。

其中中央对其模式还分为以下三种:

TIM定时器-14

内部时钟再分频因子可将进入TIM定时器的72MHz频率继续分频,一般为了方便计算不再继续分频,这里设置为1:

如果因子设置为2,则周期为原来的2倍;设置为4则周期为原来的4倍。

TIM定时器-15

3、开启定时器的更新中断

使用TIM库函数:

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //使能TIM2定时器的更新中断

4、设置中断的优先级

NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; //中断通道号
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能开关
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //从优先级

//对中断优先级进行配置
NVIC_Init(&NVIC_InitStruct);

5、打开定时器

//启动定时器
TIM_Cmd(TIM2, ENABLE);

6、编写中断服务函数

在启动文件中找到TIM2的中断服务函数: void TIM2_IRQHandler();

同样的,进入中断函数后还要对中断标志位进行判断:

完整代码实现:

#include "Timer.h"
#include "stdio.h"
void Timer2_init() {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //内部时钟再分频因子
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //定时器的计数模式
    TIM_TimeBaseInitStruct.TIM_Period = 999; //计数周期(次数)ARR
    TIM_TimeBaseInitStruct.TIM_Prescaler = 7199; //预分频的分频系数 PSC
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //设置需要定时几次后进入中断

    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; //中断通道号
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能开关
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //从优先级

    //打开TIM2的外设时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    //对TIM2进行基本初始化
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);

    //打开定时器的更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    //对中断优先级进行配置
    NVIC_Init(&NVIC_InitStruct);

    //启动定时器
    TIM_Cmd(TIM2, ENABLE);
}

void TIM2_IRQHandler() {
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
        printf("You into the interrupt!\n");
        printf("The time is 100ms.");
    }
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
//  TIM_ClearFlag(TIM2, TIM_IT_Update);
}

三、定时器产生PWM波形

STM32F103ZET6一共有8个定时器,除了6和7这两个基本定时器不能产生PWM,其余的6个都可以,高级定时器1个可产生7路,通用定时器1个可以产生4路,因此一共可以生成2x7+4x4=30路

  • PWM1在向上计数时,CNT<CRR,则输出的为有效电平
  • PWM2在向上计数时,CNT<CRR,则输出的为无效电平

TIM定时器-16

CRRx = 25% = (ARR+1)/4

定时器定时原理:

  • 首先通过CNT寄存器负责脉冲计数,在经过分频系数分频过后,确定每个脉冲的时间(如果不经过分频,每个脉冲的时间就是1/72us)。
  • 当设置的限制值ARR = 999时,即设置脉冲的计数值为999;当脉冲计数到999时,脉冲计数到达设置值,在下一个脉冲计数时进入中断并将计数值清零。所以一个技术周期所需要的脉冲数为999 + 1。
  • PWM的生成和定时器定时同理,也是设置脉冲计数值,这里是CCRx寄存器负责PWM的脉冲计数。

PWM生成原理:

  • 如果CCRx中的匹配值设置为200,当脉冲计数到200与匹配值相等时,将输出的电平取反;当脉冲计数到达ARR = 999时,电平反转回原来的状态。
  • 由图可知此时PWM的周期与定时器的定时时间是相同的,其高电平的时间取决于CCRx这个寄存器内匹配值的大小以及每个计数脉冲的周期长短。

编程步骤:

TIM定时器-17

1、使能时钟

  • TIM3通道1 = PA6/PA7
  • GPIO_AF_PP

2、定时器复位

  • TIMDeinit

3、定时器初始化

  • OC1Init(通道1)
  • 电平高
  • 解调1
  • Pulse占空比参数
  • Mode = PWM模式
  • 状态enable
  • Baseinit(基本初始化)

4、cmd使能

四、定时器的输入捕获

输入捕获可以用来测量脉冲宽度或者测量频率

在C8T6中每个定时器都有输入捕获功能;而ZET6除了TIM6和TIM7之外,其余定时器都有输入捕获功能。

TIM定时器-18

输入捕获的过程:

输入检测就是检测定时器通道上的边沿信号,在边沿信号发生跳变(上升沿/下降沿)时,将当前定时器的计数值(TIMx_CNT)存放到对应通道的捕获/比较寄存器(TIMx_CCRx)当中,由此完成一次捕获。同时还可以配置捕获时是否触发中断/DMA等。