第4章 PWM 与电机驱动:PWM 原理与定时器配置、直流减速电机驱动(L9110S/H桥)、占空比与转速控制
各位同学,欢迎来到第四章。这一章我们要聊的是共享单车锁控系统里最“动感”的部分——电机驱动。你想想看,车锁的弹开、锁舌的收回,靠的就是电机那一下精准的转动。而控制电机转多快、转多大力,核心就是PWM。
我最早接触PWM,是在做一款智能窗帘的项目。当时电机一启动就“嗡嗡”响,转速还忽快忽慢。后来才发现,是PWM频率没选对,占空比抖动得厉害。从那以后,我对PWM和电机驱动就格外上心。今天咱们就把这块彻底讲透。
4.1 PWM 原理:说白了就是“开关”的艺术
PWM,全称 Pulse Width Modulation,脉冲宽度调制。名字听着唬人,其实原理特简单——就是让一个信号在“高电平”和“低电平”之间快速切换。通过改变高电平持续的时间(也就是脉宽),来模拟一个连续变化的电压。
你想想看,如果给电机通5V电,它全速转。通0V,它停。那如果我在1秒钟内,让5V通0.5秒、0V通0.5秒,电机平均得到的电压就是2.5V。它就会以一半的速度转。这就是PWM的核心思想。
这里有两个关键参数:
- 频率:1秒钟内开关多少次。单位是Hz。频率太低,电机会“咔咔”响,像步进电机一样一顿一顿的。频率太高,开关损耗会变大,驱动芯片发热严重。
- 占空比:高电平时间占整个周期的百分比。0%就是一直关,100%就是一直开。50%就是一半时间开、一半时间关。
核心公式:
平均电压 = 电源电压 × 占空比
比如:5V × 60% = 3V。电机就以3V对应的转速运行。
我个人习惯,在共享单车这种电池供电的场景下,PWM频率选在1kHz到10kHz之间。太低会有噪音,太高会浪费电池。嗯,这里要注意,频率选好后,定时器的配置就要跟着来。
4.2 定时器配置:让硬件自动干活
PWM信号不能靠软件延时去“手动”翻转IO口,那样CPU就废了。必须用定时器的硬件PWM模式。以STM32为例,我常用的就是通用定时器TIM3或TIM4。
配置步骤其实就四步:
- 使能定时器时钟:比如RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
- 配置GPIO为复用推挽输出:把对应的引脚(比如PA6)设为AF_PP模式。
- 设置定时器参数:包括预分频器PSC和自动重装载值ARR。这两个值决定了PWM的频率。
- 配置PWM通道:设置比较值CCR,这个值直接控制占空比。
频率的计算公式很简单:
PWM频率 = 定时器时钟 / ( (PSC+1) × (ARR+1) )
举个例子,STM32的定时器时钟通常是72MHz。我想要一个1kHz的PWM,那么可以设PSC=71,ARR=999。算一下:72,000,000 / (72 × 1000) = 1000 Hz。完美。
占空比则由CCR控制:占空比 = CCR / (ARR+1) × 100%。如果ARR=999,CCR=500,占空比就是50%。
我的小技巧: 在调试时,我习惯先用示波器看PWM波形。频率对不对?占空比稳不稳?一眼就能看出来。千万别只靠感觉调参数,示波器是嵌入式工程师最好的朋友。
代码示例(基于STM32标准库):
// 定时器3 PWM初始化,频率1kHz
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. 配置PA6为复用推挽输出
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. 定时器基础配置
TIM_TimeBaseStructure.TIM_Period = 999; // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 4. PWM通道1配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // CCR = 500, 占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
// 5. 使能定时器
TIM_Cmd(TIM3, ENABLE);
}
// 设置占空比函数
void PWM_SetDuty(uint16_t duty)
{
TIM_SetCompare1(TIM3, duty); // duty范围0~999
}
4.3 直流减速电机驱动:L9110S 与 H桥
单片机的IO口电流太小,直接推不动电机。必须用驱动芯片。共享单车锁里,L9110S是性价比很高的选择。它内部集成了两个H桥,可以驱动两路直流电机。说白了,H桥就是四个开关管组成的电路,通过控制开关管的通断,让电流正向或反向流过电机,从而实现正转和反转。
L9110S的控制逻辑很简单:
| IA | IB | 电机状态 |
|---|---|---|
| 0 | 0 | 刹车(停止) |
| 1 | 0 | 正转 |
| 0 | 1 | 反转 |
| 1 | 1 | 刹车(停止) |
注意看,IA和IB同时为1也是刹车。这是H桥的一种特殊状态,叫“同侧导通”,相当于把电机两端短路,利用电机自身的反电动势快速制动。我在项目中遇到过,有些同学把IA和IB都置1,发现电机反而发热严重。嗯,这是因为电流过大,驱动芯片扛不住了。所以刹车时,我建议用IA=0, IB=0,让电机自由停止,或者用PWM慢慢减速。
避坑指南: 我曾经在一个项目中,把L9110S的电源和电机电源共用了一路。结果电机启动瞬间,电压被拉低,单片机直接复位。后来我加了一个100μF的电解电容在电机电源旁边,问题就解决了。记住,电机启动电流是额定电流的好几倍,电源设计一定要留余量。
4.4 占空比与转速控制:从理论到手感
占空比和转速的关系,并不是线性的。你设50%占空比,电机转速不会是最大转速的50%。因为电机有启动电压,低于某个阈值它根本不转。这个阈值叫“死区电压”。
我一般会实测一下。比如给10%占空比,电机不动。给20%,开始慢慢转。给50%,转速明显提升。给80%以上,转速基本饱和了。所以实际控制时,我会把占空比范围限制在20%~90%之间,这样控制起来更线性。
还有一个细节:电机启动时,需要较大的占空比来克服静摩擦力。我习惯在启动瞬间给一个“软启动”——先给一个较高的占空比(比如70%),持续100ms,然后降到目标值。这样电机启动更平稳,也不会因为冲击电流过大而损坏驱动芯片。
代码实现软启动:
void Motor_Start(uint16_t target_duty)
{
uint16_t duty = 700; // 先给70%占空比
PWM_SetDuty(duty);
delay_ms(100); // 保持100ms
// 然后逐渐降到目标值
while(duty > target_duty)
{
duty -= 10;
PWM_SetDuty(duty);
delay_ms(5);
}
}
你看,代码很简单,但效果很明显。共享单车锁的锁舌弹出,要求干脆利落,但又不能“哐”的一声砸出去。通过PWM控制,完全可以做到“柔中带刚”。
最后总结一下这一章的核心:
- PWM就是通过快速开关来模拟电压,频率和占空比是两个关键参数。
- 定时器配置要算好PSC和ARR,确保频率在1kHz~10kHz之间。
- L9110S驱动电机,注意电源滤波和刹车方式。
- 占空比和转速不是线性关系,要实测并做软启动处理。
下一章,我们会把PWM和电机驱动整合到锁控系统的整体逻辑中。到时候,你会看到这些底层知识是如何串联起来的。好了,今天就到这里,动手试试吧。