4. PWM初始化代码:定时器时钟配置、预分频器与自动重装载值计算、PWM模式设置
好,咱们直接进入正题。PWM 的初始化,说白了就是让定时器按照我们想要的方式去工作。你想想看,一个定时器要输出 PWM,它得知道三件事:时钟跑多快、计数到多少翻转、输出引脚怎么动作。这三件事搞定了,PWM 就出来了。
我个人习惯把初始化拆成三个步骤:时钟配置 → 时序参数计算 → 模式与输出设置。下面我一个一个讲。
4.1 定时器时钟配置——先让定时器转起来
定时器本身是挂在总线上的外设,你得先给它时钟。以 STM32F4 为例,高级定时器 TIM1 和 TIM8 挂在 APB2 上,通用定时器挂在 APB1 上。这里有个坑——APB 预分频系数不为 1 时,定时器时钟会翻倍。
时钟使能的代码很简单,以 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
我在项目中遇到过一个问题:电机控制需要 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;
}
4.4 知识体系总览
下面这张图把 PWM 初始化的三个步骤串起来了,你可以对照着看:
4.5 几个容易踩的坑
最后,我把自己这些年踩过的坑列一下,你写代码的时候可以对照着检查:
- 时钟没使能就写寄存器——写了也白写,读回来全是默认值。我建议你在初始化函数第一行就使能时钟。
- PSC 和 ARR 的 +1 忘了算——寄存器里的值是 N-1,不是 N。比如你要 100 分频,PSC 要写 99。
- 高级定时器的 MOE 位没置位——这个位在 BDTR 寄存器里,默认是 0,输出被强制关闭。我吃过这个亏。
- 预装载没使能——如果不使能 ARPE,CCR 的值会立即生效,可能导致 PWM 周期内出现不完整的脉冲。建议始终使能预装载。
- GPIO 复用功能没配——定时器的输出通道要配置成复用推挽输出,别漏了这一步。