☰
STM32定时器与PWM实战:从原理到CubeMX配置及避坑指南
2026/10/24 4:31:57 网站建设 项目流程

我前阵子在带几位刚接触嵌入式开发的同事做一个小项目:用一块开发板同时驱动两个直流减速电机和一组LED灯带。电机需要调速,LED需要无级调光,其实都是同一件事——输出频率固定、占空比可调的电平信号。绕来绕去,最后全落在STM32定时器和PWM上。这个外设可以说是入门阶段第一个真正需要"理解原理之后再动手"的模块:会了它,GPIO翻转那种一个个点的操作才能升级成"让硬件自己干活";不会它,后面做电机控制、开关电源、数字电源、舵机驱动都会卡壳。这篇文章就把我对着芯片参考手册和示波器反复折腾出来的东西整理成一份可以直接参考的笔记,内容覆盖定时器的分类和选型、计数和中断的底层逻辑、PWM的产生原理、CubeMX与HAL库配置的完整流程,以及几个我在实测中真实踩过的坑。适合刚学完点亮LED、准备进入定时器和PWM阶段的开发者阅读,也适合那些会调用库函数但不太清楚寄存器背后发生了什么的人。

1. 先给定时器排个辈分:STM32上的几类定时器与选型思路

很多新手第一次看芯片的定时器章节都会懵:为什么一页纸里有TIM1、TIM2、TIM3、TIM4这么多编号?为什么有些是16位有些是32位?为什么有的定时器有"高级"两个字?这其实不是芯片厂商故意制造复杂度,而是不同应用场景对定时器资源的要求差异太大,干脆做成几种定位不同的硬件模块。

1.1 基本、通用、高级定时器到底差在哪

把STM32里最常见的定时器分个类,大概是这样的:

类型常见实例位数主要计数方式是否支持PWM输入捕获/编码器互补输出与死区典型用途
基本定时器TIM6、TIM716位向上计数否否无定时中断、喂看门狗、DAC触发
通用定时器TIM2、TIM3、TIM4、TIM516位/32位向上、向下、中心对齐是(4个通道)支持无PWM输出、输入捕获、编码器计数
高级定时器TIM1、TIM816位向上、向下、中心对齐是(4个通道)支持有电机控制、全桥/半桥驱动、互补PWM

基本定时器内部就是"时钟源、预分频器、计数器、自动重载寄存器"这套最基本的组合,连输出比较通道都没有,所以它不能输出PWM。你能利用它的就是周期性触发更新中断,或者作为其它外设的触发源。通用定时器在基本定时器之上增加了一组比较/捕获通道,每个通道都可以独立输出PWM,也能独立捕获外部信号,这是绝大多数PWM应用的主力。高级定时器在通用定时器的基础上又加了互补输出、死区时间、刹车输入、重复计数这些功能,专门为电机驱动和处理器的桥式电路设计。

选型逻辑其实很简单:如果只做LED调光、蜂鸣器发声、小功率电机调速,通用定时器完全够用;如果要做带H桥的直流电机驱动、无刷电机控制,或者需要两路信号互为反相还要可靠地插入死区时间,那就必须占用高级定时器。

1.2 位数和时钟频率决定了你最多能"数"到哪

16位和32位定时器之间最直观的差别是自动重载寄存器ARR的表示范围:16位最大可以写到65535,32位可以写到4294967295。这个数字配合预分频值,直接决定了定时器最长能计时多久。

举个实际例子。若定时器输入时钟为72MHz,预分频系数设为7200-1,那么得到的计数频率是72MHz/7200=10kHz,也就是计数器每0.1ms加1。16位定时器ARR最大为65535,最长定时周期约6.55秒;如果不想重装ARR或者用软件扩展,这个周期对某些应用就不够。32位的TIM2或TIM5就不存在这个顾虑。日常做PWM,我们更需要关注的是分辨率:16位定时器在ARR=65535时,占空比步进最小是1/65536,折算下来约0.0015%的调节精度,绝大多数应用都够了。

经常有同学问"为什么既设预分频又设自动重载,这不麻烦吗"。原因是两个寄存器分管不同维度:预分频器PSC负责把高频时钟降到适合人类直觉的计数节拍,ARR负责定义计数的总路程。前者决定步长,后者决定范围。两者配合,既能精细控制单次增减的时间,又不会让一个计数器从头到尾溢出得太快。

2. 定时器计数的底层逻辑:时钟、预分频和重载值是怎么联手工作的

2.1 从时钟树到计数脉冲:PSC和ARR一起决定定时周期

定时器本质上是一台"数脉冲的机器"。它不直接知道"1秒"是多少,它只知道来了多少个时钟沿。所以定时的第一步,是把芯片主时钟通过时钟树喂到定时器外设,这个频率在STM32上通常和APB1/APB2总线时钟相关;第二步是用预分频器PSC做分频,得到计数脉冲;第三步是让计数器按这个脉冲递增,到达ARR之后产生更新事件并归零。

周期T的计算公式是:

T = (PSC + 1) × (ARR + 1) / 定时器时钟频率

注意这里两个地方都要加1。因为PSC和ARR的寄存器值从0开始有效:PSC=0表示不分频,PSC=71表示72分频;ARR=999表示计数器要数到1000个脉冲才清零。很多第一次写程序的人在这两个加1上翻车,算出来的时间总是差一点。

以72MHz为输入时钟,想要让定时器产生周期正好1秒的更新中断,可以取PSC=7199、ARR=9999。这样定时器时钟先分频得到10kHz,也就是计数器每0.1ms跳一次,跳10000次刚好1秒。如果想产生20kHz的PWM,ARR的选择逻辑不同:因为PWM频率等于计数频率除以(ARR+1)。取PSC=0,ARR=3599时,计数频率就是72MHz,PWM频率为72MHz/3600=20kHz。这里的"分辨率"是3600级,占空比可以以1/3600为步长变化。

2.2 向上、向下与中心对齐:三种计数模式不是摆设

向上计数(Up)最常用,计数器从0数到ARR,溢出后回到0,同时产生更新事件。向下计数(Down)恰恰相反,从ARR减到0。中心对齐(Center-aligned)模式则是在0和ARR之间来回增减,它产生的计数溢出发生在两个端点:到达ARR时产生一次更新,回到0时再产生一次更新,实际输出信号频率比计数频率少一半。

对于定时中断应用,三种模式没什么本质差别,选向上计数就可以。对于PWM应用,中心对齐模式有个独特优势:PWM波的对称性更好。边沿对齐PWM的上升沿都在同一个位置,波形中心不对称,这在某些对谐波敏感的应用里会产生额外的偶次谐波分量;中心对齐波形左右对称,谐波特性更干净。不过中心对齐模式下调节占空比时,"当前值是否大于比较值"这个判断的窗口在上下两个半周期内是不同的,软件修改CCR的时机需要稍微注意,否则可能在一两个周期内出现瞬时异常脉宽。

2.3 更新中断与捕获/比较中断的触发时机别搞混

定时器可以产生两类中断。第一类是更新中断,也就是计数器溢出或软件触发更新时产生,对应HAL库里的TIM PeriodElapsed事件;第二类是捕获/比较中断,出现在计数器的值到达通道的捕获/比较寄存器CCR这一时刻,对应TIM CaptureCompare事件。

这两者的区别对PWM应用来说尤其重要:更新中断告诉你一个完整的PWM周期结束了,你可以在这里修改下一个周期的占空比;捕获/比较中断告诉你当前周期内到达了设定的比较点,如果你需要精确控制"在这一个点完成某动作",比如ADC采样同步在PWM中点进行,就要用到它。在HAL库的回调函数里,一个是HAL_TIM_PeriodElapsedCallback,另一个是HAL_TIM_OC_DelayElapsedCallback,千万别在更新中断里等待比较中断的发生,那样白白浪费CPU。

3. PWM的输出原理:一个比较器加一列可配置的电平

PWM的全称是Pulse Width Modulation,脉冲宽度调制。它是靠固定频率、改变高电平持续时间来"表现"不同平均电压的。最通俗的理解方式是把一个高频率开关信号接在LED上,50%占空比时LED亮度大约是全亮的50%,虽然实际上灯在一秒内开开合合几万次,但人眼反应不过来,只能感受到平均亮度。

3.1 边沿对齐PWM的工作过程:计数器、比较器和电平翻转

边沿对齐PWM向上计数的过程可以这样描述:定时器从0开始递增,当计数值小于比较值CCR时,输出引脚保持一种电平状态;当计数值达到CCR的那一瞬间,电平翻转;随后计数值继续上升到ARR,产生更新事件,计数器归零,电平再翻转回来,开始下一个周期。

所以一个PWM周期里,真正决定高电平时间长短的就是CCR。ARR决定整个周期长度,PSC决定计数脉冲的速度,而CCR决定输出信号在哪一个计数值处切换。这样一来,占空比的计算公式就很简单:

占空比 = CCR / (ARR + 1)

要注意的是不同极性配置下"高电平对应CCR区间还是CCR之外区间"会有差异。在PWM模式1下,向上计数时,计数值小于CCR时为有效电平,大于等于CCR时为无效电平;PWM模式2则相反。所以"有效电平"是否等于高电平,还取决于极性配置。项目里如果发现"占空比越大灯越暗",多半就是有效极性配反了。

3.2 频率与占空比的参数设计:先定频率还是先定分辨率

实际开发中,PWM参数设计的顺序应该是先确定频率需求,再根据频率反推ARR和PSC,最后根据占空比需求设置CCR。

比如驱动一个无源蜂鸣器,想让蜂鸣器发4kHz的声音,PWM频率就应该设为4kHz。定时器时钟72MHz,可以选择PSC=0,ARR=17999,得到频率72MHz/18000=4kHz。如果同时要求占空比步进0.1%,那就需要分辨率至少1000级,也就是ARR不能小于999。但4kHz对应ARR=17999,分辨率天然就是1/18000,远超需求。反过来,如果PWM频率要求1kHz,而原始时钟频率只有1MHz,分频之后又要满足高分辨率,就必须在PSC和ARR之间做平衡:PSC越大,ARR可以越小,频率不变但分辨率下降;PSC越小,ARR越大,分辨率更高但频率受限于最大计数值。

有一个很多人忽略的约束:PWM频率、分辨率和定时器时钟之间满足一个关系——

PWM频率 = 定时器时钟 / ((PSC + 1) × (ARR + 1))

其中(ARR+1)就代表占空比分辨率等级数。固定时钟频率下,想提高PWM频率,就要缩小PSC或ARR;想提高分辨率,就必须增大ARR。两者冲突时,要么提高定时器输入时钟频率,要么用更高位数的定时器。实际做电机驱动时,20kHz左右的PWM频率比较常见,因为超过人耳听觉范围,不会产生刺耳的啸叫;但同时也要求分辨率够高,避免电机低速运转时一顿一顿。

3.3 极性、互补输出和死区时间:做电机控制才真正用得上

普通PWM只要一路信号,配置相对简单。但做全桥或半桥驱动时,同一桥臂的上下两个功率管不能同时导通,否则会直通短路。硬件上需要一对逻辑相反的PWM信号分别驱动上下管,问题又来了:功率管从导通切换到关断需要时间,如果上管的关断还没完成,下管的导通就开始了,一瞬间的直通足以烧毁功率管。

高级定时器为此提供了互补输出和死区功能。所谓互补,就是通道1和通道1N的信号逻辑相反;所谓死区,就是在两者切换的过渡中插入一段两者同时关闭的时间。比如死区时间设为1微秒,在驱动信号翻转时,高级定时器硬件会保证先关断的一个通道保持低电平,等死区计时结束后另一通道才打开。这段可配置的时间由定时器的死区寄存器控制,具体的插入时长取决于功率管的开关损耗与安全需求的折中。

我第一次做电机驱动的时候没有用高级定时器的死区功能,想着"我软件里处理一下就行"。真到带负载测试才发现,软件翻转的总线上有毛刺,关断延迟一抖动就可能出现直通。后来老老实实换上TIM1的互补输出加死区,波形干净利落。结论只有一个:高级定时器这些特性不是噱头,是专门解决这类实际硬件问题的。

4. 手把手配置一个PWM呼吸灯:CubeMX加HAL库的完整流程

下面用一个最简单的呼吸灯Demo把整个流程串起来。目标:用TIM2的通道1输出一个频率1kHz、占空比从0%到90%周期变化的PWM信号,驱动一颗LED实现缓慢亮灭效果。

4.1 为什么要用CubeMX生成初始化代码

有人说学寄存器更"底层",我完全赞同先把寄存器搞懂,但实际项目开发时,用CubeMX生成初始化部分更高效。它会把时钟树、引脚复用、中断优先级这些琐碎配置可视化,并且生成经过验证的初始化代码。相比手工翻寄存器手册,至少能把配置出错的概率降低一半。

当然,CubeMX生成的只是骨架,重点业务逻辑还是要自己写。我的经验是:用CubeMX处理"外设时钟开没开、引脚复用对没对"这一类基础设施问题,用HAL库函数处理"启动、停止、改占空比"这类运行时操作,遇到性能瓶颈再回到寄存器层面优化。这套组合在工程效率上很划算。

4.2 CubeMX里的关键配置项:时钟树、引脚与定时器参数

打开CubeMX后,首先在System Core的RCC里把HSE设为Crystal/Ceramic Resonator,然后在Clock Configuration里把系统时钟配置到芯片标称最高频率。我用的开发板外部晶振是8MHz,通过PLL倍频到72MHz,这些配置在图形界面直接拖拽即可。

接着把TIM2的Channel1模式设为PWM Generation CH1,此时CubeMX会自动把对应引脚分配为定时器复用功能。重点填写下方参数:

  • Prescaler:设71,即PSC=71,让定时器输入时钟从72MHz分频到1MHz
  • Counter Period:设999,即ARR=999,计数器从0数到999
  • Auto-Reload Preload:Enable。这条要特别说明:使能预装载后,写入ARR和CCR的数值不会立即生效,而是等下一个更新事件才更新,可以有效避免在PWM运行过程中写新占空比时突然跳变出一个异常宽的脉冲
  • Pulse:设500,即初始CCR=500,占空比50%
  • PWM Mode:Mode 1
  • Pulse Polarity:High,即有效电平为高

这几项配置好,引脚和定时器的逻辑就完全确定。生成代码后,MX_TIM2_Init里就是标准初始化流程:配置预分频、自动重载值、比较值,然后给输出比较通道装载初始值。

4.3 HAL库代码:启动PWM并在线修改占空比

生成代码后,在main函数里加入一行启用PWM通道:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 启动TIM2通道1的PWM输出 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); uint16_t duty = 0; uint8_t direction = 1; while (1) { // 更新占空比:CCR从0到900,再从900回到0 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty); if (direction) { duty += 10; if (duty >= 900) { duty = 900; direction = 0; } } else { duty -= 10; if (duty == 0) { direction = 1; } } HAL_Delay(10); } }

__HAL_TIM_SET_COMPARE这个宏做的事情就是写通道的CCR寄存器。因为刚才开启了预装载,CCR写入后会在下一个更新事件时统一更新,不会在周期中途产生割裂的波形。这段代码运行后,LED亮度会从暗到亮再回到暗,整个过程周期大约2秒多,视觉上非常柔顺。

如果不用HAL库,这一行对应的寄存器操作其实也很简单:

TIM2->CCR1 = duty;

4.4 占空比不要一步改到顶:软件渐变背后的真实需求

呼吸灯这种渐变动画的占空比变化绝不能一步到位,需要分小步慢慢调整。原因倒不是LED受不了,而是人眼和电路都有"感知惯性"。一次跳变如果超过一定幅度,人眼会觉得闪烁;频率较低时,这个跳变还会通过电源线产生可闻的噪声。

我常用的渐变策略有两种:固定步长加固定延时,或者固定变化速率动态计算延时。第一种适合演示,第二种适合实际产品中需要精确控制变化时间的情况。变化速率通常设为每10毫秒调整一级或两级,这样1kHz的PWM下,人眼看到的是一条平滑的亮度曲线。实际项目中如果觉得亮度曲线不均匀,可以在软件里把步长按指数曲线分配,因为人眼对亮度的感知是对数关系,线性改变占空比不代表线性改变"感觉到的亮度"。

5. 定时器中断的正确打开方式:更新中断与回调函数的协作逻辑

5.1 从配置到回调:一条完整的中断链路

定时器中断的链路比PWM稍长一点:首先要让定时器产生中断事件,然后由NVIC把中断分发给CPU,CPU进入异常处理函数,HAL库在中断处理函数里判断标志位并调用对应的弱回调函数。

先在CubeMX中把TIM2的Global interrupt使能,同时把NVIC优先级设置好。然后在main启动定时器时使用带中断标志的函数:

HAL_TIM_Base_Start_IT(&htim2);

之后自己重写回调函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { // 每1秒执行一次 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }

这里有一个非常容易被新手忽略的细节:HAL库的中断处理函数HAL_TIM_IRQHandler会自动检查更新标志并清除它,但前提是定时器的更新中断没有被屏蔽。如果你在用HAL_TIM_Base_Start_IT启动后,又手动操作了TIM2->DIER寄存器清除更新中断使能位,中断就会无声消失。排查这种问题最好用调试器在中断入口处打一个断点,看CPU是否真的进中断。

5.2 中断服务函数里千万不能做的事

定时器更新中断常用于周期任务,比如每1ms做一次按键扫描、每10ms做一次PID计算。但中断服务函数里最忌讳的是:调用HAL_Delay、执行长延时,或者做耗时的浮点运算和打印输出。

原因很简单:中断服务函数会抢占主循环,如果它在里面干等,其它中断全部被堵住。更糟的是,HAL_Delay依赖于SysTick中断,如果你的定时器中断优先级比SysTick更高,并且又在中断里调用HAL_Delay,整个系统会直接死锁。这种死锁很难排查,因为它看起来像"系统随机卡死"。

正确做法是中断里只做两件事:标记标志位、保存必要的计数数据;真正复杂的逻辑放到主循环里轮询标志位再处理。比如:

volatile uint8_t tick_1s_flag = 0; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { tick_1s_flag = 1; } } // main loop: while (1) { if (tick_1s_flag) { tick_1s_flag = 0; HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }

5.3 多定时器中断的优先级规划:先定频率再定抢占优先级

项目里同时使用多个定时器时,中断优先级的分配不能随便填。一个稳妥的设计思路是:中断频率越高、实时性要求越严格的事件,抢占优先级就越高。

举个例子,假设系统里有三个定时器:TIM2产生1kHz时基用于控制环路,TIM3产生100Hz用于按键扫描,TIM4产生50Hz用于LED闪烁。合理的优先级分配是TIM2优先级最高,TIM3次之,TIM4最低。原因是1kHz控制环如果被延时会直接影响系统稳定性,而LED晚几毫秒翻转根本看不出来。

在CubeMX的NVIC设置里,抢占优先级组通常设为4位抢占、0位子优先级,这样每个外设都可以独立分配抢占优先级,逻辑更清晰。HAL库中数字越小优先级越高。调试时发现中断响应不规律,大多是优先级分配和中断内部的耗时没有考虑好。

6. 实测中的几个坑和排查思路:从示波器上看到的真实问题

6.1 引脚完全没有输出:先查复用功能再查定时器时钟

有一次某同学跑来和我说,配置好PWM后引脚上量不到任何波形。我让他先看GPIO模式,他信誓旦旦说选的是复用推挽输出。我再让他看系统时钟配置,结果他用的是内部时钟,CubeMX里选的是HSE外部晶振,但板子上压根没有焊接外部晶振。

这个案例说明,引脚没有输出时首先是"链路"问题,不是某一个寄存器的问题。排查顺序建议这样:

  1. 用万用表或示波器确认引脚没有波形,排除测量点错误
  2. 检查GPIO模式是否为AF_PP,也就是复用推挽输出
  3. 检查GPIO的复用映射是否正确,比如TIM2_CH1在PA0还是PA5,不同芯片和不同封装可能不同
  4. 检查定时器的时钟是否已经使能并配置正确,RCC相关寄存器有没有被意外改写
  5. 检查是否调用了HAL_TIM_PWM_Start,而不是只配置了HAL_TIM_PWM_Init

大多数"没输出"问题出在第2和第4步。

6.2 定时周期和理论值对不上:PSC和ARR的加一问题

有个典型实验:配置72MHz、PSC=7199、ARR=999,希望产生1秒更新中断,但实测周期差了一点点。

这种偏差最常见的原因是计算时把"7199分频"理解成"除以7199"。实际上PSC=7199时分频系数是7200,也就是10kHz计数频率;ARR=999时计数值是1000。如果代码里按PSC=7200、ARR=1000去填寄存器,得到的效果就是分频7201、计数值1001,周期自然变成约1.0001秒。误差看似微小,在需要长时间积分的应用中会越积越大。

另一个排查点是APB1分频器。STM32的定时器时钟并不总是等于总线时钟,APB1预分频不为1时,定时器时钟会倍频到总线时钟的两倍。这个细节不搞清楚,怎么算都差一倍。

6.3 占空比调节失效:改CCR没反应但频率正常

这个坑也很有代表性:PWM波形正常输出,频率完全正确,但不管怎么改CCR,占空比都固定在一个值不变。

排查后发现,问题出在定时器通道的工作模式被配置成了"Input Capture"而不是"Output Compare"。输入捕获模式下,CCR寄存器是硬件在捕获事件时写入的,软件对它写入的值会被硬件覆盖。如果在CubeMX里看到的Channel模式确实是Input Capture,赶紧改成PWM Generation或Output Compare。

还有一情况:代码里用了__HAL_TIM_SET_COMPARE,但这个宏展开后写入的是htim->Instance->CCR1。如果你同时在中断服务函数里修改了htim句柄指向的实例,或者启动PWM之后又把htim结构体重新初始化了一遍,CCR就可能被重置成初始值。解决办法是保持htim句柄的生命周期稳定,不要重复调用MX_TIMx_Init。

6.4 高级定时器"神秘失效":主输出使能MOE是隐形开关

用TIM1或TIM8做PWM时,还有一个非常隐蔽的坑:初始化完成、调用HAL_TIM_PWM_Start之后,引脚依然没有输出波形。看寄存器,CCR、ARR都对,输出比较配置也没问题。

真正的原因是高级定时器多了一个主输出使能位MOE。只有当MOE置1,定时器的输出引脚才会被激活。HAL库的HAL_TIM_PWM_Start在内部会调用__HAL_TIM_MOE_ENABLE,通常能正常拉开这个开关。但如果你在启动之前手动配置了刹车输入功能,或者使能了定时器的刹车引脚,一旦刹车信号有效,MOE会被硬件自动清零,输出立刻关闭。

调试这个问题的场景我印象很深:波形正常工作了几秒钟,然后突然消失。用手摸了一下芯片,有点热,但没到异常程度。后来在调试器里看寄存器,发现MOE被清掉了,再追下去发现是刹车引脚悬空被噪声误触发了。解决办法是把刹车功能完全禁用,或者把刹车输入引脚拉到确定的电平状态。这个坑在结构简单的Demo里不容易遇到,一旦做到电机驱动、电源控制这种真正要对抗噪声的应用,就会跳出来。

6.5 调节占空比时波形出现毛刺:预装载没开是关键

最后一类常见问题是:程序正常,但示波器观察PWM波形时,偶尔会发现某一个周期的脉宽突然异常,或者调节占空比的过程中波形的边沿位置出现抖动。

这种情况大多是因为关闭了自动重载预装载功能。关闭预装载时,写入ARR和CCR会立即生效。如果你在一个周期中段更新了CCR,这一瞬间的比较值突然改变,输出电平会立刻响应,导致当前周期出现一个不符合预期的脉冲宽度。在电机驱动中,这种异常脉宽会产生额外力矩冲击,严重时会引发过流保护。

道理和前面呼吸灯章节讲的是一样的:只要在PWM运行期间需要动态修改占空比,就应该把Auto-Reload Preload设为Enable,让CCR的更新推迟到更新事件。这是一个举手之劳但极其有效的稳定性手段。

7. 把定时器学到这个程度之后还能往哪些方向走

定时器和PWM的边界绝不止"输出一个方波"。理解这套比较器与计数器协同工作的逻辑之后,向三个方向的延伸会变得很自然:第一是输入捕获,把定时器方向反过来,用外部信号的边沿去更新CCR并记录时间戳,来实现频率测量、脉宽测量或红外遥控解码;第二是编码器接口,通用定时器可以直接接正交编码器输出,读取电机转速和方向,这几乎是轮式机器人项目的标配外设;第三是事件触发联动,用定时器更新事件去触发ADC采样或DMA传输,实现"零CPU参与"的精确采样。

我个人的体会是,定时器是STM32学习路线上第一个真正检验"会不会看数据手册"的分水岭。函数调用谁都会,但PSC和ARR的加一逻辑、预装载的作用、主输出使能这种隐性开关,不在真机上一根头一根头地排查,很难真正记住。如果你刚踩完"灯不亮、波形不对"这类坑,不要急着怀疑是芯片坏了,按本文第六章的排查链路从时钟、复用、模式到MOE逐项过一遍,绝大多数问题半小时内都能定位。最后再分享一个小技巧:口袋里常备一块逻辑分析仪比示波器更实用,因为它能同时抓多路信号,捕获PWM边沿和时间戳,配合软件触发功能,排查"毛刺来自哪一路信号"比示波器直观很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询