1. 从“计时”到“控制”STM32定时器的核心价值如果你刚开始接触STM32可能会觉得定时器Timer不就是个“闹钟”吗设置一个时间到了就触发个中断干点别的事。我以前也是这么想的直到在一个电机控制项目里因为对定时器理解不透彻PWM波形死活调不出来电机转得跟抽风似的才真正意识到这玩意儿远不止“计时”那么简单。在STM32的世界里定时器是连接软件逻辑与硬件实时性的桥梁是驱动PWM、编码器、输入捕获、输出比较等高级功能的核心外设。它更像一个高度可编程的“时间引擎”能帮你精准地测量脉冲宽度、生成复杂波形、甚至构建简单的操作系统心跳。理解它是玩转STM32实时控制应用的必修课。市面上很多教程一上来就讲寄存器、讲库函数配置虽然详细但容易让人陷入细节而迷失整体。这篇内容我想从一个实际开发者的角度帮你把STM32定时器的脉络理清楚。我们不只讲“怎么配”更要讲清楚“为什么这么配”以及在不同应用场景比如PWM生成、输入捕获测频率、编码器接口下定时器的各个部分是如何协同工作的。你会发现无论是基础的TIM6/TIM7还是功能强大的高级定时器TIM1/TIM8其底层逻辑是一脉相承的。掌握了核心思想再去看数据手册和CubeMX的配置就会豁然开朗。2. 定时器的家族图谱与核心时钟链STM32的定时器家族成员众多根据其功能和复杂度大致可以分为三类基本定时器、通用定时器和高级定时器。这个分类不是随意的它直接决定了你能用这个定时器做什么。基本定时器如TIM6, TIM7这是定时器家族的“基本功”。它们通常只包含一个16位的递增计数器、一个预分频器PSC和一个自动重载寄存器ARR。功能非常纯粹就是定时。到达设定时间后产生一个更新事件UEV和中断/DMA请求。它常被用作时基比如为操作系统提供系统滴答SysTick也是类似的原理或者为DAC提供触发信号。因为它没有输入/输出通道所以不能直接连接GPIO去捕获信号或输出波形。通用定时器如TIM2-TIM5, TIM9-TIM14这是你打交道最多的“多面手”。它们在基本定时器的基础上增加了输入捕获Input Capture和输出比较Output Compare通道有的还支持编码器接口和霍尔传感器接口。这意味着它不仅能定时还能测量通过输入捕获功能测量外部脉冲的宽度或频率。生成通过输出比较功能生成特定频率和占空比的PWM波或者输出单脉冲。计数可以作为编码器接口直接读取正交编码器的位置和速度。 通用定时器通常有4个独立的通道CH1-CH4每个通道都可以被灵活配置为输入或输出模式。高级定时器如TIM1, TIM8这是家族中的“瑞士军刀”功能最全、最复杂。它包含了通用定时器的所有功能并额外增加了互补输出与死区控制这是驱动三相电机如BLDC、PMSM的关键。它可以生成6路带死区Dead Time的互补PWM信号CH1/CH1N, CH2/CH2N, CH3/CH3N防止上下桥臂的功率管同时导通而短路。刹车Break功能当外部出现故障如过流时可以快速安全地关闭所有PWM输出保护硬件。重复计数器RCR允许在产生更新事件前进行多次计数循环用于生成非常精确的低频PWM或复杂脉冲序列。理解了这个分类你在选型时就能快速定位。比如你只是想做个1秒闪一次的LED用基本定时器甚至SysTick就够了如果要控制舵机需要PWM就得用通用定时器如果要做一个无人机电调或者变频器高级定时器就是唯一的选择。核心时钟链理解计时的源头定时器的一切都始于时钟。STM32定时器的时钟源可以来自内部时钟APB总线、外部时钟、甚至其他定时器。但最常用的是内部时钟。这里有个关键点定时器实际工作的时钟频率并不直接等于APB总线的频率。以常见的TIM2挂载在APB1上为例假设你的系统主频是72MHzAPB1预分频器设为2那么APB1的时钟频率是36MHz。但是如果APB1的预分频系数是1则定时器时钟CK_INT等于APB1时钟36MHz如果APB1预分频系数不为1比如是2、4、8...则定时器时钟会是APB1时钟的2倍即72MHz。这是STM32内部的一个设计旨在当APB低速时仍能为定时器提供较高的时钟源以保证性能。在CubeMX的时钟树配置界面你可以清晰地看到这个倍频关系。定时器得到基础时钟CK_INT后会先经过一个**预分频器PSC**进行分频得到计数器实际递增的时钟CK_CNT。CK_CNT CK_INT / (PSC 1)。PSC是一个16位寄存器所以分频系数可以是1到65536。通过调整PSC你可以改变计数器“走”的快慢以适应不同的定时精度需求。计数器CNT在CK_CNT的驱动下从0开始向上或向下或向上向下计数当计数值达到**自动重载寄存器ARR**设定的值时就会产生一个“更新事件”Update Event计数器清零或从ARR值开始向下重新开始计数如此循环。这个循环的周期就是你的定时周期。注意在CubeMX或代码中配置定时器频率和周期时脑子里一定要有这条时钟链系统时钟 - APB总线时钟 - 定时器输入时钟CK_INT- 预分频后时钟CK_CNT- 计数到ARR。任何一个环节算错最终定时时间都会不对。3. PWM生成的深度解析从ARR、PSC到CCR生成PWM脉冲宽度调制信号是定时器最经典的应用之一用于控制LED亮度、舵机角度、电机转速等。其核心原理是利用定时器的输出比较功能。PWM生成的硬件逻辑假设我们配置一个通用定时器如TIM3的通道1为PWM输出模式。硬件上定时器的计数器CNT会不停地从0累加到ARR值假设向上计数模式。同时我们设置了一个捕获/比较寄存器CCR1。硬件会比较CNT和CCR1的值当CNT CCR1时输出通道1被设置为有效电平比如高电平。当CNT CCR1时输出通道1被设置为无效电平比如低电平。这样一个周期内高电平的时间就由CCR1的值决定。通过改变CCR1就能改变PWM的占空比通过改变ARR和PSC就能改变PWM的频率。关键参数的计算与配置逻辑定时器时钟CK_INT这是源头需要根据你的系统时钟和总线分配确定。假设为72MHz。目标PWM频率Fpwm比如我们需要一个1kHz的PWM波。周期 Tpwm 1 / 1000Hz 1ms。预分频系数PSC与自动重载值ARR这两个值共同决定了计数周期。计数周期 (ARR 1) * (PSC 1) / CK_INT。通常我们会先确定ARR的值。为了获得更精细的占空比调节分辨率ARR应尽可能大但不能超过寄存器最大值16位定时器是65535。但同时ARR和PSC必须是整数。一种常见的策略是先设定ARR为一个方便计算的整数值比如999。那么每个计数周期的时间 (ARR1)/CK_CNT。要使得总周期为1ms就需要CK_CNT (ARR1) / Tpwm 1000 / 0.001 1,000,000 Hz (1MHz)。然后计算PSC CK_INT / CK_CNT - 1 72,000,000 / 1,000,000 - 1 71。验证PWM频率 CK_INT / ((PSC1)*(ARR1)) 72M / (72 * 1000) 1000Hz。完美。捕获/比较寄存器CCR1与占空比Duty占空比 CCR1 / (ARR 1)。如果ARR是999想要50%占空比就设置CCR1 500。在CubeMX中的实操配置打开CubeMX选择一个TIM比如TIM3 Channel1模式选择“PWM Generation CH1”。Parameter Settings标签页Prescaler (PSC - 16 bits value)填入71。Counter Mode选择Up向上计数。Counter Period (AutoReload Register - 16 bits value)填入999。Internal Clock Division (CKD)保持默认No Division。auto-reload preload建议使能Enable。这会让对ARR的修改在下一个更新事件时才生效防止在修改途中产生错误的PWM周期。下方具体的PWM配置ModePWM mode 1或2。区别在于有效电平的定义。通常PWM mode 1表示CNTCCR时为有效电平高PWM mode 2相反。根据你的硬件电路比如是低电平驱动还是高电平驱动选择。Pulse (16 bits value)这里就是初始的CCR1值可以先填个500。Output compare preload建议使能。和ARR预装载类似保证CCR修改的同步性。Fast Mode一般禁用。使能后当CNTCCR时会强制输出反转用于产生频率固定的信号但占空比只能是50%。CH Polarity选择有效电平是高High还是低Low。需要和PWM mode配合。生成代码后在main.c的初始化部分你会看到HAL库启动了PWM输出HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);。之后你就可以在程序中动态修改占空比__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, new_CCR_value);。实操心得调试PWM时最直观的工具是示波器。如果没有示波器可以接一个LED通过肉眼观察亮度变化是否平滑来粗略判断PWM频率是否合适频率太低会闪烁频率太高可能因LED响应跟不上而亮度变化不明显。另外注意GPIO的复用功能是否正确映射到了定时器通道上这个在CubeMX的引脚分配图里可以直观看到。4. 输入捕获精准测量脉冲宽度与频率如果说PWM输出是“创造”时间信号那么输入捕获就是“测量”时间信号。它的典型应用是测量方波的频率、占空比或者测量一个高电平/低电平的持续时间例如超声波测距中的回响脉冲宽度。输入捕获的工作原理输入捕获功能通常使用定时器的一个通道比如TIM2_CH1将该通道配置为输入模式并映射到捕获单元。当通道引脚上出现指定的边沿上升沿或下降沿时硬件会立即将当前计数器CNT的值“捕获”并锁存到对应的**捕获/比较寄存器CCRx**中同时可以产生一个中断或DMA请求。通过记录连续两个边沿例如一个上升沿和一个下降沿触发时捕获到的CNT值就能计算出这两个边沿之间的时间差从而得到脉冲宽度。测量频率与占空比的经典方法以测量一个未知方波的频率和占空比为例通常需要捕获两个边沿首次捕获配置通道在上升沿捕获。当上升沿到来时捕获值CCR1_rise被记录并在中断服务程序中将捕获边沿改为下降沿。第二次捕获当下一个下降沿到来时捕获值CCR1_fall被记录。此时高电平的持续时间PulseWidth_High (CCR1_fall - CCR1_rise) * 计数周期。同时在中断中再将捕获边沿改回上升沿。第三次捕获当再下一个上升沿到来时捕获值CCR1_rise_next被记录。这样一个完整周期的持续时间Period (CCR1_rise_next - CCRR1_rise) * 计数周期。计算频率Freq 1 / Period。占空比Duty PulseWidth_High / Period。这里有一个关键细节计数器CNT是循环计数的从0到ARR。当CCR1_fall小于CCR1_rise时说明在下降沿到来前计数器已经溢出并重新开始计数了。因此不能简单相减需要处理溢出。一种稳健的方法是在每次捕获中断中不仅读取CCRx的值还记录一个溢出计数变量capture_overflow。当发生定时器更新中断溢出中断时capture_overflow加1。这样两次捕获之间的实际计数值差为delta (capture_overflow_diff * (ARR1)) (CCRx_new - CCRx_old)。HAL库的输入捕获例程通常演示了这种方法。在CubeMX中的配置要点选择定时器和通道模式设为“Input Capture direct mode”。Parameter SettingsPSC和ARR的设置需要权衡。ARR决定了最大无溢出测量范围。最大测量时间 (ARR1) * (PSC1) / CK_INT。如果你要测低频信号ARR要设大一些或者PSC分频大一些但会降低时间分辨率。分辨率 1 / CK_CNT。GPIO Settings注意上拉/下拉电阻的配置根据你的输入信号特性选择确保空闲时有确定的电平。NVIC Settings使能定时器的捕获中断和更新溢出中断。生成代码后你需要重写对应的中断回调函数HAL_TIM_IC_CaptureCallback()和更新中断回调函数HAL_TIM_PeriodElapsedCallback()在其中实现上述的记录CCRx值、切换边沿、处理溢出的逻辑。避坑指南输入捕获测量高频信号时如果信号频率接近甚至超过定时器的计数频率CK_CNT就会因为无法在单个计数周期内捕获到边沿而导致测量失败或误差极大。因此测量前要对信号频率有个大致估计合理设置PSC。一个经验法则是信号的最高频率应小于CK_CNT / 10以保证有足够的计数分辨率。对于频率变化范围很大的信号如编码器转速可能需要动态调整PSC或使用定时器的“分频”输入模式。5. 编码器接口模式读取电机位置与速度的利器对于带编码器的电机我们需要实时读取编码器输出的两路正交相位差90度方波信号A相和B相。如果使用普通的GPIO中断来计数在高转速下会大量消耗CPU资源且容易丢失脉冲。STM32定时器的编码器接口模式就是为这种场景量身定制的硬件解决方案。编码器模式是如何工作的将定时器的通道1和通道2配置为编码器接口模式并分别接到编码器的A相和B相信号上。定时器硬件会自动根据A、B两相的边沿和相对相位关系控制计数器CNT的计数方向和计数。正转顺时针通常A相超前B相90度。硬件设计为在A相的上升沿检查B相的电平。如果B相是低电平则CNT向上计数如果B相是高电平则CNT向下计数具体响应边沿和极性可以通过寄存器配置。这样正转时CNT递增。反转逆时针B相超前A相90度。同样在A相上升沿检查B相电平情况正好相反导致CNT递减。如此一来CNT的值就实时反映了编码器的相对位置累计脉冲数。读取CNT值就能知道电机转了多少个最小角度单位取决于编码器线数。获取速度信息仅有位置还不够控制中更需要速度信息。编码器模式本身不直接输出速度但我们可以结合定时器的另一个功能——定时器级联或者使用另一个定时器作为时基——来测量速度。M法测速频率测量法在一个固定的短时间窗口内比如10ms由另一个定时器中断产生读取编码器计数器CNT的变化量Delta_CNT。速度Speed Delta_CNT / (时间窗口 * 编码器线数)。这种方法在高速时精度高低速时可能因为窗口内脉冲数太少而误差大。T法测速周期测量法测量编码器两个相邻脉冲之间的时间间隔。这可以利用输入捕获功能捕获A相的边沿计算时间差。速度Speed (1 / 时间间隔) / 编码器线数。这种方法在低速时精度高高速时时间间隔太短测量误差大。M/T法测速结合两者优点既测量固定时间窗口内的脉冲数又测量窗口开始和结束时最后一个脉冲的时间精度更高但实现也稍复杂。CubeMX配置编码器模式配置非常简单选择一个有编码器接口功能的定时器通常是通用定时器TIM2, TIM3, TIM4, TIM5。将Channel1和Channel2的模式都选择为“Encoder Mode”。在Parameter Settings中Encoder Mode选项有Encoder Mode TI1: 仅对TI1通道1的边沿计数。Encoder Mode TI2: 仅对TI2通道2的边沿计数。Encoder Mode TI1 and TI2: 对TI1和TI2的边沿都计数。这是最常用的4倍频模式因为它在A、B相的每个上升沿和下降沿都计数将编码器的分辨率提高了4倍。例如一个1000线的编码器采用此模式后每转可获得4000个计数。设置Polarity这取决于你的编码器信号是上升沿有效还是下降沿有效通常保持默认Rising Edge即可。关键一步设置Counter Period (ARR)为最大值65535对于16位定时器。因为编码器模式下ARR通常用作计数器的上限我们希望计数器能循环计数。也可以设置为编码器每转计数值的整数倍方便处理溢出。AutoReload Preload建议Disable以便计数器在溢出时能立即从0开始。生成代码后启动编码器接口HAL_TIM_Encoder_Start(htim3, TIM_CHANNEL_ALL);。之后随时可以通过__HAL_TIM_GET_COUNTER(htim3)读取当前位置值。注意处理计数器的溢出超过65535后回到0这需要你在速度计算时进行软件处理。经验分享在电机启停或反转瞬间由于机械振动或信号抖动编码器可能会产生一些毛刺脉冲导致计数错误。可以在软件中做一个简单的数字滤波比如连续读取几次位置如果变化量在很小的范围内比如±1则认为是抖动忽略这次变化。更硬件的方法是在编码器信号输入引脚处加入RC低通滤波电路。