前言
我总感觉定时器是单片机的核心。在所有入门章节中基本上是最难的,也是我认为对于单片机是否入门的判断标准
大一玩51单片机,定时器我看了俩个星期多。(主要大一还在体验大学的美好并没有认真较真的学习)51单片机从学完定时器后感觉就没什么瓶颈了。(在大学51用的不深,后面做项目接外设,可能也就串口部分麻烦了些,其他的基本上都是看看例程,使用使用,了解了解外设的原理,差不多就能实现相应的功能)
大二学STM32,也是,定时器是我感觉入门章节中最难的,(进阶的RTOS和PID等不算入门)后面的ADC,DMA,PWM等等都要用到定时器。
也方便更深刻的学习的TIM定时器。
定时器部分打算分几章写完
定时中断,输入输出比较,时钟树系统,PWM等等吧。尽量清晰,方便用于复习。
提示:以下是本篇文章正文内容,下面案例可供参考
一、TIM是什么?TIM结构?
TIM(Timer)定时器
最基本的功能 是对输入的时钟进行计数,并在计数值达到设定值时触发中断。若这个输入是一个可靠的基准时钟,那么对这个基准时钟技术就实现了计时的功能。
定时器分三种,基本定时器,通用定时器,高级定时器。
我学STM32时,师从江科大。一直在用通用定时器。最近马上放假过五一,有时间我也打算搞搞基本定时器,高级定时器看看效果。
时基单元:中间的粉色部分。
运行控制:控制寄存器的一些位,如启动停止、向上或向下计数等,操作这些寄存器就可以控制时基单元的运行了。
内部时钟模式、外部时钟模式2、外部时钟模式1:外部时钟源选择。这个选择器的输出就是为时基单元提供时钟。
编码器模式:编码器独有的模式,一般用不到。
中断申请控制:由于定时器内部有很多地方要申请中断,“中断申请控制”就用来使能控制这些中断是否使能。比如,中断信号会先在状态寄存器里置一个中断标志位,这个
标志位会通过中断输出控制,到NVIC申请中断
定时器核心的部位我感觉是时基单元,理解时基单元,并可以写出相应的代码。定时器就算理解的七七八八了
单说原理,内部晶振常用为8MHZ,但是STM32会将8MHZ的定时器进行倍频至72MHZ
1MHZ=1000KHZ=1000000HZ 所以72MHZ=72000000HZ,这就是默认的频率
有了单片机的频率,就可以进入系统预分频器PSC,
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;//PSC预分频的值7200
72000000/7200=10000,此时,进入时基单元的频率为10000HZ
最后再操纵自动重装值,使内部的频率达到自动重装值后进行定时函数。
TIM_TimeBaseInitStructure.TIM_Period = 10000-1;//ARR自动重装器的值10000
这就是时基单元部分的原理
公式概括:计数器=内部晶振/预分频器
计数器-自动重装值=0,定时器代码执行。
中断函数执行后记得清除定时中断标志位的状态。
计数器,自动重装值,预分频器 均为16进制,最大数据65536;
内外时钟源选择和主从触发模式结构:上面的一大块。下面介绍各种各样的内外时钟源:
- 内部时钟CK_INT【常用】:通常为72MHz,基本定时器只能选择CK_INT,通用定时器和高级定时器则新增了下面的时钟源。
- 外部TIMx_ETR【常用】:引脚的位置可以参考引脚定义表,如stm32f103c8t6的PA0引脚复用了TIM2_CH1_ETR等。外部输入了方波时钟,然后通过极性选择、滤波
等电路进行整形,然后兵分两路,一路ETRF进入触发控制器,紧跟着就可以选择成为时基单元的时钟(外部时钟模式2);一路进入选择器等待成为TRGI。
TRGI主要用作触发输入来使用,可以触发定时器的从模式,本小节仅用做外部时钟(外部时钟模式1),其他功能后续再讲。 - 外部ITR信号:包括ITR0~ITR4(引脚定义见参考手册“表78 TIMx内部触发连接”),来自其他定时器,实现了定时器的级联。这个ITR信号从上一级定时器的主模式
TRGO引脚来(图片右上方)。 - 外部TI1F-ED:来自于输入捕获单元的TIMx_CH1引脚,后缀“ED”意为边沿,也就是说该路时钟的上升沿和下降沿均有效,也就是CH1引脚的边沿。
- 外部TI1FP1:来自CH1引脚时钟。
- 外部TI2FP2:来自CH2引脚时钟。
编码器接口:可以读取正交编码器的输出波形,后续会再介绍。
TRGO引脚:定时器的主模式输出,可以将内部的一些事件映射到TRGO上,相比基本定时器这些事件的范围显然更广。
注:最后三种外部时钟用于输入捕获和测频率,后续介绍
相比于通用定时器,高级定时器主要增加了以下功能:对输出比较模块的升级
重复次数计数器:可以实现每个几个计数周期,才发生一次更新事件和更新中断,相当于自带一级的定时器级联。
DTG(Dead Time Generate):死区生成电路。将输出引脚由原来的一个变为两个互补的输出,可以输出一对互补的有死区的PWM波(防止出现短暂的直通现象),可以
驱动三相无刷电机(如四轴飞行器、电动车后轮、电钻等)。
BRK刹车输入:为了给电机驱动提供安全保障,如果外部引脚BKIN(Break IN)产生了刹车信号或者内部时钟失效,这个电路就会自动切断电机的输出,防止意外的发生。
TIPS:有人问倍频后的频率为什么还要过预分频器;
倍频用来提升性能,预分频器给用户选择,毕竟不是所有的外设都需要最高频率来使用。
看懂公式,就已经掌握了基本定时器。此时再翻到上面的基本定时器结构,应该一目了然。
通用定时器相比较基本定时器,可选配置增加,原理不变
例如晶振选择有多种,内部晶振,外部晶振。
二、TIM标准库函数
TIM_DeInit :恢复默认配置。
TIM_TimeBaseInit 【重要】:时基单元初始化。用于配置时基单元。
TIM_TimeBaseStructInit :为时基单元结构体赋一个默认值。
TIM_Cmd 【常用】:对应定时中断基本结构的“运行控制”模块,用于使能时基单元的计数器。
TIM_ITConfig 【常用】:“中断输出控制”模块,用于使能中断输出信号。
下面6个函数对应时基单元的时钟源选择部分。
TIM_InternalClockConfig 【常用】:选择 内部时钟。
TIM_ITRxExternalClockConfig :选择 ITRx其他定时器 的时钟。
TIM_TIxExternalClockConfig :选择 TIx捕获通道 的时钟。
TIM_ETRClockMode1Config :选择 ETR通过外部时钟模式1 输入的时钟。
TIM_ETRClockMode2Config :选择 ETR通过外部时钟模式2 输入的时钟。
TIM_ETRConfig :不是用于选择时钟的,而是单独用来配置ETR引脚的预分频器、极性、滤波器等参数。
下面是一些可以单独更改某些参数的函数,可以防止每次更改参数都要进行一次初始化。
TIM_PrescalerConfig :单独更改时基单元 预分频 的值。还可以选择 预分频器 是否启用缓冲寄存器。
TIM_CounterModeConfig :单独改变时基单元计数器的计数模式。
TIM_ARRPreloadConfig :自动重装器的预装功能配置,也就是选择时基单元的 计数器 是否启用缓冲寄存器。
TIM_SetClockDivision :单独配置时基单元的 时钟分频系数。这个系数的作用位置比预分频器靠前,但功能与预分频器相同,只不过只能选择固定的分频系数(不分频、2分频、4
分频),对分频要求不高则一般配置为不分频。
TIM_SetCounter :给计数器写入一个值。
TIM_SetAutoreload :给自动重装载寄存器写入一个值。
TIM_GetCounter :获取当前计数器的值。
TIM_GetPrescaler :获取当前的预分频器的值。
最后4个是获取定时中断标志位和清除定时中断标志位的函数。
TIM_GetFlagStatus :用于主函数,获取定时中断标志位的状态。
TIM_ClearFlag :用于主函数,清除定时中断标志位的状态。
TIM_GetITStatus :用于中断函数,获取定时中断标志位的状态。
TIM_ClearITPendingBit :用于中断函数,清除定时中断标志位的状态。
三、TIM标准库代码
#include "stm32f10x.h" // Device header
// 定时器初始化-TIM2
void Timer_Init(void){
//1.初始化RCC内部时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//2.选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//默认使用内部时钟,也可以不写
//3.配置时基单元
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自动重装器的值10000
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;//PSC预分频的值7200
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有)
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//时基单元初始化。用于配置时基单元。
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//消除上一行TIM_TimeBaseInit立刻产生更新事件影响
//4.配置中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//5.配置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);
//6.配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
/*
//TIM2定时中断后的中断函数
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
??
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
主函数中放入Timer_Init();
按照第一个图进行系统配置,有前面两章的基础,上面代码应该理解很快。
理解时基单元,并可以写出相应的代码。定时器中断就算理解的七七八八了
中断函数执行后记得清除定时中断标志位的状态。
总结
复位后计数器的值从1开始而不是从0开始,说明上电初始化后TIM2就立刻中断了一次。这是因为时基单元初始化函数TIM_TimeBaseInit,在函数的最后生成了一个更新事件,来保证可以立刻重新装载预分频器和重复计数器的值。要消除这个影响,就在TIM_TimeBaseInit后面加一句TIM_ClearFlag来清除相应的中断标志位
高级定时器写的很不详细,我总认为没有看懂时钟树,使用这些有点云里雾里的感觉,下一章把时钟树写出来,再配合定时器等。自认为就算彻底理解定时器的使用了。
结合时钟树系统一块理解比较方便