4. PWM初始化代码:定时器时钟配置、预分频器与自动重装载值计算、PWM模式设置

好,咱们直接进入正题。PWM 的初始化,说白了就是让定时器按照我们想要的方式去工作。你想想看,一个定时器要输出 PWM,它得知道三件事:时钟跑多快、计数到多少翻转、输出引脚怎么动作。这三件事搞定了,PWM 就出来了。

我个人习惯把初始化拆成三个步骤:时钟配置 → 时序参数计算 → 模式与输出设置。下面我一个一个讲。

4.1 定时器时钟配置——先让定时器转起来

定时器本身是挂在总线上的外设,你得先给它时钟。以 STM32F4 为例,高级定时器 TIM1 和 TIM8 挂在 APB2 上,通用定时器挂在 APB1 上。这里有个坑——APB 预分频系数不为 1 时,定时器时钟会翻倍。

经验之谈: 我刚开始做项目时,查手册看到 APB1 最大 42MHz,就以为定时器时钟也是 42MHz。结果算出来的 PWM 频率全偏了。后来才发现,只要 APB1 预分频系数 > 1,定时器时钟 = APB1 时钟 × 2。说白了,这是芯片设计者为了让定时器跑得更快而留的后门。

时钟使能的代码很简单,以 TIM3 为例:

// 使能 TIM3 时钟(挂在 APB1 上)
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;

// 如果用了 TIM1(挂在 APB2 上)
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;

嗯,这里要注意:使能时钟之后,定时器寄存器才能被访问。我曾经见过有人忘了这一步,结果写寄存器怎么都写不进去,查了半天才发现时钟没开。

4.2 预分频器与自动重装载值计算——频率和占空比怎么定

这是 PWM 初始化里最核心的计算部分。公式其实很简单:

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

其中 PSC 是预分频器,ARR 是自动重装载值。这两个值决定了 PWM 的周期。

举个例子,假设定时器时钟是 84MHz,我想要一个 10kHz 的 PWM:

// 目标频率:10kHz
// 定时器时钟:84MHz
// 公式:84000000 / ((PSC+1) * (ARR+1)) = 10000

// 方案一:PSC = 83, ARR = 99
// 84000000 / (84 * 100) = 10000 Hz

// 方案二:PSC = 839, ARR = 9
// 84000000 / (840 * 10) = 10000 Hz
关键点: 方案一和方案二虽然频率一样,但分辨率完全不同。方案一的 ARR = 99,占空比可以调 0~99 共 100 级;方案二的 ARR = 9,只能调 10 级。所以 PSC 尽量小,ARR 尽量大,这样占空比调节更精细。

我在项目中遇到过一个问题:电机控制需要 20kHz 的 PWM,但占空比分辨率要求 0.1%。算了一下,ARR 至少要 1000。如果定时器时钟是 72MHz,那 PSC 就不能超过 3。嗯,这个约束条件一定要提前想清楚。

占空比的计算就更直接了:

// 占空比 = CCR / (ARR + 1)
// 比如要 50% 占空比,ARR = 999
TIM3->CCR1 = 500;  // 50%

4.3 PWM 模式设置——输出极性、对齐方式、预装载

频率和占空比算好了,接下来告诉定时器怎么输出。这里要配置几个寄存器:

  • TIMx_CCMRx:设置通道为 PWM 模式(模式 1 或模式 2)
  • TIMx_CCER:使能输出、设置极性
  • TIMx_CR1:设置计数方向(向上/向下/中央对齐)
  • TIMx_BDTR:高级定时器需要配置刹车和死区

我常用的 PWM 模式 1 和模式 2 的区别:

模式 计数方向 输出电平变化 典型应用
PWM 模式 1 向上计数 CNT < CCR 时有效,CNT >= CCR 时无效 通用 PWM,占空比随 CCR 增大而增大
PWM 模式 2 向上计数 CNT < CCR 时无效,CNT >= CCR 时有效 反相 PWM,占空比随 CCR 增大而减小

完整的初始化代码示例:

void PWM_Init(void)
{
    // 1. 时钟配置
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    
    // 2. 时序参数:84MHz 时钟,10kHz PWM,1000 级分辨率
    TIM3->PSC = 83;      // 预分频:84MHz / (83+1) = 1MHz
    TIM3->ARR = 99;      // 自动重装载:1MHz / (99+1) = 10kHz
    
    // 3. PWM 模式设置
    // 通道 1 配置为 PWM 模式 1,预装载使能
    TIM3->CCMR1 |= (6 << 4);   // OC1M = 110 (PWM 模式 1)
    TIM3->CCMR1 |= TIM_CCMR1_OC1PE;  // 预装载使能
    
    // 4. 输出使能和极性
    TIM3->CCER |= TIM_CCER_CC1E;      // 使能通道 1 输出
    // TIM3->CCER |= TIM_CCER_CC1P;    // 如果需要高电平有效,注释掉这行
    
    // 5. 自动重装载预装载使能
    TIM3->CR1 |= TIM_CR1_ARPE;
    
    // 6. 使能计数器
    TIM3->CR1 |= TIM_CR1_CEN;
    
    // 7. 初始占空比设为 50%
    TIM3->CCR1 = 50;
}
注意: 高级定时器(TIM1/TIM8)需要额外配置 TIMx_BDTR 寄存器中的 MOE 位(主输出使能),否则 PWM 不会输出。我曾经在调试 BLDC 驱动时,死活看不到波形,最后发现是 MOE 没置位。这个位在刹车和死区寄存器里,很容易被忽略。

4.4 知识体系总览

下面这张图把 PWM 初始化的三个步骤串起来了,你可以对照着看:

PWM 初始化知识体系 ① 时钟配置 ② 时序参数计算 ③ 模式与输出设置 使能 RCC 对应总线时钟 确认 APB 预分频系数 PSC:预分频器 ARR:自动重装载值 CCR:比较值(占空比) CCMR:PWM 模式 1/2 CCER:输出使能 + 极性 CR1:计数方向 + 预装载 BDTR:MOE 使能(高级定时器) 核心公式与约束 • PWM 频率 = 定时器时钟 / ((PSC+1) × (ARR+1)) • 占空比分辨率 = 1 / (ARR+1) × 100% • 设计原则:PSC 尽量小,ARR 尽量大,以获得更高分辨率

4.5 几个容易踩的坑

最后,我把自己这些年踩过的坑列一下,你写代码的时候可以对照着检查:

  • 时钟没使能就写寄存器——写了也白写,读回来全是默认值。我建议你在初始化函数第一行就使能时钟。
  • PSC 和 ARR 的 +1 忘了算——寄存器里的值是 N-1,不是 N。比如你要 100 分频,PSC 要写 99。
  • 高级定时器的 MOE 位没置位——这个位在 BDTR 寄存器里,默认是 0,输出被强制关闭。我吃过这个亏。
  • 预装载没使能——如果不使能 ARPE,CCR 的值会立即生效,可能导致 PWM 周期内出现不完整的脉冲。建议始终使能预装载。
  • GPIO 复用功能没配——定时器的输出通道要配置成复用推挽输出,别漏了这一步。
调试小技巧: 初始化完成后,先别急着调占空比。用示波器看看 PWM 频率对不对,再检查一下极性。我一般会先设一个 50% 的占空比,看波形是否对称。如果频率对了但占空比不对,多半是 CCR 的计算或者极性配置出了问题。