STM32通用定时器使用详解

发布者:紫菜包饭最新更新时间:2018-12-29 来源: eefocus关键字:STM32  通用定时器  使用详解 手机看文章 扫描二维码
随时随地手机看文章

1.通用定时器基本介绍


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


STM32通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。


每个定时器都是完全独立的,没有互相共享任何资源。它们可以一起同步操作。


定时器可以进行定时器基本定时,输出4路PWM,输入捕获 


本文详细介绍这三个功能并且利用定时器3并且示例代码使用


2.开发环境


开发平台:keil5 


单片机:STM32F103ZET6


3.基本定时功能

 

3.1定时器时钟来源分析


STM32部分时钟树: 

 

定时器部分时钟树 

  

3.1.1 首先我们我们的系统时钟(SYSCLK 72MHz) 经过AHB分频器给APB1外设,但是APB1外设最大的只能到36Mhz,所以必须要系统时钟的二分频。下面又规定了如果APB1预分频系数为1则频率不变,否则频率X2至定时器2~7,所以定时器2~7的时钟频率为还是72MHz


3.1.2 分配给我们定时器的时钟是72MHz,我们可以根据自己的需求再设置定时器的分频,设置它的定时值


    * 初始化定时器的时候指定我们分频系数psc,这里是将我们的系统时钟(72MHz)进行分频


    * 然后指定重装载值arr,这个重装载值的意思就是当 我们的定时器的计数值 达到这个arr时,定时器就会重新装载其他值.


        例如当我们设置定时器为向上计数时,定时器计数的值等于arr之后就会被清0重新计数


    * 定时器计数的值被重装载一次被就是一个更新(Update)


    * 计算Update时间公式

    Tout = ((arr+1)*(psc+1))/Tclk


    公式推导详解:

        Tclk是定时器时钟源,在这里就是72Mhz 


        我们将分配的时钟进行分频,指定分频值为psc,就将我们的Tclk分了psc+1,我们定时器的最终频率就是Tclk/(psc+1) MHz


        这里的频率的意思就是1s中记 Tclk/(psc+1)M个数 (1M=10的6次方) ,每记一个数的时间为(psc+1)/Tclk ,很好理解频率的倒数是周期,这里每一个数的周期就是(psc+1)/Tclk 秒


        然后我们从0记到arr 就是 (arr+1)*(psc+1)/Tclk


    举例:比如我们设置arr=7199,psc=9999

    我们将72MHz (1M等于10的6次方) 分成了(9999+1)等于 7200Hz

    就是一秒钟记录9000数,每记录一个数就是1/7200秒


    我们这里记录9000个数进入定时器更新(7199+1)*(1/7200)=1s,也就是1s进入一次更新Update

*/

//简单进行定时器初始化,设置 预装载值 和 分频系数

void MY_TIM3_Init(u16 arr,u16 psc){


    //初始化结构体

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;


    //1.分配时钟

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);


    //2.初始化定时器相关配置

    TIM_TimeBaseStructure.TIM_Period = arr;

    TIM_TimeBaseStructure.TIM_Prescaler = psc;


    /*在这里说一下这个TIM_ClockDivision 是设置与进行输入捕获相关的分频

        设置的这个值不会影响定时器的时钟频率,我们一般设置为TIM_CKD_DIV1,也就是不分频*/

    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数

    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);


    //3.打开定时器

    TIM_Cmd(TIM3,ENABLE);

}


/****************** 主函数 ********************/

//在主函数中我们可以调用初始化

int main(){

    //定时器初始化

    MY_TIM3_Init(7199,9999);

    while(1){


        //检测更新标志位

        if(TIM_GetFlagStatus(TIM3,TIM_IT_Update)){

            //清除标志位

            TIM_ClearFlag(TIM3,TIM_IT_Update);

            //....(每隔一秒执行任务)

        }


    }

}


4.定时器输出PWM

 

4.1基本介绍


  4.1.1 PWM是脉冲宽度调制,我们是通过改变脉冲的宽度来达到改变输出电压的效果,本质上就是调节占空比实现的,STM32除了基本定时器(TIM6,TIM7)不能输出PWM以外,其它的定时器都具有输出PWM,其中高级定时器(TIM1和TIM8)还能输出7路PWM,基本定时器(TIM2,TIM3,TIM4,TIM5)也可以输出4路PWM


输出PWM是很有用的,比如我们可以通过控制电机来玩小车,或者通过输出PWM改变LED的亮度,制造呼吸灯等等


  4.1.2 我们通用定时器能输出PWM的IO口是固定的,虽然我们可以通过重映射可以改变引脚,具体是哪一些IO口我们要通过查阅STM32的参考手册


这里涉及到一个重映射的概念,重映射就是管脚的外设功能映射到另一个管脚,但是不是可以随便映射的,具体对应关系参考手册上的管脚说明。这样优点是可以优化电路设计;扩展功能,减少外设芯片资源


/**

    定时器3,可产生四路的PWM输出,四个通道分别对应的引脚情况如下

    TIM3_CH1,TIM3_CH2,TIM3_CH3,TIM3_CH4

    没有重映像的对应情况:

    PA6,PA7,PB0,PB1

    部分重映像:

    PB4,PB5,PB0,PB1

    完全重映像:

    PC6,PC7,PC8,PC9 


    当我们的IO口不仅仅是做普通的输入输出使用的时候,作为别的外设(AD,串口,定时器等)的特定功能引脚,就需要开启外设.


    这里我们还需要开启APB2外设上的复用时钟AFIO,同时IO口采用的是复用输出!


    我们这里是没有使用重映射功能.

*/

// 宏定义

//判断当前是处于哪一种模式,以便于我们初始化IO口

#define NO_REAMP   0

#define PART_REAMP 1

#define FULL_REAMP 2


// ---> 这里是需要制定的参数


//指定这里的 当前的模式,我们给她默认指定是 没有重映射

#define CURRENT_MODE NO_REAMP 


//*************根据当前模式初始化IO口 函数

void MY_TIM3_GPIO_Init(void){


    GPIO_InitTypeDef    GPIO_InitStructure;


    //1.开启AFIO时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


    //2. 根据当前的重映像的模式 配置时钟 和 初始化相关引脚

    switch(CURRENT_MODE){


        //2.1 如果没有重映射

        case NO_REAMP:{


            // 时钟分配

            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);

            // 初始化IO口

            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;

            GPIO_Init(GPIOA,&GPIO_InitStructure);

            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;

            GPIO_Init(GPIOB,&GPIO_InitStructure);


            break;

        }

        //2.2 部分重映射

        case PART_REAMP:{


            // 时钟分配

            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

            // 初始化IO口

            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;

            GPIO_Init(GPIOB,&GPIO_InitStructure);


            break;

        }

        //2.3 全映射

        case FULL_REAMP:{


            // 时钟分配

            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);

            // 初始化IO口

            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;

            GPIO_Init(GPIOB,&GPIO_InitStructure);


            break;

        }

        default:break;

    }   

}


//***************** 定时器PWM输出初始化函数

void MY_TIM3_PWM_Init(u16 arr,u16 psc){


    //初始化结构体

    TIM_OCInitTypeDef TIM_OCInitstrcuture;


    //1.初始化定时器 和 相关的IO口

    MY_TIM3_Init(arr,psc); 

    MY_TIM3_GPIO_Init();


    //2.初始化PWM的模式


    /**

    选择PWM模式:

        PWM1模式:

            向上计数时,当我们 当前的 计数值 小于我们的设置阈值为有效电平,否则为无效电平,向下计数时与向上计数时相反

        PWM2模式:

            与PWM1模式向上向下计数时完全相反

    */

    TIM_OCInitstrcuture.TIM_OCMode = TIM_OCMode_PWM1;

    TIM_OCInitstrcuture.TIM_OutputState = TIM_OutputState_Enable;

    TIM_OCInitstrcuture.TIM_OCPolarity = TIM_OCPolarity_High;   //输出电平为高,也就是有效电平为高

    TIM_OC1Init(TIM3,&TIM_OCInitstrcuture);                     //这里是设置利用通道1输出


    //这里只初始化通道1,我们可以根据自己需求初始化其它通道


//  TIM_OC2Init(TIM3,&TIM_OCInitstrcuture);

//  TIM_OC3Init(TIM3,&TIM_OCInitstrcuture);

//  TIM_OC4Init(TIM3,&TIM_OCInitstrcuture);


    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能预装载寄存器

}


//*********************主函数调用

int main(){


    //因为我们单片机引脚输出电压3.3V左右,我们设置预装载值为330

    MY_TIM3_PWM_Init(330,0);


    //我们初始化的时候选择的是PWM1模式,当计数值小于我们的设定值100时为有效电平,这里是高电平

    //所以对于的1通道(PA6)电压是大概就是 3.3 * (100/330) = 1V 左右,我们可以用万用表测量

    TIM_SetCompare1(TIM3,100);


    while(1);

}


5.定时器输入捕获


 5.1基本介绍


上面介绍了定时器的四路通道可以输出PWM,同样的我们也可以捕获该定时器这四路通道上的边沿状态(上升沿,下降沿)


由此可见基本定时器也不能进行输入捕获,没有思路通道


我们可以通过输入捕获的来测量高电平脉宽时间,首先捕获到高电平,记录下改时间,然后切换为捕获低电平,得到时间


 5.2开发步骤


   输入捕获 (捕获边沿信号,上升沿和下降沿)


   首先我们需要以一定的频率检测电平的跳变,然后对部分跳变(也就是部分输入的波形)进行过滤 

      —— 这就是定时器里面的滤波器的任务


指定输入滤波器时钟频率,首先是系统时钟分给定时器72Mhz,我们首先初始化定时器的时候指定了TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 没有分频,输入给滤波器的时钟频率还是72MHz,TIM_ClockDivision也可以指定为2分频或者4分频

波形过滤(TIM_ICFilter),这里有一个指定过滤器的参数(参考芯片手册),例如我们设置参数为0101(二进制),采样频率(fsampling)为 滤波器频率/2 = 36Mhz,N=8.当检测到一个上升沿的时候,再以fsampling频率连续8次检测到高电平才确认是一个有效的上升沿,这样可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤高频波的效果。 


这里写图片描述


配置输入分频(TIM_ICPrescaler),如果我们设置不分频,一个边沿(上升沿或者下降沿)就触发一次捕获,二分频就是两次边沿触发捕获,这里这个分频可以为1,2,4,8


//定时器输入捕获初始化

void MY_TIM3_Cap_Init(u16 arr,u16 psc){


    //初始化结构体

    TIM_ICInitTypeDef TIM_ICInitStructure;


    //1.初始化定时器 和 相关的IO口

    MY_TIM3_Init(arr,psc); 


    //这里的IO口根据自己需求改成输入,我这改成下拉输入,具体代码就不展现了

    MY_TIM3_GPIO_Init();


    //2.初始化定时器输入捕获

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1 ; // 设置输入捕获的通道


    //不使用过滤器,假设我们想使用,例如上述举例使用0101

    //我们就给TIM_ICFilter  = 0x05 ,(0000 0101),根据上表可以知道这个值范围(0x00~0x0F)

    TIM_ICInitStructure.TIM_ICFilter = 0x00;


    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获

    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;       //配置输入分频,这里不分频,1次检测到边沿信号就发生捕获


    /*

        这里说一下定时器通道可以进行交叉捕获,通道1捕获通道2引脚上的边沿信号,通道2捕获通道1引脚,通道3可以捕获通道4对应引脚,... 

        但是只能相邻一对可以相互捕获,例如通道2不能捕获通道3引脚边沿信号

        TIM_ICSelection_DirectTI 表示直接捕获,通道1对应通道1引脚,通道2对应通道2引脚

        TIM_ICSelection_IndirectTI 表示进行交叉捕获

    */

    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射捕获对应通道的引脚

    TIM_ICInit(TIM3,&TIM_ICInitStructure);                                                  


}

//****************主函数

int main(){

    //初始化输入捕获

    MY_TIM3_Cap_Init(1000,0);


    while(1){

        //检测是否捕获到上升沿

        if(TIM_GetFlagStatus(TIM3,TIM_IT_CC1)){

            TIM_ClearFlag(TIM3,TIM_IT_CC1);

            //捕获到上升沿之后的任务...

            //一般测量高电平脉宽,我们可以先捕获上升沿再捕获下降沿

            //TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); 修改为下降沿捕获

        }


    }

}


6.定时器中断

 

1.谈及到中断,我们就必须涉及到NVIC,具体关于NVIC请参考我的另外一篇,这里是直接使用,我们使能定时器3中断并且配置完抢占优先级和响应优先级之后,再在主函数中使能其更新中断和输入捕获中断


//使能更新中断和输入捕获通道1的中断

TIM_ITConfig(TIM3,TIM_IT_Update|TIM_IT_CC1,ENABLE);

1

2

 2.我们使用中断的一个主要目的就是能够及时处理信息,不用在主函数的while循环里面等待


//定时器3的中断处理函数

void TIM3_IRQHandler(void){


    //1.判断是什么中断


    // 1.1定时器更新中断

    if(TIM_GetITStatus(TIM3,TIM_IT_Update)){

        //...处理定时器更新之后任务

    }

    // 1.2如果是定时器 通道1的捕获中断

    else if( TIM_GetITStatus(TIM3,TIM_IT_CC1) ){

            //处理输入捕获之后的任务

            //TIM_OC1PolarityConfig(TIM3,TIM_ICPolarity_Falling);更改为下降沿捕获

    }


    //2.最后将中断标志位都清理掉

    TIM_ClearITPendingBit(TIM3,TIM_IT_Update|TIM_IT_CC1);

}


关键字:STM32  通用定时器  使用详解 引用地址:STM32通用定时器使用详解

上一篇:STM32 定时器计数器 更新事件
下一篇:stm32定时器(基本定时器)操作寄存器版

推荐阅读最新更新时间:2024-03-16 16:21

STM32 ADC采样配置
简介: 本次ADC采样主要采用stm32 103XB 芯片,用于对温度和漏电的采样。此次会进行最多16路的采样。ADC采集16路模拟信号,并由DMA传输到内存,之后从内存中提取数据进行计算。此次记录主要以配置为主,不同的情况下,采用的计算方式不同没有太大必要。系统时钟是72MHz。 正文程序及解释: #include stm32f10x.h #include #include ADC.h #define ADC_COUNT 320 //每通道采 320次 次数可修改 //0-15通道单次转换(0-4095) 定义范围(-32768~+32768)有符号16位 volatile int16_t ADC_V
[单片机]
STM32 GPIO相关知识点
简介:开漏输出:OC门的输出就是开漏输出;OD门的输出也是开漏输出。TTL电路有集电极开路OC门,MOS管也有和集电极对应的漏极开路的OD门,它的输出就叫做开漏输出。它可以吸收很大的电流,但是不能向外输出电流。所以,为了能输入和输出电流,它使用的时候要跟电源和上拉电阻一齐用。 OC门开漏输出和OD门开漏输出都是为了同一个目的,都是为了实现逻辑器件的线与逻辑,当然选用不同的外接电阻也可以实现外围驱动能力的增加。当你应用此电路的时候,要注意应用时要加上拉电阻接电源,这样才能保证逻辑的正确,在电阻上要根据逻辑器件的扇入扇出系数来确定,但一般mos电路带载同样的mos电路能力比较强,所以电阻通常可以选择2.2k,4.9k这样一些常用的
[单片机]
STM32应用-5-LORA模块测试
在一个物品定位项目中,需要用Lora实现物品定位功能。此项目没有选择NBIOT因为客户对于NB需要SIM卡,以及NB的成本并不满意,因此选择更低成本,且无需SIM卡的LORA方式。 硬件连接 其中,Lora模块的管脚连接方式如下: 序号 引脚 引脚方向 备注 1 M0 输入(不可悬空) 和M1配合,决定模块的工作模式(极弱上拉,如不使用可接地) 2 M1 输入(不可悬空) 和M0配合,决定模块的工作模式(极弱上拉,如不使用可接地) 3 RXD 输入 TTL串口输入,连接到外部TXD引脚(可配置为漏极开路或上拉输入,详见手册) 4 TXD 输出 TTL串
[单片机]
<font color='red'>STM32</font>应用-5-LORA模块测试
STM32 控制74HC595 驱动点阵 文字能移动
遇到了很奇怪的问题,程序明明没错但就是不显示,最后把在main函数中定义的变量count移动到main 外面就正常了 。仿真后发现,在main 函数中定义的局部变量 初始值不为0 ,超出控制范围 , 导致程序跑飞。按理说keil 定义变量默认初始化为0才对 。最后将定义count初始化为0,程序正常运行。下次定义变量一定要初始化啊!!! 在单步调试中,发现这个问题。不得不说一下,keil 在线仿真非常好用。 #include stm32f10x.h #include intrins.h void spi2Init(void); void gpioInit(void); void delay(unsigned i
[单片机]
STM32之中断嵌套控制器
STM32 (Cortex-M3) 中的优先级概念 STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作“亚优先级”或“副优先级”,每个中断源都需要被指定这两种优先级。 1. 何为占先式优先级(pre-emption priority) 高占先式优先级的中断事件会打断当前的主程序/中断程序运行—抢断式优先响应,俗称中断嵌套。 2. 何为副优先级(subpriority) 在占先式优先级相同的情况下,高副优先级的中断优先被响应; 在占先式优先级相同的情况下,如果有低副优先级中断正在执行, 高副优先级的中断要等待已被响应的低副优先级中断执行结束后才能得到响应—非抢断
[单片机]
STM32单片机中RTC的秒中断的原理解析
RTC(Real Time Clock)是实时时钟的意思,它其实和TIM有点类似,也是利用计数的原理,选择RTC时钟源,再进行分频,到达计数的目的。 该文主要讲述关于RTC的秒中断功能,这个功能类似SysTIck系统滴答的功能。RTC秒中断功能其实是每计数一次就中断一次。注意,这里所说的秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决定的“秒”的时间,当然也是可以做到1秒钟中断一次。 本文章提供的实例工程,其实验效果是: 主函数间隔0.5秒LED变化一次; 秒中断一次打印数据“RTC Sec.。.”; 也就是LED变化一次,串口打印一次数据“RTC Sec.。.” 扩展部分的功能RTC计数:可以实现RTC闹钟,本文
[单片机]
<font color='red'>STM32</font>单片机中RTC的秒中断的原理解析
关于ST-Link下载STM32程序的使用
ST-Link非常好用,既可以像JLINK那样在软件中直接下载,,也可以下载Hex文件, 自己买的这种,,,, 其实就是SWD下载模式 安装驱动 所有用到的 链接: http://pan.baidu.com/s/1c10Twsg 密码:m4dx 先安装好驱动现在用软件下载 现在用这个软件下载Hex文件,,,安装步骤可以百度下 ST-Link可以软件下载,也可以下载Hex文件,,关键是比串口下载快,,省时
[单片机]
关于ST-Link下载<font color='red'>STM32</font>程序的<font color='red'>使用</font>
Event Recoder调试组件在stm32上的使用
本文目标:Event_Recoder调试组件在stm32上的使用 按照本文的描述,应该可以在你所处的硬件上跑通代码。 先决条件:装有编译和集成的开发环境,比如:Keil uVision5。 板子硬件要求:无,属于调试功能。 起源 因为做产品开发,设计东西有时候考虑得多,mcu的并没有多余的串口供使用调试,在调试一些初期进行验证时,必要的调试的打印信息是需要的。 Event Recoder调试组件简介 嵌入式的Event_Recoder调试组件是一种可以在MDK开发环境下使用的高级调试工具,它可以记录软件运行的一些标志信息,并以图形化的形式显示出来。它可以帮助你了解和分析内部操作,支持Keil RTX操作系统调试以及MDK自带的
[单片机]
Event Recoder调试组件在<font color='red'>stm32</font>上的<font color='red'>使用</font>
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved