第一部分,主要是定时器基本定时的功能,也就是定一个时间,让定时器每隔这个时间产生一个中断,来实现每隔一段时间执行一段程序的目的。具体可以应用在,做个时钟、秒表,或者使用一些程序算法。
第二部分,主要是定时输出比较功能,输出比较模块最常见的用途就是产生PWM波形,用于驱动电机等设备。在这个部分将学习到,使用STM32输出PWM波形来驱动舵机和直流电机的例子。
第三部分,主要是定时器输入捕获功能。在这个部分将学习到,输入捕获这个模块来实现测量方波频率的例子。
第四部分,定时器的编码器接口。使用这个编码器接口,能够更加方便地读取正交编码器的输出波形。在编码电机测速中,应用广泛。
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 这里计数器就是用来执行计数定时的一个寄存器,每来一个时钟,计数器加1。预分频器,可以对计数器的时钟进行分频,让这个计数更加灵活。自动重装寄存器就是计数的目标值,也就是想要计多少个时钟申请中断。这一块电路构成了定时器最核心的部分,这一块电路被称为时基单元 2的16次方是65536,也就是如果预分频器设置最大,自动重装也设置最大,那定时器的最大的定时时间就是59.65s,这个是靠72M/65536/65536,得到中断频率,然后取倒数,就是59.65s多,即,1/( 72000/65536/65536) 59.65的计算:1/( 72000/65536/65536) STM32定时器还接受级联的模式,也就是一个定时器的输入当作另一个定时器的输入,这样加一起,最大定时时间就是59.6565536655536,这个时间大概是八千多年,再级联一个定时器,定时时间还会再延长65536*65536倍,这个时间大概是34万亿年
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
类型 | 编号 | 总线 | 功能 |
---|---|---|---|
高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 。(三相无刷电机驱动使用) |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC(数字/模拟转换)的功能 |
STM32F103C8T6定时器资源:TIM1(高级定时器)、TIM2、TIM3、TIM4 ( 通用定时器 )即一个高级定时器和三个通用定时器,没有基本定时器。
1. 基本定时器
- 时钟源:时钟源来自RCC的TIMXCLK,就是内部时钟(CK_INT)直接经过控制器传给时基单元充当CK_PSC
基本定时器只能选择内部时钟,内部时钟来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHz,所以通向时基单元的计数基准频率就是72M。
- 控制器:控制定时器的复位、使能、计数、DAC(数字/模拟转换)触发
- 时基单元(最基本的计数计时电路):
- 预分频器:分频、得到计时器的时钟,即CNT计数1次所需要的时间,预分频器时16位的寄存器、所以可分频为1-65536
对输入的基准频率提前进行一个分频操作,例如:如果预分频器写1,那就是2分频,输出频率=输入频率/2=36MHz,以此类推,所以预分频器的值和实际分频系数相差了1,即实际分频系数=预分频器的值+1。这里预分频器是16位的,所以最大值可以写65535,也就是65536分频
- 计数器:用来计数、基本定时器的CNT计数器只能向上计数
对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就加1,这个计数器也是16位的,所以里面的值可以从0一直加到65535,再加的话,计数器就会回到0重新开始,所以计数器的值在计时过程中不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时任务
- 自动装载寄存器ARR:CNT加到ARR的值之后,会产生一个事件或中断或DMA请求,中断用得比较多
用来存储目标值的寄存器,也是16位,存的是写入的计数目标,在运行的过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值时,也就是计时时间到了,那么它就会产生一个中断信号,并且清零计数器,计数器自动开始下一次的计数计时。
图上画的一个向上的折线箭头,就代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,一般称之为“更新中断”,这个更新中断之后就会通往NVIC,再配置好NVIC的定时器中断,那么定时器的更新中断就能够得到CPU的响应了。
图上所画向下的箭头,代表的是会产生一个事件,对应的事情被叫做“更新事件”,更新事件不会触发中断,但可以触发内部其他电路的工作
主从触发模式
它能让内部硬件在不受程序控制下实现自动运行。可以减轻CPU的负担。
主模式触发DAC(数字/模拟转换),在使用DAC(数字/模拟转换)时,可能会用DAC(数字/模拟转换)输出一段波形,那就需要每隔一段时间来触发一次DAC(数字/模拟转换),让它输出下一个电压点。
如果使用正常的思路来实现的话,要先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC(数字/模拟转换)转换,然后DAC(数字/模拟转换)输出,这会导致主程序处于频繁被中断的状态,影响主程序的运行和其他中断的响应。
使用主模式,可以把这个定时器的更新事件映射到这个触发TRGO(Trigger Out)的位置,然后TRGO直接接到DAC(数字/模拟转换)的触发转换引脚上,这样定时器的更新就不需要通过中断来触发DAC(数字/模拟转换)转换了,整个过程不需要软件参与,实现了硬件的自动化,这就是主模式的作用。减轻了CPU的负担。
2. 通用定时器
上半部分电路-时钟输入
最核心的部分时基单元,这部分结构还是和基本定时器一样的,每部分工作流程和基本定时器也是一样的
计数模式
对于通用计时器和高级定时器而言,计数模式就不止向上计数这一种了,还支持向下计数模式和中央对齐模式。
- 向上计数模式就是计数器从0开始,向上自增,计到重装值,清零同时申请中断,然后开始下一轮,依次循环。
- 向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断。
- 中央对齐模式,从0开始,先向上自增,计到重转值,申请中断,然后再向下自减,减到0,再申请中断。
通用定时器上半部分的结构是内外时钟源选择和主从触发模式
通用定时器,时钟源不仅可以选择内部72MHz时钟(CK_INT)(来自RCC的TIMxCLK),还可以选择外部时钟
外部时钟源选择
- “外部时钟模式2”:第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟,ETR(External)引脚的位置,可以参考一下引脚定义表,在这个TIM2的ETR引脚,也就是PA0上接一个外部方波时钟,然后配置一下内部的极性选择、边沿检测和预分频电路,再配置一下输入滤波电路,这些电路可以对外部时钟进行一定的整形,因为是外部引脚的时钟,所以难免会有些毛刺,那这些电路就可以对输入的波形进行滤波,同时可以选择一下极性和预分频器,最后,滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟。想在ETR外部引脚提供时钟,或者想对ETR时钟进行计数,就把这个定时器当作计数器来用的话,可以配置这一路的电路。在STM32中,(ETR→ETRP→ETRF→触发控制器)这一路也成为“外部时钟模式2”。
- “外部时钟模式1”:第二个外部时钟就是TRGI(Trigger In),主要是作为触发输入来使用的,可以触发定时器的从模式。当其作为外部时钟来使用时,这一路被叫做“外部时钟模式1”。通过这一路的外部时钟有,
- ETR引脚的信号(它有两条道路,只不过下面一条会占用触发输入的通道),
- ITR信号:这一部分时钟信号是来自其他定时器的,右边看到的,主模式输出的TEGO可以通向其他定时器,那么就接到了其他定时器的ITR引脚上来了。(具体的连接方式见表)
通过这一路可以实现定时器的级联功能,比如先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO,后面再选择外部时钟模式1,这样TIM3的更新事件就可以驱动TIM2的时基单元,也就实现了定时器的级联。
主从触发模式
- 还可以选择TI1F_ED,(这里连接的是这里输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,这里加个后缀ED(Edge)就是边沿的意思,也就是通过这一路输入的时钟,上升沿和下降沿均有效),
- 最后这个时钟还能通过TI1FP1和TI2FP2获得,
总结来说外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚边沿、CH1引脚和CH2引脚。一般情况下,外部时钟通过ETR引脚就可以了,下面设置这么复杂的输入(TI1F_ED、TI1FP1*和TI2FP2),不仅仅是为了扩大时钟输入范围,更多的还是为了特殊应用场景而设计的,比如定时器级联,后面部分会在输入捕获和测频率时用到
- 编码器接口,只是一个编码器的接口,可以读取正交编码器的输出波形。
- TIGO:定时器的主模式输出,这部分电路可以把内部的一些事件映射到这个TRGO引脚上,比如将更新事件映射到TRGO,用于触发DAC。在通用定时器中,可以把定时器内部的一些事件映射到这里来。 用于触发其它定时器、DAC或者ADC。
对于时钟输入而言,最常用的还是内部72MHz的时钟,如果使用外部时钟首选外部时钟模式2的输入
下半部分电路-输入捕获电路
- 输出比较电路
总共有四个通道,分别对应CH1到CH4的引脚,可以用于PWM输出波形驱动电机。
- 输入捕获电路
也有四个通道,对应的也是CH1到CH4的引脚,可以用于测输入方波的频率等。
- 捕获/比较寄存器
输入捕获和输出比较电路共用的,因为输入捕获和输出比较不能同时使用,所以这里的寄存器是共用的,引脚也是共用的
3. 高级定时器
重复次数计数器
在申请中断的地方,增加了一个重复次数计数器,有了这个计数器之后,就可以实现每隔几个计数周期,才发生一次更新事件和更新中断,原来的结构是每个计数周期完成后就都会发生更新,现在是有个计数器在这里,可以实现每隔几个周期再更新一次,这个就相当于对输出的更新信号又做了一次分频。那对于高级定时器的话,我们之前计算的最大定时时间59秒多在这里就还需要再乘一个65536,这就又提升了很多的定时时间了,这就是这个重复计数器的工作流程。
DTG(Dead Time Generate)
DTG(Dead Time Generate)是死区生成电路,右边的输出引脚由原来的一个,变为了两个互补的输出,可以输出一对互补的PWM波,这些电路是为了驱动三相无刷电机的,三相无刷电机还是比较常用的,比如四轴飞行器、电动车的后轮、电钻等,里面都可能是这个三相无刷电机。因为三相无刷电机的驱动电路一般需要3个桥臂,总共需要6个大功率开关管来控制,所以这里的输出PWM引脚的前三路就变为了互补的输出,而第四路却没什么变化,因为三相电机只需要三路就行了,另外,为了防止互补输出的PWM动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象,所以前面这里就加上了死区生成电路,在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。
刹车输入
那最后一部分,就是刹车输入的功能了,这个是为了给电机驱动提供安全保障的,如果外部引脚BKIN (Break IN)产生了刹车信号或者内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生
4.定时中断基本结构
定时中断和内外时钟源选择。
PSC(Prescaler) 预分频器、CNT (Counter)计数器、ARR(AutoReloadRegister)自动重装器这三个寄存器构成的时基单元。运行控制(控制寄存器):启动停止、向上向下计数。。运用这几个寄存器控制时基单元运行。
左半部分是为时基单元提供时钟的部分,可以选择RCC提供的内部时钟(实验:定时器定时中断)、ETR引脚提供的外部时钟模式2(实验:定时器外部时钟)、使用触发输入作为外部时钟,外部时钟模式1(ETR外部时钟、ITRx其他定时器、TIx输入捕获通道)、编码器模式。
右边部分这里,就是计时时间到,产生更新中断后的信号去向。如果是高级定时器,还会有一个重复计数器。那这里中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。因为申请中断的地方太多了。
预分频时序
预分频器的参数从1变到2时,计数器的时序图。
CK_PSC(预分频器的输入时钟)选内部时钟的话一般是72MHz,不断运行。
CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止。开始时,计数器未使能,计数器时钟不运行。
CK_CNT,计数器时钟,既是预分频器的时钟输出,也是计数器的时钟输入。
CK_CNT = 定时器时钟 ,CK_PSC =定时器时钟源 ,PSC = 预分频值
CNT_EN使能后,
前半段,预分频器系数为1,计数器的时钟等于预分频器前的时钟。
后半段,预分频器系数变为2了,计数器的时钟就也变为预分频器前时钟的一半了。
在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在中间的这个位置计数器寄存器的值为FC之后,计数值变为0了。这里可以推断出ARR自动重装值就是FC。
当计数值计到和重装值相等,并且下一个时钟来临时,计数值才清零。同时,下面这里产生一个更新事件(UEV)。这就是一个计数周期的工作流程。
然后下面还有三行时序。
这里描述的其实是这个预分频寄存器的一种缓冲机制,也就是这个预分频寄存器,实际上是有两个:
一个是这个供我们读写用的。它并不直接决定分频系数。
另外还有一个缓冲寄存器,或者说是影子寄存器,缓冲寄存器和影子寄存器。这两个说法其实是一个意思。
这个缓冲寄存器才是真正起作用的寄存器。比如我们在某个时刻,把预分频寄存器由0改成了1。如果在此时立刻改变时钟的分频系数了,那么就会导致这里在一个计数的周期内,前半部分和后半部分的频率不一样。这里计数计到一半,计数频率突然就会改变了。这虽然一般并不会有什么问题,但是stm32的定时器比较严谨。设计了这个缓冲寄存器。这样,当计数计到一半的时候改变了分频值这个变化并不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频器的值才会被传递到缓冲寄存器里面去才会生效。所以在这里看的,即使我的计数中途改变了预分频值。计数频率仍然会保持为原来的频率。直到本轮计数完成在下一轮计数时,改变后的分频值才会起作用。
最后,预分频器内部实际上也是靠计数来分明的。
当预分频值为0时,计数器就一直为0,直接输出原频率。
当预分频值为1时,计数器就01010101这样计数,在回到0的时候输出一个脉冲。这样输出频率就是输入频率的二分频与预分频器的值,和实际的分频系数之间有一个数的偏移。 那下面就有这样一个公式,就是计数器计数频率(CK_CNT)。
$$CK\_CNT = \frac{CK\_PSC}{(PSC +1)}$$
计数器时序(分频系数为2)
这个图是计数器时序图内部时钟分频因子为2,就是分频系数为2。 第一行是内部始 终72mhz第二行是时钟使能高电平启动第三。
CK_INT(内部时钟):72Mhz
CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止。
CK_CNT,计数器时钟,因为分频系数为2,所以这个频率是CK_INT除2
计数器寄存器,在CK_CNT(定时器时钟)每个上升沿自增,当自增到0036的时候发生溢出,并在下一个上升沿计数器清零,计数器溢出,产生一个更新事件脉冲。 然后计数器在这个时钟每个上升沿自增,当自增0036的时候发生溢出,当再一次增加到36之后,再来一个上升沿,计数据清零,计数器溢出,产生一个更新事件脉冲。
更新中断标志位UIF,这个标志位,只要置1了,就会去申请中断,然后中断响应后,需要在中断程序中手动清零。 这就是计数器的工作流程。 下面有个式子计数器溢出频率CK_CNT_OV
$$CK\_CNT\_OV=\frac{CK\_CNT}{(ARR+1)} = \frac{\frac{CK\_PSC}{(PSC +1)} }{(ARR +1)}$$
这就是计算定时时间的一个式子。
$$CK\_CNT\_OV=\frac{CK\_CNT}{(ARR+1)} = \frac{\frac{72MHz}{(PSC +1)} }{(ARR +1)}$$
用72Mhz÷(PSC+1)÷(ARR+1)就能得到溢出频率。 如果想算溢出时间,那就只需要再取个倒数就行了。
计数器有无预装时序(影子寄存器)
预分频器,为了防止计数中途更改数值,造成错误,设计了缓寄存器。这个计数器,也有这样的设计。 我们可以看一下结构图。这里面像这样带一个黑色阴影的寄存器,都是由影子寄存器这样的缓冲机制的,包括预分频器,自动重装寄存器和捕获比较寄存器。所以计数的这个ARR自动重装寄存器也是有一个缓冲寄存器的,并且这个缓冲寄存器是用还是不用,是可以自己设置的。 下面这两个图第一个计数器无预装时序,就是没有缓冲寄存器的情况。第二个有预装时序,就是有缓存寄存器的情况。通过设置这个ARPE位就可以选择是否使用预装功能。
计数器正在进行自增计数,突然更改了自动加载寄存器(自动重装寄存器),由FF改成了36,那计数值的目标值就有FF变成了36。所以这里计到36之后就直接更新,开始下一段计数。 再看一下下面这个图。
有预装的情况。在计数的中途,我突然把计数目标由F5改成了36。下面有个影子寄存器,这个影子寄存器才是真正起作用的,它还是F5。所以现在计数的目标还是计到F5产生更新事件。同时要更改的36才被传递的影子寄存器在下一个技术周期,这个更改的三六才有效。所以可以看出,引入这个影子寄存器的目的实际上是为了同步,就是让值的变化和更新事件同步发生。防止在运行途中更改,造成错误。在这个例子也可以看出,如果这里不使用影子寄存器的话,F5改到36,立刻生效,但此时计数值已经到了F1已经超过36了。F1只能增加,但它的目标却是36,比它还小。这样F1就只能一直加到FFFF,再回到0再加到36才能产生更新。这就会造成一些小问题。当然,如果你不介意这样的问题的话,那就不用管这些细节了。毕竟stm32设计出来要考虑到各种各样的情况,所以做的比较严谨。
5.RCC时钟树结构图。
这个时钟数就是stm32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统时钟是所有外设运行的基础。所以时钟也是最先需要配置的东西。程序中主函数之前还会执行一个SystemInit()的函数。这个函数就是用来配置这个时钟数的。这个结构看上去挺复杂的。配置起来还是比较麻烦的。不过在st公司已经帮我们写好了配置这个时钟数的system以内的函数。 左边的都是时钟的产生电路。 右边的都是时钟的分配电路。 中间的这个System Clock就是系统时钟72mhz
左边的时钟产生电路
在时钟产生电路有4个震荡源:
内部的8mhz高速RC振荡器。
外部的4~16mhz高速石英晶体振荡器,也就是晶振,一般都是接8mhz
外部的32.768khz低速晶振。这个一般是给RTC提供时钟的。
内部的40khz低速RC振荡器。这个可以给看门狗提供时钟。
8mhz高速RC振荡器和4~16mhz高速石英晶体振荡器这两个高速晶振,是用来提供系统时钟的。AHB、APB2、APB1的时钟都是来源于这两个高速晶振。这里内部和外部都有一个8mhz的晶振都是可以用的。只不过是外部的石英振荡器比内部的rc振荡器更加稳定。所以一般我们都用外部晶振。但是如果你系统很简单而且不需要那么精确的时钟,那也是可以使用内部rc振荡器的。这样就可以省下外部晶振的电路了。
在SystemInit()的函数里st是这样来配置时钟的。
首先,启动内部时钟,选择内部8mhz为系统时钟,暂时以内部8mhz的时钟运行,
然后再启动外部时钟(4~16mhz高速石英晶体振荡器),进入PLL锁相环进行倍频,8mhz倍频9倍,得到72mhz,等到锁相环输出稳定后选择锁相环输出为系统时钟。这样就把系统时钟由8mhz切换为了72mhz。这是st配置的流程。
如果外部晶振出问题了,可能会导致一个现象,就是你会发现你程序的时钟慢了大概10倍。比如你用定时器定一个一秒的时间,结果过了大概10秒才进中断。这个问题就出在这里。如果外部晶振出问题了,系统时钟就无法切换到72mhz,那它就会以内部的8mhz运行,8M相比于72M大概就慢了10倍。
CSS(Clock Security System)时钟安全系统,它也是负责切换时钟的,它可以监测外部时钟的运行状态。一旦外部时钟失效,它就会自动把外部时钟切换回内部时钟保障系统始终在运行,防止程序卡死,造成事故。另外,在高级定时器这里也有CSS。在这个刹车输入,这里一旦CSS检测到外部时钟失效,这里通过或门就会立刻反映到输出比较这里,让这个输出控制的电机立刻停止,防止意外。这就是这个stm32里面的一些安全保障措施。
右边的时钟分配电路。
首先,系统时钟72mhz进入AHB总线 。AHB总线有个预分频器在SystemInit()里配置的分频系数为1。那AHB的时钟就是72mhz。
然后进入APB1总线,这里配置的分频系数是2,所以APB1总线的时钟为72mhz÷2=36mhz。 通用定时器和基本定时器是接在APB1上的。而APB1的时钟是36mhz。按理说他们的时钟应该是36mhz,
但是为什么一直都说的是所有的定时器的时钟都是72mhz?
因为下面还有一条支路,上面写的是:如果APB1预分频系数=1,则频率不变,否则频率x2。然后再看右边,发现这一路是单独为定时器2~7开通的。因为这里预分频系数,我们给的是2。所以这里频率要再乘2。所以通向定时器2~7的时钟就又回到了72mhz。所以这里就可以有个结论。无论是高级定时器,还是通用定时器,还是基本定时器,它们的内部基准时钟都是72mhz。这个就给我们的使用带来了方便。不用再考虑不同定制器的时钟不一样的问题了。当然前提是你不乱改它SystemInit()里面的默认配置。要是改了这里的时钟还得再另行分析。
然后APP2的时钟,这里给了分频系数为1。所以APP2的时钟和AHB一样,都是72mhz。这里接在APP2上的高级定时器也单开了一路,上面写的也是:如果APB2预分频系数=1,则频率不变,否则频率×2。但是这里APB2的预分频系数就是1所以频率不变。定时器1和8的时钟就是72mhz。
在这些时钟输出这里都有一个与门进行输出控制。控制位写的是外部时钟使能。这就是在程序中写RCC_APB2PeriphClockcmd()或RCC_APB1PeriphClockcmd()外设时钟控制作用的地方。打开时钟,就是在这个位置写1,让左边的时钟能够通过与门输出给外设。
点击数:38