TIM简介

STM32的TIM(定时器)是一种非常常用的外设,用于实现各种定时和计数功能。它是基于时钟信号进行计数,并在计数值达到设定值时触发中断,执行相应的操作

定时器类型

一般来说,STM32中有三类定时器:
在这里插入图片描述
在我们这款STM32F03C9T6有4种定时器资源:TIM1,TIM2,TIM3,TIM4;

对于定时器,类型越高级,拥有的功能越多,且向下兼容;
我们将以通用定时器进行讲解

通用定时器

通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成
它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。
使用定时器预分频器和RCC时钟控制器预分频器,脉冲信号长度和波形周期可以在几个微妙到几个毫秒间调整。
每个定时器都是完全独立的,没有互相共享任何资源。它们可以同步操作。
在这里插入图片描述
这是通用定时器的总框图,我将会分为几部分进行讲解。

在这里插入图片描述
这是定时器最基本的结构,通过RCC内部时钟产生的脉冲频率通向预分频器,频率分频后到计数器,当达到自动重装载寄存器的值,将会发出信号,或者触发中断

RCC的TIMxCLK会产生一个72MHz的脉冲频率;
在这里插入图片描述
这一部分称为时基单元,是TIM计时器最主要的计数计时结构。

PSC预分频器会将72MHZ进行分频,可以按1到65536之间的任意值分频;将输入频率除以预分频器值就得到分频结果;

通过频率会在计数器中计数,通过传输的频率的上升沿,计数器将加一
计数器取值范围为0到65535;所以计数的快慢由输入频率决定;
对于通用计数器来说,计数器是有多种模式进行计数的。
向上计数模式:计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
向下计数模式:计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。
中央对齐模式:计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器
溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。

自动重装载寄存器相当就是给计数器一个周期值,当计数器达到这个值就会产生中断,并清零计数器;计数器溢出中断后,会产生更新中断,传到NVIC中,最后传到CPU,那么定时器就能产生中断了。也会产生更新事件,它会触发内部其他电路的工作。

在这里插入图片描述
这部分,是定时器时钟频率的来源,在通用定时器中,不止有内部时钟,还有外部时钟。
第一个外部时钟TIMx_ETR,如果在引脚上默认有该功能,就能直接使用,作为外部时钟的连接引脚;
在这里插入图片描述
传输进来的方波信号会通过极性选择,边沿检测,滤波等进行整形,处理掉一些毛刺;滤波后的信号兵分两路,第一路是走到ETRF,通过触发控制器走到复位使能,这种走法称为“外部时钟模式2”。(TRIGO是映射功能,能够从主模式触发DAC)。第二路是TRGI,主要用作触发使用的,可以走到从模式;当然也可以走复位,使能那里,那么这样的外部时钟称为“外部时钟模式1”。

第二个的来源就是ITR,TRIGO可以通向其他定时器,其他定时器就是通过ITR引脚来连接的。
在这里插入图片描述
这是内部定时器连接的方式。可以允许4种定时器进行连接到定时器上,但是只允许一个定时器连接着一个定时器。

第三个一个是TIIF_ED,这里连接着输入捕获单元的CH1,ED为Edge,边沿的意思,触发方式上升沿和下降沿都有效。
在这里插入图片描述

最后一个是TI1FP1和TI2FP2
在这里插入图片描述
后续将会讲解。
在这里插入图片描述
下边的,左半部分为输入捕获电路,右半部分为输出比较部分,每部分都有4个通道可以进入,且输入和输出共用一个寄存器,意味着不能边输入边输出,具体功能将会后续讲解。

预分频器时序

在这里插入图片描述
这是一个预分频器从1变到2的时序图。
CK_PSC是时钟频率,一般都为72MHZ;
CNT_EN是计时器使能,只有在使能高电平状态下,才能运行。
CK_CNT是计时器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入;当使能为高电平时,CNT开始运行,前半段频率跟时钟一样,后半段预分频器从1变到2,CNT让定时器上升沿减半,即一个周期有效,一个周期无效(保持低电平)。
在计时器时钟的驱动下,计时器寄存器也不断增加,当达到FC时(与自动重转载寄存器的值一样)将会从0开始;
更新事件UEV,当计数器寄存器到FC时,更新事件将会触发。
下面三个时序将一起看,这是预分频控制寄存器的缓冲机制,我们写入的值会到预分频控制寄存器上,当在计数器未归零之前写入时,为了保持完整性,将会在更新事件后才会进行分频。所以到预分频缓冲器上才是所读的正确结果,而预分频计数器会在1时保持定时器时钟为低电平,为0时保持原先状态。

计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
PSC相对我们输入者来说,就是0开始的,当对于PSC来说,是从1开始的。就像一块蛋糕,不切时它就是1份完整的,切一刀时,就会被分成两份。

计数器时序

在这里插入图片描述
大体来说与预分频器一致,当计数器寄存器满时,将会使计数器溢出,更新事件发生,更新中断标志。

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

定时中断基本结构

对于我们来说,由于有库函数的提供,不需要管哪些寄存器,我们需要了解一些代码逻辑结构。
在这里插入图片描述
通过外部引脚GPIO就可连接外部时钟,然后选择时钟模式,接着对时基单元初始化,接上NVIC即可。

TIM内部中断工程

连接方式:
在这里插入图片描述

OLED函数可以点击连接

该工程将会实现走秒的例子。
Timer.h

#ifndef __TIMER_H__
#define __TIMER_H__

void Timer_Init();

#endif

Timer.c

#include "stm32f10x.h"                  // Device header

void Timer_Init()
{
    //开启APB1外设开关
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    //配置TIM2为内部时钟
    TIM_InternalClockConfig(TIM2);
    //时钟结构体初始化
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //划分TIM2
    TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计时器模式,上升沿计时
    TIM_TimeBaseInitStructure.TIM_Period=10000-1; //自动加载寄存器周期值
    TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1; //预分频值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //指定重复计时器的值,这里不用到
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    //清除标志位
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    //启用TIM2中断
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    //配置优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //NVIC初始化
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_InitStructure);
    //启用TIM2外设控制
    TIM_Cmd(TIM2,ENABLE);
}

对于内部时钟,没有外部引脚的使用,记住TIM所在总线是APB1,先开启外设开关,接着配置TIM2的内部时钟,然后对时基单元结构体成员进行初始化,对于预分频器值,通过公式可知需要-1才能达到我们想要的数字,重复计时器是高级计时器的操作,这里不需要用到。
在这里插入图片描述
在初始化完将会生成一个更新事件,立即重新加载预分频和计时器的计算。在更新一个事件后,同时也会产生中断标志,为了让计时时从0开始,就采用了清除标志的函数。
最后记得启用TIM2的外设,否则无效。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Count;
int main()
{
	OLED_Init();
    Timer_Init();
	while(1)
    {
        OLED_ShowNum(1,1,Count,4);
       
    }
}

//中断函数
void TIM2_IRQHandler()
{
//表示已经触发中断了
    if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
    {
        Count++;
        //中断挂起位,中断结束后需要将中断位挂起,让下一个能进入中断
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    }
}

TIM外部中断工程

接线方式:
在这里插入图片描述
通过对射式红外传感器的电平变化作为CNT的触发条件,然后通过10次的电平变化,让计时器溢出进1;

Timer.h

#ifndef __TIMER_H__
#define __TIMER_H__

void Timer_Init();

#endif

Timer.c

#include "stm32f10x.h"                  // Device header

void Timer_Init()
{
    //开启APB1外设开关
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    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;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    //配置TIM2为外部时钟模式2
    TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
    //时钟结构体初始化
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //表示不分频
    TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计时器模式
    TIM_TimeBaseInitStructure.TIM_Period=10-1; //自动加载寄存器周期值
    TIM_TimeBaseInitStructure.TIM_Prescaler=1-1; //预分频值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //指定重复计时器的值,这里不用到
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    //
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    //启用TIM2中断
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    //配置优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //NVIC初始化
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_InitStructure);
    //启用TIM2外设控制
    TIM_Cmd(TIM2,ENABLE);
}

外部时钟模式2:
TIM_ExtTRGPrescaler:外部触发预分频器
TIM_ExtTRGPolarity_NonInverted:触发极性为上升沿或高电平;
ExtTRGFilter:最后一个参数,表示滤波频率高低,可选范围0x00 and 0x0F;一般来说,滤波频率越高,毛刺与不规则信号处理的越干净。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Count;
int main()
{
	OLED_Init();
    Timer_Init();
	while(1)
    {
        OLED_ShowNum(1,1,Count,4);
        OLED_ShowNum(2,1,TIM_GetCounter(TIM2),5);
    }
}

void TIM2_IRQHandler()
{
    if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
    {
        Count++;
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    }
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐