Skip to content

STM32 入门 5

TIM

TIM (Timer) 定时器可以对输入时钟进行计数,并在计数值达到定值时触发中断

16位计数器、预分频器、自动重装寄存器的时基单元,在 72MHz 计数时钟下可以实现最大 59.65s 的计时

STM32 计时器具备基本的定时中断功能,而且包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

根据复杂度和应用场景分为高级定时器、通用定时器和基本定时器

TIM 类型

高级定时器

TIM1、TIM8

APB2 总线

通用定时器+重复计数器、死区生成、互补输入、刹车输入等功能

通用定时器

TIM2-TIM5

APB1 总线

基本定时器+内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式

基本定时器

TIM6、TIM7

APB1 总线

定时中断、主模式触发 DAC 功能

STM32F103C8T6 有 TIM1 到 TIM4 四个定时器

TIM 电路

基本定时器

PSC 预分频器,分频系数 = 寄存器值 + 1

PSC 连接 CNT 计数器,自增,和自动重装载寄存器的值比较,相等时产生更新中断/事件

时钟只能选择内部时钟

通用定时器

和基本定时器相同的时基单元(预分频+自增计数+自动重装载寄存器)

通用定时器和高级定时器还支持向下计数(某个值到 0)和中央对齐(0 到某个值再到 0)的计数模式

可以选择外部时钟,来自 TIMx_ETR 引脚的时钟,接极性选择、边沿检测和预分频器电路,滤波再接触发控制器,再接到时基单元

可以选择 ITR 信号,来自于其他定时器信号,手册中有表格告知 ITR 和定时器的连接关系,可以实现定时器级联功能

还有其他电路有关输入捕获和输出比较等等

高级定时器

高级定时器结构更加复杂,再通用定时器基础上,申请中断的地方增加了重复次数计数器,可以实现每隔多个周期更新,相当于又做了分频

还有死区生成电路,支持互补输出,增加了刹车输入功能

定时中断基本结构

RCC -> 内部时钟模式 -> 时基单元
GPIO -> ETR 外部时钟 -> 外部时钟模式2 -> 时基单元
GPIO -> ETR -> 外部时钟模式1 -> 时基单元
ITRx 其他定时器 -> 外部时钟模式1 -> 时基单元
GPIO -> TIx 捕获通道 -> 外部时钟模式1 -> 时基单元
GPIO -> TIx 捕获通道 -> 编码器模式 -> 时基单元

时基单元 -> 终端输出控制 -> NVIC

换算关系

计数器计数频率 CK_CNT = CK_PSC/(PSC+1)

计数器溢出频率 CK_CNT_OV = CK_CNT/(ARR+1) = CK_PSC/(PSC+1)/(ARR+1)

为了防止在某个周期中间频率改变,STM32 有缓冲寄存器防止突然变频,可以通过设置 ARPE 寄存器的值不使用这种缓冲功能(自动加载寄存器的值可以认为直接改变而不是到上一个值结束以后再变)

TIM2 使用内部时钟初始化

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2 使能,在 APB1 总线

    TIM_InternalClockConfig(TIM2); // 使用内部时钟,可以不写,因为默认是内部时钟

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    // 外部时钟一般有滤波器,原理是如果连续 N 个采样点全部相同,说明信号稳定,输出信号,否则输出上次或低电平,频率越低,点数越多效果越好信号延迟越大
    // 这个滤波可以直接是内部时钟,也可以加一个分频,就是 ClockDivision 的意义
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; // ARR自动重装器
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // 预分频器
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); // 这句为了初始化,中断标志位置 1,主要是为了初始化 psc 和 cnt,但是又不想进中断函数,就在下一句把上个函数触发的标志位清零

    TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 由于此时 TIM_ITConfig 和 NVIC 都没配置,不会进中断,只要清除标志位就可以保证在二者配置以后不会直接进中断

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能中断,开启更新中断到 NVIC 通路

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    TIM_Cmd(TIM2, ENABLE); // 只有这一步给定时器使能了,CNT才会向上计数
}

TIM2 使用外部时钟初始化

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2 使能,在 APB1 总线
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 推荐浮空输入,上拉输入也可以,浮空情景:外部输入信号功率小,内部上拉电阻可能影响输入信号
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted,0x00); //反向-低电平或下降沿,不反向-高电平或上升沿,最后一个值指定滤波器的f和N,手册里对于这个值有说明


    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; // ARR自动重装器
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; // 预分频器
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

    TIM_ClearFlag(TIM2, TIM_FLAG_Update);

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能中断,开启更新中断到 NVIC 通路

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    TIM_Cmd(TIM2, ENABLE); // 只有这一步给定时器使能了,CNT才会向上计数
}