交互式模拟器中级

步进电机模拟器:失步与转子滞后角

看步进电机的转子如何落后于指令磁场,把它推过 90 度电角度,亲眼看清一台机器如何在毫无察觉的情况下丢失位置。

类别
嵌入式系统
时长
20–40 分钟
平台
浏览器 · Arduino · ESP32
步进电机模拟器:失步与转子滞后角 技术示意图FIELDROTORCARRIAGE80 STEPS/MM · 1.8° · 1/16LAG ANGLE DECIDES WHETHER IT ARRIVES

01 / 从这里开始

简介

步进电机并不“迈步”——它是拖着转子跟在一个旋转的磁场后面,而两者之间的夹角决定了这段运动能否到位。这个实验台直接对转子的运动方程做积分:其中没有任何代码去检测失步,所以当滞后角一路越过 90° 的失步极限时,电机会自己失去同步,就像台架上的真实电机一样。

Live lab / Open-loop step integrity

Stepper motor simulator

Command a move and watch the rotor trail the field. Push the acceleration, drop the voltage or add load until the lag angle runs past the 90° pull-out limit — and the motor silently stops arriving.

Browser native
This lab plots a stepper move twice: the position the driver believes the axis has reached against the position the rotor actually reached, and below it the lag angle between the commanded magnetic field and the rotor. Use the controls below to change the move, the drive and the load.

Upper plot: the dashed line is where the driver thinks the axis is, the solid line is where the rotor actually is. Lower plot: the lag angle between them. The shaded band is where a steady load can sit — past ±90 electrical degrees the torque starts falling instead of rising, so there is no equilibrium out there, and a rotor that keeps going reaches 180° and slips a pole.

  • Commanded (what the driver counted)
  • Actual rotor position
  • Where a load can sit (±90°e)
  • Sync lost
Try it

There is nothing to start — the whole move is re-integrated as you drag any control. Step through the five presets to see the four ways an open-loop move stops arriving.

The move
The drive
The load

The rotor stays inside the stable band for the whole move and lands on target.

Full steps lost
0
Peak lag
25°e
Position error
0.000 mm
Resolution
80 steps/mm
Pull-out torque
0.311 N·m
Corner speed
599 rpm

Every action has a button above. If you prefer the keyboard, focus the plots and press P for the next preset, M for the next microstepping setting, N to toggle the acceleration ramp, R to reset and F for full screen; sliders respond to the arrow keys.

控件

实验台在每次输入时都会把整段运动重跑一遍,所以曲线会跟着你拖动的滑块走,而不用等按下“开始”按钮。也没有什么东西需要开始——这段运动是 30 000 个积分步,你的指头还没挪回来它就算完了。

控件 它改变什么
预设 五种算好的情形,从一次干净的运动到四种不同的失败方式
步进频率 梯形加速斜坡,或者步进频率直接跳到全速
距离 / 最高速度 / 加速度 你所要求的运动
电源电压 驱动器要把电流灌进绕组所需的电压
限流 你用微调电位器设定的值——它就是峰值扭矩
细分 磁场被分得多细,1/1 到 1/16
机构 GT2 皮带(40 mm/转)或 T8 丝杠(8 mm/转)
阻力矩 电机轴上的摩擦与负载

上图是位置:虚线是驱动认为轴所处的位置,实线是转子实际所处的位置。下图是两者之间的滞后角,稳定的 ±90° 区间做了阴影。

原理

步进电机并不“迈步”。驱动器以增量的方式旋转磁场,转子跟随着它,始终落后某个角度——而它产生的扭矩就是这个角度的正弦:

T = T_peak(ω) × sin(δ)

δ 以电角度度量。一台 1.8° 的电机有 50 个转子齿,因此它的磁路每转重复 50 次,一个整步恰好是 90 电角度。这个换算是整个专题里唯一的算术。

在 −90° 到 +90° 之间,电机相当于一根弹簧:落后越多,拉回力就越大。正是这个稳定区让开环步进电机得以工作,任何稳态负载都必须待在里面。越过 90° 之后正弦就翻转了,于是落后越多,产生的恢复扭矩反而越小,这又让它落得更远——到了 180°,扭矩彻底反向,开始朝下一个磁极拉。转子滑到下一个稳定齿,那是一个电周期——四个整步——之外。

这是两个不同的数字,实验台把它们分得很清楚。90° 是平衡终止的地方;短暂冲过它是可以承受的,这正是整步指令——每一步都是一次 90° 的冲击——不会毁掉机器的原因。180° 则是不可返回的临界点。

有三样东西会让滞后角增大:

  • **负载。**在稳速下 sin(δ) = 需求 ÷ 可用。负载为可用扭矩的一半时,滞后角停在 30°,还留有 60° 的裕量。
  • **加速度。**让转子动起来要从同一份预算里花掉扭矩,所以滞后在斜坡阶段最严重,而不是在高速段。
  • 速度,因为 T_peak 本身会随电机转速提高而下降。

最后这一条最不显然。绕组是一个电感,电机转得越快,就越难把电流灌进去再逼出来——感抗随电频率上升,而转子自身旋转产生的反电动势又会吃掉电源电压。结果就是一条扭矩曲线:在拐点速度之前是平的,越过之后开始下滑,而这个拐点由电源电压决定。

这就引出了实验台存在的意义——让两条规则变得显而易见:更大的电流买到扭矩,更高的电压买到速度。

算法

转子是直接积分的。这段代码里没有任何东西去检测失步、去插入失步,甚至不知道失步是可能的——它是正弦定律积分之后的自然结果。

// per 20 us tick
const ideal    = sampleProfile(plan, time).position;         // mm the planner wants
const command  = Math.round(ideal * stepsPerMm) / stepsPerMm; // the driver only does whole microsteps
const lag      = rotorTeeth * (commandAngle - theta);         // electrical radians

const { torque } = peakTorque(setup, omega);                  // falls with speed
const friction   = loadTorque * Math.tanh(omega / 0.5);       // smooth Coulomb, one line
const accel      = (torque * Math.sin(lag) - friction - damping * omega) / inertia;

theta += omega * dt + 0.5 * accel * dt * dt;
omega += accel * dt;

有两处细节比看上去更重要。

**在转子看到指令之前,指令已经先被量化到细分栅格上了。**那道阶梯就是激励:整步运行时,每一条指令都是在阻尼极轻的共振点上施加的一次 90 电角度冲击,这正是整步电机啸叫而细分电机不叫的原因。

绕组模型决定了扭矩天花板:

const backEmf   = kT * speed;
const impedance = Math.hypot(resistance, rotorTeeth * speed * inductance);
const current   = Math.min(currentLimit, Math.max(0, supplyVolts - backEmf) / impedance);
const torque    = Math.SQRT2 * kT * current;

这是对斩波驱动器的集总稳态近似,而不是逐相仿真。它能复现真实扭矩曲线的形状,两个工程结论都算得对;只有在最高速端偏保守。

指令轨迹来自本站已有的运动曲线规划器,所以“带斜坡”和“不带斜坡”走的是同一条代码路径,只是加速度上限不同——并没有为失败单独写一条分支。

引导实验

每一个实验只隔离一个变量,其他什么都别动。

1. 斜坡就是整台机器。其他都保持默认值,把步进频率在斜坡和直全速之间来回切换。

斜坡 直全速
峰值滞后 25°e 1.2 ms 时越过 90°,到 3.0 ms 就彻底没了
丢失的整步数 0 280
轴最终停在哪里 60.00 mm 4.0 mm

同样的电机、同样的驱动器、同样的电流、同样的目标。唯一的区别是步进频率是否做了斜坡,而这两者之间的差别就是整段运动的差别。

2. 找出起动频率。设为直全速,然后从 100 mm/s 开始逐步提高最高速度。它能工作,能工作,然后在 200 mm/s 附近的某个地方彻底不工作了——而且不是逐渐劣化。那个阈值就是起动频率,它是真实电机上的真实数值。

3. 电压买到速度。加载12 V 电源、快速运行预设:运动会失速。现在把电源电压提到 24,保持限流不动,看着它跑完。然后退回 12 V,改用限流去救——救不回来。越过拐点速度之后,驱动器本来就没能达到设定电流。

4. 细分买不到精度。把其他都固定,把细分从 1/1 调到 1/16,读两个数:

细分 步长 相对路径的最坏偏差 负载引起的稳态误差
整步 0.200 mm 0.127 mm 0.049 mm
1/4 0.050 mm 0.061 mm 0.044 mm
1/16 0.0125 mm 0.045 mm 0.044 mm

跟踪精度改善了大约三倍,然后就停住了。负载误差一点没动。整步时它是 0.044 mm,十六细分数时它还是 0.044 mm——区别在于 0.044 mm 是四分之一个整步、同时又是三个半细分步,所以细分做细之后,同一个误差在驱动器所报告的单位下看起来要糟糕得多。

5. 失步。在速度设得不高的情况下,缓慢提高阻力矩。观察下图中的滞后角朝虚线攀升。它会先停在 30°,再到 50°,再到 70°——然后在滑块只加一小格的幅度里,同步就没了。之所以没有预警区,是因为 90° 之后已经不存在可供它稳定的平衡点。

7. 整步就是一次 90° 的冲击。加载整步运行预设,读出峰值滞后:约 102°e。每一步都指令完整的 90°,靠转子的惯性把它带过去。它仍然能到位——什么都没丢——但整段运动都在一个带载电机承受不起的区域里来回摆荡。这同时解释了为什么整步运行很吵,以及为什么它给负载留下的裕量如此之小。

6. 机构会改变问题本身。在其他都不动的情况下,把机构切换到 T8 丝杠。分辨率从 80 步/mm 变成 400 步/mm,同样的最高速度现在需要五倍的步进频率。

你应该观察到什么

  • **失败是悬崖,不是斜坡。**没有任何渐进退化。滞后角平滑地增大到 90°,然后整段运动就凭空消失了。这就是步进电机与舵机式闭环之间最本质的区别——后者在负载下只会变慢。
  • **机器永远不知道。**虚线的指令线总是精确地停在目标上,因为驱动器数过了它收到的每一个脉冲。只有实线才说真话,而真实机器上没有这条线。
  • **负载误差永远存在。**即使在一次完美的运行中,转子也停在驱动器所说的位置之后几十微米处。这不是故障,而是 sin(δ) = 负载 ÷ 扭矩 所要求的。
  • **丢失的步数永远是四的倍数。**你试试看能不能造出一个不是的数。

硬件落地

这个实验台 真实轴
理想的 50 kHz 步进时序 单片机的定时器抖动,以及 Arduino Uno 上大约 10–15 kHz 的上限
一个刚性质量块 皮带拉伸、背隙,以及会变形的机架
恒定的阻力矩 沿轴线变化的摩擦,以及线缆拖曳
集总绕组模型 真实的扭矩曲线,带有定位转矩和中频共振
完美的电流调节 限流设得过高时会热降额的驱动器
永远不会发热 每台电机一旦通电就持续消耗 8 W

最重要的一条区别换个角度看是最后一行:**真实机器不会给你下图。**你看不到滞后角,因此也看不到自己运行在多大的裕量上。实际的替代办法是故意去找失效点——把速度和加速度加到轴开始跳步为止,然后回退 30% 到 50%,就稳定运行在这个位置。

可以从接线与限流实操开始,用尺子标定每毫米步数,然后在绘图机器人制作里把整套东西组装起来。

源代码

在真实机器上,斜坡不是可选项。下面这个阻塞版本就是多数人第一版草图的样子,也正是会发出嗡嗡声的那一个:

// What not to ship: the step rate jumps from zero to full.
void moveBlocking(long steps, unsigned delayUs) {
  for (long i = 0; i < steps; i++) {
    digitalWrite(STEP_PIN, HIGH);
    delayMicroseconds(2);
    digitalWrite(STEP_PIN, LOW);
    delayMicroseconds(delayUs);      // constant => no acceleration at all
  }
}

带斜坡的非阻塞版本。这就是实验台规划的同一个梯形曲线,逐步生成,这样单片机上的其他东西都不会被卡住:

#include <AccelStepper.h>

const float STEPS_PER_MM = 80.0;          // 20T GT2 pulley at 1/16 stepping
AccelStepper axis(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

void setup() {
  pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, LOW);              // active low: energise

  axis.setMaxSpeed(200 * STEPS_PER_MM);   // 200 mm/s
  axis.setAcceleration(2000 * STEPS_PER_MM);
}

void loop() {
  if (axis.distanceToGo() == 0) {
    axis.moveTo(axis.currentPosition() == 0 ? lround(60 * STEPS_PER_MM) : 0);
  }
  axis.run();                             // returns immediately — call it constantly
  checkEndstops();                        // which is only possible because it does
}

setMaxSpeed 和 setAcceleration 只会给你一条梯形曲线,仅此而已——AccelStepper 里没有 S 形曲线的选项。要做跃度受限曲线,就得自己生成设定点,并由定时器喂给 moveTo。

如果你想知道自己的机器是否已经接近极限,便宜的测试方法是重复性测试:

// Drive a long move out and back, N times, then check whether home moved.
// Anything other than a multiple of four full steps is not step loss.
for (int i = 0; i < 20; i++) { moveTo(LONG_MOVE); runToPosition(); moveTo(0); runToPosition(); }
// now re-home and compare the reported position against zero

假设与硬件差异

转子动力学是诚实的:扭矩正比于 sin(δ) 正是混合式步进电机的工作方式,而滑移行为是对它积分的必然结果,而不是写进代码里的某条规则。这些常数描述的是一台 17HS4401 级别的 NEMA 17——1.8°、1.7 A、2.8 Ω、3.2 mH、保持扭矩 0.44 N·m,轴上还带一个折算质量 300 g 的滑座。

模型做了哪些简化:绕组被集总为一个稳态阻抗和反电动势项,而不是逐相仿真,因此扭矩曲线最末端偏保守。定位转矩——你徒手转动一台不通电的步进电机时感觉到的那点齿槽感——没有建模,中频共振没有建模,驱动器的衰减模式行为没有建模,任何热效应也都没有建模。时序是理想的,因此真实单片机的步进抖动不会出现。

这些都不会改变该拧哪个旋钮。但它们全都是应该去验证一台真实机器、而不是直接相信这一页上某个数字的理由。

电路图

这里所建模的物理接线是:电机到驱动器四根线,驱动器到单片机两根线,外加一个人们总是漏掉的电容:

Arduino 5V  ──── VDD
Arduino GND ──── GND (logic) ──┬── GND (motor) ──── PSU −
PSU + (12–24 V) ──── VMOT ──┬──┴── 100 µF electrolytic, close to the board
Pin 3 ──── STEP             RESET ──┬── SLEEP
Pin 4 ──── DIR              MS1, MS2, MS3 ──── 5 V   (1/16)
Pin 5 ──── ENABLE (active low)
Coil A ──── 1A, 1B          Coil B ──── 2A, 2B

A4988 零件页上有完整的引脚定义、两种采样电阻版本的 Vref 对照表,以及那个电容为什么不能省。

硬件清单

元器件

  • 一台双极步进电机——1.8°、每转 200 整步的那种
  • 一个带可调限流的电流调节型驱动器
  • 12 V 或更高的电机电源,并且记得去测量它
  • 一根皮带轴或丝杠轴,因为步/mm 只是答案的一半

探索关联图谱

哪里用到这个模拟器

以这个实验台为基础的项目、学习路线和教程。

继续搭建

下载资源

完成项目时可以参考本页上的这些资料。可下载的项目包只会在其源文件和版本发布之后才会添加。

常见问题

常见问题解答

为什么我的步进电机会丢步?

因为转子落后被指令的磁场太多了。在 90 电角度以内(一个整步的范围),电机会把自己拉回正轨,所以任何稳态负载都必须待在这个范围内。越过 90° 之后,落后得越多,可用的扭矩就越小;到了 180°,扭矩已经反向,转而朝下一个磁极拉,于是转子滑到一个完整电周期之外的下一个稳定点。有三样东西会把它推过这个极限:大于可用扭矩的负载;陡到让转子动起来所需的扭矩超过剩余扭矩的加速度;以及速度,因为电机转得越快,可用扭矩就越低。这个实验台让你可以分别触发每一种情况。

为什么它每次丢的恰好是四步?

因为磁场的分布每四个整步重复一次。转子失去同步时并不会停在某个任意的角度上——它会一直滑,直到卡进下一个稳定齿,那是一个完整电周期之外的位置;一个电周期是 360 电角度,也就是四个整步。实验台从没有强制规定这一点;它只是对正弦扭矩定律做积分,而丢失的步数自然总是四的倍数。如果你的机器偏差的步数不是四个整步的倍数,那么原因就不是失步。

细分会让我的机器更精确吗?

它让机器更平顺,而不是更精确。在固定负载下把细分选择从 1/1 调到 1/16,分别观察两个数字:相对指令路径的偏差下降了大约三倍,因为你不再每次给转子一个 90 电角度的冲击,它也就不再振铃了。而负载引起的稳态误差一点没动——整步时是 0.044 mm,十六细分时还是 0.044 mm。把步长分细,只是让这个误差以步为单位看起来更大了;对误差本身毫无作用。

要解决失速,应该提高电压还是电流?

取决于它在哪里失速,而实验台会把这个区别展示得一清二楚。在拐点速度以下,驱动器能维持你设定的限流,所以扭矩由电流决定——提高限流即可。在拐点速度以上,驱动器已经无法克服感抗和反电动势,把设定的电流灌进绕组,所以扭矩由电源电压决定,提高限流除了让一切更烫之外什么也改变不了。加载 12 V 预设,看着同一段在 12 V 下失败的运动,在不改电流的情况下于 24 V 时顺利完成。

为什么同一段运动加了斜坡能成功,不加就失败?

因为让转子加速到目标速度需要扭矩,而在运动开始的那一瞬间,转子还静止着,被指令的磁场却已经在狂奔。把步进频率控件切换到“直全速”,其他一切不变,滞后角大约在一毫秒内就越过 90°,到三毫秒时同步就彻底丢了:驱动器数完了整段 60 mm 运动的脉冲,而轴只走到 4 mm。这个极限就是起动频率,也就是从静止状态能指令的最高步进频率,而加速斜坡正是让你保持在它之下的手段。

真实的电机也是这样工作的吗?

转子动力学是精确的——扭矩与滞后角的正弦成正比,这正是混合式步进电机的实际工作原理,而滑步行为是对它积分的自然结果。绕组模型是对斩波驱动器的集总稳态近似,而不是逐相仿真,所以它能复现数据手册中扭矩曲线的形状,并给出正确的工程答案,但在高速尾段偏保守。用它来建立直觉、看清哪个旋钮要紧;在为机器选型时,请使用真实的扭矩曲线。

延伸阅读

参考资料

权威资料,帮助你越过本模拟器有限的教学模型,进一步深入学习。