2. 時間刻みとパルス発生
計算刻みは、パルス間隔、計算時間、上限ループ数のバランスで自動決定します。各刻みでは加速度制限後のPPSを積算し、整数パルスになった分だけ指令電気角を進めます。
Δt=clamp(min(25 µs, Tpulse/8, Tsim/18000), 4 µs, 50 µs)
PPSnow=min(|PPStarget|, apps · t)
θcmd,e+=pulses · (π/2) / M
対応ソース: simulate() の刻み設定とパルス積算
const stepsPerRev = 360 / values.stepAngleDeg;
const rotorTeeth = stepsPerRev / 4;
const microsteps = Math.max(1, values.microsteps);
const dtCandidate = Math.min(25e-6, pulseInterval / 8, duration / 18000);
const dt = clamp(dtCandidate, 4e-6, 50e-6);
const commandElecStep = (Math.PI / 2) / microsteps;
const accelLimitedPps = Math.min(Math.abs(values.pps), Math.max(0, values.accelPps2 * t));
pulseAccumulator += accelLimitedPps * actualDt;
commandElec += wholePulses * commandElecStep * direction;
3. 相電流とドライバ制御
指令電気角からA/B相の理想正弦波電流を作り、巻線の R/L、逆起電力、VM、Rds(on)、decayモードで実電流の変化率を制限します。チョッパの細かいPWM波形は積分せず、電流リップルはチャート表示用の幅として扱います。
IA*=Ilim cos(θcmd,e)
IB*=Ilim sin(θcmd,e)
Veff=VM · umode - |I| Rds
dI/dt=clamp((I* - I)Bctrl, dIfall/dt, dIrise/dt)
dIrise/dt=(Veff - RI - Ebemf) / L
dIfall/dt=(-Veffddecay - RI - Ebemf) / L
対応ソース: getControlProfile(), stepCurrent()
let decayFactor = 0.62;
let bandwidth = values.chopperKhz * 70;
let voltageUtil = 0.9;
const targetA = currentLimit * Math.cos(commandElec);
const targetB = currentLimit * Math.sin(commandElec);
const error = target - i;
const vDrop = Math.abs(i) * driver.rdsTotal;
const bus = Math.max(0.2, values.supplyVoltage * control.voltageUtil - vDrop);
const desiredDiDt = error * control.bandwidth;
const maxRise = (bus - resistance * i - bemf) / inductance;
const maxFall = (-bus * control.decayFactor - resistance * i - bemf) / inductance;
let diDt = clamp(desiredDiDt, maxFall, maxRise);
4. 逆起電力、トルク、機械負荷
ホールディングトルクからトルク定数 Kt を作り、同じ値を逆起電力定数 Ke として使います。ロータ電気角に対するq軸電流を求め、電磁トルク、ディテントトルク、負荷、粘性摩擦から角速度と角度をオイラー法で更新します。
Kt=Thold / (chold · Irated)
Iq=-IA sin(θrot,e) + IB cos(θrot,e)
Tem=Kt · Iq · saturation
Jtotal=Jrotor + Jload / G2
α=(Tmotor - Tload - Bω) / Jtotal
Trequired=Tload + Bω + Jtotal·αcmd
ω目安上限=√(VM² - (R I)²) / (Ke + zrL I)
ωmax=1.25 · VM / Ke
対応ソース: simulate() のトルク/運動方程式と速度クランプ
const ktDivisor = values.ktConvention === "simple" ? 1 : Math.SQRT2;
const kt = values.holdingTorque / (ktDivisor * Math.max(0.05, values.ratedCurrent));
const ke = kt;
const bemfA = -ke * omega * Math.sin(rotorElec);
const bemfB = ke * omega * Math.cos(rotorElec);
const qCurrent = -ia * Math.sin(rotorElec) + ib * Math.cos(rotorElec);
const saturation = 1 / (1 + Math.pow(Math.max(0, Math.abs(qCurrent) / Math.max(values.ratedCurrent, 0.1) - 1.15), 2) * 0.4);
const electromagneticTorque = kt * qCurrent * saturation;
// 自走の到達速度の目安(逆起電力≒電源電圧)。これを超えた数値発散を防ぐ。
const bemfCeilingOmega = Math.max(1, values.supplyVoltage) / Math.max(ke, 1e-4);
const omegaMax = bemfCeilingOmega * 1.25;
const alpha = (motorTorque - load - friction) / totalInertia;
omega += alpha * actualDt;
omega = Math.max(-omegaMax, Math.min(omegaMax, omega)); // 速度クランプ
theta += omega * actualDt;
必要トルク Trequired には負荷・粘性に加えて加速トルク Jtotal·αcmd を含めます(慣性負荷では加速トルクが支配的になるため)。トルク余裕はこの Trequired に対する比です。
目安上限rpm は VM/Ke ではなく、必要電流 I を保つのに要する相電圧が VM に達する速度(巻線 R・L・電流 I・電気角の歯数 zr を含む)で算出します。高インダクタンスのモータでは VM/Ke よりずっと早く頭打ちになります。
速度クランプ: 脱調後に角速度が非物理的に発散して相電流波形がエイリアス化(ジャギー)するのを防ぐため、ω を ωmax(≈ VM/Ke に余裕を持たせた緩い上限)でクランプします。脱調が発生したら、それ以降の速度・追従誤差・トルクは参考外とし(最終速度は「脱調」表示)、追従誤差の統計も頭打ちにします。最終速度は瞬間値ではなく終盤(最後10%)の平均+速度リップルで表示します。外力で回される回生・オーバーホール負荷は本ツールの対象外です。
5. 判定指標
警告は、ICのVM範囲、電流上限、PWM/PPS比、追従誤差、相電流誤差、逆起電力比、トルク余裕、簡易ジャンクション温度を組み合わせています。脱調相当は最大追従誤差がおよそ2.2 full-stepを超えた場合に強く扱います。
estep=(θcmd,m - θ) / (2π / Nstep)
Dspeed=1 / (1 + (2.2 · max(Ebemf/VM))2)
marginT=Tavailable · Dspeed / Trequired
Tj=Tamb + Pdriver · RθJA
対応ソース: simulate(), estimateDissipation(), buildWarnings()
const currentRms = Math.sqrt(currentSqSum / ((loops + 1) * 2));
const driverLoss = currentRms * currentRms * driver.rdsTotal * 2;
const junctionTemp = values.ambientTemp + driverLoss * driver.thermalRja;
const availableLowSpeed =
(values.holdingTorque * (currentLimit / Math.max(values.ratedCurrent, 0.1))) / ktDivisor;
const speedDerate = 1 / (1 + Math.pow(maxBemfRatio * 2.2, 2));
const torqueMargin = (availableLowSpeed * speedDerate) / Math.max(0.001, requiredMotorTorque);
if (stats.maxErrorSteps > 2.2) { ... }
if (stats.junctionTemp > 145) { ... }
if (stats.torqueMargin < 1.2) { ... }