4. 定时器与PWM输出:STM32定时器配置、PWM模式设置、输出通道映射

各位同学,咱们今天聊点实在的。PWM 输出是电机控制的命脉,而定时器就是那个发号施令的指挥官。我见过不少新手,代码写得飞起,结果电机就是抖得像筛糠——一查,定时器配置全是坑。这节课,咱们就把 STM32 的定时器怎么配、PWM 模式怎么设、通道怎么映射,掰开了揉碎了讲清楚。

4.1 定时器选型:别拿大炮打蚊子

STM32 的定时器家族分三档:基本定时器、通用定时器、高级定时器。做电机控制,基本定时器基本没用——它连 PWM 输出都不支持。通用定时器是主力,高级定时器是王牌。

定时器类型 PWM 输出 互补输出 刹车功能 适用场景
基本定时器 (TIM6/7) 不支持 不支持 不支持 基础定时、触发 DAC
通用定时器 (TIM2/3/4/5) 支持 不支持 不支持 普通直流电机、舵机
高级定时器 (TIM1/8) 支持 支持 支持 BLDC、伺服电机、FOC
我的建议:做 FOC 或者 BLDC 控制,直接上高级定时器。别问我为什么——互补 PWM 和刹车功能,关键时刻能救你的 MOSFET 一命。

4.2 定时器时基配置:算清楚再动手

配置定时器,核心就三个参数:预分频器(PSC)、自动重装载值(ARR)、计数模式。PWM 频率的计算公式很简单:

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

举个例子。我用 STM32F103,定时器挂载在 APB1 上,时钟 72MHz。想要 20kHz 的 PWM 频率——这是电机控制的常见频率,人耳听不到,电机也不叫。

// 目标:20kHz PWM
// 72MHz / (PSC+1) / (ARR+1) = 20000
// 取 PSC = 71,则 (ARR+1) = 72000000 / 72 / 20000 = 50
// 所以 ARR = 49

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 71;        // 预分频 72
TIM_TimeBaseStructure.TIM_Period = 49;           // 自动重装载 50
TIM_TimeBaseStructure.TIM_ClockDivision = 0;     // 不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
注意:PSC 和 ARR 都是 16 位寄存器,最大值 65535。别算出来超了范围,否则定时器直接罢工。我曾经在项目里把 PSC 算成了 70000,查了三天才找到原因——嗯,说出来都是泪。

4.3 PWM 模式设置:边沿对齐 vs 中央对齐

STM32 的 PWM 模式有两种:边沿对齐(Mode 1 / Mode 2)和中央对齐(Mode 1 / Mode 2 / Mode 3)。

  • 边沿对齐模式:计数器从 0 数到 ARR,然后跳回 0。PWM 信号在计数值与 CCR 比较时翻转。简单、高效,适合大多数电机控制。
  • 中央对齐模式:计数器从 0 数到 ARR,再倒着数回 0。一个周期内比较两次,PWM 脉冲居中。谐波更少,但频率减半。

我个人习惯,普通直流电机用边沿对齐就够了。做 FOC 或者对噪声敏感的场景,才上中央对齐。

// 边沿对齐 PWM 模式 1:CNT < CCR 时输出有效电平
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 25;  // 50% 占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
关键点:占空比 = CCR / (ARR + 1)。ARR=49 时,CCR=25 就是 50% 占空比。别把 CCR 设得比 ARR 还大,否则输出一直高电平——电机全速跑,停不下来。

4.4 输出通道映射:找到对的引脚

定时器的 PWM 信号最终要送到 GPIO 引脚。STM32 的引脚复用功能(AFIO)决定了哪个定时器通道对应哪个引脚。查数据手册的「Alternate Function Mapping」表是基本功。

以 TIM3 为例,它的通道映射关系:

定时器通道 默认引脚 重映射引脚
TIM3_CH1 PA6 PC6
TIM3_CH2 PA7 PC7
TIM3_CH3 PB0 PC8
TIM3_CH4 PB1 PC9

配置 GPIO 为复用推挽输出:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
避坑指南:我曾经在 STM32F103 上做六步换相,用了 TIM1 的三个通道,结果发现 CH2 和 CH3 的引脚和 SPI 冲突了。后来查了重映射表,把 TIM1 的部分通道重映射到其他引脚才解决。所以,画 PCB 之前,一定先把引脚分配表画好。

4.5 高级定时器的特殊配置

高级定时器(TIM1/TIM8)多了几个东西:互补输出、死区插入、刹车功能。做 BLDC 或者伺服控制,这些是标配。

互补输出就是同一个通道输出两路相反的 PWM 信号,一路给上桥臂,一路给下桥臂。死区时间防止上下管直通——说白了就是让上下管切换时有个短暂的「都关断」时间。

// 死区时间设置:以 TIM1 为例
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
TIM_BDTRInitStructure.TIM_DeadTime = 72;  // 1us 死区(72MHz 时钟)
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
警告:高级定时器配置完 PWM 后,必须使能主输出(TIM_CtrlPWMOutputs(TIM1, ENABLE)),否则引脚上不会有信号。我见过有人调了一整天,最后发现是这行代码没写——嗯,那个人就是我。

4.6 知识体系总览

下面这张图,把定时器配置到 PWM 输出的完整链路画出来了。你照着这个流程走,基本不会出错。

定时器与PWM输出配置流程 定时器与PWM输出配置流程 选择定时器类型 时基配置 (PSC/ARR) 设置PWM模式 GPIO通道映射 使能PWM输出 关键参数 • 定时器时钟源 • 预分频器 PSC • 自动重装载 ARR • 比较值 CCR • 计数模式 • PWM模式 1/2 • 输出极性 • 引脚复用功能 • 死区时间(高级) • 刹车功能(高级) • 主输出使能 配置顺序 从上到下 不可颠倒

4.7 完整配置示例

最后,给一个完整的 TIM3 输出 PWM 的代码框架。你照着这个模板改参数就行。

void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 1. 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 2. 配置 GPIO 为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 3. 配置时基
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_Period = 49;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 4. 配置 PWM 模式
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 25;  // 50% 占空比
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);
    
    // 5. 使能定时器
    TIM_Cmd(TIM3, ENABLE);
}

嗯,到这里,定时器和 PWM 输出的核心内容就讲完了。你回去拿示波器量一下波形,看看占空比和频率对不对。有问题随时翻这节课——我写的代码,至少能保证电机转起来不抖。