4. 定时器与PWM输出:STM32定时器配置、PWM模式设置、输出通道映射
各位同学,咱们今天聊点实在的。PWM 输出是电机控制的命脉,而定时器就是那个发号施令的指挥官。我见过不少新手,代码写得飞起,结果电机就是抖得像筛糠——一查,定时器配置全是坑。这节课,咱们就把 STM32 的定时器怎么配、PWM 模式怎么设、通道怎么映射,掰开了揉碎了讲清楚。
4.1 定时器选型:别拿大炮打蚊子
STM32 的定时器家族分三档:基本定时器、通用定时器、高级定时器。做电机控制,基本定时器基本没用——它连 PWM 输出都不支持。通用定时器是主力,高级定时器是王牌。
| 定时器类型 | PWM 输出 | 互补输出 | 刹车功能 | 适用场景 |
|---|---|---|---|---|
| 基本定时器 (TIM6/7) | 不支持 | 不支持 | 不支持 | 基础定时、触发 DAC |
| 通用定时器 (TIM2/3/4/5) | 支持 | 不支持 | 不支持 | 普通直流电机、舵机 |
| 高级定时器 (TIM1/8) | 支持 | 支持 | 支持 | BLDC、伺服电机、FOC |
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);
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);
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);
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);
4.6 知识体系总览
下面这张图,把定时器配置到 PWM 输出的完整链路画出来了。你照着这个流程走,基本不会出错。
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 输出的核心内容就讲完了。你回去拿示波器量一下波形,看看占空比和频率对不对。有问题随时翻这节课——我写的代码,至少能保证电机转起来不抖。