ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32定时器时基完全解析:从时钟树到PWM与测频应用

STM32定时器时基完全解析:从时钟树到PWM与测频应用 刚接触 STM32 定时器的时候我栽过一个很经典的跟头用 HAL 库初始化完 TIM2想着让它 1 秒进一次中断结果示波器一量LED 翻转频率硬是快了 16 倍多。查了半天问题居然不在定时器配置而在定时器吃的时钟源上。从那以后我就明白——玩 STM32 定时器第一件事不是去翻寄存器手册而是先把“定时器到底在数什么”搞清楚。定时器就是一个计数器它所有功能不管是延时、PWM、输入捕获还是编码器模式本质上都在做同一件事数时钟脉冲。只要把这件事想明白时基的来源、预分频、重载值、溢出频率全部都是顺理成章的数学题。这篇文章用最直白的话把 STM32 定时器的时间基准从哪来、内部怎么数数、算周期时要避开哪些坑全部捋一遍。适合刚入门准备做 PWM、超声波测距、频率测量的朋友也适合被时钟树绕晕但想彻底搞懂的老手。1. 定时器到底在“数”什么时基的本质1.1 从“滴答”到“时间”计数的本质定时器这个名字很容易让人产生误解以为芯片里有个类似秒表的东西能直接“产生时间”。实际上STM32 的定时器核心单元就是一组 16 位或 32 位的计数器寄存器它的全部工作就是把外部或内部送来的脉冲一个个数下去。所以关键问题就是这些脉冲是谁给的每隔多少时间给一个举个例子你把一个计数器接到某个引脚上引脚每次从低电平变成高电平计数器就加 1。如果这个引脚的翻转频率是 100 Hz那计数器数到 100就代表过去了 1 秒。这里“引脚翻转”就是计数脉冲脉冲之间的间隔就是时间基准。芯片内部的定时器也一样只是脉冲来源通常换成了内部时钟信号。内部时钟源源不断产生固定频率的脉冲比如 72 MHz 意味着每秒钟来 72000000 个脉冲。定时器每收到一个脉冲CNT 就加 1。CNT 的每个数字变化都对应一个精确的时钟周期时间就在这里被“翻译”出来了。理解了这层关系你就能明白为什么定时器精度取决于时钟源精度。时钟源抖一下所有延时、PWM 占空比都会跟着抖。这也是为什么高速通信、精密测频场合要专门校时钟。1.2 边沿、时钟源和计数方向时基的三个可控维度定时器的时基并不是简单“一个时钟进来数一下”它有几个可以调节的维度。第一个维度是计数边沿。STM32 定时器的内部时钟默认情况下每个上升沿计数一次。但某些模式或者外部时钟输入模式下可以选择在上升沿、下降沿或者双边沿计数。这意味着同样的时钟频率在双边沿模式下计数频率等于时钟频率的两倍。测外部信号的周期时这个细节特别容易忽略。第二个维度是时钟源本身。除了默认的内部时钟定时器还能选择外部时钟模式 1外部引脚输入、外部时钟模式 2ETR 引脚、内部触发时钟ITRx比如从其他定时器的更新事件引出。这些模式看起来高级但它们只是换了“脉冲来源”计数原理没有任何变化。说白了定时器根本不挑食给脉冲就数。第三个维度是计数方向。向上计数模式CNT 从 0 数到 ARR然后归零触发更新事件。向下计数模式CNT 从 ARR 一路减到 0。中心对齐模式则是先向上后向下一个完整周期有两次溢出。方向不同更新事件的产生时机就不同直接影响你算出来的周期。实操里最容易懵的其实是——很多人以为定时器“只能向上数”。其实在 PWM 中心对齐模式或者编码器接口模式下它会双向走。你在计算频率时要先确认模式否则算出的溢出时间可能是实际的两倍。1.3 数完了之后呢更新事件是时基的“节拍器”计数器往上数、往下数都不重要重要的是“数到头”之后发生了什么。这个“到头”的动作也就是更新事件是整个时基体系里最重要的同步信号。向上计数时CNT 等于 ARR 的瞬间硬件会自动生成更新事件。这个事件可以触发中断、触发 DMA也可以作为内部触发信号送给其他定时器。更厉害的是它还能被映射到某个输出引脚上形成精确的周期性波形。简单理解更新事件就是定时器对外喊的一句“到点了”。你想让定时器产生一个 1 kHz 的信号实际上就是让更新事件按 1 kHz 频率发生 1000 次。后面的 PWM 输出、时基级联、ADC 定时采样全部挂在这个节拍器上。所以研究时基重点不是研究 CNT 本身而是研究以下三件事计数脉冲频率是多少、数到多少算到头、到头后产生什么事件。2. 时基单元三兄弟PSC、ARR、CNT2.1 预分频器 PSC把时钟“拆小”如果系统时钟是 72 MHz定时器直接拿它来数那 CNT 每秒钟要数 7200 万次。这个速度对大多数应用来说太快了——比如你想让 LED 每秒翻转一次计数器得跑到 7200 万才到 1 秒16 位的计数器根本装不下中间还得补一堆软件判断。预分频器 PSC 解决的就是这个问题。它的作用是把输入时钟频率除以PSC 1让计数器吃到的脉冲慢一点。注意是 PSC 1。这是新手最容易记错的地方。寄存器写 0 时不分频写 1 时 2 分频写 71 就是 72 分频。举例说明72 MHz 的定时器时钟PSC 设为 71定时器时钟就变成 1 MHz。此时 CNT 数一下正好是 1 微秒。这就是“1 微秒 tick”的经典配置做延时和软件计时都非常好算。预分频器本身也是 16 位的也就是说 F1 系列上 PSC 最大可以写成 65535。极限情况下72 MHz 可以被分到大约 1.099 kHz也就是每个 tick 约 0.91 毫秒。想要更慢那就只能把 ARR 也拉大或者换 32 位定时器。2.2 自动重载寄存器 ARR 和计数器 CNT周期的上边界分频做好之后计数器吃的脉冲频率固定了比如 1 MHz。接下来就是 CNT 从 0 开始逐个 tick 增加一直增到 ARR 寄存器的值然后在下一个 tick 回到 0。ARR 就是计数的终点同时也是周期的上边界。它决定了单个计数周期的长度。比如计数器时钟是 1 MHz每个 tick 是 1 微秒那么 ARR 设为 999数完一个周期需要 1000 个 tick周期正好是 1000 微秒也就是 1 毫秒。ARR 的位数决定了周期上限。标准定时器是 16 位ARR 最大 65535所以 1 MHz tick 下最长周期约 65.5 毫秒。有些高级定时器在特定模式下可以 32 位操作比如 TIM2、TIM5 在 STM32F103 上可以通过级联或者直接配置实现 32 位计数最大计数范围是 4294967295以 1 MHz tick 来算能连续数 4294 秒左右。实际计算周期时大多数人喜欢直接套公式周期 (PSC 1) × (ARR 1) / 定时器时钟频率。这个公式其实反映的就是拆成两段的乘积——先分频决定每 tick 的时间再数固定的 tick 数。把这两个数相乘总时间就出来了。2.3 把寄存器翻译成时间一张表几个例子光看公式容易晕我直接把常见配置列一个表以 STM32F103 的 72 MHz 定时器时钟为例。目标周期PSC 设置ARR 设置计数器时钟计算公式1 微秒 tick1 毫秒周期719991 MHz1000 × 1 微秒1 毫秒 tick1 秒周期719999910 kHz1000 × 0.1 毫秒10 微秒 tick50 毫秒周期7194999100 kHz5000 × 10 微秒1 微秒 tick最大约 65.5 毫秒71655351 MHz65536 × 1 微秒注意 ARR 和 PSC 都要加 1。比如要得到 1 MHz 的计数器时钟72 MHz ÷ 72 1 MHz所以分频系数是 72PSC 71。想得到 10 kHz 的计数器时钟72 MHz ÷ 7200 10 kHz所以 PSC 7199。还有一点要提前说PSC 和 ARR 的搭配不是唯一的。同样是 1 秒周期你可以用 PSC 71、ARR 71999999但 ARR 是 16 位的根本放不下。所以设计时务必要让两者都在 0~65535 范围内。这种“拆搭配”的约束就是时基设计的第一道门槛。3. 时间基准从哪来STM32 时钟树与定时器时钟3.1 APB1/APB2 预分频与定时器“偷偷加倍”定时器内部怎么数弄清楚了接下来要问定时器拿到的输入时钟到底是多少这一块如果不看图光看代码特别容易误解。STM32 内部有一个 RCC 时钟树各种外设挂在不同的总线上。一般来说高级定时器 TIM1、TIM8 挂在 APB2 总线上通用和基本定时器挂在 APB1 总线上。但关键点来了——定时器时钟并不总是等于总线时钟这里有个倍频器规则。以 STM32F103 为例系统时钟 SYSCLK 通常是 72 MHz。APB1 分频器如果设置为 /2则 APB1 总线时钟是 36 MHz。但是当 APB1 预分频系数不等于 1 时定时器时钟会自动翻倍变成 72 MHz。同样地如果 APB1 预分频是 /4定时器时钟又会在 18 MHz 基础上翻倍还是 36 MHz。也就是说定时器时钟不是“APB1 时钟”而是“APB1 时钟乘以 2在预分频不为 1 时”。这正是文章开头那个 16 倍问题的根源——我把定时器时钟当成 36 MHz 算实际它吃的却是 72 MHz。对应的判断逻辑很简单只要 APBx 预分频大于 1定时器时钟 APBx 时钟 × 2。如果 APBx 预分频等于 1定时器时钟 APBx 时钟。对于系统时钟 72 MHz、APB1 预分频 /2 的典型配置APB1 上的定时器时钟就是 72 MHzAPB2 没有预分频所以 TIM1 和 TIM8 直接吃 72 MHz。这个细节使用 Cubemx 时会被自动处理但你一旦手写寄存器或者手动修改时钟树配置就很容易踩到。强烈建议你在改完时钟树后用 CubeMX 的时钟树页面看一眼各个定时器时钟频率别用猜的。3.2 不同系列的时钟树差异F1 和 F4 不能一概而论从 F1 过渡到 F4 的人第二个常见误区是把 F1 的时钟分配逻辑直接套到 F4 上。F4 的 APB 分频规则虽然类似APB 预分频不为 1 时定时器时钟翻倍但它的系统时钟频率和 APB 分频搭配常见不同而且 F4 的定时器时钟上限也不一样。F4 的 SYSCLK 最高 168 MHzF405/F407或 180 MHzF427/F429 等APB1 预分频通常是 /4APB1 总线时钟 42 MHz乘以 2 后定时器时钟为 84 MHz。APB2 预分频通常 /2APB2 总线时钟 84 MHz定时器时钟也是 168 MHzF407 上。换句话说F4 的 APB1 定时器和 APB2 定时器虽然吃到的频率都是 84 MHz 和 168 MHz但总线本身跑得慢。很多人看到总线时钟 42 MHz下意识以为定时器也是 42 MHz结果所有时间和频率算出来全偏一倍。再补充一个冷门坑F4 上有部分定时器时钟来源可以选 HSE 或者 PLL而不只是 APB 倍频。虽然默认配置下用不到但你在做高精度时钟或者低功耗模式切换时会遇到这类选项。看到具体型号参考手册里“TIMx clock”一节再下手。3.3 定时器时钟的替代来源外部、触发与私有时钟除了总线提供的时钟定时器也能接受外部脉冲作为时基。用途很直接——测外部信号频率时用一个定时器对外部信号计数比内部时钟测频更直接。外部时钟模式 1使用某个定时器通道的输入引脚比如 TIM2_CH1这时引脚上的每个有效边沿让 CNT 加 1。外部时钟模式 2使用 ETR 引脚原理类似。这两种模式在测频率、测转速时非常有用你不用额外接内部时钟直接数外部脉冲配合一个已知时间的内部定时器就能算出频率。更常见的还有内部触发时钟 ITRx也就是把定时器 A 的更新事件作为定时器 B 的时基。这种方法可以做出长时间测量让一个定时器在后台不断产生更新事件另一个定时器把它作为时钟就能实现超长时间计数而不需要改 32 位寄存器。这里要提醒一点外部时钟的输入频率不能太快必须小于当前定时器时钟的一半这是数字电路采样的基本限制。实际应用里我会再留出余量把外部信号频率控制在定时器时钟的四分之一以内波形会稳定很多。4. 定时器家族与时基能力从基本到高级4.1 基本定时器 TIM6/TIM7最单纯的时基F1 上的基本定时器只有 TIM6 和 TIM7。它们没有输入捕获、没有 PWM 输出、没有外部引脚从外观上看就是个“能产生中断的计数器”。但千万别小看它们。正因为没有多余功能基本定时器的时基链路非常干净非常适合做系统 tick、软件定时、喂狗计时、触发 ADC 采样等任务。ADC 定时触发这个应用特别典型——你可以让 TIM6 精确地每隔一段时间发出更新事件触发 ADC 开始转换时间抖动比软件查询小得多。基本定时器只有向上计数模式PSC 和 ARR 都是 16 位这也决定了它的周期范围上限。在 72 MHz 定时器时钟下最长约 65.5 毫秒。想要更长时间得在前面加一个变量更新中断里数几次或者在代码里扩展计数位宽。4.2 通用定时器 TIM2/3/4/5时基的多面手TIM2、TIM3、TIM4、TIM5 是 F1 的通用定时器也是日常用得最多的一组。它们除了时基单元还有四个独立的输入捕获/输出比较通道支持 PWM 输出、输入捕获、编码器接口、霍尔传感器接口等。从时基角度看通用定时器最突出的优势是灵活性。TIM2 和 TIM5 内部计数器物理上是 32 位的虽然也可以按 16 位模式用但 32 位意味着 ARR 最大可以去到 4294967295。以 1 MHz tick 计算单次可以数到约 71.6 分钟非常适合做长期计时比如记录设备累计运行时间。选择时基配置时还要注意通道输出的极性。PWM 模式下输出极性会影响占空比的反相但不会影响周期周期完全由 PSC、ARR 和时钟频率决定。很多人调试 PWM 时发现占空比对了频率不对基本都是在时钟频率这里错位。4.3 高级定时器 TIM1/TIM8带刹车功能的“重武器”TIM1 和 TIM8 在 F1 上属于高级定时器它们的时基单元和通用定时器几乎一样但多了几个和电机控制相关的机制互补 PWM 输出、死区插入、刹车输入、重复计数。做电机驱动、逆变器这类场合基本离不开它们。重复计数器的存在让高级定时器的更新事件产生频率和 PWM 输出频率解耦了。简单说你可以让计数器溢出很多次但只在指定的第几次才产生更新事件和中断。这个特性在电机控制里很实用——比如需要每出现 10 个 PWM 周期才执行一次电流环计算用重复计数器就可以硬件自动完成不用软件判断。另一个需要理解的是刹车功能。当刹车输入有效时PWM 输出会被硬件强制拉到安全电平这个动作和软件无关即时基崩溃也能兜底。做硬件设计时必须把刹车输入接好不能只靠软件防错。4.4 滴答定时器 SysTick轻量级时基严格来说 SysTick 不算 STM32 外设定时器而是 Cortex-M3/M4 内核自带的一个 24 位递减计数器。它通常被 RTOS 或者 HAL 库拿来当系统 tick 使用比如 HAL 里常见的HAL_GetTick()就是基于 SysTick 实现的。SysTick 的时钟来源有内核时钟比如 72 MHz和 HCLK/8 两种可选。在标准库中你可以用SysTick_Config()简单地配置中断周期。常用做法是把 SysTick 配置为 1 毫秒中断一次然后在一个全局变量里递增毫秒计数。它的缺点是只有 24 位最长可计数范围有限以 72 MHz 内核时钟为例SysTick 的 ARR 最大 16777215对应大概 0.233 秒。所以它一般只用来做 tick不适合长时间定时。如果你在心里纠结“为什么 SysTick 不像 TIM2 一样能数一分钟”那说明你还没理解它的定位——它是个轻量级节拍器不是一个多用途外设。5. 从时基到应用周期、PWM、测频的实际计算5.1 动手算产生一个 1 kHz 的 PWM理论讲了不少直接上手做一遍才是硬道理。目标用 STM32F103 的 TIM3在 PA6 上输出 1 kHz、占空比 50% 的 PWM。先确定定时器时钟。假设系统时钟已经由外部 8 MHz 晶振倍频到 72 MHzAPB1 预分频 /2则 TIM3 时钟等于 72 MHz。为了让 tick 容易换算我习惯先把定时器时钟分到 1 MHz分频系数 72 MHz / 1 MHz 72PSC 72 - 1 71接下来确定 ARR。PWM 频率公式 PWM 频率 定时器时钟 / ((PSC 1) × (ARR 1))把 1 kHz 代入 1000 72000000 / (72 × (ARR 1)) ARR 1 1000 ARR 999所以时基配置就是 PSC 71ARR 999。此时 CNT 从 0 数到 999花 1000 微秒即 1 毫秒一个周期频率 1 kHz。占空比 50%则比较值 CCR 设为 ARR 的一半即 500。占空比就是 CCR / (ARR 1) 500 / 1000 50%。CubeMX 里配置完成后可以在参数面板直接看到最终的定时器频率计算值。手动配置时强烈建议先导出寄存器值再对照参考手册验证一遍。5.2 用输入捕获测量外部频率时基如何参与测频测外部信号频率核心思路是“数边沿查时间”。常见方案是使用定时器的输入捕获模式外部信号接到定时器通道引脚上第一路捕获当前 CNT 值第二路再捕获两次捕获值相减就得出了一个周期内计数器走过的 tick 数。时间基准在这里同样起决定作用。假如定时器时钟是 72 MHz预分频后计数器时钟为 1 MHz两次捕获差值如果是 500则信号周期是 500 微秒频率是 1 / 500 微秒 2 kHz。需要注意的坑是测量脉冲宽度时必须保证计数器时钟频率远高于被测信号频率。一般经验是被测频率至少小于计数器时钟的 1/10否则因为只能整数计数测出来会明显跳变。同时记得清除捕获标志后立即读取 CCR避免读到上一次旧值。再补充一个双通道测量技巧用通道 1 捕获上升沿、通道 2 捕获下降沿就可以直接读出高电平时间。这在测占空比或者 PWM 信号分析时非常方便两者都基于同一个时基不存在时间基准不一致的问题。5.3 超声波测距中的时基选择微秒级计时STM32 超声波测距是 HC-SR04 最常用的玩法它的原理就是测量声波从发出到返回的时间差再乘以声速除以 2 得到距离。HC-SR04 的 Trig 脚给一个 10 微秒以上的高电平触发然后 Echo 脚会输出一个与飞行时间同宽的高电平。你的任务就是准确测出 Echo 高电平的持续时间。用定时器输入捕获实现时我一般把定时器时钟配置为 1 MHz即每个 tick 等于 1 微秒声速大概取 340 m/s。如果是向上计数模式ARR 要设置得足够大比如 9999保证最长测量距离约为 9999 微秒 × 0.17 毫米每微秒实际上这里有点绕严谨做法是时间 × 声速 / 2 距离再换算。比如测到 Echo 高电平持续 2000 个 tick即 2000 微秒 距离 340 m/s × 0.002 秒 / 2 0.34 米。这里时基选择的关键是精度。1 MHz 时每个 tick 是 1 微秒对应距离变化约 0.17 毫米对于常规避障完全够用。但如果你需要毫米级精密测量可以把定时器时钟提到 10 MHz即每个 tick 0.1 微秒分辨率就是 0.017 毫米。代价是 ARR 要支持更大的上限或者改用长计时方式。6. 定时器时基调试的常见坑与排查技巧6.1 预分频重装、影子寄存器与临时修改很多人启动定时器后想在运行中修改 PSC 来改变定时器频率结果发现改了没反应或者导致计数错乱。原因一般是两个一是没有把更新事件或者强制更新置位让影子寄存器接收新值二是修改 ARR 时没有考虑 CNT 当前的运行位置导致溢出时刻不确定。以 TIM 的预分频器为例PSC 寄存器前面其实还有一级“预装载影子寄存器”。你写入 PSC 时实际生效要等更新事件发生或者你在代码里手动生成一个更新事件TIM_GenerateEvent(TIMx, TIM_EVENTSOURCE_UPDATE)才会立即生效。修改 ARR 同理。在正常运行中把 ARR 从 999 改成 1999CNT 不会立即清零它可能停在当前值继续往上数。如果你刚好在 CNT 超过 999 之后改了 ARR那这一轮周期就会异常。解决方式是在修改前先暂停定时器或者把自动重载预装载使能打开让新的 ARR 在下一个更新事件再生效。6.2 更新事件到底什么时候来上升沿和溢出时刻更新事件产生的具体时刻不同的计数模式差异很大。向上计数模式下更新事件在 CNT 从 ARR 回到 0 的瞬间产生向下计数模式则是 CNT 从 0 回到 ARR 的瞬间中心对齐模式下向上计数和向下计数各产生一次更新事件取决于 CMS 位配置。这个细节直接影响中断频率。中心对齐模式下如果 CMS 配置为“中央对齐模式 1”更新事件只在向下计数结束时产生模式 2 只在向上计数结束时模式 3 则两边都产生。所以同样是 ARR 999、1 MHz 计数时钟中心对齐模式 3 的中断频率可能是 2 kHz而不是你预想的 1 kHz。在调试 PWM 和测速项目时建议用示波器同时看输出引脚和更新事件中断标志别看代码里目光所及的“感觉”因为这里太容易差一倍了。6.3 ARR 溢出、中断挤占与响应延迟时基配置正确代码看起来也没问题但实际运行中定时器中断的频率还是不对。这时候要往两个方向排查中断优先级和中断处理耗时。如果定时器更新中断的优先级不够高被其他中断阻塞的时间过长更新事件就会积压。虽然中断标志位还在但响应已经延迟了用示波器看输出的周期就会出现抖动。解决办法是给定时器中断设置较高优先级比如 NVIC 优先级分组后给抢占优先级 1同时把中断服务函数里的处理步骤尽量缩短确确实实做到“中断里只标记主循环里做事”。另外一个隐蔽问题是重复中断。如果你在更新中断里不小心把中断标志清早了但代码又刚好再次进入中断可能会出现一个周期触发两次的现象。正确做法是进入中断后第一时间读取并清标志如果使用 HAL 库直接调用__HAL_TIM_CLEAR_IT清对应标志位不要在中断末尾再清一次。6.4 时基误差HSI、HSE 与温度偏移很多人把定时器时钟源想得过于理想化以为 72 MHz 就真的是 72.000000 MHz。实际上 STM32 内部 RC 振荡器 HSI 的精度一般在 ±1% 左右少数情况下误差更大外部晶振 HSE 精度通常在 ±20 ppm 到 ±50 ppm 之间好一点的温补晶振可以到 ±5 ppm。如果你只是点个灯、转个舵机±1% 误差无所谓。但做串口波特率、高精度测频、PWM 控制电动工具时时基误差可以直接摧毁你的设计。比如超声波测距里声速本来就随温度变化如果时基再来 1% 偏差距离误差会叠加。所以临时调试建议直接用 HSI 也没事但正式产品一定要用外部晶振或者经过校准的时钟源。如果硬件上实在没法用 HSE至少做一次软件校准用一个已知频率的信号去测定时器的实际 tick反推修正参数。不要不校准就把 1% 误差带到所有时间计算里去。结尾一点调试习惯和扩展思路最后再说一个自己常用的调试习惯任何定时器配置完成后先不要着急写业务代码先用一个 GPIO 翻转引脚接示波器验证时基周期是否和你算出来的完全一致。这个动作 30 秒就能完成却能把“算错时钟频率”这类问题在代码堆起来之前就暴露掉。如果手头没有示波器也可以用逻辑分析仪或者干脆用另一个定时器去捕获这个 GPIO 的电平变化把周期读出来。借助定时器本身的输入捕获来验证定时器时基属于“用定时器验证定时器”的做法实测很方便还不依赖额外仪器。后续想继续深入的话建议按这个顺序上手先自己做一次 TIM2 的 1 秒延时中断然后在 PWM 输出模式下调频率和占空比再做输入捕获测外部方波频率最后尝试用两个定时器级联做长时间计时。每一步都能把“时基”这个概念用得更透。定时器的寄存器不多难的不是背公式而是搞清楚时钟从哪来、数到哪去、事件什么时候发生这三件事弄明白基本上所有定时器应用都能一眼看穿。
返回列表