三篇文章看懂STM32-TIM定时器(1)超详细介绍及练习源码

纲要

一、定时器基本定时功能,也就是定一个时间,然后让定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,例如做时钟、秒表时都需要一个定时中断的功能

二、定时器的输出比较功能(OC),产生PWM波形,用于驱动电机等设备

三、定时器的输入捕获功能(IC),实现测量方波频率

四、定时器的编码器接口,可以更方便的读取正交编码器的输出波形

定时器配置步骤

①RCC开启时钟,此时基准时钟和整个外设的工作时钟都被打开

②选择时基单元的时钟源(内部时钟模式)

③配置时基单元

④配置输出中断控制,允许更新中断输出到NVIC

⑤配置NVIC,打开中断通道,并分配优先级

⑥配置运行控制,需要对计数器使能

⑦最后再写定时器的中断函数

输出比较PWM配置步骤

①用RCC 把TIM外设与GPIO外设的时钟打开

②配置时基单元

③配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能)

④配置GPIO,PWM对应的GPIO配置为复用推挽输出

⑤运行控制

输入捕获配置步骤

①RCC开启TIM时钟和GPIO时钟

②GPIO初始化,配置成输入模式,一般为上拉输入/浮空输入

③配置时基单元,让CNT计数器在内部时钟的驱动下自增运行

④配置输入捕获单元(包括滤波器、极性、直连通道)

⑤选择从模式的触发源,选择TI1FP1

⑥选择触发执行的Reset操作

⑦调用TIM_cmd( )启动

定时器需要用到的库函数

  1. TIM定时器介绍(基本功能)

  1. 1)TIM定时器简介

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

  • 16位计数器、分频器对计数器时钟进行分频,让计数更加灵活自动重装寄存器计数的目标值,计N个时钟后申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s(72M/65536/65536)的定时

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

  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

  • 主模式触发DAC的功能:能让内部的硬件在不受程序的控制下实现自动运行

  • 2))基本定时器

  • PSC分频器:实际分配系数=预分频器系数加1

  • 基本定时器的功能和数量较少,一般用于DAC(数模转换)和DMA(直接存储器访问)且只能使用内部时钟

  • 3)通用定时器

  • 4)高级定时器

  • 高级定时器比通用定时器多了关于电机驱动输出功能,目前阶段不需要掌握

  • 5)定时中断基本结构图

  • 6)预分频器时序

  • 预分频控制寄存器和预分频缓冲器在计数计到一半的时候改变了分频值,这个变化并不会立刻生效,而是会等到本次数周期结束时,产生了更新事件,预分频寄存器的值才会被传递到缓冲寄存器里面去,才会生效

  • 7)计数器时序

  • 计数器无预装时序

    计数器有预装时序

  • 8)RCC时钟树

  • 9)定时器定时中断project

  • 代码
    Timer.c模块代码
    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:定时中断初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Timer_Init(void)
    {
            /*开启时钟*/
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);       //开启TIM2的时钟
            
            /*配置时钟源*/
            TIM_InternalClockConfig(TIM2);   //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
            
            /*时基单元初始化*/
            TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;                //定义结构体变量
            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;               //预分频器,即PSC的值
            TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;              //重复计数器,高级定时器才会用到
            TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);               //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元        
            
            /*中断输出配置*/
            TIM_ClearFlag(TIM2, TIM_FLAG_Update);        //清除定时器更新标志位
                                                         //TIM_TimeBaseInit函数末尾,手动产生了更新事件
                                                         //若不清除此标志位,则开启中断后,会立刻进入一次中断
                                                         //如果不介意此问题,则不清除此标志位也可
            
            TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);   //开启TIM2的更新中断
            
            /*NVIC中断分组*/
            NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);           //配置NVIC为分组2
                                                                                                                                    //即抢占优先级范围:0~3,响应优先级范围:0~3
            /*NVIC配置*/
            NVIC_InitTypeDef NVIC_InitStructure;                                                //定义结构体变量
            NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;                                //选择配置NVIC的TIM2线
            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                //指定NVIC线路使能
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        //指定NVIC线路的抢占优先级为2
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;               //指定NVIC线路的响应优先级为1
            NVIC_Init(&NVIC_InitStructure);                                  //将结构体变量交给NVIC_Init,配置NVIC外设
            
            /*TIM使能*/
            TIM_Cmd(TIM2, ENABLE);       //使能TIM2,定时器开始运行
    }
    
    /* 定时器中断函数,可以复制到使用它的地方
    void TIM2_IRQHandler(void)
    {
            if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
            {
                    
                    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
            }
    }
    */
    

    main.c模块代码
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"
    
    uint16_t Num;                        //定义在定时器中断里自增的变量
    
    int main(void)
    {
            /*模块初始化*/
            OLED_Init();                //OLED初始化
            Timer_Init();                //定时中断初始化
            
            /*显示静态字符串*/
            OLED_ShowString(1, 1, "Num:");                        //1行1列显示字符串Num:
            
            while (1)
            {
                    OLED_ShowNum(1, 5, Num, 5);                  //不断刷新显示Num变量
            }
    }
    
    /**
      * 函    数:TIM2中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void TIM2_IRQHandler(void)
    {
            if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)        //判断是否是TIM2的更新事件触发的中断
            {
                    Num ++;                                         //Num变量自增,用于测试定时中断
                    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);     //清除TIM2更新事件的中断标志位
                                                                    //中断标志位必须清除
                                                                    //否则中断将连续不断地触发,导致主程序卡死
            }
    }
    

  • 10)定时器外部时钟project

  • 硬件接线图

    代码
    Timer.c模块代码
    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:定时中断初始化
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数配置为外部时钟,定时器相当于计数器
      */
    void Timer_Init(void)
    {
            /*开启时钟*/
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);                  //开启TIM2的时钟
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);                //开启GPIOA的时钟
            
            /*GPIO初始化*/
            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);                 //将PA0引脚初始化为上拉输入
            
            /*外部时钟配置*/
            TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
                                                        //选择外部时钟模式2,时钟从TIM_ETR引脚输入
                                                        //注意TIM2的ETR引脚固定为PA0,无法随意更改
                                                        //最后一个滤波器参数加到最大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;           //计数周期,即ARR的值
            TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;        //预分频器,即PSC的值
            TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;    //重复计数器,高级定时器才会用到
            TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);     //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元        
            
            /*中断输出配置*/
            TIM_ClearFlag(TIM2, TIM_FLAG_Update);                      //清除定时器更新标志位
                                                                       //TIM_TimeBaseInit函数末尾,手动产生了更新事件
                                                                       //若不清除此标志位,则开启中断后,会立刻进入一次中断
                                                                       //如果不介意此问题,则不清除此标志位也可
                                                                                                                                    
            TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);                 //开启TIM2的更新中断
            
            /*NVIC中断分组*/
            NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);            //配置NVIC为分组2
                                                                       //即抢占优先级范围:0~3,响应优先级范围:0~3
                                                                       //此分组配置在整个工程中仅需调用一次
                                                                       //若有多个中断,可以把此代码放在main函数内,while循环之前
                                                                      //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
            
            /*NVIC配置*/
            NVIC_InitTypeDef NVIC_InitStructure;                             //定义结构体变量
            NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;                  //选择配置NVIC的TIM2线
            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                  //指定NVIC线路使能
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        //指定NVIC线路的抢占优先级为2
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;               //指定NVIC线路的响应优先级为1
            NVIC_Init(&NVIC_InitStructure);                                  //将结构体变量交给NVIC_Init,配置NVIC外设
            
            /*TIM使能*/
            TIM_Cmd(TIM2, ENABLE);                        //使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:返回定时器CNT的值
      * 参    数:无
      * 返 回 值:定时器CNT的值,范围:0~65535
      */
    uint16_t Timer_GetCounter(void)
    {
            return TIM_GetCounter(TIM2);        //返回定时器TIM2的CNT
    }
    
    /* 定时器中断函数,可以复制到使用它的地方
    void TIM2_IRQHandler(void)
    {
            if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
            {
                    
                    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
            }
    }
    */
    main.c模块代码
  • #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"
    
    uint16_t Num;                        //定义在定时器中断里自增的变量
    
    int main(void)
    {
            /*模块初始化*/
            OLED_Init();                //OLED初始化
            Timer_Init();                //定时中断初始化
            
            /*显示静态字符串*/
            OLED_ShowString(1, 1, "Num:");                        //1行1列显示字符串Num:
            OLED_ShowString(2, 1, "CNT:");                        //2行1列显示字符串CNT:
            
            while (1)
            {
                    OLED_ShowNum(1, 5, Num, 5);                        //不断刷新显示Num变量
                    OLED_ShowNum(2, 5, Timer_GetCounter(), 5);         //不断刷新显示CNT的值
            }
    }
    
    /**
      * 函    数:TIM2中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void TIM2_IRQHandler(void)
    {
            if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)                //判断是否是TIM2的更新事件触发的中断
            {
                    Num ++;                                                                                                //Num变量自增,用于测试定时中断
                    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);             //清除TIM2更新事件的中断标志位
                                                                                                                            //中断标志位必须清除
                                                                                                                            //否则中断将连续不断地触发,导致主程序卡死
            }
    }

    三篇文章看懂STM32-TIM定时器(2)超详细介绍及练习源码

  • 三篇文章看懂STM32-TIM定时器(3)超详细介绍及练习源码

  • 作者:L-22choi

    物联沃分享整理
    物联沃-IOTWORD物联网 » 三篇文章看懂STM32-TIM定时器(1)超详细介绍及练习源码

    发表回复