1. 项目概述
PWM(脉冲宽度调制)调速是控制直流电机最常用的方法之一。作为一名嵌入式开发者,我经常需要在各种项目中使用PWM来控制电机转速。这次我想分享一个通用的PWM电机控制方案,适用于市面上绝大多数单片机。
这个方案的核心优势在于其通用性。无论你使用的是Arduino、STM32还是其他单片机,只要支持PWM输出,都可以按照这个思路来实现电机调速。我在多个项目中验证过这个方案的可靠性,包括智能小车、机械臂和自动化控制系统中。
2. 电机驱动基础原理
2.1 电压差驱动原理
所有直流电机的基本工作原理都是基于电压差。当电机两端存在电位差时,电流流过电机的线圈产生磁场,与永磁体相互作用产生转矩。最简单的驱动方式就是直接将电源正负极连接到电机两端。
注意:直接使用IO口驱动电机时,务必确认单片机的IO口最大输出电流。大多数单片机的单个IO口只能提供20-50mA电流,而小型直流电机的工作电流通常在100-500mA范围。
2.2 PWM调速原理
PWM通过快速开关来控制电机的平均电压。假设PWM频率为1kHz(周期1ms),占空比为50%,意味着每1ms内有0.5ms是高电平,0.5ms是低电平。对于电机来说,这等效于施加了50%的电源电压。
关键参数选择:
- PWM频率:通常选择1kHz-20kHz
- 太低(<100Hz)电机会发出可闻噪声
- 太高(>20kHz)可能导致开关损耗增加
- 占空比分辨率:8位PWM提供0-255级调节(约0.4%步进)
3. L298N电机驱动模块详解
3.1 模块特性与选型
L298N是一款经典的双H桥电机驱动芯片,我选择它的原因主要有:
- 驱动能力强:单路最大2A持续电流
- 电压范围宽:支持5V-35V电机
- 内置5V稳压输出:可为单片机供电
- 价格低廉:约10-20元人民币
模块典型参数:
- 逻辑电压:5V
- 驱动电压:5V-35V
- 峰值电流:3A(单路)
- 持续电流:2A(需加散热片)
3.2 硬件连接指南
典型接线方式:
-
电源部分:
- 12V输入:连接锂电池正极
- GND:连接电池负极和单片机GND
- 5V输出:可为单片机供电(可选)
-
控制信号:
- IN1/IN2:控制电机A转向
- IN3/IN4:控制电机B转向
- ENA/ENB:PWM调速使能端
实操技巧:如果发现电机转动方向与预期相反,只需交换IN1/IN2或IN3/IN4的接线即可,无需修改代码。
4. Arduino平台实现
4.1 基础驱动代码
arduino复制// 定义引脚
const int IN1 = 7;
const int IN2 = 8;
const int ENA = 9; // PWM引脚
void setup() {
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(ENA, OUTPUT);
}
void loop() {
// 正转
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
analogWrite(ENA, 128); // 50%速度
delay(2000);
// 反转
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
analogWrite(ENA, 200); // ~78%速度
delay(2000);
// 刹车
digitalWrite(IN1, HIGH);
digitalWrite(IN2, HIGH);
delay(500);
}
4.2 高级调速控制
对于需要精确调速的场景,可以加入PID控制:
arduino复制#include <PID_v1.h>
// PID参数
double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, 2, 5, 1, DIRECT);
void setup() {
// 初始化PID
Setpoint = 100; // 目标转速(RPM)
myPID.SetMode(AUTOMATIC);
// 启用编码器输入(示例)
attachInterrupt(digitalPinToInterrupt(2), encoderISR, RISING);
}
void loop() {
Input = readEncoder(); // 获取当前转速
myPID.Compute();
analogWrite(ENA, Output);
}
// 编码器中断服务程序
void encoderISR() {
encoderCount++;
}
5. STM32平台实现
5.1 定时器配置
STM32的PWM功能通过定时器实现,以下是使用TIM2的配置示例:
c复制// PWM初始化
void PWM_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // PA0(TIM2_CH1), PA1(TIM2_CH2)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置定时器基础
TIM_TimeBaseInitStruct.TIM_Period = 8399; // 10kHz PWM
TIM_TimeBaseInitStruct.TIM_Prescaler = 0;
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
// 4. 配置PWM模式
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
// 通道1配置
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
// 通道2配置
TIM_OC2Init(TIM2, &TIM_OCInitStruct);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
// 5. 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
5.2 电机控制函数
c复制// 设置电机速度和方向
void Motor_Control(uint8_t motorNum, int16_t speed) {
if(motorNum == 1) {
if(speed >= 0) {
GPIO_SetBits(GPIOA, GPIO_Pin_4); // 方向控制
TIM_SetCompare1(TIM2, speed);
} else {
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
TIM_SetCompare1(TIM2, -speed);
}
}
// 同理实现motor2控制
}
6. 常见问题与解决方案
6.1 电机不转排查步骤
-
检查电源:
- 测量驱动板输入电压
- 确认5V输出正常(如有使用)
-
检查信号:
- 用万用表测量PWM引脚电压
- 确认方向控制信号正确
-
检查接线:
- 确认电机接线牢固
- 检查所有GND共地
6.2 典型问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机抖动 | PWM频率太低 | 提高至1kHz以上 |
| 驱动芯片发热 | 电流过大 | 加散热片或降低负载 |
| 转速不稳定 | 电源功率不足 | 使用更大容量电池 |
| 干扰严重 | 未加滤波电容 | 在电机两端并联100uF电容 |
7. 进阶技巧与优化
7.1 硬件优化建议
-
添加续流二极管:在电机两端并联二极管(如1N4007),防止反电动势损坏电路
-
使用MOSFET驱动:对于大电流应用(>2A),建议使用IR2104+MOSFET方案
-
增加电流检测:通过采样电阻+运放实现过流保护
7.2 软件优化技巧
- 平滑调速:使用加速度限制,避免突变
arduino复制void setMotorSpeed(int target) {
static int current = 0;
int step = (target > current) ? 1 : -1;
while(current != target) {
current += step;
analogWrite(ENA, current);
delay(10); // 调整这个值改变加速度
}
}
- 死区补偿:针对PWM低占空比时电机不启动的问题
c复制// 非线性映射函数
uint16_t applyDeadbandComp(uint16_t duty) {
if(duty < 30) return 0; // 死区
if(duty < 50) return 50; // 启动阈值
return duty; // 线性区
}
在实际项目中,我发现PWM频率选择对电机性能影响很大。对于有刷直流电机,5-10kHz通常是最佳选择,既能避免可闻噪声,又不会导致过多的开关损耗。而对于无刷电机,可能需要更高的频率(15-20kHz)。
另一个实用技巧是在电机启动时给予一个短时的高占空比脉冲(比如300ms的100%占空比),这能帮助克服静摩擦力,使电机更容易启动,特别是在低转速场合。启动后再降回目标占空比即可。