1.本博文基于STM32F103ZET6芯片和ST官方提供的3.5.0库函数;
2.学习过51单片机学习STM32定时器会容易理解一些,但是两个定时器的实力有很大的距离;
3.定时器是难点也是重点;本博文以通用定时器TIM2~TIM5来说明;例程是TIM3
6.定时器结构稍复杂,寄存器较多,本博文根据一个简单的定时器中断实验展开,所列出的相关寄存器是专门针对本次实验来说的,其他不相关定时器不再列出;
1.高级定时器: TIM1和TIM8
2.通用定时器: TIM2,3,4,5;
3.基本定时器: TIM6和TIM7;
(1)16位向上,向下,向上/向下自动装载寄存器(TIMx_CNT)(向上/向下模式也就是中间对齐模式)
(2)16位可编程逻辑预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间的任意值;
(3)4个独立通道(TIMx_CH1~4),这些通道可以用来作为:
① 输入捕获 比如:测量输入信号的脉冲宽度
② 输出比较
③ PWM生成(边缘或中间对齐模式)
④ 单脉冲模式输出
(4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用1个定时器控制另一个定时器)的同步电路
(5)如果以下事件发生时产生中断/DMA:
A.更新:计数器向上或向下溢出,计数器初始化(通过软件或内部/外部触发)
B.触发事件(计数器启动,停止,初始化或者内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理;
因为时钟是计算定时器周期的关键因素,所以时钟源的大小和属性必须要熟悉;
(1) 首先看相对应的时钟图
需知1:
基于3.5.0库开发的基础上,程序在运行到main函数之前会调用SystemInit()函数,此函数会配置STM32时钟系统作为初始化,初始化的结果就是:
SYSCLK 72MHz
AHB 72MHz
PLCK1 36MHz
PLCK2 72MHz
PLL 72MHz
APB1 36MHz
需知2:
三种定时器的时钟源(下表中的时钟是基于库函数的配置的)
定时器的时钟不是直接来自APB1或APB2,而是来自于输入为APB1或APB2的一个倍频器。
下面以定时器2~7的时钟说明这个倍频器的作用:当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍。
假定AHB=36MHz,因为APB1允许的最大频率为36MHz,所以APB1的预分频系数可以取任意数值;当预分频系数=1时,APB1=36MHz,TIM2到5的时钟频率=36MHz(倍频器不起作用);当预分频系数=2时,APB1=18MHz,在倍频器的作用下,TIM2~7的时钟频率=36MHz。
有人会问,既然需要TIM2到7的时钟频率=36MHz,为什么不直接取APB1的预分频系数=1?答案是:APB1不但要为TIM2到7提供时钟,而且还要为其它外设提供时钟;设置这个倍频器可以在保证其它外设使用较低时钟频率时,TIM2~7仍能得到较高的时钟频率。
再举个例子:当AHB=72MHz时,APB1的预分频系数必须大于2,因为APB1的最大频率只能为36MHz。如果APB1的预分频系数=2,则因为这个倍频器,TIM2~7仍然能够得到72MHz的时钟频率。能够使用更高的时钟频率,无疑提高了定时器的分辨率,这也正是设计这个倍频器的初衷。
需知3: 定时器的时钟源有4个
(1)内部时钟(CK_INT)
本实验采用CK_INT,CK_INT的来源如下图
(2)外部时钟模式1:外部输入引脚(TIx)
(3)外部时钟模式2:外部触发输入(ETR)
(4)内部触发输入(ITRx):使用定时器A作为定时器B的预分频器(A为B提供时钟源);
时钟源的选择取决于从模式控制寄存器TIMx_SMCR;
四 涉及到的相关寄存器
(1)控制寄存器1 TIMx_CR1
符号解释:Tdts 采样频率基准 ; Tck_int 定时器输入频率;
时钟分频因子:这个在介绍定时器输入捕获的时候会经常用到;这里先简单介绍,后来再详细介绍:
对于定时器时钟分频因子需要控制寄存器1TIMx_CR1的CKD[1:0]位和从模式寄存器TIMx_SMCR的ETF[3:0]位的配合;
栗子:当Fck_int = 72MHz时,选择Fdts = Fck_int/2 = 36MHz,采样频率Fsampling = Fdts/2 = 18MHz且N=6,则频率高于3MHz的信号将会被滤波器滤除,有效的屏蔽于高于3MHz的干扰;
(2)计数器 TIMx_CNT
(3)预分频器 TIMx_PSC
[15:0]:预分频的值可以在1~65536范围内任意选取;这个值就决定将定时器的输入时钟源频率(比如72MHz)分频为多少;
(4)自动重载寄存器TIMx_ARR
在物理层面上,ARR有两个,分别是程序员可以操作的ARR寄存器和影子寄存器(不能被直接操作的寄存器);实际上真正起作用的是影子寄存器;在TIMx_CR1寄存器中的ARPE = 1时(TIMx_ARR寄存器被装入缓冲器),在每次更新事件发生的时候,ARR内的值将会被装载到影子寄存器;
(5)中断/DMA中断使能寄存器 TIMx_DIER
[0]:即UIE位为更新中断允许位,控制所有更新中断的产生;
(6)状态寄存器 TIMx_SR
(5)事件产生寄存器TIMx_EGR
这里暂时只看[0]位;
[0]:如果置1将产生更新事件;
这里强调一个很重要的概念,加深一下对更新的理解:
(1)什么是更新?
顾名思义就是一个东西不再继续而回到了原点,比如定时器溢出(这里比如说是上溢出),就是一个更新事件,这件事情一旦发生,就会使得定时器清零,然后继续递增数值,直到下次溢出,然后往复循环;
(2)更新后会造成一些什么?
〇所有的寄存器都将被更新;
Ⅰ预分频器的计数器清零被清零(但是分频系数不变);
Ⅱ如果使能TIMx_DIER寄存器的第[0]位,则会产生产生更新中断,进入中断函数;
Ⅲ硬件同时(依据URS位)设置更新标志位(TIMx_SR寄存器中的UIF)
Ⅳ重复计数器被重新加载为TIMx_RCR寄存器的值(TIMx_ARR)
(3)产生更新时间的方式
Ⅰ计数器上溢/下溢;
Ⅱ设置UG位;
T_out = (arr+1)(psc+1)/CK_INT;
T_out:中断产生周期;
arr:自动重装载的值;
psc:预分频寄存器的值;
CK_INT:内部时钟频率;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//第一步:开启要使用的定时器的使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//初始化定时器:配置定时器的时基单元(TIMx_CNT,TIMx_PSC,TIMx_ARR),计数模式,和分频系数;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置TIMx_CR1寄存器的[9:8]位;这里设置成1分频,即不分频;定时器3的时钟频率等于APB1后的倍频来的值(这里是72MHz)
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置TIMx_CR1寄存器的[6:5]位;这里设置为上升模式;
TIM_TimeBaseInitStructure.TIM_Period = 4999; //设置TIMx_ARR寄存器;也就是定时器溢出周期;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7199; //设置TIMx_PSC寄存器;定时器的预分频值;(1~65536)
//这里计算一下:溢出周期T = (4999+1)*(7199+1)/72000000 = 0.5s;级每0.5s溢出一次;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); //普通定时器初始化;
//第三步:开启定时器中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //配置TIMx_DIER寄存器的[0]位,使能定时器的更新中断;
//第四步:开启定时器;
TIM_Cmd(TIM3,ENABLE); //开启定时器
//第五步:相应中断函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET) //检测TIMx_SR寄存器[0]位是否被置位(也即是更新中断是否发生)
{
//逻辑代码;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIMx_SR寄存器[0]位;
}