|
电机控制完全指南:步进/伺服/直流电机 + 闭环控制实战

电机控制完全指南:步进/伺服/直流电机 + 闭环控制实战

引言

电机控制是嵌入式开发中最基础也最重要的技能之一。无论是 CNC 雕刻机、机器人关节、智能小车还是太阳能系统,都离不开对电机的精准控制。

本指南从 5 个经典实战项目出发,覆盖**步进电机(A4988)、舵机(SG90/MG996)、直流电机(L298N PWM)、编码器闭环 PID 控制、太阳能充电控制器(PWM vs MPPT)**五大主题,将零散知识整合为一份完整的参考资料。


一、步进电机精准控制:A4988 驱动详解

1.1 为什么选择步进电机?

步进电机可以精准控制转动角度,不需要编码器就能实现开环位置控制。NEMA 17(42mm)步进电机搭配 A4988 驱动器,是 CNC 雕刻机、3D 打印机、机器人云台的核心方案。

1.2 硬件清单

物品型号价格备注
步进电机NEMA 17 (42mm)¥25-351.8° 步距角,1.5A
驱动器A4988¥8-12最大 2A,支持微步
开发板Arduino Uno¥25或其他兼容板
电位器10kΩ¥2调节电机电流
电容100μF¥1电源滤波
电源12V 2A¥30电机供电

总成本:约 ¥100 以内

1.3 A4988 关键引脚

+-----+
        VMOT ──────┤1   16│────── GND
         GND ──────┤2   15│────── 1B
          VDD ──────┤3   14│────── 1A
          2B ───────┤4   13│────── 2A
          2A ───────┤5   12│────── GND
        MS1 ────────┤6   11│────── VDD
        MS2 ────────┤7   10│────── STEP
        MS3 ────────┤8    9│────── DIR
                    +-----+
  • VMOT/GND:电机电源 (8-35V),必须加 100μF 电容滤波
  • VDD/GND:逻辑电源 (3-5.5V),接 Arduino 5V
  • 1A/1B/2A/2B:电机线圈输出
  • STEP:脉冲信号,每个脉冲走一步
  • DIR:方向控制,高电平正转,低电平反转
  • MS1/MS2/MS3:微步设置引脚

1.4 微步设置表

MS1MS2MS3微步模式每转步数
000全步200
1001/2 步400
0101/4 步800
1101/8 步1600
1111/16 步3200

推荐使用 1/16 微步,运行更平滑,噪音更低。

1.5 电流调节(重要!)

A4988 通过参考电压 Vref 设置电机电流,公式:Vref = 电流 × 0.8

对于 1.5A 的 NEMA 17:Vref = 1.5 × 0.8 = 1.2V

调节步骤:

  1. 断开电机电源,准备万用表(调到直流电压档)
  2. 给 A4988 通电(只接 VDD,不接 VMOT),避免电机转动
  3. 用螺丝刀缓慢旋转 A4988 上的电位器
  4. 测量电位器对 GND 的电压,直到读数接近 1.2V

1.6 Arduino 控制代码

基础版:AccelStepper 库(推荐)

#include <AccelStepper.h>

// 定义步进引脚
#define STEP_PIN 9
#define DIR_PIN 8

// 创建步进对象
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

void setup() {
  Serial.begin(9600);
  stepper.setMaxSpeed(1000);        // 最大速度 (步/秒)
  stepper.setAcceleration(500);     // 加速度 (步/秒²)
  stepper.moveTo(3200);             // 正转一圈 (@ 1/16 微步)
  Serial.println("电机开始运动");
}

void loop() {
  if (stepper.distanceToGo() == 0) {
    stepper.moveTo(-stepper.currentPosition());  // 反向
    Serial.print("当前位置:");
    Serial.println(stepper.currentPosition());
  }
  stepper.run();
}

安装库: Arduino IDE → 工具 → 管理库 → 搜索 “AccelStepper” → 安装

进阶:多轴同步控制(CNC 三轴)

#include <AccelStepper.h>

AccelStepper xAxis(AccelStepper::DRIVER, 2, 3);
AccelStepper yAxis(AccelStepper::DRIVER, 4, 5);
AccelStepper zAxis(AccelStepper::DRIVER, 6, 7);

void setup() {
  xAxis.setMaxSpeed(1000);
  yAxis.setMaxSpeed(1000);
  zAxis.setMaxSpeed(1000);
}

void loop() {
  xAxis.moveTo(1000);
  yAxis.moveTo(500);
  zAxis.moveTo(200);
  xAxis.run();
  yAxis.run();
  zAxis.run();
}

1.7 速度计算

转速 (RPM) = (脉冲频率 × 60) / 每转步数

例如 1/16 微步模式下,每秒 1000 个脉冲:转速 = (1000 × 60) / 3200 = 18.75 RPM

注意: A4988 最大脉冲频率约 200kHz,实际使用建议不超过 50kHz,高速时扭矩会下降。

1.8 常见问题排查

电机不转但有嗡嗡声:

  1. 检查线圈接线是否正确,用万用表测通断确认同一组线圈
  2. 增大 Vref 电流设定值(但不要超过电机额定电流)
  3. 尝试用手拨动电机轴,检查是否机械卡死

驱动器发烫严重:

  1. 检查 Vref 是否过大,降低到电机额定电流
  2. 加装散热片(铝制散热片效果明显)
  3. 检查通风环境,避免密闭空间运行
  4. 考虑更换为 DRV8825 或 TMC2208(效率更高)

电机抖动不连续:

  1. 改用更高的微步模式(1/16 或 1/32)
  2. 避开共振速度区间(通常在中低速范围)
  3. 检查接线是否牢固,杜邦线接触不良是常见原因

高速时丢步:

  1. 降低加速度设定,给电机足够的响应时间
  2. 在额定范围内适当提高电流
  3. 检查电源供电是否充足(电压跌落会导致扭矩不足)

Arduino 复位:

  1. 电机供电和 Arduino 供电分开,只共地
  2. VMOT 端加装 100μF 电解电容滤波
  3. 检查所有共地点是否牢固
  4. 必要时使用光耦隔离 STEP/DIR 信号

1.9 项目应用

学会步进电机控制后,可以做:

  1. DIY 小型 CNC 雕刻机(三轴联动)
  2. 3D 打印机(配合加热头和挤出机)
  3. XY 绘图机器人(CoreXY 结构)
  4. 自动传送带(精确送料)
  5. 天文望远镜赤道仪(跟踪天体运动)
  6. 蠕动泵(精确流体控制)

二、舵机角度控制:SG90 vs MG996 对比

2.1 舵机工作原理

舵机内部是个闭环控制系统:PWM 信号 → 控制电路 → 电机 → 齿轮组 → 电位器反馈 → 比较电路

PWM 信号周期 20ms(50Hz),脉宽 0.5-2.5ms 对应 0-180 度。

2.2 SG90 与 MG996 核心对比

对比项SG90MG996
齿轮材质塑料金属
轴承塑料套双滚珠轴承
扭矩1.6kg/cm10kg/cm
死区较大(约 5°)较小(约 2°)
精度±5°±2°
价格¥8-12¥35-45

2.3 实测数据

目标角度SG90 实测SG90 误差MG996 实测MG996 误差
+3°+1°
45°47°+2°46°+1°
90°92°+2°91°+1°
135°133°-2°134°-1°
180°177°-3°178°-2°

结论: MG996 精度明显更好,平均误差 ±1° vs SG90 的 ±2-3°。

2.4 Arduino 控制代码

#include <Servo.h>

Servo myServo;
int lastAngle = -1;
const int DEAD_ZONE = 3;  // 死区 3 度,减少抖动

void moveToAngle(int targetAngle) {
  if (abs(targetAngle - lastAngle) > DEAD_ZONE) {
    myServo.write(targetAngle);
    lastAngle = targetAngle;
    delay(300);
  }
}

void setup() {
  // 手动指定脉宽范围(单位:微秒),根据实际校准
  myServo.attach(9, 500, 2500);
  Serial.begin(9600);
}

void loop() {
  moveToAngle(0);
  delay(1000);
  moveToAngle(90);
  delay(1000);
  moveToAngle(180);
  delay(1000);
}

2.5 常见问题

舵机不转动: 检查独立 5V 供电是否充足(MG996 峰值 1A+);确认 PWM 信号线连接到 Arduino PWM 引脚;确认 Arduino 与舵机共地;用已知正常的代码测试排除程序问题。

舵机抖动严重: 供电不足是首要原因,换用 5V 3A+ 电源;在代码中加入死区(3-5°);检查 PWM 线是否过长或接触不良;避免超出舵机实际角度范围(部分舵机不是精确 0-180°)。

多舵机时 Arduino 复位: 舵机启动电流叠加导致 Arduino 供电电压跌落。解决方法:使用独立电源供电(至少 5V 3A);在电源端加装 470μF 大电容;逐个初始化舵机,避免同时启动。

2.6 选型建议

  • 选 SG90:预算有限、负载轻(<500g)、精度要求不高、学习项目
  • 选 MG996:需要可靠耐用、负载较大(500g-1.5kg)、精度要求 ±2°、长期运行
  • 其他选择:MG90S(金属齿轮版,性价比好)、DS3235(数字舵机,±0.5°,高端项目)、总线舵机(多舵机串联,省 IO 口)

三、直流电机 PWM 调速:L298N 实战

3.1 PWM 调速原理

PWM(脉冲宽度调制)通过快速开关电源控制平均电压:

占空比 100%: ████████████████████  平均电压 = 5V
占空比 50%:  ████████████████      平均电压 = 2.5V
占空比 25%:  ████                  平均电压 = 1.25V

Arduino 的 analogWrite() 生成约 490Hz 的 PWM 信号,占空比范围 0-255。

3.2 L298N 正反转控制逻辑

IN1IN2电机状态
HIGHLOW正转
LOWHIGH反转
LOWLOW停止
HIGHHIGH停止(刹车)

3.3 基础调速代码

const int ENA = 9;    // PWM 调速引脚
const int IN1 = 8;
const int IN2 = 7;

void setup() {
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // 正转加速
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  for (int speed = 0; speed <= 255; speed += 10) {
    analogWrite(ENA, speed);
    delay(200);
  }

  // 反转减速
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  for (int speed = 255; speed >= 0; speed -= 10) {
    analogWrite(ENA, speed);
    delay(200);
  }
}

3.4 软启动/软停止

避免电机突然启停造成机械冲击:

void softStart(int targetSpeed, int stepTime = 10) {
  for (int s = 0; s <= targetSpeed; s += 5) {
    analogWrite(ENA, s);
    delay(stepTime);
  }
}

void softStop(int stepTime = 10) {
  int currentSpeed = 255;  // 假设当前全速
  for (int s = currentSpeed; s >= 0; s -= 5) {
    analogWrite(ENA, s);
    delay(stepTime);
  }
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
}

3.5 双电机差速转向

智能小车转向的经典方案:

void setMotor(int motor, int speed) {
  int in1, in2, en;
  if (motor == 1) { in1 = IN1; in2 = IN2; en = ENA; }
  else { in1 = 3; in2 = 4; en = 10; }

  if (speed > 0) {
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
  } else {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    speed = -speed;
  }
  analogWrite(en, constrain(speed, 0, 255));
}

void turnLeft(int speed) {
  setMotor(1, -speed);  // 左电机反转
  setMotor(2, speed);   // 右电机正转
}

3.6 常见问题与升级方案

电机不转: 检查电机供电电压;ENA 跳线帽是否拔掉(接 PWM 时必须拔掉);IN1/IN2 方向引脚是否正确;用万用表测量 OUT1/OUT2 是否有输出。

发热严重: L298N 在大电流下发热正常,持续电流不要超过 2A。如果烫手,加装散热片或考虑升级到 TB6612FNG(MOS 管驱动,效率高、发热小)。

驱动模块优点缺点价格
L298N便宜、资料多发热大、效率低¥15-25
TB6612FNG效率高、发热小电流较小(1.2A)¥20-30
DRV8833双通道、过流保护需要 3.3V 逻辑¥15-25
VNH2SP30大电流(30A)贵、体积大¥50-80

四、编码器反馈:闭环 PID 控制

4.1 为什么需要闭环?

开环控制的致命问题:你不知道电机实际转了多少。负载变化、电压波动、摩擦力变化都会导致实际位置偏离预期。闭环控制通过编码器实时反馈,实现精准控制——这就是 CNC 机床、机器人关节都用闭环的原因。

4.2 编码器类型

类型精度成本抗污染典型分辨率
光电式1000-5000 PPR
磁电式100-4096 PPR
电容式1000-10000 PPR

DIY 项目推荐磁编码器 AS5600(I2C 接口,12 位分辨率,¥15)。

4.3 PID 控制原理

误差 = 目标位置 - 实际位置
输出 = Kp×误差 + Ki×误差积分 + Kd×误差微分
  • P(比例):误差越大输出越大,纯 P 有稳态误差
  • I(积分):累积历史误差,消除稳态误差,过大会超调
  • D(微分):预测误差趋势,抑制超调,对噪声敏感

4.4 完整闭环位置控制代码

#include <Wire.h>
#include <AS5600.h>

AS5600 as5600;

const int PWM_PIN = 9;
const int IN1_PIN = 7;
const int IN2_PIN = 8;

// PID 参数
float Kp = 2.0, Ki = 0.5, Kd = 1.0;

int targetPosition = 0;
float integral = 0;
float lastError = 0;
unsigned long lastTime = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  as5600.begin(Wire);
  pinMode(PWM_PIN, OUTPUT);
  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);
  analogWrite(PWM_PIN, 0);
}

void loop() {
  if (Serial.available()) {
    targetPosition = Serial.parseInt();
    integral = 0;
    lastError = 0;
  }

  int currentPosition = as5600.readAngle();  // 0-4095
  int error = targetPosition - currentPosition;
  unsigned long currentTime = millis();
  float dt = (currentTime - lastTime) / 1000.0;

  if (dt > 0) {
    integral += error * dt;
    float derivative = (error - lastError) / dt;
    float output = Kp * error + Ki * integral + Kd * derivative;
    output = constrain(output, -255, 255);
    driveMotor(output);
    lastError = error;
    lastTime = currentTime;
  }

  Serial.print("Pos: "); Serial.print(currentPosition);
  Serial.print(" Err: "); Serial.print(error);
  Serial.print(" Out: "); Serial.println((int)output);
  delay(10);  // 100Hz 控制频率
}

void driveMotor(float pwm) {
  if (pwm > 0) {
    digitalWrite(IN1_PIN, HIGH);
    digitalWrite(IN2_PIN, LOW);
    analogWrite(PWM_PIN, (int)pwm);
  } else {
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, HIGH);
    analogWrite(PWM_PIN, (int)(-pwm));
  }
}

4.5 PID 调参经验

  1. 先 P 后 D 再 I:先把 Kp 调到系统能响应但开始振荡,加 Kd 抑制振荡,最后加 Ki 消除稳态误差
  2. 控制频率要稳定:用定时器而非 delay 保证采样周期固定
  3. 积分限幅:防止积分饱和(integral windup)
  4. 死区处理:误差小于阈值时停止输出,避免持续微调造成磨损

4.6 实际应用场景

  • 云台相机:保持相机稳定,抵消手部抖动
  • 机器人关节:精准控制机械臂角度
  • 平衡车:实时调整电机保持平衡
  • CNC 进给轴:精确控制刀具位置
  • 卷扬机:恒张力控制

五、太阳能充电控制器:PWM vs MPPT

5.1 PWM vs MPPT 对比

对比项PWMMPPT
转换效率60%~75%90%~98%
成本¥20~50¥100~500+
电路复杂度
低温/阴天表现一般明显更好
DIY 难度★★☆☆☆★★★★☆

建议:12V 小系统(太阳能板 20W 以下)用 PWM 够用;超过 20W 或想最大化利用太阳能,选 MPPT。

5.2 PWM 充电控制器 DIY

核心思路:MOSFET 做开关,MCU 控制占空比,根据电池电压调整充电状态。总成本约 ¥20。

充电三阶段(12V 铅酸电池):

typedef enum {
    CHARGE_BULK,        // 恒流充电(全功率)
    CHARGE_ABSORPTION,  // 恒压充电(逐步减小电流)
    CHARGE_FLOAT,       // 浮充维护(小电流保持)
    CHARGE_OFF          // 停止充电
} ChargeState_t;

5.3 MPPT 扰动观察法

MPPT 核心是 DC-DC Buck 变换器加追踪算法。总成本约 ¥65。

void mppt_algorithm(void) {
    uint16_t v_solar = read_solar_voltage();
    uint16_t i_solar = read_solar_current();
    uint32_t power = (uint32_t)v_solar * i_solar / 1000;

    uint32_t delta_p = power - prev_power;
    int16_t delta_v = v_solar - prev_voltage;

    if (delta_p > 0) {
        // 功率增加,继续同方向扰动
        if (delta_v > 0) set_buck_duty(get_buck_duty() - duty_step);
        else set_buck_duty(get_buck_duty() + duty_step);
    } else {
        // 功率下降,反向扰动
        if (delta_v > 0) set_buck_duty(get_buck_duty() + duty_step);
        else set_buck_duty(get_buck_duty() - duty_step);
    }
    prev_voltage = v_solar;
    prev_power = power;
}

扰动观察法工作方式:

  1. 每次调整一个小的电压/占空比步长(如 50mV)
  2. 测量调整前后太阳能板的输出功率
  3. 如果功率增加,说明调整方向正确,继续同方向调整
  4. 如果功率下降,说明调整方向错误,反方向调整
  5. 如此反复,最终稳定在最大功率点附近

5.4 太阳能板选型

太阳能板功率(W) = 电池容量(Ah) × 电池电压(V) ÷ 有效日照小时数 × 系统效率

以 12V 50Ah 铅酸电池为例:600Wh ÷ 5h ÷ 0.75 ≈ 160W

5.5 锂电池 vs 铅酸电池充电参数

参数12V 铅酸12V 锂电 (4S)
Bulk 电压14.4V16.8V
吸收电压13.8V16.8V
浮充电压13.2V14.6V
欠压保护11.5V12.0V
最大充电电流0.1C~0.25C0.5C~1C

警告: 锂电池必须搭配 BMS 使用!充电控制器只管充电曲线,BMS 负责最后一道安全防线。


总结

本指南涵盖了电机控制的五大核心领域:

主题关键芯片/模块核心技能
步进电机A4988微步设置、Vref 电流调节、AccelStepper 库
舵机SG90/MG996PWM 脉宽校准、死区处理、多舵机协同
直流电机L298NH 桥控制、PWM 调速、软启动、差速转向
闭环控制AS5600 + TB6612PID 调参、编码器读取、位置/速度闭环
太阳能控制STM32 + MOSFETPWM/MPPT 充电策略、扰动观察法

从开环到闭环,从单一电机到多轴协同,掌握这些知识后你就能应对绝大多数嵌入式电机控制项目。建议学习路径:直流电机 PWM → 步进电机开环 → 舵机角度控制 → 编码器闭环 PID → 太阳能系统

希望这篇完全指南对您有所帮助!