写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.04.04
STM32开发板学习——第13节: [6-1] TIM定时中断
- 前言
- 开发板说明
- 引用
- 解答和科普
- 一、TIM定时器
- 二、定时中断
- 问题
- 总结
前言
本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!
开发板说明
本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
原理图如下
1、开发板原理图
2、STM32F103C6和51对比
3、STM32F103C6核心板
视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。
下图是实物图
引用
【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
数据手册
解答和科普
第一部分:定一个时间,然后让定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的。定时中断
第二部分:主要讲的是定时器输出比较的功能,输出比较这个模块最常见的用途就是产生PWM波形,用于驱动电机等设备。
第三个部分:定时器输入捕获的功能,输入捕获这个模块来实现测量方波频率的例子
第四个部分:定时器的编码器接口,能够更加方便地读取正交编码器的输出波形
一、TIM定时器
定时器定时中断,定时器外部时钟
定时器的基本功能就是:定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断;定时触发中断。也是计数器。
比如在STM32中。定时器的基准时钟般都是主频72MHz,如果我对72MH计72个数,那就是1MHz也就是1us的时间,如果计72000个数,那就是1KHz也就是1ms的时间.
这里计数器就是用来执行计数定时的一个寄存器,每来一个时钟,计数器加1,预分频器,可以对计数器的时钟进行分频,让这个计数更加灵活,自动重装寄存器就是计数的目标值,就是我想要计多少个时钟申请中断。这些寄存器构成了定时器最核心的部分,我们把这一块电路称为时基单元。
这个时基单元里面的计数器、预分频器、自动重装寄存器都是16位的,2的16次方是65536。也就是如果预分频器设置最大,自动重装也设置最大,那定时器的最大的定时时间就是59.65s。接近1分钟. 72M/655536/65536,得到的是中断频率,然后取倒数就是59.65秒多。还支持级联的模式,也就是一个定时器的输出,当作另一个定时器的输入,这样加一起,最大定时时间就是59.656553665536大概是8千多年。
中断功能,内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式。
高级定时器,通用定时器,基本定时器
定时器类型
高级定时器:TIM1、TIM8是高级定时器,总线在APB2上,在开启时钟时要注意。向下兼容的功能。
STM32F103C8T6:拥有高级定时器TIM1和三个通用寄存器:TIM2、TIM3、TIM4.
基本定时器
基本定时器可以完成定时中断和主模式触发DAC的功能:
首先下面一部分,有三个最重要的寄存器:预分频器、计数器和自动重装寄存器,它们构成了最基本的计数计时电路,所以这一块电路就叫做时基单元。
预分频器之前,连接的就是基准计数时钟的输入,最终来到上面控制器的位置,由于基本定时器只能选择内部时钟,所以你可以直接认为,这根线直接连到了输入端的这里,也就是内部时钟CK_INT,内部时钟的来源是RCC_TIMxCLK,这里的频率值一般都是系统的72Mhz,所以通向时基单元的计数基准频率就是72M;
再来看时基单元,预分频器它可以对这个72Mhz的计数时钟进行预分频,比如这个寄存器写0,那就是不分频,或者说是1分频,这时候输出频率就等于输入频率=72Mhz,如果预分频器写1,那就是2分频,输出频率=输入频率/2=36Mhz,如果写2,那就是3分频,输出=输入/3,以此类推。所以预分频器的值和实际的分频系数相差了1,及实际分频系数=预分频器的值+1;这个预分频器是16位的,所以最大值可以写65535,也就是65536分频。这就是预分频器,就是对输入的基准频率提前进行一个分频的操作;
然后是计数器,这个计数器可以对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就加1,这个计数器也是16位的,所以里面的值可以从0-一直加到65535,如果再加的话,计数器就会回到0重新开始,所以计数器的值在计时的工程中会不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时的任务,所以现在还需要一个储存目标值的寄存器,那就是自动重装寄存器了。
自动重装寄存器也是16位的,它存储的是目标值,在运行的过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值时,也就是计时时间到了,那它就会产生一个中断信号,并且清零计数器,计数器自动开始下一次的计数计时。
图中向上的折现箭头,就代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,我们一般把它叫做“”更新中断“”,这个更新中断之后会通向NVIC,我们配置好NVIC的定时器通道,那定时器的更新中断就能够得到CPU的响应了。
向下的箭头代表的是会产生一个事件,这里对应的事件就叫做"更新事件",更新事件不会触发中断,但可以触发内部其他电路的工作,以上这些就是基本定时器中断的全部流程了。
从基准时钟,到预分频器,再到计数器,计数器计数自增,同时不断地与自动重装寄存器进行比较,它俩值相等时,即计时时间到,这是会产生一个更新中断和更新事件,CPU响应更新中断,就完成了我们定时中断的任务了,到这里,定时中断和时基单元的工作流程就完成了。
主模式触发DAC的功能
STM32定时器的一大特色,就是这个主从触发模式,它能让内部的硬件在不受程序的控制下实现自动运行,在某些情境下,会极大地减轻CPU的负担。
主模式触发ADC有什么用:就是在我们使用DAC的时候,可能会用DAC输出一段波形,那就需要每隔一段时间来触发一次DAC,让它输出下一个电压点,如果用正常的思路来实现的话,就是先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC转换,然后DAC输出,这样也是没问题,但是这样会使主程序处于频繁被中断的状态,这会影响主程序的运行和其他中断的响应,所以定时器就设计一个主模式,使用这个主模式可以把定时器的更新事件,映射到这个触发输出TRGO(Trigger OUT)的位置,然后TRGO直接接到DAC的触发转换引脚上,这样定时器的更新就不需要再通过中断来触发DAC转换了,仅需要把更新事件通过主模式映射到TRGO,然后TRGO就会直接去触发DAC了,整个过程不需要软件参与,实现了硬件的自动化,这就是主模式的作用,当然除了这个主模式外,还有更多硬件自动化的设计。
通用定时器
基本定时器只支持向上计数模式,通用定时器和高级定时器还支持向下计数模式和中央对齐模式。
首先中间最核心的部分,还是时基单元,这部分结构和基本定时器是一样的,有预分频器、计数器、自动重装寄存器构成,每部分的工作流程和基本定时器也是一样的,预分频器对时钟进行预分频,计数器自增计数,当计数值达到自动重装值时,计数值清零同时产生更新中断和更新事件,不过对于通用定时器而言,计数器的计数模式就不止向上计数这一种了,向上计数:计数器从0开始,向上自增,计到重装值,清零同时申请中断,然后开始下一轮,依次循环,这就是向上计数;通用计数器和高级定时器还支持向下计数模式和中央对齐模式,向下计数模式:就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环,这就是向下计数;中央对齐模式:就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后进行下一轮,依次循环,这就是中央对齐模式;
时钟源选择:对于基本定时器而言。定时只能选择内部时钟。也就是系统频率72MHz, 到了通用定时器这里。时钟源不仅可以选择内部的72MHz时钟,还可以选择外部时钟,第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟,
参考引脚定义表,我们可以在这个TIM2的ETR引脚,也就是PA0上接一个外部方波时钟,然后配置一下内部的极性选择、边沿检测和预分频器电路,再配置一下输入滤波电路,这两块电路可以对外部时钟进行一定的整形,因为是外部引脚的时钟,所以难免会有毛刺,那这些电路就可以对输入波形进行滤波,同时也可以选择一下极性和预分频器,最后滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧接着就可以选择作为时基单元的时钟了,如果你想在ETR外边引脚提供时钟,或者相对ETR时钟进行计数,把这个定时器当作计数器来用的话,那就可以配置这一路的电路,在STM32中,这一路也叫做"外部时钟模式2"。
除了外部ETR引脚可以提供时钟外,下面这里还有一路可以提供时钟,就是TRGI,这一路从名字上来看的话,它主要是用作触发输入来使用的,这个触发输入可以触发定时器的从模式,后续再讲本次用的是这个触发输入作为外部时钟来使用的情况。暂且当作外部时钟的输入来看,当这个TRGI当作外部时钟来使用的时候,这一路就叫做"外部时钟模式1",那通过这一路的外部时钟都有哪些呢,往左看,第一个就是ETR引脚的信号,这里ETR引脚既可以通过上面这一路进来当作时钟,又可以通过下面这一路进来当作时钟,两种情况对于时钟输入而言是等价的,只不过下面这一路输入会占用触发输入通道而已;第二个,就是ITR信号,这一部分的时钟信号是来自其他定时器的,从右边可以看出,这个主模式的输出TRGO可以通向其他定时器,那通向其他定时器的时候,就接到了其他定时器的ITR引脚上来了,这个ITR0到ITR3分别来自其他4个定时器的TRGO输出。
通过这一路就可以实现定时器级联的功能,比如我们可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO,然后后面选择时钟为外部时钟模式1,这样TIM3的更新事件就可以驱动TIM2的时基单元,也就实现了定时器的级联;
还可以选择TI1F_ED,这里连接的是输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,这里后缀加一个ED(Edge)就是边沿的意思,也就是通过这一路输入的时钟,上升沿和下降沿均有效,最后,这个时钟还能通过TI1FP1和TI2FP2获得,其中TI1FP1是连接到了这里,就是CH1引脚的时钟,TI2FP2连接到了这里,就是CH2引脚的时钟,到这里外部时钟模式1就完了。
总结一下就是,外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚、很复杂的,一般情况下外部时钟通过ETR引脚就可以了,下面设置这么复杂的输入,不仅仅是为了扩大时钟输入的范围,更多的还是为了某些特殊应用场景而设计的。比如为了定时器级联设计的这一部分,下面这一部分,之后输入捕获和测频率时,还会继续讲到。还有定时器的编码接口。
定时器主模式输出:这部分电路可以把内部的一些事件映射到这个TRGO引脚上,用于触发DAC, 这里也是一样。它可以把定时器内部的一些事件映射到这里来, 用于触发其它定时器、DAC或者ADC, 可见这个触发输出的范围是比基本定时器更广—些的.
下面这一部分:
右边是输出比较电路,总共有四个通道,分别对应CH1到CH4引脚,可以用来输出PWM波形,驱动电机;
左边这一块是输入捕获电路,也是有四个通道,对应的也是CH1到CH4引脚,可以用来测量输入方波的频率等;
中间这个寄存器是捕获/比较寄存器,是输入捕获和输出比较电路共用的,因为输入捕获和输出比较不能同时使用,所以这里的寄存器是共用的,引脚也是共用的;本节主要讲的是定时中断和内外时钟源选择。
高级定时器
第一个是这里,申请中断的地方,增加了一个重复次数计数器,有了这个计数器,就可以实现每隔几个计数周期,才发生一次更新事件和更新中断,原来的结构是,每个计数周期完成后就都会发生更新,现在是有个计数器在这里,可以实现每隔几个计数周期更新一次,这就相当于对输出的更新信号又做了一次分频,那对于高级定时器的话,我们之前计算的最大定时时间59秒多,在这里就还需要再乘一个65536,这就又提升了很多的定时时间了;
下面有个印象就行了,这个DTG是死区生成电路,右边这里的输出引脚由原来的一个,变味了两个互补的输出,可以输出一对互补的PWM波,这些电路是为了驱动三相无刷电机的,第四路则没什么变化。
刹车输入功能,是为了给电机驱动提供安全保障的,如果外部引脚BKIN产生了刹车信号,或者内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生,这就是刹车输入的功能。
二、定时中断
定时中断基本结构图:
首先中间最重要的是预分频器(PSC)、计数器(CNT)、自动重装器(ARR)这三个寄存器构成的时基单元,下面是运行控制,就是控制寄存器的一些位,比如启动停止,向上或向下计数等等;
左边是为时基单元提供时钟的部分,可以选择RCC提供的内部时钟,也可以选择ETR引脚提供的外部时钟模式2,触发输入外部时钟模式1:对应的有ETR外部时钟、ITRx其他定时器、TIx输入捕获通道这些就是定时器的所有可选择的时钟源了;最后还有个编码器模式,这一般是编码器独用的模式,普通时钟用不到这个;
接下来右边,这里就是计时时间到,产生更新中断后的信号去向,在这里如果是高级定时器的话,还会多一个重复计数器;这里中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断,为什么会有一个中断输出控制呢,因为定时器模块有很多地方需要申请中断,比如前面的图,不仅更新要申请中断,这里触发信号也会申请中断,还有下面的输入捕获和输出比较匹配时也会申请中断,所有这些中断都要经过中断输出控制,如果需要这个中断,那就允许,如果不需要,那就禁止,简单来说,这个中断输出控制就是一个终端输出的允许位,如何需要那个中断,就记得允许一下。
这个图时,当预分频器的参数从1变到2时,计数器的时序图,第一行是CK_PSC,预分频器的输入时钟,就是图输入时钟,选择内部时钟的话,一般是72Mhz,下面的CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止;CK_CNT,计数器时钟,即使预分频器的时钟输出,也是计数器的时钟输入;开始时,计数器未使能,计数器时钟不运行,然后使能后,前半段,预分频器系数为1,计数器的时钟等于预分频器前的时钟,后半段,预分频器系数变为2了,计数器的时钟也变为预分频器时钟的一半了,在计数器时钟的驱动下,下面的计数器寄存器也随时钟的上升沿不断自增,在中间的这个位置FC之后,计数值变为0了,这里虽然没写,但是可以推断出ARR自动重装值就是FC,当计数值计到和重装值相等,并且下一个时钟来临时,计数值才清零,同时,下面这里产生一个更新事件,这就是一个计数周期的工作流程。
下面三行描述的是预分频器寄存器的一种缓冲机制,也就是这个预分频寄存器实际上是由两个,一个是这个,供我们读写用的,他并不直接决定分频系数,另外还有一个缓存寄存器或者说是影子寄存器,这个缓冲寄存器才是真正起作用的寄存器,比如我们在某个时刻,把预分频器由0改为了1,如果在此刻立即改变时钟的分频系数,那么就会导致这里,在一个计数周期内,前半部分和后半部分的频率不一样,这里计数器计到一半,计数频率突然就会改变了,这虽然一般并不会有什么问题,但是STM32的定时器比较严谨,设计了这个缓冲寄存器,这样,当我们在计数计到一半的时候改变了分频值,这个变化并不会立即生效,而是会等到本次计数周期结束时,产生了更新事件,预分频器的值才会被传递到缓存寄存器里面去,才会生效,所以在这里可以看到,即使我在计数途中改变了预分频值,计数频率仍然会保持为原来的频率,直到本轮计数完成,在下一轮计数时,改变后的分频值才会起作用,最后这里也是,预分频器内部实际上也是靠计数来分配的,当预分频值为0时,计数器就一直为0,直接输出原频率,当预分频值为1时,计数器就0、1、0、1、0、1、0、1这样计数,再回到0的时候输出一个脉冲,这样输出频率就是输入频率的2分频,预分频器的值和实际的分频系数之间有一个数的偏移;
就是计数器计数频率CK_CNT=CK_PSC/(PSC+1)
这个图是计数器时序图,内部时钟分频因子为2,就是分频系数为2;
第一行是内部时钟72Mhz,第二行是时钟使能,高电平启动,第三行时计数器时钟,因为分频系数为2,所以这个频率是上面这个除2,然后计数器在这个时钟每个上升沿自增,当增到0036的时候,发生溢出,那计到36之后,再来一个上升沿,计数器清零,计数器溢出,产生一个更新事件脉冲,另外还会置一个更新中断标志位UIF,这个标志位只要置1了,就会去申请中断,然后中断响应后,需要在中断程序中手动清零,这就是计数器的工作流程;
公式:CN_CNT_OV=CK_CNT/(ARR+1)
把上面这个值带进去计数器计数频率CK_CNT=CK_PSC/(PSC+1)
CN_CNT_OV=CK_PSC/(PSC+1)/(ARR+1)
这就是我们在计算定时时间的一个式子,用72Mhz/(PSC+1)/(ARR+1)就能得到溢出频率,溢出时间再取一个倒数就行了。
那刚才说了。预分频器为了防止计数中途更改数值造成错误, 设计了缓冲寄存器。这个计数器那肯定也少不了这样的设计了。我们可以看一下这个结构图。这里面像这样带一个黑色阴影的寄存器, 都是有影子寄存器这样的的缓冲机制的。包括预分频器。自动重装寄存器和下面的捕获比较寄存器,所以计数的这个ARR自动重装寄存器,也是有一个缓冲寄存器的,并且这个缓冲寄存器是用还是不用。是可以自己设置的。通过设置ARPE位,可以选择是否使用预装功能。
在这里我突然更改了自动加载寄存器,就是自动重装寄存器,由FF改成36,那计数值的目标值就由FF变成了36,所以这里计到36之后,就直接更新,开始下一轮计数。
在计数的中途,我突然把计数目标由F5改为了36,可以看到下面有个影子寄存器,这个影子寄存器才是真正起作用的,它还是F5,所以现在在计数的目标还是计到F5,产生更新事件,同时要更改的36才被传递到影子寄存器,在下一个计数周期这个更改的36才有效;
所以回以看出。引入这个影子寄存器的目的实际止是为了同步,就是让值的变化和更新事件同步发生。防止在运行途中更改造成错误。F5改到36立即生效,但此时计数值已经到了F1,已经超过36了,目标却是36,这样F1就只能一直加。一直加。一直加到用FFFF, 再回到0。再加到36。才能能断产生更新。这就会造成一些小问题。
RCC时钟树:这个时钟树,就是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统,时钟是所有外设运行的基础,所以时钟也是最先需要配置的东西,我们之前说过,程序主函数之前还会执行一个SystemInit函数,这个函数就是用来配置这个时钟树的。
左边的都是时钟的产生电路,右边都是时钟的分配电路,中间的这个SYSCLK就是系统时钟72MHz,在时钟产生电路,有四个震荡源,分别是内部的8Mhz高速RC振荡器,外部4-16Mhz高速石英晶体振荡器,也就是晶振,一般都是接8Mhz,外部的32.768KHz低速晶振,这个一般是给RTC提供时钟的,最后是内部的40KHz低速RC振荡器,这个可以给看门狗提供时钟,上面这两个高速晶振,是用来提供系统时钟的,我们的AHB、APB2、APB1的时钟都是来源于这两个高速晶振,这里内部和外部都有一个8Mhz的晶振,都是可以用的,只不过外部的石英振荡器比内部的RC振荡器更加稳定,所以一般用外部晶振,也可以用内部RC振荡器,就可以省下来外部晶振电路了。
在SystemInit函数中,ST是这样来配置时钟的,首先它会启动内部时钟,选择内部8Mhz为系统时钟,暂时以内部8Mhz的时钟运行,然后再启动外部时钟,配置外部时钟走这一路,进入PLL锁相环进行倍频,8Mhz倍频9倍,得到72Mhz,等到锁相环输出稳定后,选择锁相环输出为系统时钟。这样就把系统时钟由8Mhz切换为了72Mhz,(外部石英晶振)。外部晶振坏了,系统时钟无法切换到72Mhz,就会以内部8Mhz运行,大概就慢了10倍。
CSS是时钟安全系统:他也是负责切换时钟的,他可以监测外部时钟的运行状态,一旦外部时钟失效,他就会自动把外部时钟切换到内部时钟。保证系统时钟的运行,防止程序卡死造成事故。高级定时器也有CSS的身影,安全保障措施。
右边是时钟分配电路:
首先系统时钟72Mhz进入AHB总线,AHB有个预分频器,在SystemInit里配置的分配系数为1,那AHB的时钟就是72Mhz,然后进入到APB1总线,这里配置的分频系数为2,所以APB1总线的时钟为72Mhz/2=36Mhz,现在有个问题是,就是我们刚才说,通用定时器和基本定时器是接在APB1上的,而APB1的时钟是36Mhz,按理说它们的时钟也应该是36Mhz,但是在说定时器的时候,一直都说的是所有的定时器的时钟都是72Mhz,因为这下面还有一条支路,上面写的是如果APB1预分频系数等于1,则频率不变,否则频率×2,右边这一路是单独为定时器2~7开通的,那因为我们之前的预分频系数给的是2,所以通向定时器2 ~ 7的时钟,就又回到了72Mhz,所以这里就有个结论无论是高级定时器、还是通用定时器、还是基本定时器,它们的内部基准时钟都是72Mhz。定时器时钟都是72Mhz,前提是不改SystrrmInit的默认配置;
APB2的分频系数为1,所以APB2的时钟和AHB一样都是72Mhz,这里接在APB2上的高级定时器也单开了一路,上面写的是如果APB1预分频系数等于1,则频率不变,否则频率×2,这样就保证了定时器的时钟为72Mhz。
在这些时钟输出这里,都有一个与门进行输出控制,控制位写的是外部时钟使能,这就是我们在程序中写RCC_APB2/1PeriphClockCmd作用的地方,打开时钟就是在这个位置写1,让左边的时钟能够通过与门输出给外设。
手册
问题
1、好多呀计数器
2、RCC时钟树
总结
本节课主要是学习了定时器,定时器的类型:高级定时器,通用定时器,基本定时器;然后讲解了定时器的流程,定时器中断,外部模式2和1,外部时钟选择,最会对时基单元,输入时钟,和输出时钟,进而到外部中断,整个流程;还明白了寄存器的功能,最后还了解了RCC时钟树的功能。