定时器最基本的功能就是定时处理事情。比如定时发送USART数据、定时采集AD数据、定时检测IO口电位、还可以通过IO口输出波形等。可以实现非常丰富的功能。定时器是一个很强大的外设,不同行业使用的方式不同,知识面很广。
01、定时器介绍
首先我们可以在STM32F207数据手册找到定制器的资源,从下图可以看到STM32F207一共10个通用定时器,2个高级定时器,2个基本定时器。
不同定时器的区别
在STM32F207的用户参考手册中可以看到定时器的基本框图,下图是定时器1&8的看图。
由上图看出,不同寄存器具有不同的参数,位数的区别,计数模式的区别,DMA请求的区别,通道得的区别,互补输出的区别和其他。在具体项目中选择哪个定时器,需要看具体的应用场景。下文主要讲解定时器的基础定时功能,选择定时器3。其他定时器原理是相同的,理解定时器3的定时功能,其他定时器也就能理解了。对于STM32系列的单片机,外设基本都是一致的,并且其他家的MCU也是类似的,国内的有兆易创新、新唐科技、上海灵动微电子等等。
02、时钟源
定时器基本定时功能框图。
①CK_PSC是定时器时钟TIMxCLK,经APB1预分频器后分频提供。
②定时器时钟经过PSC 预分频器之后,即CK_CNT,用来驱动计数器计数。
③计数器CNT 是一个16 位的计数器,向上,向下,向上/下计数模式,最大计数值为65535。当计数达到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。
④自动重装载寄存器ARR 是一个16位的寄存器,这里面装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。
定时器说白了就是个计数器,就像我们用心跳粗略估算时间一样,心脏跳动粗略可以认为是1s,那么我们计时60次心跳就过了60秒。其中CK_CNT时钟就类似心跳,CNT计数器就类似心跳次数。举一个极端简单的例子,我们要实现60秒定时,CK_CNT是1s,我们设置CNT计数器向上计数开启中断,因为只有溢出时,也就是计数到65535时才会有中断,那么我们设置CNT计数器为65535-60=65475,开始及时,那么60秒后就会产生中断。我们设置自动重装载寄存器ARR也为65475,当CNT计数器溢出时,自动重装载寄存器ARR就会自动装载到CNT计数器中,就能实现自动循环定时60秒。
经过上面分析,精确定时的关键在于CK_CNT的频率,而CK_CNT是由定时器时钟分频而来的。那么我们就要知道timer3的定时器时钟。我们就要看时钟系统部分,具体看文章《STM32F207时钟系统解析》,这篇文章主要讲解了系统的120M时钟如何从外部的25M的晶振得到的。其中说到APB外设时钟的问题。
定时器在APB定时器时钟下,具体在APB1还是在APB2时钟下我们可以从STM32F207数据手册上看到,图片名字STM32F20xblock diagram。
从上文我们看timer3是在APB1下的。
那么我们来分析APB1的频率
从上图看出,APB1定时器的从系统120M时钟(系统时钟可配置的,我们使用默认的120M时钟)经过AHB分频,APB分频得到的。
在这里说上图红框中的“错误”,萌新可能不太理解。首先手册中少了个右括号。修改后应该为:
if(APBx presc == 1)
X1
else
X2
也就是说
APB分频系数如果是1,频率不变,APB输出的频率就是APB下面时钟的频率。
APB分频系数不是1,频率X2,APB输出的频率乘以2是APB下面时钟的频率。
下面我们分析APB1时钟,从system_stm32f2xx.c中的SetSysClock函数中如下
/* HCLK = SYSCLK / 1*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK / 2*/
RCC->CFGR |=RCC_CFGR_PPRE2_DIV2;
/* PCLK1 = HCLK / 4*/
RCC->CFGR|= RCC_CFGR_PPRE1_DIV4;
可以看到AHB分频系数是1,APB1分频系数是4。
timer3的时钟为120M/1/4*2= 60MHZ。
这里有个疑问,ST提供的system_stm32f2xx.c注释为什么是HCLK,PCLK2,PCLK1,却没有上文提到的APB,AHB字眼,具体看下我之前写过的文章《STM32F207时钟系统解析》。
其实我们出了分析代码,system_stm32f2xx.c文件头也是有注释的,方便查看。
当然,这要求我们的外部晶振是25M的,且system_stm32f2xx.c是没有被修改过的,如果大家需要修改这个文件单片机超频运行,建议把文件头的注释修改,养成一个良好的习惯。
03、时基单元
可编程高级定时器控制模块主要是一个带有相关自动重载16位计数器。这个计数器可以向上计数,向下计数或者交替递增和递减计数。计数器时钟可以通过一个分频器分频。
计数器的自动重载寄存器和预分频寄存器可以通过软件读写。即使当计数器正在运行也可以读写。
时基单元包括
计数器寄存器 (TIMx_CNT)
预分频器寄存器 (TIMx_PSC)
自动重载寄存器 (TIMx_ARR)
重复计数器寄存器 (TIMx_RCR)
自动重载寄存器是预装载的。从自动重载寄存器写入或读取会访问预装载寄存器。预装载寄存器的内容既可以直接传送到影子寄存器,也可以在每次发生更新事件(UEV)时传送到影子寄存器,这取决于TIMx_CR1 寄存器中的自动重载预装载使能位(ARPE)。当计数器达到上溢值(或者在递减计数时达到下溢值)并且TIMx_CR1 寄存器中的UDIS 位为0时,将发送更新事件。该更新事件也可由软件产生。
计数器由预分频器输出CK_CNT 提供时钟,仅当TIMx_CR1 寄存器中的计数器启动位(CEN)置1 时,才会启动计数器。
预分频器说明
预分频器可对计数器时钟频率进行分频,分频系数介于1 和65536 之间。该预分频器基于TIMx_PSC寄存器中的16 位寄存器所控制的16位计数器。由于该控制寄存器具有缓冲功能,因此可对预分频器进行实时更改。而新的预分频比将在下一更新事件发生时被采用。
下图以一些示例说明在预分频比实时变化时计数器的行为。
预分频器分频由1 变为2 时的计数器时序图
预分频器分频由1 变为4 时的计数器时序图
04、计数模式
4.1、向上计数模式
在向上计数模式中,计数器从0增加到自动重载值(TIMx_ARR寄存器的值),然后从0重新开始并产生一个计数器溢出事件。
如果使用重复计数器,则当递增计数的重复次数达到重复计数器寄存器中编程的次数加一次(TIMx_RCR+1)后,将生成更新事件(UEV)。否则,将在每次计数器上溢时产生更新事件。
将TIMx_EGR寄存器的UG位置1通过软件或使用从模式控制器时,也将产生更新事件。
通过软件将TIMx_CR1寄存器中的UDIS位置1可禁止UEV事件。这可避免向预装载寄存器写入新值时更新影子寄存器。在UDIS位写入0之前不会产生任何更新事件。不过,计数器和预分频器计数器都会重新从0开始计数(而预分频比保持不变)。此外,如果TIMx_CR1寄存器中的URS位(更新请求选择)已置1,则将UG位置1会生成更新事件UEV,但不会将UIF标志置1(因此,不会发送任何中断或DMA请求)。这样一来,如果在发生捕获事件时将计数器清零,将不会同时产生更新中断和捕获中断。
发生更新事件时,将更新所有寄存器且将更新标志(TIMx_SR寄存器中的UIF位)置1取决于URS位)
重复计数器中将重新装载TIMx_RCR寄存器的内容
自动重载影子寄存器将以预装载值 (TIMx_ARR) 进行更新
预分频器的缓冲区中将重新装载预装载值(TIMx_PSC寄存器的内容)
计数器时序图,1 分频内部时钟
计数器时序图,2 分频内部时钟
从上面两图看出,中断标志是需要软件清除的
计数器时序图,ARPE=0 时更新事件(TIMx_ARR 未预装载)
从上面两图看出,向上计数,还没有到达0x36,就把自动重载寄存器修改为0x36,就会在计数到0x36时产生动作。
计数器时序图,ARPE=1 时更新事件(TIMx_ARR 预装载)
从上面两图看出,向上计数,还没有到达0x36,就把自动重载预装载寄存器修改为0x36,就不会在计数到0x36时产生动作,会在这个时将自动重载预装载寄存器值赋给自动重载影子寄存器。
4.2、向下计数模式
在向下计数模式中,计数器从自动重载值(TIMx_ARR寄存器的值)向下计数到0,然后从自动重载值(重新开始并产生一个计数器溢出事件。
如果使用重复计数器,则当递减计数的重复次数达到重复计数器寄存器中编程的次数加一次(TIMx_RCR+1)后,将生成更新事件(UEV)。否则,将在每次计数器下溢时产生更新事件。
将TIMx_EGR 寄存器的UG 位置1(通过软件或使用从模式控制器)时,也将产生更新事件。
通过软件将TIMx_CR1 寄存器中的UDIS 位置1 可禁止UEV 更新事件。这可避免向预装载寄存器写入新值时更新影子寄存器。在UDIS 位写入0之前不会产生任何更新事件。不过,计数器会重新从当前自动重载值开始计数,而预分频器计数器则重新从0 开始计数(但预分频比保持不变)。
此外,如果TIMx_CR1 寄存器中的URS 位(更新请求选择)已置1,则将UG 位置1 会生成更新事件UEV,但不会将UIF 标志置1(因此,不会发送任何中断或DMA 请求)。这样一来,如果在发生捕获事件时将计数器清零,将不会同时产生更新中断和捕获中断。
发生更新事件时,将更新所有寄存器且将更新标志(TIMx_SR 寄存器中的UIF 位)置1(取决于 URS 位):
重复计数器中将重新装载 TIMx_RCR 寄存器的内容
预分频器的缓冲区中将重新装载预装载值( TIMx_PSC 寄存器的内容)
自动重载活动寄存器将以预装载值( TIMx_ARR 寄存器的内容)进行更新。注意,自动重载寄存器会在计数器重载之前得到更新,因此,下一个计数周期就是我们所希望的新的周期长度
以下各图以一些示例说明当TIMx_ARR=0x36 时不同时钟频率下计数器的行为
计数器时序图,1 分频内部时钟
计数器时序图,2 分频内部时钟
计数器时序图,未使用重复计数器时更新事件
4.3、中央对齐(向上/向下计数模式)
在中心对齐模式下,计数器从0 开始计数到自动重载值(TIMx_ARR 寄存器的内容)-1,生成计数器上溢事件;然后从自动重载值开始向下计数到1 并生成计数器下溢事件。之后从0开始重新计数。
当TIMx_CR1 寄存器中的CMS位不为“00”时,中心对齐模式有效。将通道配置为输出模式时,其输出比较中断标志将在以下模式下置1,即:计数器递减计数(中心对齐模式1,CMS=“01”)、计数器递增计数(中心对齐模式2,CMS =“10”)以及计数器递增/递减计数(中心对齐模式3,CMS =“11”)。
在此模式下,TIMx_CR1 寄存器的DIR 方向位不可写入值,而是由硬件更新并指示当前计数器方向。
每次发生计数器上溢和下溢时都会生成更新事件,或将TIMx_EGR 寄存器中的UG 位置1(通过软件或使用从模式控制器)也可以生成更新事件。这种情况下,计数器以及预分频器计数器将重新从0 开始计数。
通过软件将TIMx_CR1 寄存器中的UDIS 位置1 可禁止UEV 更新事件。这可避免向预装载寄存器写入新值时更新影子寄存器。在UDIS 位写入0 之前不会产生任何更新事件。不过,计数器仍会根据当前自动重载值进行递增和递减计数。
此外,如果TIMx_CR1 寄存器中的URS 位(更新请求选择)已置1,则将UG 位置1 会生成UEV 更新事件,但不会将UIF 标志置1(因此,不会发送任何中断或DMA 请求)。这样一来,如果在发生捕获事件时将计数器清零,将不会同时产生更新中断和捕获中断。
发生更新事件时,将更新所有寄存器且将更新标志(TIMx_SR 寄存器中的UIF 位)置1(取决于URS 位):
重复计数器中将重新装载 TIMx_RCR 寄存器的内容
预分频器的缓冲区中将重新装载预装载值( TIMx_PSC 寄存器的内容)
自动重载活动寄存器将以预装载值( TIMx_ARR 寄存器的内容)进行更新。注意,如果更新操作是由计数器上溢触发的,则自动重载寄存器在重载计数器之前更新,因此,下一个计数周期就是我们所希望的新的周期长度(计数器被重载新的值)。
以下各图以一些示例说明不同时钟频率下计数器的行为
计数器时序图,1 分频内部时钟,TIMx_ARR = 0x6
计数器时序图,2 分频内部时钟
计数器时序图,ARPE=1 时的更新事件(计数器下溢)
计数器时序图,ARPE=1 时的更新事件(计数器上溢)
05、基础定时代码
10ms中断配置代码
关于设置分频值
TIM3CLK = 2 * PCLK1=2*HCLK / 4= HCLK / 2 = SystemCoreClock /2=60MHZ
所以下图红框内就是TIM3CLK
这里的值是分频系数=TIM3CLK/定时器实际频率,所以定时器频率是10000,也就是说除数就是定时器频率。一个clk是1/10000s。定时时间=1/10000*定时器重载值。根据上面的配置,定时器重载值是100,也就是定时器中断周期是=1/10000*100=0.01s=10ms,也就是100HZ。
如果在定时器翻转LED灯,那么LED灯闪烁频率是50Hz。
上面的的分频值当然可以直接赋值5999,如果想修改为定时器频率为1000,那么还要重新计算。如果按照上面的写法,直接将除数修改为1000即可。
看到这里大家会有疑问,给的重载值明明是99,分频率值也减去1。下面将说明分频值和自动重载周期值都需要减去1的原因。
自动重载值:因为从0开始计算,赋值10,从0开始计数到10是11次。
分频值:在TIMx_PSC寄存器有以下描述。
特别说明
时钟分频因子
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;
其实仔细看过技术手册后发现这句话与PWM输出实验其实是没关系的,这句话是设置定时器时钟(CK_INT)频率与数字滤波器(ETR,TIx)使用的采样频率之间的分频比例的(与输入捕获相关),0表示滤波器的频率和定时器的频率是一样的。
首先这个colck_division时钟分割系数并不是对定时器的时钟频率进行分割。我们都知道输入捕获模式下有一个数字滤波器,这个数字滤波器可以通过配置寄存器改变他的采样频率,从而将一些频率滤除。
具体细节在输入捕获中详解。
我们也可以根据定时器的计数器的特性,使用查询计数器的方法实现精确延时,具体请看《STM32延时函数的四种方法》。
定时器代码开源地址:
https://github.com/strongercjd/STM32F207VCT6
点击查看本文所在的专辑,STM32F207教程