STM32F103系列单片机定时器介绍(二)
引言
上篇文章中我们介绍了系统滴答定时器的作用和常见用法,系统滴答定时器作为所有定时器中最简单的一类功能也比较单一,在我们使用的ZET6芯片中总共有着8个定时器:2个基本定时器(TIM6,7)、4个通用定时器(TIM2-5)、2个高级定时器(TIM1,8),接下来我向大家介绍基本定时器相较于之前提到的系统滴答定时器更加灵活,同时功能更加丰富,结构也有所不同
基本定时器
基本定时器相较于其它定时器(系统滴答定时器除外)是结构最简单、功能最存粹的成员,基本定时器之所以被称为"基本",是因为它剥离了所有与外部物理世界交互的功能,只保留了最核心的时基单元,主要特征如下:
- 16位向上计数:基本定时器中的计数器(
CNT)只有16位(最大值为65535,相较于滴答时器容量较小),并且只能向上递增计数(从0增加到设定的最大值,然后回到0继续计数) - 没有外部引脚:基本定时器不能与外部设备进行交互,它的数据流完全封闭在单片机内部
- 时钟源单一:它只能够使用系统提供的内部时钟作为时钟源
功能框图
从STM32F10xx参考手册中我们可以找到基本定时器对应的定时器框图

接下来我们会将当前图片分成三个部分进行分析
-
时钟源输入(最上方)
- 来自
RCC的TIMxCLK:所有的定时器都需要一个时钟源,由于基本定时器没有外部引脚,所以它唯一的时钟源就是芯片内部由时钟控制模块(RCC)提供的内部时钟 - 在
STM32F103中,基本定时器挂载在APB1总线上,默认的时钟频率通常为72Mhz
- 来自
-
核心时基单元(中间底部区域)
这是基本定时器的核心组成部分,他们在图中依次相连:
PSC预分频器(Prescaler):它接收来自控制器的时钟信号(CK_PSC),由于72Mhz的主频较快因此预分频器需要对时钟进行降频操作,降低后的时钟叫做CK_CNT- +/-
CNT计数器(Counter):它接收分频之后的时钟脉冲,每接收到一个脉冲,计数值就会加一(在基本定时器中只能做16位递增计数) - 自动重装载寄存器(
ARR):它设定了计数器的计数上限,当计数器中的值一直递增,直到与自动重装载寄存器里的值相等时,就会产生溢出
-
事件与触发输出(右侧):当
CNT计数器计满溢出时,定时器通知外界的路径有如下两条:- 触发控制器(
TRGO):这是基本定时器的一项重要功能,图中明确标出了TRGO信号直接连接着DAC,其作用为触发数模转换 - 中断和
DMA输出:图中的U代表更新事件,UI代表更新事件,这就是我们常说的定时器中断,定时器溢出后会发送中断信号
- 触发控制器(
绝大多数情况下我们使用基本定时器往往是用作定时功能
影子寄存器
我们分析当前功能框图得过程中我们不难发现:当前的功能框图中的自动重装载寄存器和预分频器的框图下面有一片黑色的影子。这并不是因为系统框图画错了而是在单片机底层有着影子寄存器的存在,上述两个寄存器实际上包含了两个寄存器:预加载寄存器和影子寄存器,他们二者的区别如下:
- 预加载寄存器:它是暴露给程序员的软件接口,当我们尝试通过代码来修改自动重装载器或者预分频器中的值得时候这个值会写入到预装载寄存器中,我们可以随时去读取和写入寄存器中的值
- 影子寄存器:它是隐藏在芯片内部的实际控制单元,直接与计数器和时钟电路相连,程序员无法直接寻址和读写其中的值,它里面存储的是机器当前正在执行的"实时运行参数",它里面的值直接影响了整个系统的运作
当我们想要将数据写到自动重装载寄存器或者预分频器中时数据会先被写入预加载寄存器中,然后再更新到影子寄存器。每当我们想要查看计数器是否溢出时,我们查看的往往都是影子寄存器的值,我们也可以控制是否开启预加载模式,如果我们选择关闭预加载模式那么每次一旦数据写入到预加载寄存器后就会立刻更新到影子寄存器中,如果我们选择开启预加载模式那么当我们向预加载器中写入值的时候在计数器触发了更新事件后才会将预加载器中的值写入到影子寄存器中。至于单片机中为什么要设计影子寄存器主要原因如下:
-
自动重装载寄存器:
为了理解影子寄存器的重要性,我们可以通过一个动态修改定时周期的场景来进行对比:
假如没有影子寄存器: 假设我们给基本定时器设定的自动重装载值为
ARR = 1000。当计数器(CNT)刚好向上数到800时,程序为了缩短周期,突然通过代码将ARR的值修改为了500。 此时会发生一个致命的逻辑错误:由于当前计数值(800)已经大于新的重装载天花板(500),硬件错过了匹配点。因此,计数器不会立刻清零,而是会继续向上自增,一直加到 16 位计数器的存储上限(65535)发生溢出后,再从0重新开始自增,直到再次数到500时才会触发中断。 原本是为了缩短周期,结果却导致当前这一个周期变得极其漫长,这在电机控制或精准延时中是灾难性的引入影子寄存器后(实现平滑过渡):有了影子寄存器机制,寄存器被分为了两层:"预装载寄存器"(可见的输入框)和"影子寄存器"(底层的实际控制者)。 当我们在代码中尝试将
ARR的值修改为500时,这个新值实际上只是被暂存到了预装载寄存器 中。此时,底层起实际作用的影子寄存器 依然保持着旧值1000。 因此,计时器会不受任何干扰地继续从800计到1000。当计满1000时,定时器正常触发更新事件 。就在产生更新事件的这一瞬间,硬件底层会自动将计数器清零,并立刻将预装载寄存器里的新值(500)拷贝给影子寄存器生效 。 这样一来,既保证了当前周期的平稳结束,又让下一个周期能够完美按照新的500周期运行 -
预分频器:
预分频器中的预加载模式是硬件强制开启的,我们无法通过代码去控制是否开启预加载模式,所以在这里只做简单介绍,由于预分频器的作用是确定计数器自增一次需要多少时间,所以预加载寄存器的作用为防止产生不规则的毛刺时钟边沿
综合上述分析我们不难看出影子寄存器的作用为保护当前程序,程序员直接操作的都是影子寄存器的代理(预加载寄存器),我们只能间接的修改对应寄存器中的值,这样能够防止程序的突然崩溃,这就像我们去银行取钱时只能通过ATM机或者前台工作人员间接的进行存钱和取钱的操作,这样才能维护整个银行的稳定性
计算定时时间
前面说过基本定时器的作用通常为简单定时,这里简单介绍一下如何计算和设定定时器的定时时间,基本定时器的频率和时间主要由三个核心参数决定:输入源时钟频率 、预分频器值 、自动重装载值,具体计算过程如下:
-
计数器的时钟频率 (fCK_CNTf_{CK\_CNT}fCK_CNT)计算
"时钟频率"是指:计数器(
CNT)每秒钟到底能数多少个数-
输入源时钟(fCK_PSCf_{CK\_PSC}fCK_PSC)
在
STM32F103芯片中,基本定时器挂载在APB1总线上,虽然APB1的最大总线频率是36Mhz,但是单片机内部对基本定时器的时钟源进行了二倍频操作,因此基本定时器的默认输入源时钟频率通常为72Mhz(即一秒钟震荡72,000,000次) -
计算公式
预分频器(
PSC)的作用就是对这个极高的频率进行降频,具体的计算公式如下:fCK_CNT=fCK_PSCPSC+1f_{CK\CNT} = \frac{f{CK\_PSC}}{PSC + 1}fCK_CNT=PSC+1fCK_PSC
-
举个例子:
假设系统输入时钟为
72Mhz,我们在代码里设置预分频器PSC= 71,那么,计数器的实际跳动频率为:fCK_CNT=72,000,00071+1=1,000,000 Hzf_{CK\_CNT} = \frac{72,000,000}{71 + 1} = 1,000,000 \text{ Hz}fCK_CNT=71+172,000,000=1,000,000 Hz
这意味着计数器现在每秒钟会向上数1,000,000下(或者说,每数一下需要1微秒)
-
-
定时器的计数周期计算
"计数周期"是指:计数器从0开始向上数,一直到我们设定的自动重装载值(ARR),然后触发一次更新事件中断所需要消耗总时间
-
原理:
由于计数器是从0开始数到
ARR,所以它实际上总共走过了(ARR - 1)个节拍,我们将总结拍数乘以每一个节拍消耗的时间,就能得出总的溢出时间 -
计算公式:
Tout=ARR+1fCK_CNTT_{out} = \frac{ARR + 1}{f_{CK\_CNT}}Tout=fCK_CNTARR+1
如果把上面计算fCK_CNTf_{CK\_CNT}fCK_CNT的公式带进来,我们就得到了完整的定时器时间计算公式
Tout=(PSC+1)×(ARR+1)fCK_PSCT_{out} = \frac{(PSC + 1) \times (ARR + 1)}{f_{CK\_PSC}}Tout=fCK_PSC(PSC+1)×(ARR+1)
上一步中,我们将计数器的跳动频率降低到了
1Mhz(每微秒跳一下),如果我们想让定时器每1ms触发一次中断,因为1ms=1000us,也就是我们需要让它跳1000下,那么我们需要设置ARR = 999,随后即可带入相应公式进行验证:Tout=(71+1)×(999+1)72,000,000=72,00072,000,000=0.001 sT_{out} = \frac{(71 + 1) \times (999 + 1)}{72,000,000} = \frac{72,000}{72,000,000} = 0.001 \text{ s}Tout=72,000,000(71+1)×(999+1)=72,000,00072,000=0.001 s
此时的计算结果刚好等于
1ms
-
总结
至此有关基本定时器的理论知识就告一段落了,接下来我会介绍一下如何通过基本定时器来控制超声波传感器实现非阻塞式的测距功能