轻松掌握TIM定时器的工作原理及简单应用

前言:本文章部分代码参考自野火的例程

本人使用的是野火家的指南者开发板,芯片型号是STM32f103VET6

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

源代码在这里

1 定时器原理

B站这位UP主讲51单片机定时器工作原理 讲得很好

2 STM32定时器简介

stm32有3种定时器,分别是基本定时器、通用定时器、高级定时器

基本定时器是一个 16 位的只能向上计数的定时器,只能定时,没有外部IO

通用定时器是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有四个外部 IO

高级定时器 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有 8 个外部 IO

3 基本定时器

基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。 它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它 们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。

TIM6和TIM7定时器的主要功能包括:

16位自动重装载累加计数器

16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值 分频

触发DAC的同步电路

在更新事件(计数器溢出)时产生中断/DMA请求

3.1 基本定时器功能框图

①时钟源

②控制器

③时基单元

3.1.1 时钟源

时钟源来自RCC的TIMx_CLK(属于内部的CK_INT)

3.1.2 控制器

控制器用于控制定时器的:复位、使能、计数、触发DAC

涉及到的寄存器为:CR1/2、DIER、EGR、SR

3.1.3 时基

定时器最主要的就是时基部分,时基单元包含以下寄存器,软件可读写

● 计数器寄存器(TIMx_CNT)

● 预分频寄存器(TIMx_PSC)

● 自动重装载寄存器(TIMx_ARR)

16位的预分频器PSC对内部时钟CK_PSC进行分频之后,得到计数器时钟CK_CNT=CK_PSC/(PSC+1)

计数器CNT在计数器时钟的驱动下开始计数,计数一次的时间为1/CK_CNT

定时器使能(CEN 置 1)后,计数器 CNT在CK_CNT 驱动下计数,当 CNT 值与 ARR 的设定值相等时就自动生成事件并 CNT 自动清零,然后自动重新开始计数,如此重复以上过程。

3.2 实现500ms定时

1、PSC = 72-1,定时器频率 72M/(PSC+1)=1MHZ

2、ARR = 1000-1,从0计数到999,则计了1000次

3、中断周期T = 1000 *1/1000000 = 1mS

4、main定义time_cnt这个全局变量,在中断服务函数调用,中断一次,time_cnt++

3.3 定时器时基初始化结构体

typedef struct
{
  uint16_t TIM_Prescaler;        //分频因子        
  uint16_t TIM_CounterMode;      //计数模式,基本定时器只能向上计数    
  uint16_t TIM_Period;           //自动重转载值     
  uint16_t TIM_ClockDivision;    //外部输入时钟分频因子,基本定时器没有
  uint8_t TIM_RepetitionCounter; //重复计数器,高级定时器专用
} TIM_TimeBaseInitTypeDef;     

3.4 代码实现

3.4.1 配置时基初始化结构体

主要修改BASIC_TIM_Period和BASIC_TIM_Prescaler的值

一般设置预分频系数,为(72-1),那么定时器周期为72M/(预分频系数+1)=1MHz

设置自动重转载寄存器的值为(1000-1),那么每1000*1/1M秒产生一次中断

static void BASIC_TIM_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;   
    // 开启定时器时钟,即内部时钟CK_INT=72M
    BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);   
    // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;    //(1000-1)
    // 时钟预分频数为
    TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;//(72-1)
    // 初始化定时器
    TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);        
    // 清除计数器中断标志位
    TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);      
    // 开启计数器中断
    TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);        
    // 使能计数器
    TIM_Cmd(BASIC_TIM, ENABLE);
}

3.4.2 开启定时器更新中断(即定时时间到了)

void BASIC_TIM_IRQHandler(void)
{
    if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) 
    {    
        time++;
        TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);           
    }             
}

3.4.3 配置中断优先级

static void BASIC_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);        
    // 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;    
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;     
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;    
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

3.4.4 使能定时器

TIM_Cmd(BASIC_TIM, ENABLE);

3.4.5 编写中断服务函数

void BASIC_TIM_IRQHandler(void)
{
    if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) {    
        time++;
        TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);           
    }             
}

3.4.6 编写main函数

int main(void)
{    
    /* LED 端口初始化 */
    LED_GPIO_Config();   
    BASIC_TIM_Init(); 
    while(1){
        if( time == 500 ){
            time = 0;
            LED1_TOGGLE;
        }        
    }    
}

4 通用定时器

(通用定时器有的功能,高级定时器都有,所以详细介绍在高级定时器部分)

通用定时器TIMx包括 (TIM2、TIM3、TIM4和TIM5)

通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。 它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)。 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个 毫秒间调整。

●4个独立通道:

─ 输入捕获

─ 输出比较

─ PWM生成(边缘或中间对齐模式)

─ 单脉冲模式输出

● 使用外部信号控制定时器和定时器互连的同步电路

● 如下事件发生时产生中断/DMA:

─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)

─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)

─ 输入捕获

─ 输出比较

● 支持针对定位的增量(正交)编码器和霍尔传感器电路

● 触发输入作为外部时钟或者按周期的电流管理

4.1 通用定时器功能框图

4.1.1 时钟源

计数器时钟可由下列时钟源提供:

内部时钟(CK_INT)

内部时钟 CK_INT 即来自于芯片内部,等于 72M,一般情况下,我们都是使用内部时钟。当从模 式控制寄存器 TIMx_SMCR 的 SMS 位等于 000 时,则使用内部时钟。

外部时钟模式1:外部输入引脚(TIx)

外部时钟模式2:外部触发输入(ETR)

外部时钟1和2的区别:

1.外部时钟1提供没有divider(分频器),外部时钟2提供divider,2模式可以进行降频。

2.外部时钟1共有4个通道可用(CHx),外部时钟2仅有一个通道可用(CH1)。

4.1.2 时基单元

这部分与基本定时器一样

4.1.3 计数器模式

向上、向下、中央对齐模式

4.1.4 输入捕获

输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽。

输入捕获的大概的原理就是,当捕获到信号的跳变沿的时候,把计数器 CNT 的值锁存到捕获寄存器 CCR 中,把前后两次捕获到的 CCR 寄存器中的值相减,就可以算出脉宽或者频率。如果捕获的脉宽的时间长度超过你的捕获定时器的周期,就会发生溢出,这个我们需要做额外的处理。

4.2 实验1——4路PWM输出

时钟源设置为内部时钟,CK_INT

4.2.1 GPIO初始化

输出比较通道1~4分别用到了PA6、PA7、PB0、PB1

GPIO全部设置为GPIO_Mode_AF_PP,即复用推挽输出

4.2.2 PWM信号,周期和占空比的计算

CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)

PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M

占空比P=CCR/(ARR+1)

4.2.3 定时器模式配置

static void GENERAL_TIM_Mode_Config(void)
{
    // 开启定时器时钟,即内部时钟CK_INT=72M
    GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
    // 配置周期,这里配置为100K
    
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断 这里设置为9
    TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;    
    // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
    TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;           
    // 计数器计数模式,设置为向上计数
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;           
    // 初始化定时器
    TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

    /*--------------------输出比较结构体初始化-------------------*/    
    // 占空比配置
    uint16_t CCR1_Val = 5;
    uint16_t CCR2_Val = 4;
    uint16_t CCR3_Val = 3;
    uint16_t CCR4_Val = 2;
    
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    // 配置为PWM模式1
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 输出通道电平极性配置    
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    
    // 输出比较通道 1
    TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
    TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
    
    // 输出比较通道 2
    TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
    TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
    
    // 输出比较通道 3
    TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
    TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
    
    // 输出比较通道 4
    TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
    TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
    
    // 使能计数器
    TIM_Cmd(GENERAL_TIM, ENABLE);
}

4.2.4 main函数

int main(void)
{
    /* led 端口配置 */ 
    LED_GPIO_Config();
    
    /* 定时器初始化 */
    GENERAL_TIM_GPIO_Config();
    GENERAL_TIM_Mode_Config();
    
  while(1)
  {      
  }
}

4.2.5 实验现象

PA6、PA7、PB0、PB1分别输出占空比为50%、40%、30%、20%的PWM波

4.3 实验2——输入捕获,测量脉冲宽度

时钟源为 外部时钟模式1:外部输入引脚,这里选择TIM5_CH1,对应PA0

4.3.1 GPIO初始化

输入通道1即PA0设置为浮空输入

static void GENERAL_TIM_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;
  // 输入捕获通道 GPIO 初始化
  RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH1_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);    
}

4.3.2 中断优先级配置

static void GENERAL_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);        
    // 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQ ;    
    // 设置主优先级为 0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;     
    // 设置抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;    
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

4.3.3 定时器模式配置

static void GENERAL_TIM_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
    GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/    
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_PERIOD;    
    // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
    TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_PSC;    
    // 时钟分频因子 ,配置死区时间时需要用到
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;        
    // 计数器计数模式,设置为向上计数
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;        
    // 重复计数器的值,没用到不用管
    TIM_TimeBaseStructure.TIM_RepetitionCounter=0;    
    // 初始化定时器
    TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

    /*--------------------输入捕获结构体初始化-------------------*/    
    TIM_ICInitTypeDef TIM_ICInitStructure;
    // 配置输入捕获的通道,需要根据具体的GPIO来配置
    TIM_ICInitStructure.TIM_Channel = GENERAL_TIM_CHANNEL_x;
    // 输入捕获信号的极性配置
    TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
    // 输入通道和捕获通道的映射关系,有直连和非直连两种
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    // 输入的需要被捕获的信号的分频系数
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    // 输入的需要被捕获的信号的滤波系数
    TIM_ICInitStructure.TIM_ICFilter = 0;
    // 定时器输入捕获初始化
    TIM_ICInit(GENERAL_TIM, &TIM_ICInitStructure);
    
    // 清除更新和捕获中断标志位
    TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update|GENERAL_TIM_IT_CCx);    
    // 开启更新和捕获中断  
    TIM_ITConfig (GENERAL_TIM, TIM_IT_Update | GENERAL_TIM_IT_CCx, ENABLE );
    
    // 使能计数器
    TIM_Cmd(GENERAL_TIM, ENABLE);
}

4.3.4 中断服务函数

这个函数的作用如下:

上升捕获中断,让各种寄存器清零,并把捕获边沿设置为下降沿;当下降沿捕获中断,把捕获边沿设置为下降沿,读取捕获比较寄存器的值,这个值就是捕获到的高电平的时间的值。

当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断,这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去。

最终高电平计数器的值time为:捕获周期次数(Capture_Period)*通用定时器周期(GENERAL_TIM_PERIOD+1)+捕获寄存器的值(Capture_CcrValue+1)

高电平时间为:

printf ( "\r\n测得高电平脉宽时间:%d.%d s\r\n",time/TIM_PscCLK,time%TIM_PscCLK );

void GENERAL_TIM_INT_FUN(void)
{
    // 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
    // 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
    if ( TIM_GetITStatus ( GENERAL_TIM, TIM_IT_Update) != RESET )               
    {    
        TIM_ICUserValueStructure.Capture_Period ++;        
        TIM_ClearITPendingBit ( GENERAL_TIM, TIM_FLAG_Update );         
    }

    // 上升沿捕获中断
    if ( TIM_GetITStatus (GENERAL_TIM, GENERAL_TIM_IT_CCx ) != RESET)
    {
        // 第一次捕获
        if ( TIM_ICUserValueStructure.Capture_StartFlag == 0 )
        {
            // 计数器清0
            TIM_SetCounter ( GENERAL_TIM, 0 );
            // 自动重装载寄存器更新标志清0
            TIM_ICUserValueStructure.Capture_Period = 0;
            // 存捕获比较寄存器的值的变量的值清0            
            TIM_ICUserValueStructure.Capture_CcrValue = 0;
            // 当第一次捕获到上升沿之后,就把捕获边沿配置为下降沿
            GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Falling);
            // 开始捕获标准置1            
            TIM_ICUserValueStructure.Capture_StartFlag = 1;            
        }
        // 下降沿捕获中断
        else // 第二次捕获
        {
            // 获取捕获比较寄存器的值,这个值就是捕获到的高电平的时间的值
            TIM_ICUserValueStructure.Capture_CcrValue = 
            GENERAL_TIM_GetCapturex_FUN (GENERAL_TIM);

            // 当第二次捕获到下降沿之后,就把捕获边沿配置为上升沿,好开启新的一轮捕获
            GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Rising);
            // 开始捕获标志清0        
            TIM_ICUserValueStructure.Capture_StartFlag = 0;
            // 捕获完成标志置1            
            TIM_ICUserValueStructure.Capture_FinishFlag = 1;        
        }
        TIM_ClearITPendingBit (GENERAL_TIM,GENERAL_TIM_IT_CCx);        
    }        
}

5 高级定时器

高级控制定时器(TIM1和TIM8)由一个16位的自动装载计数器组成,它由一个可编程的预分频器 驱动。 它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、 PWM、嵌入死区时间的互补PWM等)。 使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几 个毫秒的调节。

5.1 高级定时器功能框图

5.1.1 时钟源

内部时钟源、外部时钟模式1和2与通用定时器一样

1-内部时钟源CK_INT

2-外部时钟模式1—外部的GPIO Tix(x=1 2 3 4)

3-外部时钟模式2—外部的GPIO ETR

4-内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

5.1.2 控制器

1-控制器就是用来控制的,发送命令的

2-CR1、CR2、SMCR、CCER,主要学习这几个寄存器即可。

5.1.3 时基单元

1-16位的预分频器 PSC,PSC

2-16位的计数器CNT, CNT

3-8位的重复计数器RCR,RCR(高级定时器独有)

4-16位的自动重装载寄存器ARR,ARR

5.1.4 输入捕获

输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽和测量 PWM 输入信号的频率和占空比这两种。

输入捕获的大概的原理就是,当捕获到信号的跳变沿的时候,把计数器 CNT 的值锁存到捕获寄存器 CCR 中,把前后两次捕获到的 CCR 寄存器中的值相减,就可以算出脉宽或者频率。如果捕获的脉宽的时间长度超过你的捕获定时器的周期,就会发生溢出,这个我们需要做额外的处理。

捕获通道就是图中的IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器 CCR1/2/3/4,当发生捕获的时候,计数器 CNT 的值就会被锁存到捕获寄存器中。

这里我们要搞清楚输入通道和捕获通道的区别,输入通道是用来输入信号的,捕获通道是用来捕获输入信号的通道,一个输入通道的信号可以同时输入给两个捕获通道。比如输入通道 TI1 的信号经过滤波边沿检测器之后的 TI1FP1 和 TI1FP2 可以进入到捕获通道IC1 和IC2,其实这就是我们后面要讲的 PWM输入捕获,只有一路输入信号 (TI1) 却占用了两个捕获通道 (IC1 和IC2) 。当只需要测量输入信号的脉宽时候,用一个捕获通道即可。输入通道和捕获通道的映射关系具体由寄存器 CCMRx 的位 CCxS[1:0]配置。

5.1.5 输出比较

输出比较就是通过定时器的外部引脚对外输出控制信号,有冻结、将通道 X(x=1,2,3,4) 设置为匹配时输出有效电平、将通道 X 设置为匹配时输出无效电平、翻转强制变为无效电平、强制变为有效电平、PWM1和 PWM2 这八种模式,具体使用哪种模式由寄存器 CCMRx的位 OCxM2:0]配置。其中 PWM模式是输出比较中的特例,使用的也最多。

5.1.6 输出控制

在输出比较的输出控制中,参考信号 OCxREF 在经过死区发生器之后会产生两路带死区的互补信号 OCx DT和 OCXN DT (通道 13 才有互补信号,通道4没有,其余跟通道1~3 一样),这两路带死区的互补信号就进入输出控制电路,如果没有加入死区控制,那么进入输出控制电路的信号就直接是 OCXREF。

进入输出控制电路的信号会被分成两路,一路是原始信号,一路是被反向的信号,具体的由寄存器 CCER 的位 CCxP 和 CCxNP 控制。经过极性选择的信号是否由 OCx引脚输出到外部引脚 CHx/CHxN 则由寄存器 CCER 的位 CxE/CxNE 配置。

如果加入了断路 (刹车) 功能,则断路和死区寄存器 BDTR的MOE、OSSI和 OSSR这三个位会共同影响输出的信号。

5.1.7 输出引脚

输出比较的输出信号最终是通过定时器的外部IO 来输出的,分别为 CH1/2/3/4,其中前面三个通道还有互补的输出通道 CH1/2/3N。更加详细的 IO 说明还请查阅相关的数据手册。

5.2 实验1——PWM输入捕获,测量PWM周期和占空比

通用定时器产生PWM波,通过PA6、PA7、PB0、PB1分别输出占空比为50%、40%、30%、20%的PWM波信号,频率为100KHz。之后在开发板上接入到PA8,PWM信号输入高级定时器TIM1的TI1,分别被IC1和IC2捕获,IC1捕获整个周期,IC2捕获高电平时间。

通用定时器的代码跟4.2的4路PWM输出一样

接下来主要讲解高级定时器的输入捕获代码

5.2.1 NVIC中断配置

设置中断源为TIM1_CC_IRQn

5.2.2 GPIO配置

TIM1对应的PA8配置为浮空输入

5.2.3 高级定时器模式配置

时基部分:输入捕获能捕获到的最小的频率为 72M/{ (ARR+1)*(PSC+1) },即72M/{1000*72}=1/1000,这个是定时 器在不溢出的情况下的最大计数周期,也就是说周期小于 1ms 的 PWM 信号都可以被捕获到,转 换成频率就是能捕获到的最小的频率为 1KHz

static void ADVANCE_TIM_Mode_Config(void)
{
    // 开启定时器时钟,即内部时钟CK_INT=72M
    ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;    
    // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
    TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_PSC;    
    // 时钟分频因子 ,配置死区时间时需要用到
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;        
    // 计数器计数模式,设置为向上计数
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;        
    // 重复计数器的值,没用到不用管
    TIM_TimeBaseStructure.TIM_RepetitionCounter=0;    
    // 初始化定时器
    TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);

    /*--------------------输入捕获结构体初始化-------------------*/    
    // 使用PWM输入模式时,需要占用两个捕获寄存器,一个测周期,另外一个测占空比
    
    TIM_ICInitTypeDef  TIM_ICInitStructure;
    // 捕获通道IC1配置
    // 选择捕获通道
    TIM_ICInitStructure.TIM_Channel = ADVANCE_TIM_IC1PWM_CHANNEL;
    // 设置捕获的边沿
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    // 设置捕获通道的信号来自于哪个输入通道,有直连和非直连两种
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    // 1分频,即捕获信号的每个有效边沿都捕获
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    // 不滤波
    TIM_ICInitStructure.TIM_ICFilter = 0x0;
    // 初始化PWM输入模式
    TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);
    
    // 当工作做PWM输入模式时,只需要设置触发信号的那一路即可(用于测量周期)
    // 另外一路(用于测量占空比)会由硬件自带设置,不需要再配置
    
    // 捕获通道IC2配置    
//    TIM_ICInitStructure.TIM_Channel = ADVANCE_TIM_IC1PWM_CHANNEL;
//  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
//  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
//  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
//  TIM_ICInitStructure.TIM_ICFilter = 0x0;
//  TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);
    
    // 选择输入捕获的触发信号
    TIM_SelectInputTrigger(ADVANCE_TIM, TIM_TS_TI1FP1);        

    // 选择从模式: 复位模式
    // PWM输入模式时,从模式必须工作在复位模式,当捕获开始时,计数器CNT会被复位
    TIM_SelectSlaveMode(ADVANCE_TIM, TIM_SlaveMode_Reset);
    TIM_SelectMasterSlaveMode(ADVANCE_TIM,TIM_MasterSlaveMode_Enable); 

    // 使能捕获中断,这个中断针对的是主捕获通道(测量周期那个)
    TIM_ITConfig(ADVANCE_TIM, TIM_IT_CC1, ENABLE);    
    // 清除中断标志位
    TIM_ClearITPendingBit(ADVANCE_TIM, TIM_IT_CC1);
    
    // 使能高级控制定时器,计数器开始计数
    TIM_Cmd(ADVANCE_TIM, ENABLE);
}

当捕获到 PWM 信号的上升沿时,产生中断

5.2.4 中断服务函数

这里我设置了一个变量cpatureTime,当产生10000次中断时(每秒输出100K/10000=10次),通过串口输出PWM波的频率和占空比

void ADVANCE_TIM_IRQHandler(void)
{
    /* 清除中断标志位 */
    TIM_ClearITPendingBit(ADVANCE_TIM, TIM_IT_CC1);
    cpatureTime++;
    
    if(cpatureTime==10000){
        /* 获取输入捕获值 */
        IC1Value = TIM_GetCapture1(ADVANCE_TIM);
        IC2Value = TIM_GetCapture2(ADVANCE_TIM);
            
        // 注意:捕获寄存器CCR1和CCR2的值在计算占空比和频率的时候必须加1
        if (IC1Value != 0){
            /* 占空比计算 */
            DutyCycle = (float)((IC2Value+1) * 100) / (IC1Value+1);

            /* 频率计算 */
            Frequency = (72000000/(ADVANCE_TIM_PSC+1))/(float)(IC1Value+1);
                printf("占空比:%0.2f%%   频率:%0.2fHz\n",DutyCycle,Frequency);
        }
        else{
            DutyCycle = 0;
            Frequency = 0;
        }
        cpatureTime=0;
    }
}

5.3 实验2——带死区的互补输出

TIM1 输出比较通道为PA8,

TIM1 输出比较通道的互补通道PB13,

TIM1 输出比较通道的刹车通道PB12

以上三个GPIO口全部设置为复用推挽输出

5.3.1 高级定时器模式配置

定时器时基为72M/{ (ARR+1)*(PSC+1) },即72M/{8*9}=1M

static void ADVANCE_TIM_Mode_Config(void)
{
    // 开启定时器时钟,即内部时钟CK_INT=72M
    ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;    
    // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
    TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_PSC;    
    // 时钟分频因子 ,配置死区时间时需要用到
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;        
    // 计数器计数模式,设置为向上计数
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;        
    // 重复计数器的值,没用到不用管
    TIM_TimeBaseStructure.TIM_RepetitionCounter=0;    
    // 初始化定时器
    TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);

    /*--------------------输出比较结构体初始化-------------------*/        
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    // 配置为PWM模式1
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 互补输出使能
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; 
    // 设置占空比大小
    TIM_OCInitStructure.TIM_Pulse = ADVANCE_TIM_PULSE;
    // 输出通道电平极性配置
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    // 互补输出通道电平极性配置
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    // 输出通道空闲电平极性配置
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    // 互补输出通道空闲电平极性配置
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable);

    /*-------------------刹车和死区结构体初始化-------------------*/
    // 有关刹车和死区结构体的成员具体可参考BDTR寄存器的描述
    TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
  TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
  TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
  TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
    // 输出比较信号死区时间配置,具体如何计算可参考 BDTR:UTG[7:0]的描述
    // 这里配置的死区时间为152ns
  TIM_BDTRInitStructure.TIM_DeadTime = 11;
  TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
    // 当BKIN引脚检测到高电平的时候,输出比较信号被禁止,就好像是刹车一样
  TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
  TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
  TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);
    
    // 使能计数器
    TIM_Cmd(ADVANCE_TIM, ENABLE);    
    // 主输出使能,当使用的是通用定时器时,这句不需要
    TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE);
}

5.3.2 死区时间配置

这里CKD[1:0]为00

TIM_BDTRInitStructure.TIM_DeadTime = 11;

11对应二进制为0000 1011

那么DTG[7:5]为000,即DT=11/72M=152ns

物联沃分享整理
物联沃-IOTWORD物联网 » 轻松掌握TIM定时器的工作原理及简单应用

发表评论