第1章:定时器与PWM — 从原理到实战
各位同学,咱们今天聊聊剃须刀电机控制里最核心的一环——定时器与PWM。说实话,我刚开始做电机驱动那会儿,觉得PWM不就是个方波嘛,有啥好研究的?直到有一次,我调了一整天的电机转速,发现怎么调都不对劲,最后才发现是定时器的预分频器配错了。嗯,从那以后,我再也不敢小看这个基础模块了。
1.1 STM32定时器概述
STM32的定时器,说白了就是个会数数的硬件。你给它一个时钟源,它就开始计数,计到某个值就触发事件。我个人习惯把定时器分成三类:
- 基本定时器:功能最简单,只能计时。适合做延时、触发ADC采样这类活儿。
- 通用定时器:带PWM输出、输入捕获、输出比较。电机控制的主力军。
- 高级定时器:在通用基础上加了互补输出、死区插入、刹车功能。三相电机驱动必备。
你想想看,剃须刀电机通常就一个直流无刷电机,用通用定时器就足够了。我见过有人拿高级定时器去驱动单相电机,其实有点大材小用。
关键点:定时器的核心是计数器、预分频器、自动重装载寄存器这三个东西。计数器决定当前计数值,预分频器决定计数速度,自动重装载决定周期。
1.2 PWM模式配置
PWM模式,其实就是让定时器的输出引脚在计数值匹配时翻转电平。STM32支持两种PWM模式:
- PWM模式1:向上计数时,计数值小于比较值时输出有效电平,大于时无效。
- PWM模式2:反过来,小于时无效,大于时有效。
我个人习惯用PWM模式1,因为逻辑更直观——你想让占空比大一点,就把比较值设大一点。我在项目中遇到过一个问题:电机低速时抖动得厉害,后来发现是PWM模式选错了,导致有效电平反了,电机在死区附近来回切换。
配置步骤其实不复杂,我一般这么干:
- 使能定时器时钟
- 配置GPIO为复用推挽输出
- 设置预分频器和自动重装载值
- 配置PWM模式、极性、比较值
- 使能定时器
小技巧:配置GPIO时,别忘了设置输出速度。我习惯设到High,尤其是电机控制这种高频切换的场景,否则波形边缘会变圆,影响控制精度。
1.3 占空比与频率计算
这两个参数是PWM的灵魂。频率决定电机转动的平滑度,占空比决定转速。
频率计算公式:
PWM频率 = 定时器时钟频率 / (预分频器值 + 1) / (自动重装载值 + 1)
举个例子,STM32F103的定时器时钟是72MHz,我想输出20kHz的PWM:
预分频器 = 71 → 分频后时钟 = 72MHz / 72 = 1MHz
自动重装载值 = 49 → 频率 = 1MHz / 50 = 20kHz
占空比计算公式:
占空比 = 比较值 / (自动重装载值 + 1) × 100%
比如自动重装载值是999,比较值设成500,占空比就是50%。
注意:我曾经犯过一个低级错误——把预分频器值设成了0,结果定时器直接按72MHz跑,PWM频率高得离谱,电机根本转不起来。后来才意识到,预分频器值要减1,因为硬件是从0开始计数的。
剃须刀电机一般用多少频率?我建议15kHz到25kHz之间。低于15kHz,人耳能听到啸叫声;高于25kHz,MOS管的开关损耗会明显增加。我做过测试,20kHz是个不错的折中点。
1.4 实战配置示例
下面给出一段完整的配置代码,基于STM32F103的TIM2输出PWM:
void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置PA0为TIM2_CH1复用输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
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; // 72MHz / 72 = 1MHz
TIM_TimeBaseStructure.TIM_Period = 49; // 1MHz / 50 = 20kHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 配置PWM通道
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 25; // 占空比 25/50 = 50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 5. 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
这段代码配置了20kHz、50%占空比的PWM输出。如果你想调转速,改TIM_Pulse的值就行。我一般会在主循环里用个变量动态调整这个值,实现电机加减速。
调试建议:第一次跑PWM时,别急着接电机。先用示波器看波形,确认频率和占空比都对得上。我吃过这个亏——代码看起来没问题,结果发现是GPIO的复用功能没开对,波形死活出不来。
1.5 常见问题与避坑
- 波形频率不对:检查预分频器和自动重装载值是否算对了。别忘了减1。
- 占空比调不动:看看比较值有没有超过自动重装载值。超过的话,输出会一直有效或一直无效。
- 电机不转:先确认GPIO有没有配置成复用功能。我见过有人配成了推挽输出,结果PWM信号被强制拉高。
- 电机抖动:可能是PWM频率太低,或者死区时间不够。剃须刀电机一般不需要死区,但如果你用H桥驱动,就得加上。
好了,这一章的内容就到这里。定时器和PWM是电机控制的基石,搞懂了它们,后面的闭环控制、速度调节才能玩得转。下一章咱们聊聊如何用PWM控制电机转速,以及怎么避免那些常见的坑。