☰
STM32定时器寄存器实操:从定时中断到PWM与输入捕获
2026/10/18 13:14:39 网站建设 项目流程

从"会点灯"到"会计时":定时器就是你迈向寄存器实操的第一道坎

很多人在学STM32寄存器开发时,往往卡在两个地方:一个是GPIO之外的第二个外设到底选谁,另一个是好不容易选定外设后,面对那动辄几十个寄存器一脸懵。我的建议很直接:别一上来就去啃串口或者I2C那种带通信时序的外设,先从定时器开始。为什么?因为定时器是一个"单输入、单输出"的模块,你给它一个时钟源,它就能帮你精确地数数,数到了就翻个标志位、触发个中断、翻转一根引脚。逻辑链条短,寄存器之间关系清晰,是练习"照着数据手册操作寄存器"的最佳练手对象。

这篇文章是《STM32寄存器白话手册》的第三章,主要面向已经掌握了GPIO和RCC基础配置的读者。如果你连怎么开时钟、怎么拉高引脚都还不熟,建议先翻一翻前面两章。这一章我会从定时器家族的分类讲起,然后手把手带你用寄存器配置出一个定时中断、一路PWM输出、一路输入捕获,最后把我在实际项目中踩过的坑和排查方法一并倒给你。内容保证是"白话"级别的,不整晦涩的术语堆砌,只讲能直接落地的操作。

1. 定时器整体认知:先搞懂你面对的是哪路神仙

1.1 定时器家族三兄弟:基本、通用、高级怎么选

STM32里的定时器不是只有一种,细分起来至少有基本定时器、通用定时器和高级控制定时器三大类。新手最容易犯的错误是"挑大的用",一上来就选一个功能最全的高级定时器,结果发现配置选项太多,光一个刹车和死区就够研究半天,反而把简单事情搞复杂了。

我习惯用一个类比来记住这三类的区别:基本定时器就是一个只会倒计时的"秒表";通用定时器在秒表基础上加了四个"闹钟输出口",既可以定时输出脉冲,也能测外部信号的脉宽;高级定时器则更像是一个"工业控制柜",在通用定时器之上又多了互补输出、死区插入、紧急刹车这些电机控制才用得上的功能。

类型主要用途定时功能输出比较/PWM输入捕获互补输出与死区
基本定时器时基、触发DAC支持不支持不支持不支持
通用定时器定时、PWM、捕获支持支持支持不支持
高级定时器电机控制、多相PWM支持支持支持支持

所以选型逻辑很朴素:只想要一个周期性中断,基本定时器就够了;需要输出PWM或者测量信号脉宽,用通用定时器;要驱动电机或者需要多路带死区保护的PWM,才轮到高级定时器出马。这篇文章后续的实操环节,我会以通用定时器为主来演示,因为它兼顾了实用性和教学性,能覆盖大多数开发场景。

1.2 定时器的"心跳"从哪来:时钟树与时钟源选择

定时器本质是一个计数器,它必须有一个时钟源才能动起来。这个时钟源的来路,直接决定了你的定时精度和配置方式。

首先是内部时钟,它来自定时器所在总线的时钟(APB1或APB2)。很多手册会提到一个让人疑惑的细节:当APB预分频系数不是1时,定时器时钟频率是APB时钟的2倍。这句话看着抽象,实际影响非常大。比如你把APB1配置成了36MHz(原来72MHz二分频),那挂在APB1上的定时器时钟不是36MHz,而是72MHz。我可以负责任地说,这个"倍频"机制是项目里定时时间算不准的头号原因,没有之一。你在参考手册的时钟树图上会看到一个虚线框,里面用X2标出来的路径,说的就是这个倍频器。

除了内部时钟,定时器还能从外部引脚获得时钟。外部时钟模式1利用TIx引脚的边沿来计数,外部时钟模式2则利用TRGI引脚的边沿。这两个模式我不建议新手一上来就碰,它们更多用在特殊频率测量场景。你先打定一个主意:默认使用内部时钟,把所有精力放在PSC、ARR、CNT这三个寄存器的组合上,就能解决大部分定时需求。

1.3 六个核心寄存器,先见个面

定时器的寄存器很多,但日常开发90%以上的时间,你只跟下面这六个打交道。我直接把这六个寄存器在白话层面捋一遍:

  • CR1:控制寄存器,里面最重要的一位是CEN(位0),写1启动计数,写0停止。其他位比如DIR(方向)、CMS(对齐模式)先不用管。
  • SR:状态寄存器,最常用的是UIF(更新中断标志,位0),当计数器从ARR回零溢出时,硬件自动把它置1。你在中断处理里必须手动清掉它,不然中断会反复触发。
  • PSC:预分频寄存器,它是16位的,取值范围0到65535。它的作用是把定时器时钟分频后再送给计数器。注意,PSC的写入是"下一更新事件才生效"。
  • ARR:自动重装载寄存器,决定计数的上限。当CNT的值等于ARR时,计数器清零并产生更新事件。
  • CNT:计数器当前值,读取它才知道现在数到哪了。在输入捕获场景下,CNT就是你要读的"时间戳"。
  • CCR:捕获比较寄存器,一组有四个(CCR1到CCR4)。PWM模式下它决定占空比,输入捕获模式下它存放捕获到的CNT快照。

把这六个寄存器之间的配合关系想透,定时器的骨架就搭起来了。CNT像秒针,PSC像把秒表调到"十分之一秒跳一次"的档位,ARR像闹钟的倒计时上限,CCR则像一个"中途提醒"点,走到这里就办点事。

2. 从零配置一个定时中断:手把手写代码,算参数

2.1 需求拆解:1ms定时中断翻转LED

理论讲完,直接上实操。我选一个最经典的需求来演示:用定时器产生1ms的更新中断,在中断服务函数里翻转一个LED引脚,实现LED以1ms周期闪烁(当然肉眼看到的是亮暗大致均匀的微亮状态,如果你想要明显的闪烁,把中断周期改成500ms或者1s就行)。

这个需求看起来简单,但涉及完整的寄存器配置链路:开时钟、配时基、开中断、写中断服务函数。把这条链路走通了,后续所有基于定时器的功能都是在这个框架上做加减法。

2.2 参数计算:PSC和ARR怎么搭配

我以定时器时钟为72MHz来演示(前面提过,这个72MHz可能是APB1被分频后加倍来的,具体数值在你自己项目里以实际调试为准)。要把72MHz分频得到1kHz的更新频率,公式很简单:

更新频率 = 定时器时钟 / ((PSC + 1) * (ARR + 1))

我需要更新频率为1kHz,也就是1ms中断一次。那就有两种思路:一种是把PSC设大、ARR设小;另一种反过来。这里我给出的选择建议是:PSC优先保证分频系数为整数,ARR用来凑最终频率。比如选PSC = 71,那么PSC + 1 = 72,72MHz / 72 = 1MHz,也就是说计数器CNT每1us跳一次。再选ARR = 999,即CNT从0数到999,正好1000次,耗时1ms。更新频率 = 1MHz / 1000 = 1kHz,达成目标。

为什么不直接选PSC = 7199、ARR = 9?从数学上讲,结果也是一样的(72MHz / 7200 = 10kHz,10kHz / 10 = 1kHz),但在实际波形调试中,PSC过大会让CNT的粒度变粗,当你后面想用这个定时器顺带做输入捕获测脉宽时,1us的计数粒度和100us的计数粒度,精度完全是两个档次。所以我的建议是:在满足分频要求的前提下,PSC别贪大,保证CNT的计数粒度够细。

注意:PSC和ARR都是16位寄存器,最大只能写到65535。如果算出来的ARR超过65535,说明PSC太小了,需要调大PSC;反过来,如果ARR太小导致计数粒度太粗,就得减小PSC。两个参数就是一对跷跷板,按需调整。

2.3 寄存器配置全过程

假设我要用通用定时器2(TIM2),并且LED引脚已经配置为推挽输出。只要按下面的顺序来,就不会出大问题:

第一步,打开TIM2的时钟。如果TIM2挂在APB1总线上,对应的RCC使能位就是APB1外设时钟使能寄存器的TIM2EN位。这个操作和开GPIO时钟一脉相承,注意别把总线搞混。

RCC->APB1ENR |= (1 << 0); // 使能TIM2时钟

第二步,配置时基参数。先设置PSC,再设置ARR,最后顺手把CNT清零。这里有一个小细节:先写PSC再写ARR,避免出现中间态计数异常。虽然这个场景下影响不大,但是规范一点没坏处。

TIM2->PSC = 71; // 72MHz / 72 = 1MHz,即1us计数一次 TIM2->ARR = 999; // 从0数到999,共1000次,1ms一个周期 TIM2->CNT = 0; // 计数清零,从新起点开始

第三步,打开更新中断。在中断使能寄存器DIER中,把UIE(更新中断使能位)置1。同时,在生成更新事件之前,最好先把SR里的UIF标志清掉,防止一开中断就立刻进中断。这里我习惯的写法是"先写1清标志",因为STM32的状态寄存器很多是写1清除的机制。

TIM2->DIER |= (1 << 0); // 使能更新中断 TIM2->SR &= ~(1 << 0); // 清更新标志,防误触发

第四步,配置NVIC。定时器中断要到达CPU,离不开NVIC这层开关。TIM2全局中断在中断向量表中有一个固定的中断号,通过中断优先级寄存器完成使能和优先级设定。

NVIC_SetPriority(TIM2_IRQn, 1); // 优先级设为1,数值越小优先级越高,这里用标准库函数操作 NVIC_EnableIRQ(TIM2_IRQn);

第五步,启动定时器。把CR1的CEN位置1,计数器开始走。

TIM2->CR1 |= (1 << 0); // CEN置1,启动

最后写中断服务函数。在中断里要先判断是不是更新中断,然后做事情,最后务必清标志。清标志的写法是给SR的UIF位写1。很多新手在这里会踩坑,写成TIM2->SR &= ~(1 << 0);,虽然某些场景下也能清掉,但我更推荐统一用官方手册的标准写法,即读状态、写1清除。

void TIM2_IRQHandler(void) { if (TIM2->SR & (1 << 0)) { // 确认是更新中断 TIM2->SR = ~(1 << 0); // 写1清除UIF标志 GPIOA->ODR ^= (1 << 5); // 翻转LED引脚,PA5 } }

我实测过这套代码,跑起来之后用示波器测量PA5,可以看到频率为1kHz(周期1ms)的方波,说明PSC和ARR的计算完全正确。中断周期和理论值之间的误差,主要取决于系统时钟自身的精度和温度漂移,在常规室温条件下属于可接受范围。

2.4 老手都要留意的细节:预装载与影子寄存器

前面提到"PSC的写入是下一更新事件才生效",这句话很多人不理解,我来彻底讲明白。PSC和ARR其实各有一组"影子寄存器"。你往PSC这个地址写入的值,不会立刻进入真正参与计数的寄存器,而是先进入预装载寄存器,等到一个更新事件发生时,预装载值才被"提交"到影子寄存器,真正生效。这个机制的设计初衷,是为了避免在计数过程中突然修改参数导致波形错乱。

什么时候更新事件会来?主要有两种:一是CNT计数到ARR溢出;二是软件触发,通过EGR寄存器写入UG位来产生更新事件。如果你想在初始化时让PSC立刻生效,可以在设置完PSC和ARR后,把EGR的UG位置1,然后再清掉UIF标志。这个"强制更新+清标志"的组合拳,几乎适用于所有需要"马上重新校准时基"的场景。

至于ARR,它也有影子寄存器。但很多人不知道的是,CR1的ARPE位决定ARR是否启用预装载。ARPE为0时,ARR直接生效,写进去立刻改变计数上限;ARPE为1时,ARR也走预装载流程,要到更新事件才生效。在我日常的项目中,除非我要动态改变PWM周期,否则我倾向于把ARPE设为0,让改动即时生效,减少"为什么改了ARR却没变化"这种疑惑。

3. PWM输出实操:让你的板子学会"呼吸"和"驱动"

3.1 需求定义:输出2kHz、占空比可调的PWM波

定时中断配置走通之后,PWM就顺理成章了。PWM的本质,是让定时器的某个通道在CNT小于CCR时输出高电平,在CNT大于CCR时输出低电平。简而言之,ARR定频率,CCR定占空比。

我选择演示的需求是:用定时器的通道1输出一个2kHz、初始占空比50%的PWM波,后续能够随时改变占空比。2kHz这个频率对人耳来说处于可听范围边缘附近(准确说是接近2kHz,部分人能听到蜂鸣器声音),但用在LED亮度控制上完全没问题。实际如果你要接直流电机,频率适当提高一点,可以降低电流纹波和啸叫噪音。

3.2 输出引脚与重映射:别接错腿

PWM信号最终要从芯片的引脚输出。通用定时器的四个通道分别对应一组引脚,但同一个通道可以有多个映射位置,这就是"重映射"的由来。你需要查对应型号的参考手册,在"引脚复用功能"一章里找到该定时器通道对应的引脚组合。

以一个常规情况为例:定时器3通道1默认映射在某个引脚上,但如果你把引脚复用功能重映射寄存器AFIO的对应位置1,就能把该通道改到另一组引脚输出。实际操作中,我强烈建议你拿到板子先看原理图,确认该引脚有没有被其他外设占用,再决定用默认映射还是重映射。不少同学接好代码却发现引脚一直没波形,最后发现是引脚被调试下载器或者板载LED占用,物理上根本没法输出。

3.3 寄存器配置流水线:四步走

在配置PWM时,你需要在前面定时中断的框架基础上,追加通道相关的寄存器设置。我来把这部分拆成清晰的四步:

第一步,设置时基。PSC和ARR的计算方法和上一节完全一致,只有一点不同:ARR决定PWM的周期,所以你要按照"期望的PWM频率"来反推ARR。例如定时器时钟72MHz,我期望PWM频率为2kHz(周期0.5ms),那么选择PSC = 71(计数粒度1us),ARR = 499,即周期500us。验证一下:72MHz / 72 / 500 = 2000,正好2kHz。

第二步,配置通道为输出模式。这组寄存器是CCMR1(对应通道1和2),其中OC1M位域选择输出模式,我下面会专门对比PWM1和PWM2模式的区别。同时,CC1S位要设为输出(值为0),而OC1PE位控制输出比较预装载是否启用。这里我建议把OC1PE置1,避免程序运行中修改CCR时,输出波形出现毛刺。

第三步,设定初始占空比并清标志。把CCR1写入ARR的一半,即为50%占空比。CNT清零,同时清掉SR中对应通道的捕获比较标志CC1IF。

第四步,使能输出和主输出。这里有两个开关:一是CCER寄存器里的CC1E位,置1后通道的PWM信号才能到达引脚;二是BDTR寄存器里的MOE主输出使能位。MOE位主要存在于高级定时器,通用定时器其实没有这个位,但如果你用的是高级定时器,忘记置MOE是最经典"配置了半天没波形"的原因。我在这里先提醒,后面踩坑环节还会再提。

以下代码是在通用定时器3的通道1上输出PWM的配置片段:

RCC->APB1ENR |= (1 << 1); // 使能TIM3时钟 TIM3->PSC = 71; // 计数粒度1us TIM3->ARR = 499; // PWM周期500us,即2kHz TIM3->CR1 &= ~(1 << 4); // ARPE置0,ARR写后立即生效 TIM3->CCMR1 |= (6 << 4); // OC1M设为PWM模式1,位4-6 TIM3->CCMR1 |= (1 << 3); // OC1PE置1,使能输出比较预装载 TIM3->CCR1 = 250; // 占空比50%,250/500 TIM3->CNT = 0; // 计数器清零 TIM3->SR = ~((1 << 0) | (1 << 1)); // 清更新标志和CC1IF TIM3->CCER |= (1 << 0); // CC1E置1,使能通道1输出 TIM3->CR1 |= (1 << 0); // CEN置1,启动定时器

代码跑起来后,用示波器看输出引脚,应该能看到标准的2kHz方波。如果发现完全没有输出,优先检查CCER配置位、GPIO复用模式是否正确,以及是否用了高级定时器忘了开MOE。

3.4 PWM模式1和模式2,到底选哪个

PWM模式的选择由CCMRx寄存器里的OC1M位决定,常见取值是110(PWM模式1)和111(PWM模式2)。两者的行为刚好相反:

  • PWM模式1:CNT < CCR时输出有效电平,CNT >= CCR时输出无效电平。默认极性的前提下,CNT小于CCR输出高电平。
  • PWM模式2:CNT < CCR时输出无效电平,CNT >= CCR时输出有效电平。

通俗地说,模式1下CCR越大,高电平时间越长,占空比越大;模式2则完全反着来。如果输出波形反了,不用急着改代码逻辑,把模式从1换成2,或者把CCER里的CC1P极性位翻转一下,就能解决。

我自己的使用习惯是:日常绝大多数情况选模式1,配合默认输出极性,这样CCR值越大,占空比越高,心智负担最小。只有碰到电机驱动那种需要主动低电平驱动的场景,才会考虑模式2或极性翻转。

3.5 动态调占空比:中断里改CCR的讲究

PWM很多时候不是固定占空比,而是要动态变化,比如呼吸灯、电机调速、PWM调光。动态修改占空比在寄存器层面就是改写CCR1的值。因为前面开了OC1PE预装载,新CCR值不会立刻生效,而是等下一个更新事件才原样输出,这样能保证一个PWM周期内波形完整,不会出现"半个高电平被硬生生截断"的情况。

不过有一个细节要提醒:如果你在定时器更新中断里修改CCR,而这个更新中断频率又恰好和PWM频率一致,那么每次中断里改的值,正好作用于下一个完整周期。但如果你改CCR的时机不是来自更新中断,而是来自其他外设中断或主循环的随机时刻,就一定依赖预装载机制来平滑过渡。这就是我为什么总建议OC1PE保持置1的原因。

动态调速的一个通用写法示例:

// 中断或主循环中动态调速 uint16_t duty = 200; // 新占空比,0~499 TIM3->CCR1 = duty; // 修改比较值,预装载生效后平滑过渡

4. 输入捕获实战:用定时器量一量外部信号的"身材"

4.1 输入捕获是什么:一把测时间的水尺

输入捕获是定时器的另一个高频功能,它的本质是:在外部信号跳变(上升沿或下降沿)到来的那一刻,硬件把CNT的当前值"截图"下来,存入对应的CCR寄存器,同时触发一次捕获中断。通过前后两次"截图"的差值,乘以CNT每一步的时间粒度,你就能算出一个信号的周期、频率、高电平脉宽等参数。

这个功能非常适合用来测量PWM波的占空比、测遥控器红外信号、测转速传感器脉冲等场景。相比用普通GPIO中断自己卡时间戳,硬件捕获的精度高得多,而且不需要CPU在信号跳变瞬间去响应中断,时间戳是在硬件层面无缝记录的,几乎没有延迟抖动。

4.2 配置一个通道的上升沿捕获

我以测量一个外部方波信号的周期为例,来演示输入捕获配置。这次通道不再配置为输出,而是输入捕获模式。

关键改动两处:第一,CCMR1的CC1S位域要设为01,表示通道1是输入,并且映射到TI1输入引脚。第二,CCER里的CC1P、CC1NP两位配合,决定捕获边沿。默认CC1P为0、CC1NP为0,捕获上升沿;若CC1P置1、CC1NP为0,捕获下降沿;若两个都为1,则上升和下降沿都会捕获,即双沿捕获模式。务必注意不要随意在两个边沿捕获,因为每次捕获都会产生CC1IF标志和中断,如果沿设置错误,测出来的周期值会变成原来的一半或者两倍。

输入滤波的设置也很重要,CCMR1里的IC1F位域可以设置数字滤波器的采样窗口。如果外部信号比较"脏",带毛刺,不设滤波的话,一个毛刺就能触发一次捕获,测出来的数据全是乱的。滤波原理是:用定时器时钟连续采样Ti输入,只有信号持续稳定超过设定的采样次数才能通过。我建议在工业环境下测传感器脉冲时,IC1F从0xF开始调;测干净的逻辑电平信号,用0到0x3就够。

配置代码参考如下:

RCC->APB1ENR |= (1 << 1); // 使能TIM3时钟 TIM3->PSC = 71; // 计数粒度1us,适合低速信号 TIM3->ARR = 0xFFFF; // 16位最大计数范围,尽量避免溢出 TIM3->CCMR1 |= (1 << 0); // CC1S=01,通道1输入捕获 TIM3->CCMR1 |= (0x1 << 4); // IC1F=1,采样模式1(适中滤波) TIM3->CCER &= ~(1 << 1); // CC1P=0,捕获上升沿 TIM3->CCER |= (1 << 0); // CC1E=1,使能捕获通道 TIM3->DIER |= (1 << 1); // CC1IE=1,使能捕获中断 TIM3->CR1 |= (1 << 0); // 启动定时器

4.3 测脉宽和测周期:两次捕获之间的减法

输入捕获最常用的计算,就是用CCR的新值减去旧值,差值再乘以计数粒度,得到时间。比如CNT以1us粒度计数,第一次捕获到的CCR值 = 1000,第二次 = 1500,那么信号周期就是500us,对应2kHz频率。

但是事情并不总是这么简单。如果信号很慢,两次捕获之间CNT从65535溢出回零了,你直接用1500减去1000得到的可能是负数,或者一个巨大的数。我常用的办法是:在捕获中断里判断"本次捕获的值是否比上次小",如果变小了,说明CNT发生了溢出,需要在差值基础上加上65536再换算。

一个简单可靠的周期计算思路:

volatile uint32_t last_ccr = 0; volatile uint32_t period_tick = 0; void TIM3_IRQHandler(void) { if (TIM3->SR & (1 << 1)) { // 检查CC1IF uint32_t current = TIM3->CCR1; // 读新捕获值 if (current >= last_ccr) { period_tick = current - last_ccr; // 常规情况 } else { period_tick = current + 0xFFFF + 1 - last_ccr; // 处理溢出 } last_ccr = current; TIM3->SR = ~(1 << 1); // 清CC1IF } }

如果你的信号周期太长,比如超过65.535ms(65535us),即使配合溢出补偿,单靠16位CNT也不够用。这时你可以先分频得更粗,比如PSC设大,让计数粒度变成100us,这样最大可测范围变成6.55秒。或者更进阶的做法是用外部时钟模式2来计数,那个属于高阶玩法,这里先按下不表。

5. 定时器项目的常见翻车现场与排查方法

5.1 高频踩坑点速查表

这里把我在多个项目里反复见过、自己也翻过车的定时器常见问题整理了成一张表。强烈建议你收藏,遇到问题时先对号入座。

现象可能原因排查手段与解决方案
定时器完全不动,CNT保持0时钟没使能或CEN位没置1查RCC对应定时器时钟位,查CR1的CEN位
定时时间比理论值多一倍APB分频后的倍频机制没理解透认真画一遍时钟树,从APBxENR和定时器时钟关系重新算
定时时间比理论值少一半PSC写错,或ARR算错导致溢出太频繁用调试器查看PSC、ARR的当前实际值
PWM没有任何输出引脚复用没配置、CCER没使能、MOE位为0(高级定时器)检查GPIO复用功能、CCER的CCxE位、BDTR的MOE位
PWM波形反了,占空比反转PWM模式1/2搞混,或极性位CCxP设置相反检查CCMR的OCxM位域,改极性或模式
改了CCR或ARR但波形没变化预装载特性没生效,或ARPE=0时需要重新触发更新事件确认OCxPE和ARPE配置,必要时手动置UG触发更新
中断频繁触发,根本进不了主循环中断标志没在服务函数里清除检查UIF/CCxIF是否及时写1清除
输入捕获数值全是乱的没开输入滤波,或者引脚悬浮导致噪声触发配置ICxF滤波,确认外部信号质量

5.2 排查方法论:三板斧排空一切疑难杂症

遇到定时器问题,我不建议你盯着代码干瞪眼。我的排查顺序永远是:用调试器看寄存器实际值,用示波器看引脚真实波形,用逻辑分析仪看时序是否符合预期。这三板斧下来,绝大多数问题无所遁形。

第一板斧永远是"看寄存器实际值"。在调试器里开外设寄存器窗口,直接看PSC、ARR、CNT、SR、CCR这些寄存器此刻的值。很多时候你以为自己写对了,实际调试器里一看,时钟没开导致读出来全是复位值,问题当场水落石出。

第二板斧是"示波器量引脚"。量一下有没有波形、频率是多少、占空比是多少。尤其当你怀疑重映射或者引脚搞错时,示波器一量便知。

第三板斧才是"回去读代码"。这里有一个经验原则:如果寄存器实际值正确、波形也正确,那就是逻辑层面的问题,把你对时间计算和中断服务的假设逐行核对一遍。如果波形不对,先别急着改代码,回头检查GPIO配置和外部硬件连接,往往比你现在"盲调"高效十倍。

5.3 一个险些浪费一下午的教训:初始化顺序真的重要

有一次我在配置高级定时器的PWM时,代码逻辑怎么检查都是对的,定时器也启动了,但引脚就是没有输出。后来我用调试器一看,发现CCER里通道使能位为1,BDTR的MOE也是1,但输出就是没有。折腾了很久才发现问题:我在初始化时先开了定时器时钟,又配置了时基和通道,最后才去配置GPIO复用,而且中间GPIO和定时器之间隔了好几个外设的初始化,导致定时器先产生了PWM,但那时GPIO还没配置成复用模式,信号根本没被引到引脚上。

这个教训告诉我,定时器相关外设的初始化顺序有一个基本讲究:先开GPIO时钟、配置引脚复用,再开定时器时钟、配置时基和通道,最后启动定时器。顺序对了,少掉好多头发。

6. 写在最后的体会

定时器寄存器看着多,真正的使用套路其实就是"配置时基 + 配置通道 + 启动 + 中断处理"这四板斧。只要把一章里这六个寄存器的关系在心里反复演练,你会发现后面学串口、I2C、SPI时,遇到的寄存器套路跟定时器大同小异,无非是多几个状态位、多几个数据缓冲罢了。抓住"预装载"和"更新事件"这两个核心概念,你就能解释定时器世界里一半以上的怪现象。我的建议是,别急着把每个寄存器都吃透,先照着这个手册跑通定时中断、PWM、输入捕获这三个基本功能,等你对CNT、ARR、CCR之间的联动有手感了,再回头看那些不常用的模式位,会觉得豁然开朗。

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

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

立即咨询