多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32定时器到底在数什么?从时钟树到PWM与捕获全解析

STM32定时器到底在数什么?从时钟树到PWM与捕获全解析 定时器到底在数什么这个问题我被人问过很多次面试官问刚入门的朋友也问。一句话回答定时器在数时钟脉冲时间只是“数数”的副产物。你先想通这句话后面所有跟定时器相关的事——PWM输出、输入捕获、频率测量、精确延时——就都顺了。这篇文章围绕STM32的时间基准展开我想把芯片内部那条从晶振到定时器的“时间运输线”完整捋一遍顺便聊聊几个我实际踩过的坑。适配正在学STM32的初学者也适合那些用了很久定时器但一换系列就发懵的工程师。1. 定时器到底在“数”什么一切从“数数”开始1.1 定时器的本体是计数器时间只是“数出来”的结果很多人第一次用定时器时总觉得这是个闹钟一样的神秘外设。其实内部就是一个计数器一个二进制寄存器每个时钟脉冲的上升沿让它加一。加满之后溢出回到零再继续同时产生更新事件或者中断。就这么简单。关键点来了如果这个脉冲的来源是频率恒定的时钟那“计数值”就和“时间”形成一一对应。换句话说定时器并不“知道”时间它只知道“我已经数了N个脉冲”。时钟频率一旦固定外部世界的时间就跟着脉冲一起流动。电表也是这么干的数的是焦耳但显示出来是度数定时器数的是脉冲但换算出来就是秒、毫秒、微秒。有些初学者会纠结“为什么定时器叫定时器而不叫计数器”。因为它在系统里干的最多的事就是设定一个目标值数到目标之后做动作看起来像是在定时。可一旦你进入输入捕获、编码器计数这些场景它“计数器”的本性就暴露出来了。1.2 从51定时器到555定时器数脉冲这件事从来都没变过如果你学过51单片机应该记得T0和T1。51的定时器数的是机器周期12MHz晶振下12个时钟周期一个机器周期计数器每1微秒加一。你设置初值打开中断然后等它溢出。本质上和STM32没有区别只是脉冲源不同。555定时器也一样它用RC电路充放电产生振荡脉冲后面接计数器就能做事件计数。很多人把它当成“芯片里长出来的定时器”其实它只是一个脉冲振荡器真正干活的是时钟脉冲本身。想通这一层再看STM32就清爽了定时器永远在数脉冲你要做的是搞清楚脉冲是多快的频率、从哪里来、怎么控制它。2. 时间基准从哪里来先把时钟树捋顺2.1 芯片里的“心跳源”HSI、HSE与PLLSTM32的定时器不是凭空有节奏感的它需要吃一个时钟信号。这个时钟信号最终来自芯片内部的振荡源。F103上主要有两个源头HSI内部高速RC振荡器频率8MHz上电就能用成本低但精度一般温漂明显HSE外部高速晶振通常接8MHz石英晶体精度高适合对时间要求严格的场景。但8MHz对大部分需要高速运算的场景不够用所以芯片里还有一个PLL锁相环本质是个倍频器。把HSE或者HSI的8MHz送进去倍频到72MHz就成了整个系统的主频SYSCLK。这也是为什么CubeMX里常见的配置是HSE 8MHz、PLL x9、SYSCLK 72MHz——路径就是 HSE → PLL → SYSCLK。有些代码里直接用HSI跑系统也能正常工作但用HAL_Delay做长时间延时或者在CAN、USB这类对波特率敏感的场合HSI的精度问题就会暴露出来。高速通信偶尔丢包、时间轴飘排查到最后经常是时钟源选错了。2.2 SYSCLK是怎么一步步变成定时器的计数时钟主频SYSCLK不是直接给定时器的中间还隔着一道AHB和APB总线。SYSCLK先经过AHB预分频器变成HCLK给内核、内存、DMA用。HCLK再经过APB预分频器变成APB1或者APB2外设总线时钟定时器就挂在上面。F103是个很好的教学样本因为它的APB分频很有代表性TIM1和TIM8挂在APB2上APB2时钟通常和SYSCLK一样是72MHzTIM2到TIM7挂在APB1上APB1总线时钟通常是36MHz但定时器时钟不是简单的等于总线时钟这里有个STM32团队埋下的“补偿机制”。这个机制就是当APB预分频系数不为1时定时器时钟会翻倍。原因是APB1被分频成36MHz如果定时器也吃36MHz那定时器分辨率就降低了一半这对很多场景没法接受。为了让定时器尽量跑满主频硬件在内部把定时器时钟再乘回来变成72MHz。2.3 用CubeMX一眼看清时钟树以F103为例在CubeMX的Clock Configuration页签里这个路径是可视化的。你会在界面上看到一串方块和数字HSE填8MHzPLL M、N、P之类的参数F103没有这么复杂的公式但你要注意旁边那排小字。当APB1 Pre-scaler选择2时下面那行APB1 Peripheral clock会显示36MHz但再往下看TIM2、TIM3、TIM4、TIM5的时钟很可能标着72MHz前面还带一个“x2”的小标志。我第一次看到这个“x2”时也愣了半天以为自己哪里配错了。后来查手册才明白这就是分频器自动倍频的结果。理解了这个标志你才真正看懂时钟树。3. 预分频器与自动重装载决定“一秒有多长”的两个旋钮3.1 溢出时间公式PSC与ARR到底怎么配合定时器拿到高频时钟后不是你想要的任何频率都直接可用所以硬件上给了两个调节旋钮PSC预分频器和ARR自动重装载寄存器。PSC做的事是把定时器时钟先分频成计数频率。ARR做的事是规定计数器数到多少溢出。两者配合起来溢出频率和溢出时间的公式如下计数频率 定时器时钟 / (PSC 1) 溢出频率 计数频率 / (ARR 1) 溢出时间 1 / 溢出频率注意这里全是“加一”因为PSC和ARR都是从零开始数的。如果PSC设为71ARR设为999定时器时钟是72MHz那计算过程是计数频率 72MHz / 72 1MHz溢出时间 (9991) / 1MHz 1ms。这两个旋钮的取舍直接关系到你的系统分辨率PSC越小计数器步长就越小时间分辨率越高ARR越大单次溢出周期越长。想要高分辨率又长周期靠一个定时器很难两全要么用32位定时器如TIM2和TIM5要么级联或软件计数扩展。3.2 手把手算一个1ms定时中断TIM3 LED闪烁这个例子是真正的“抄作业”场景。需求很常见用TIM3产生1ms中断在中断里翻转一个LED实现闪烁。我习惯的配置是PSC71ARR999这个组合在72MHz下正好得到1ms。CubeMX里的操作很简单TIM3的Clock Source选Internal ClockPrescaler填71Counter Period填999。如果选Auto-Reload Preload重装使能更新事件会在重装后才生效。中断要在NVIC Settings里勾上TIM3 global interrupt。生成代码后在回调函数里翻转LEDvoid HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }如果你的板子无所谓具体周期把ARR改成499闪烁频率就变成2ms翻转一次。但我更建议你亲手用公式算一遍再填别直接从网上复制参数。我见过太多人问“为什么我的定时器周期不是预期值”我一问他根本不知道PSC和ARR是怎么来的。3.3 APB1那个“反直觉的×2”为什么定时器时钟是72MHz继续说那个坑。很多人在F103上调试时会按照“SYSCLK72MHzAPB136MHz定时器也在APB1所以定时器时钟应该是36MHz”来算PSC和ARR。算出来的时间差一倍代码却没毛病。实际上当当APB1分频系数为2时定时器时钟是APB1的2倍就是72MHz。这个“隐藏×2”在数据手册里写得很清楚但新手一般不会主动翻到那一页。这个补偿机制不是所有系列都长一样进入F4之后时钟树更复杂定时器时钟还可能受APB分频和PLL倍频共同决定。所以每次换芯片型号必须重新看时钟树不能靠经验直接套用。4. 定时器家族怎么选基本、通用、高级定时器的分工4.1 三类定时器到底差在哪STM32把定时器分成三个层级功能量级完全不同。基本定时器TIM6和TIM7最简单只有时基功能没有引脚输出通常做内部延时触发或者喂看门狗节奏。很多人以为“只要有定时器就能输出PWM”其实基本定时器做不到。通用定时器TIM2到TIM5是使用频率最高的它们有时基、输入捕获、输出比较、PWM生成、编码器接口。绝大多数电机测速、超声波测距、舵机PWM都在这一档完成。高级定时器TIM1和TIM8在通用定时器基础上增加了互补PWM输出、死区插入、刹车保护和重复计数等功能专门为电机驱动这类场景准备。类型典型成员核心能力典型用途基本定时器TIM6/TIM7时基、中断延时、触发DAC、系统节奏通用定时器TIM2/TIM3/TIM4/TIM5时基、捕获、比较、PWM、编码器测距、测频、舵机、速度反馈高级定时器TIM1/TIM8通用能力互补输出死区刹车电机驱动、三相PWM、FOC选型时就一句话只要你不确定先用通用定时器。高级定时器虽然强但配置复杂度也高新手在FOC或控制电机之前通常不需要碰它。4.2 高级定时器的高级互补PWM、死区与刹车高级定时器最“高级”的地方不在高频而在它对功率电路的保护。控制H桥或者三相逆变桥时同一桥臂的上下两个开关管绝对不能同时导通否则直接短路烧管子。理想情况下一个管关断另一个马上导通就行但现实中功率器件的通断需要时间。所以硬件会在切换瞬间插入一段“死区时间”让上下桥都关闭一小段间隙。这个死区是定时器硬件自动生成的。它比分立元件搭延时电路可靠得多也让PWM控制代码不需要关心底层时序细节。刹车功能则是紧急制动故障信号一到PWM输出立刻进入安全状态不用等CPU处理反应速度是纳秒级的。4.3 从PWM到FOC定时器在电机控制里的角色热词里那个“foc pwm波形和定时器”说的就是高级定时器在最复杂场景里派上的用场。FOC矢量控制虽然名字高大上但落到硬件层面三相逆变桥需要6路PWM而且必须两两互补、带死区。高级定时器把这些全包了。FOC常用的PWM是中心对齐模式计数器先递增再递减形成对称三角波在特定匹配点上更新比较寄存器这样生成的PWM更具对称性电流纹波更小。所以FOC的每个控制周期都离不开定时器的结构。理解了定时器的时间基准再看FOC的波形就不是一堆陌生信号而是定时器在干自己最擅长的事按节拍输出开关信号。5. 把时间基准用起来捕获、测距与延时5.1 超声波测距的本质是测量脉冲宽度超声波测距模块比如HC-SR04原理并不复杂单片机上给TRIG一个10微秒的高电平脉冲模块发一串超声波然后Echo引脚会输出一个高电平高电平持续的时间就是超声波往返的时间。距离等于声速乘时间除以2。关键问题变成了怎么精准测量一段高电平的宽度。用定时器输入捕获就能搞定。把定时器的捕获通道配置在上升沿触发记录当前CNT值再配置下降沿触发再记录CNT值两次相减就是高电平的脉冲宽度。64MHz还是72MHz捕获分辨率都在微秒级。需要注意一个坑如果模块测距量程很大比如5米往返时间大约29毫秒计数器很可能在测量中途溢出。这时候必须额外处理溢出事件把溢出的次数累加起来否则测出来的时间会莫名其妙小很多。我一开始没加溢出管理测近处挺好测远距离就乱跳排查了很久才想到是24位计数器溢出导致的问题。5.2 频率测量捕获测频率的两种思路“stm32定时器捕获测频率”这个热词背后有两种思路测频法和测周法。测频法适合高频信号思路是设定一个固定的时间窗口比如1秒统计这个窗口里来了多少个上升沿。频率就直接等于计数值。实现方式可以用定时器做外部时钟计数也就是把被测信号当作定时器的时钟源。这个方案在高频时精度高但低频信号1秒里可能没几个脉冲分辨率不够。测周法适合低频信号思路是捕获两个相邻上升沿的时刻相减得到周期周期取倒数就是频率。这个方案用输入捕获很好实现但高频信号周期太短计时时间分辨率不够误差会放大。贴近实际的选择是频率高用测频频率低用测周。当然更高级的方案是用定时器的主从模式可以让硬件连续捕获多个沿再做平均全程不用CPU操心。5.3 delay卡死一查一个准SysTick也是定时器很多人第一次遇到HAL_Delay卡死是在自己的代码里。其实HAL_Delay背后就是SysTick也就是滴答定时器它是ARM内核自带的24位递减定时器时钟来自HCLK。HAL库启动时HAL_Init会把SysTick配置成1毫秒中断一次中断里给一个叫uwTick的全局变量加一。HAL_Delay本质上就是不停的读uwTick看它有没有到达目标值。所以HAL_Delay卡死通常只有几个原因第一没有调用HAL_InitSysTick根本没配好第二SysTick中断被关掉了uwTick永远不更新第三SystemCoreClock全局变量和实际时钟不符导致SysTick的装载值算错1ms节拍变成了1年节拍。我处理过最隐蔽的一个就是第三类代码里改了PLL配置但没更新SystemCoreClock整个系统延时全部失真。6. 常见问题速查为什么你的定时器不干活6.1 案例一外设时钟没开PWM一片死寂STM32的外设为了让功耗更低默认情况下大多数外设时钟是关闭的。定时器也如此。用CubeMX生成的代码自动会调用__HAL_RCC_TIM3_CLK_ENABLE()这类函数把定时器时钟打开。但有人手写寄存器或者改代码时把这行删了定时器即使配置看起来全对也完全不工作。我在调PWM的时候遇到过GPIO配置没问题、定时器参数没问题引脚就是没波形。最后用调试器去看RCC寄存器里TIM3的时钟位发现全是0才意识到时钟根本没使能。排查这个问题的思路很简单先确认定时器时钟开了吗再确认GPIO复用功能配置对了吗最后确认重映射引脚选对了吗。6.2 案例二改完主频CAN通信突然连不上有一个项目跑得好好的CAN通信某天为了省电把系统主频从72MHz改成某个更低频率重新烧录后CAN瞬间连不上。结构上所有代码都没变定时器中断也正常就是CAN总线握手失败。原因就在时间基准。CAN的波特率完全由APB1时钟推导而来用到的分频器和位时序寄存器全部依赖这个时钟。主频改了APB1往往是跟着变的但CAN的分频参数还是按旧主频算的波特率就偏了。这个问题的排查路径是回到时钟树里看当前APB1实际是多少再用这个值重新算波特率不出一分钟就能定位。6.3 定时器排查速查表现象最可能的原因排查顺序定时器中断不触发NVIC没使能PSC/ARR写错时钟没开先查时钟后查中断再查参数PWM引脚没有波形GPIO复用没配重映射错误时钟没开查引脚配置表再用示波器看引脚中断间隔差一倍APB分频导致的定时器时钟理解错误重新对着时钟树算一遍捕获值偶尔飘计数器溢出没处理信号有毛刺加溢出计数查输入滤波HAL_Delay卡死SysTick没初始化SystemCoreClock不对查HAL_Init查时钟配置做嵌入式这么多年我的体会是定时器的问题十有八九不是定时器本身的问题而是时钟树的问题。什么中断不触发、PWM不输出、CAN连不上、延时时间翻倍——沿着“脉冲从哪里来、频率是多少、分频对不对”这条路去查基本都能水落石出。建议你拿到一块新板子第一件事就是打开CubeMX把时钟树截个图存起来这比收藏什么“定时器配置技巧”有用多了。理解了时间基准定时器就没了神秘感剩下的全是查表和算数的活。
返回列表