3、STM32定时器PWM输出:定时器概述、PWM模式配置、频率与占空比计算
好,咱们进入正题。PWM 这东西,说白了就是通过快速开关信号,让负载感受到一个“平均电压”。STM32 里谁干这活?定时器。我刚开始用 STM32 做电机驱动时,就被定时器那堆寄存器绕晕过。后来摸清了门道,发现其实就那么几件事:选定时器、配模式、算参数。
3.1 STM32定时器家族概览
STM32 的定时器分三类:基本定时器、通用定时器、高级定时器。PWM 输出主要靠后两种。
| 类型 | 典型型号 | PWM通道数 | 主要特性 |
|---|---|---|---|
| 基本定时器 | TIM6, TIM7 | 0 | 仅基础计时,无PWM |
| 通用定时器 | TIM2~TIM5 | 4 | 标准PWM、输入捕获、编码器 |
| 高级定时器 | TIM1, TIM8 | 4+3 | 互补PWM、死区插入、刹车功能 |
我个人习惯,做普通 LED 调光或舵机控制,直接用通用定时器就够了。要是搞电机驱动,尤其是 H 桥那种需要互补信号的,必须上高级定时器。嗯,这里要注意:高级定时器的通道 1~3 可以输出互补 PWM,还能加死区时间,防止上下管直通。
3.2 PWM输出模式配置
配置 PWM 输出,核心就三步:
- 设置时基:决定 PWM 的周期(频率)。
- 设置比较值:决定占空比。
- 配置输出通道:让引脚输出 PWM 波形。
咱们拿 STM32F103 的 TIM3 通道 1 举个例子。假设系统时钟 72MHz,想要 1kHz 的 PWM,占空比 50%。
// 1. 使能定时器时钟和GPIO时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置PA6为复用推挽输出(TIM3_CH1)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置时基:预分频器72-1,自动重装1000-1
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 1MHz / 1000 = 1kHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 4. 配置PWM模式1,比较值500(50%占空比)
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // 占空比 = 500/1000 = 50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
// 5. 使能TIM3
TIM_Cmd(TIM3, ENABLE);
关键点: 预分频器 TIM_Prescaler 和自动重装值 TIM_Period 都要减 1,因为寄存器是从 0 开始计数的。我见过新手直接写 72 和 1000,结果频率完全不对。
3.3 PWM频率与占空比计算
频率和占空比的计算公式其实很简单:
- PWM频率 = 定时器时钟 / (预分频器 + 1) / (自动重装值 + 1)
- 占空比 = 比较值 / (自动重装值 + 1) × 100%
拿上面的例子:72MHz / (72) / (1000) = 1kHz。占空比 = 500 / 1000 = 50%。
为什么会这样?你想想看,定时器从 0 往上数到自动重装值,每数一次就是一个计数周期。比较值就是在这条数轴上画一条线——计数值小于比较值时输出高电平,大于时输出低电平(PWM模式1)。
3.4 核心逻辑:PWM生成流程
下面这张图是我自己总结的,把整个 PWM 生成过程串起来。你看一遍应该就能理解定时器是怎么“变”出 PWM 的。
这张图从左到右看:时钟先经过预分频器降频,然后喂给计数器。计数器从 0 往上数,数到 ARR 值就归零重来。同时,计数器的值一直在和 CCR 比较——这就是 PWM 的核心逻辑。
3.5 实际应用中的参数选择
不同应用对 PWM 频率和分辨率的要求不一样。我整理了一个参考表:
| 应用场景 | 推荐频率 | 分辨率要求 | 说明 |
|---|---|---|---|
| LED调光 | 100Hz ~ 1kHz | 8~10位 | 避免闪烁,人眼不敏感即可 |
| 舵机控制 | 50Hz | 8~12位 | 标准舵机周期20ms |
| 直流电机 | 10kHz ~ 20kHz | 8~12位 | 避开人耳听觉范围,减少啸叫 |
| 开关电源 | 50kHz ~ 500kHz | 10~16位 | 频率越高,电感越小,但开关损耗大 |
3.6 常见配置错误与排查
嗯,这里我总结几个自己踩过的坑:
- 引脚没配置成复用推挽:GPIO 模式必须是
GPIO_Mode_AF_PP,否则输出不了 PWM 波形。 - 高级定时器没使能主输出:TIM1 和 TIM8 需要调用
TIM_CtrlPWMOutputs(TIM1, ENABLE),否则引脚一直是低电平。 - 预分频器和自动重装值搞反:预分频器决定计数时钟频率,自动重装值决定周期,两者一起决定最终频率。
- 占空比设为 0 或 100%:CCR 设为 0 时输出一直低,设为 ARR+1 时输出一直高。有些外设(如 MOSFET 驱动)不允许 100% 占空比,要注意。
我曾经在一个量产项目中,因为高级定时器的 TIM_CtrlPWMOutputs 没调用,导致整批板子的电机都不转。排查了两天才发现,从此以后我把这个函数写进了初始化模板里。
好了,这一章的内容就这些。PWM 配置其实不复杂,关键是理解时基、比较值和输出通道这三者的关系。你动手写一遍代码,再拿示波器看看波形,比看十遍文档都管用。