# Electric motor ## Microsim <iframe src="https://editor.p5js.org/sciencenibber/full/cs2PJ6ZNc" width="100%" height="620" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe> <img src="../ENGINES_Electrical-Engineering_Images/Electric_motor.png" alt="Electric_motor microsim"> [Open in the p5.js editor](https://editor.p5js.org/sciencenibber/sketches/cs2PJ6ZNc) ```js // Electric motor - a machine that converts electrical energy into mechanical rotation. A // current-carrying conductor in a magnetic field feels a force F = B*I*L; on a shaft that force // becomes a TORQUE. Modelled here as the textbook brushed permanent-magnet DC motor. The key // idea the sim is built to show is BACK-EMF: the spinning armature generates its own voltage // E_b = k*omega that opposes the supply, so the faster it spins the less current it draws -- the // motor regulates its own current. Shown through the two canonical pictures at once: a Pattern A // animated SCHEMATIC (N/S poles, a rotor that actually spins at the live operating speed, force // arrows, commutator+brushes) and a Pattern H/chart TORQUE-SPEED characteristic (descending motor // line + load line + operating point on a torque axis; the mechanical-power parabola on a power // axis). Wikitube / E.N.G.I.N.E.S. hub -> Electrical engineering room. One file, one ARTICLE. // // LEARNING OBJECTIVE // From tau_em = k*I_a and E_b = k*omega with the armature KVL V = I_a*R_a + k*omega, derive the // linear torque-speed law tau_em(omega) = kV/R_a - (k^2/R_a)*omega; read the stall torque kV/R_a // and no-load speed V/k straight off the controls; predict the operating speed where the motor // line meets the load line; and explain why mechanical power peaks at half the no-load speed // (~50% efficiency) and why back-EMF self-regulates the current below its locked-rotor value. // // PATTERN Pattern H/chart torque-speed characteristic (analytical star) + Pattern A animated // schematic. Chart: motor line tau_em(omega) on a LEFT torque axis from stall torque to no-load // speed, a horizontal load line at tau_L, the operating-point dot, and the power parabola // P(omega) on a RIGHT power axis peaking at omega_0/2. Schematic: PM stator (N top, S bottom, // baked field gradient), a rotating armature whose spin tracks the live speed, F=BIL force arrows // sized by current, and a commutator + brushes. // // MODEL ideal PM DC motor (constant field, bearing friction neglected): // I_a = (V - k*omega)/R_a armature current (back-EMF reduces it) // E_b = k*omega back-EMF (counter-EMF) // tau_em = k*I_a electromagnetic torque // tau_s = k*V/R_a stall torque (omega=0) // omega0 = V/k no-load speed (tau=0) // omega* = (tau_s - tau_L)/(k^2/R_a) steady operating speed for a constant load // P_elec = V*I_a, P_mech = tau_em*omega, P_loss = I_a^2*R_a, eta = P_mech/P_elec // Rotor speed is the one time-evolving quantity: J*d(omega)/dt = tau_em - tau_L, a first-order // linear relaxation toward omega* with mechanical time constant tau_mech = J*R_a/k^2, advanced by // the EXACT exponential update omega += (omega* - omega)*(1 - exp(-dt/tau_mech)) with // dt = min(deltaTime/1000, 0.05) -- frame-rate-independent and unconditionally stable (the // 1 - exp(-lambda*dt) family, not forward-Euler). Stall: if tau_L >= tau_s the target is 0 and // the regime flips to STALLED at the locked-rotor current V/R_a. SI units (V, A, ohm, N*m, // rad/s, W); R_a is log-mapped; rpm derived for display only. // // EDITOR-SAFE this sim uses an animating loop() (the rotor genuinely evolves in time) but honors // the reason behind the noLoop default: draw() has NO heavy per-frame inner loops (one ~90-point // power-parabola polyline as ONE path, a 2-point motor line, a handful of rotor primitives) and // ALL static scenery (panel frames, axis titles, legend, stator poles, field gradient) is baked // ONCE into an offscreen buffer in setup(); setup() is cheap. p5.disableFriendlyErrors = true. // ASCII-only strings (Golden Rule 6). Top-level names avoid p5 globals AND p5 method names // (no map, scale, mag, pow, log, exp, sqrt, rotate, ratio, split, ...): the model uses Math.* and // bespoke xForW/yForTau/yForP mappers; state is vSupply/rArm/kMot/tauLoad/omega/theta. const ARTICLE = "Electric_motor"; const TITLE = "Electric motor: back-EMF, the torque-speed line, and the operating point"; const WIKI = "en.wikitube.io/wiki/" + ARTICLE; // ---------- physical / model constants ---------- const JROT = 5e-4; // kg*m^2 rotor inertia (sets the spin-up time constant) const SPIN_REF = 250; // rad/s speed mapped to SPIN_DISP rev/s on screen const SPIN_DISP = 2.2; // rev/s on-screen rotation at SPIN_REF (legibility scale) const SPIN_GAIN = SPIN_DISP * 2 * Math.PI / SPIN_REF; // rad(screen)/s per rad/s(true) const RPM_K = 60 / (2 * Math.PI); // rad/s -> rpm const NSAMP = 90; // samples on the power parabola (one polyline) const TAU2PI = 2 * Math.PI; // ---------- state: single source of truth, mirrored by the controls ---------- let vSupply = 12; // V supply voltage let rArm = 1.0; // ohm armature resistance let kMot = 0.05; // N*m/A motor constant (= back-EMF constant V*s/rad) let tauLoad = 0.20; // N*m constant load torque let omega = 0; // rad/s rotor angular speed (the time-evolving state) let theta = 0; // rad displayed rotor angle (legibility-scaled) // ---------- defaults (reset restores ALL state, Golden Rule 4) ---------- const D_V = 12, D_R = 1.0, D_K = 0.05, D_TL = 0.20; // ---------- auto-scaled axis bounds ---------- let tauHi, wHi, pHi; // N*m, rad/s, W half/full ranges of the three chart axes // ---------- layout (computed from width/height; never hard-coded) ---------- let devX, devY, devW, devH; // schematic panel let panX, panY, panW, panH; // readout panel let rcX, rcY, rcW, rcH; // chart panel let ctrlY; // control region top let cc1, cc2, cc3, cc4; // control column x positions let plotX0, plotY0, plotW, plotH; // chart plot rect (plotY0 = bottom edge, origin) let mcx, mcy, mrR; // motor centre + rotor radius (schematic) // ---------- p5 objects ---------- let staticBuf; // baked static background buffer let vSlider, rSlider, kSlider, tlSlider, resetButton; // ---------- palette (built once) ---------- let COL; function setup() { createCanvas(720, 520); pixelDensity(2); p5.disableFriendlyErrors = true; // Golden Rule 8: zero FES noise computeLayout(); buildPalette(); staticBuf = createGraphics(width, height); bakeScene(staticBuf); // frames + axis titles + legend + stator, once // --- controls: V, k, tau_L linear; R_a log-mapped via 0..120 steps --- vSlider = createSlider(0, 24, D_V, 0.5); // V 0 .. 24 V rSlider = createSlider(0, 120, Math.round(rToSlider(D_R)), 1); // R_a 0.1 .. 10 ohm (log) kSlider = createSlider(5, 200, Math.round(D_K * 1000), 1); // k 0.005 .. 0.200 (x1000) tlSlider = createSlider(0, 1000, Math.round(D_TL * 1000), 5); // tauL 0 .. 1.000 N*m (x1000) vSlider.position(cc1, ctrlY + 18); vSlider.style('width', '150px'); rSlider.position(cc2, ctrlY + 18); rSlider.style('width', '150px'); kSlider.position(cc3, ctrlY + 18); kSlider.style('width', '150px'); tlSlider.position(cc1, ctrlY + 52); tlSlider.style('width', '150px'); vSlider.input(onCtrl); rSlider.input(onCtrl); kSlider.input(onCtrl); tlSlider.input(onCtrl); resetButton = createButton('Reset'); resetButton.position(cc4, ctrlY + 50); resetButton.mousePressed(resetAll); // animating loop() is intentional (the rotor evolves in time); draw() is light, scenery baked } function computeLayout() { const drawH = height * 0.78; // top drawing region (~405 px) const top = 42; devX = 16; devY = top; devW = width * 0.46 - devX; devH = 184; panX = devX + devW + 14; panY = top; panW = width - panX - 14; panH = devH; rcX = 16; rcY = top + devH + 14; rcW = width - 32; rcH = drawH - rcY - 10; ctrlY = drawH + 4; cc1 = 16; cc2 = 190; cc3 = 364; cc4 = 538; // chart plot rect: 52 left for torque labels, 52 right for power labels, 26 bottom for speed axis plotX0 = rcX + 52; plotW = rcW - 52 - 52; plotY0 = rcY + rcH - 26; plotH = rcH - 16 - 26; // motor centre in the left part of the schematic panel mcx = devX + devW * 0.40; mcy = devY + devH * 0.54; mrR = Math.min(devW * 0.26, devH * 0.30); } function buildPalette() { COL = { bg: color('#0b0f16'), panel: color('#111a26'), panelEdge: color(64, 80, 102), text: color('#e8eef6'), muted: color('#93a0b2'), wire: color('#7f8ea3'), iron: color('#5b6b80'), npole: color('#ff6b6b'), // north pole (red) spole: color('#5ad1ff'), // south pole (blue) rotor: color('#cfd8e6'), // rotor body cur: color('#ffae57'), // armature current / windings (amber) force: color('#5ee08c'), // F = BIL force arrows (green) motorln: color('#5ad1ff'), // motor torque line (cyan) loadln: color('#ff5252'), // load line (red) power: color('#c792ea'), // power parabola (violet) op: color('#ffd166'), // operating point (yellow) grid: color(54, 66, 84), axis: color(120, 138, 160), good: color('#5ee08c'), warn: color('#ff5252'), gauge: color('#ffd166') }; } // ============== slider transform: R_a log scale over 0.1 ohm .. 10 ohm (2 decades) ============== function rToSlider(v) { return (Math.log(v / 0.1) / Math.LN10) / 2 * 120; } // 0.1->0, 10->120 function sliderToR(s) { return 0.1 * Math.pow(10, (s / 120) * 2); } // =================== ideal PM DC-motor model (closed form for the steady targets) =================== // returns the params-only quantities (independent of the instantaneous omega) function motorTargets() { const tauStall = kMot * vSupply / rArm; // stall torque kV/R const wNoLoad = (kMot > 1e-9) ? vSupply / kMot : 0; // no-load speed V/k const damp = (kMot * kMot) / rArm; // k^2/R electrical damping coefficient let wStar = (damp > 1e-12) ? (tauStall - tauLoad) / damp : 0; // steady operating speed let stalled = false; if (wStar <= 0) { wStar = 0; stalled = (tauLoad > tauStall + 1e-9); } const tauMech = (damp > 1e-12) ? JROT / damp : 1e9; // mechanical time constant const pPeak = tauStall * wNoLoad / 4; // peak mech power at wNoLoad/2 return { tauStall: tauStall, wNoLoad: wNoLoad, damp: damp, wStar: wStar, stalled: stalled, tauMech: tauMech, pPeak: pPeak }; } // instantaneous quantities from the current omega function liveFrom(w) { const iArm = (vSupply - kMot * w) / rArm; // armature current const eb = kMot * w; // back-EMF const tauEm = kMot * iArm; // electromagnetic torque const pElec = vSupply * iArm; const pMech = tauEm * w; const pLoss = iArm * iArm * rArm; const eff = (pElec > 1e-9) ? pMech / pElec : 0; const iLock = vSupply / rArm; // locked-rotor (stall) current const gainI = (iLock > 1e-9) ? constrain(iArm / iLock, 0, 1) : 0; return { iArm: iArm, eb: eb, tauEm: tauEm, pElec: pElec, pMech: pMech, pLoss: pLoss, eff: eff, iLock: iLock, gainI: gainI }; } // nice round ceiling (1/2/5 x 10^k) for the auto-scaled axes function niceCeil(x) { if (x <= 0) return 1; const e = Math.floor(Math.log(x) / Math.LN10); const f = x / Math.pow(10, e); let m = 10; if (f <= 1) m = 1; else if (f <= 2) m = 2; else if (f <= 5) m = 5; return m * Math.pow(10, e); } // =============================== DRAW =============================== function draw() { const dt = Math.min(deltaTime / 1000, 0.05); // Golden Rule 7: clamped, frame-rate-independent const t = motorTargets(); // --- integrate the rotor speed toward the operating point (exact exponential relaxation) --- const alpha = 1 - Math.exp(-dt / t.tauMech); // 1 - exp(-dt/tau): exact for the linear ODE omega += (t.wStar - omega) * alpha; if (omega < 0) omega = 0; // advance the displayed angle at a legibility-scaled rate theta = (theta + omega * SPIN_GAIN * dt) % TAU2PI; // robust wrap even at extreme speeds const s = liveFrom(omega); // --- auto-scale the three axes --- tauHi = niceCeil(1.12 * Math.max(t.tauStall, tauLoad, 1e-6)); wHi = niceCeil(1.08 * Math.max(t.wNoLoad, omega, 1)); pHi = niceCeil(1.15 * Math.max(t.pPeak, s.pMech, 1e-6)); background(COL.bg); image(staticBuf, 0, 0); drawChart(t, s); drawSchematic(t, s); drawReadout(t, s); drawHUD(t, s); } // ---------- static scenery baked once into an offscreen buffer ---------- function bakeScene(g) { g.push(); g.background(COL.bg); panelRect(g, devX, devY, devW, devH, "Schematic: PM stator + armature + commutator"); panelRect(g, panX, panY, panW, panH, ""); // "Readouts" title drawn live panelRect(g, rcX, rcY, rcW, rcH, "Torque-speed characteristic: tau (left) + P_mech (right)"); bakeChartFrame(g); // axis titles + legend + axis lines bakeMotor(g); // stator poles + field gradient + brushes g.pop(); } function panelRect(g, x, y, w, h, title) { g.noStroke(); g.fill(COL.panel); g.rect(x, y, w, h, 8); g.noFill(); g.stroke(COL.panelEdge); g.strokeWeight(1); g.rect(x, y, w, h, 8); if (title.length > 0) { g.noStroke(); g.fill(COL.muted); g.textSize(11); g.textAlign(LEFT, BOTTOM); g.text(title, x + 4, y - 3); } } // chart frame: the two y-axis baselines, the x-axis, axis titles, and the legend (baked once) function bakeChartFrame(g) { g.push(); // axis lines (origin at bottom-left; torque on left, power on right, speed along the bottom) g.stroke(COL.axis); g.strokeWeight(1.4); g.line(plotX0, plotY0, plotX0 + plotW, plotY0); // x-axis (speed) g.line(plotX0, plotY0, plotX0, plotY0 - plotH); // left y-axis (torque) g.line(plotX0 + plotW, plotY0, plotX0 + plotW, plotY0 - plotH); // right y-axis (power) // axis titles g.noStroke(); g.fill(COL.muted); g.textSize(9.5); g.textAlign(CENTER, TOP); g.text("angular speed omega (rad/s)", plotX0 + plotW / 2, plotY0 + 13); g.push(); g.translate(rcX + 13, plotY0 - plotH / 2); g.rotate(-HALF_PI); g.textAlign(CENTER, CENTER); g.fill(COL.motorln); g.textSize(9.5); g.text("torque tau (N*m)", 0, 0); g.pop(); g.push(); g.translate(rcX + rcW - 13, plotY0 - plotH / 2); g.rotate(HALF_PI); g.textAlign(CENTER, CENTER); g.fill(COL.power); g.textSize(9.5); g.text("mech. power P (W)", 0, 0); g.pop(); // legend (top-RIGHT of the plot: the high-torque + high-speed corner the curves never reach) const lx = plotX0 + plotW - 198, ly = plotY0 - plotH + 6; g.noStroke(); g.fill(17, 26, 38, 220); g.rect(lx, ly, 192, 30, 5); g.textSize(8.5); g.textAlign(LEFT, CENTER); g.stroke(COL.motorln); g.strokeWeight(2.4); g.line(lx + 6, ly + 9, lx + 22, ly + 9); g.noStroke(); g.fill(COL.motorln); g.text("motor tau", lx + 26, ly + 9); g.stroke(COL.loadln); g.strokeWeight(1.6); bakeDash(g, lx + 96, ly + 9, lx + 112, ly + 9); g.noStroke(); g.fill(COL.loadln); g.text("load", lx + 116, ly + 9); g.stroke(COL.power); g.strokeWeight(2.2); g.noFill(); g.line(lx + 6, ly + 22, lx + 22, ly + 22); g.noStroke(); g.fill(COL.power); g.text("P_mech", lx + 26, ly + 22); g.fill(COL.op); g.circle(lx + 104, ly + 22, 6); g.text("op. point", lx + 112, ly + 22); g.pop(); } function bakeDash(g, x0, y0, x1, y1) { const n = 3; for (let i = 0; i < n; i++) { const a = i / n, b = (i + 0.55) / n; g.line(x0 + (x1 - x0) * a, y0, x0 + (x1 - x0) * b, y1); } } // stator: housing ring, N pole (top) + S pole (bottom), a faint vertical field gradient between // them, and the two brushes either side of the shaft. Baked once (the field gradient is the only // many-line loop and it runs a single time here, never in draw()). function bakeMotor(g) { g.push(); // field gradient inside the bore (N at top -> S at bottom), baked line-by-line ONCE const gx = mcx - mrR * 1.18, gw = mrR * 2.36; const gy = mcy - mrR * 1.18, gh = mrR * 2.36; g.noStroke(); for (let i = 0; i <= 40; i++) { const u = i / 40; const c = g.lerpColor(color(255, 107, 107, 26), color(90, 209, 255, 26), u); g.fill(c); g.rect(gx, gy + u * gh, gw, gh / 40 + 1); } // housing ring g.noFill(); g.stroke(COL.iron); g.strokeWeight(6); g.circle(mcx, mcy, mrR * 2.5); g.stroke(150, 170, 196, 120); g.strokeWeight(1); g.circle(mcx, mcy, mrR * 2.5 + 6); // N pole shoe (top) and S pole shoe (bottom) as filled arcs g.noStroke(); g.fill(red(COL.npole), green(COL.npole), blue(COL.npole), 200); g.arc(mcx, mcy, mrR * 2.18, mrR * 2.18, Math.PI + 0.5, TAU2PI - 0.5, PIE); g.fill(red(COL.spole), green(COL.spole), blue(COL.spole), 200); g.arc(mcx, mcy, mrR * 2.18, mrR * 2.18, 0.5, Math.PI - 0.5, PIE); // re-cut the bore so the poles read as curved shoes around the rotor g.fill(COL.panel); g.circle(mcx, mcy, mrR * 1.78); g.fill(COL.bg); g.circle(mcx, mcy, mrR * 1.6); // pole labels g.fill(COL.text); g.textSize(13); g.textStyle(BOLD); g.textAlign(CENTER, CENTER); g.text("N", mcx, mcy - mrR * 0.92); g.text("S", mcx, mcy + mrR * 0.92); g.textStyle(NORMAL); // brushes (fixed) left and right of the shaft, touching the commutator g.fill(COL.muted); g.noStroke(); g.rect(mcx - 9, mcy - 4, 6, 8, 1); g.rect(mcx + 3, mcy - 4, 6, 8, 1); g.pop(); } // =============================== CHART (torque-speed + power) =============================== function xForW(w) { return plotX0 + (constrain(w, 0, wHi) / wHi) * plotW; } function yForTau(v) { return plotY0 - (constrain(v, 0, tauHi) / tauHi) * plotH; } function yForP(p) { return plotY0 - (constrain(p, 0, pHi) / pHi) * plotH; } // one mech-power curve P(w) = (tau_s - damp*w)*w drawn as a SINGLE polyline on the power axis function powerPath(t) { beginShape(); for (let i = 0; i <= NSAMP; i++) { const w = (i / NSAMP) * t.wNoLoad; const p = (t.tauStall - t.damp * w) * w; vertex(xForW(w), yForP(p)); } endShape(); } function drawChart(t, s) { // --- gridlines + live tick labels (torque left, speed bottom, power right; all auto-scaled) --- push(); textSize(8.5); const tauStep = niceCeil(tauHi / 4), wStep = niceCeil(wHi / 4), pStep = niceCeil(pHi / 4); // torque ticks (left) + horizontal gridlines for (let v = 0; v <= tauHi + 1e-9; v += tauStep) { const gy = yForTau(v); if (v > 0) { stroke(COL.grid); strokeWeight(1); line(plotX0, gy, plotX0 + plotW, gy); } noStroke(); fill(COL.motorln); textAlign(RIGHT, CENTER); text(fmtTorque(v), plotX0 - 6, gy); } // power ticks (right) for (let p = 0; p <= pHi + 1e-9; p += pStep) { const gy = yForP(p); noStroke(); fill(COL.power); textAlign(LEFT, CENTER); text(fmtWatt(p), plotX0 + plotW + 6, gy); } // speed ticks (bottom) + vertical gridlines for (let w = 0; w <= wHi + 1e-9; w += wStep) { const gx = xForW(w); if (w > 0) { stroke(COL.grid); strokeWeight(1); line(gx, plotY0, gx, plotY0 - plotH); } noStroke(); fill(COL.muted); textAlign(CENTER, TOP); text(fmtSpeedShort(w), gx, plotY0 + 3); } pop(); // clip data to the plot rectangle push(); drawingContext.save(); drawingContext.beginPath(); drawingContext.rect(plotX0, plotY0 - plotH, plotW, plotH); drawingContext.clip(); // --- power parabola: one glowing single path on the power axis --- drawingContext.save(); drawingContext.shadowBlur = 8; drawingContext.shadowColor = 'rgba(199,146,234,0.55)'; noFill(); stroke(COL.power); strokeWeight(2.2); strokeJoin(ROUND); powerPath(t); drawingContext.restore(); // peak-power marker at omega0/2 const wPk = t.wNoLoad / 2, pPk = t.pPeak; noStroke(); fill(COL.power); circle(xForW(wPk), yForP(pPk), 6); // --- load line (horizontal dashed at tau_L) --- stroke(COL.loadln); strokeWeight(1.6); drawingContext.setLineDash([6, 4]); line(plotX0, yForTau(tauLoad), plotX0 + plotW, yForTau(tauLoad)); drawingContext.setLineDash([]); // --- motor torque line: stall torque -> no-load speed (single straight segment, glowing) --- drawingContext.save(); drawingContext.shadowBlur = 8; drawingContext.shadowColor = 'rgba(90,209,255,0.6)'; stroke(COL.motorln); strokeWeight(2.6); strokeCap(ROUND); line(xForW(0), yForTau(t.tauStall), xForW(t.wNoLoad), yForTau(0)); drawingContext.restore(); // --- operating point (steady) + the live point (current omega on the line) --- if (!t.stalled && tauLoad > 1e-9) { const ox = xForW(t.wStar), oy = yForTau(tauLoad); stroke(COL.op); strokeWeight(1); drawingContext.setLineDash([3, 3]); line(ox, plotY0, ox, oy); line(plotX0, oy, ox, oy); drawingContext.setLineDash([]); noStroke(); fill(COL.op); circle(ox, oy, 9); } // the live operating point slides up the line during spin-up const lx = xForW(omega), ly = yForTau(s.tauEm); noStroke(); fill(COL.good); circle(lx, ly, 6); drawingContext.restore(); pop(); } // =============================== SCHEMATIC (stator + spinning rotor + force + commutator) =============================== function drawSchematic(t, s) { // --- motion-blur arc behind the rotor (length tracks the true speed) --- const blurAng = constrain(omega / Math.max(t.wNoLoad, 1) * 1.4, 0, 1.4); if (blurAng > 0.02) { push(); noFill(); stroke(red(COL.rotor), green(COL.rotor), blue(COL.rotor), 60); strokeWeight(3); arc(mcx, mcy, mrR * 1.7, mrR * 1.7, theta - blurAng, theta); pop(); } // --- the rotor: disc + winding band that spins with theta --- push(); translate(mcx, mcy); rotate(theta); // rotor iron disc noStroke(); fill(red(COL.rotor), green(COL.rotor), blue(COL.rotor), 40); circle(0, 0, mrR * 1.5); stroke(COL.rotor); strokeWeight(1.4); noFill(); circle(0, 0, mrR * 1.5); // armature winding band across the rotor, glow tracks the current const glow = s.gainI; drawingContext.save(); drawingContext.shadowBlur = 6 + 14 * glow; drawingContext.shadowColor = 'rgba(255,174,87,' + (0.35 + 0.5 * glow) + ')'; stroke(COL.cur); strokeWeight(5); strokeCap(ROUND); line(-mrR * 0.66, 0, mrR * 0.66, 0); drawingContext.restore(); // current-direction markers on the two conductors: dot (out of page) and cross (into page) noStroke(); fill(COL.text); circle(mrR * 0.66, 0, 5); // current out stroke(COL.text); strokeWeight(1.3); line(-mrR * 0.66 - 3, -3, -mrR * 0.66 + 3, 3); // current in (cross) line(-mrR * 0.66 - 3, 3, -mrR * 0.66 + 3, -3); pop(); // --- F = BIL force arrows at the rim: a steady couple (commutator keeps the torque sign) --- const fLen = (10 + 30 * s.gainI); drawForceArrow(mcx + mrR * 0.75, mcy, 0, -1, fLen, COL.force); // right conductor pushed up drawForceArrow(mcx - mrR * 0.75, mcy, 0, +1, fLen, COL.force); // left conductor pushed down // --- commutator split-ring at the shaft (rotates with theta) --- push(); translate(mcx, mcy); rotate(theta); stroke(COL.cur); strokeWeight(2); noFill(); arc(0, 0, 16, 16, 0.25, Math.PI - 0.25); arc(0, 0, 16, 16, Math.PI + 0.25, TAU2PI - 0.25); pop(); noStroke(); fill(COL.muted); circle(mcx, mcy, 5); // shaft // --- labels + regime banner --- push(); noStroke(); textSize(9.5); textAlign(CENTER, TOP); fill(COL.force); text("F = B*I*L", mcx + mrR * 1.05, mcy - 6); fill(COL.muted); textAlign(CENTER, TOP); textSize(8.5); text("commutator + brushes", mcx, mcy + mrR * 1.32); const rl = regionInfo(t); fill(rl.c); textStyle(BOLD); textSize(11.5); textAlign(LEFT, TOP); text(rl.t, devX + 8, devY + devH - 18); textStyle(NORMAL); // live rpm under the rotor fill(COL.text); textSize(10); textAlign(CENTER, TOP); text(fmtRpm(omega), mcx, mcy - mrR * 1.34); pop(); } // a force arrow at (x,y) pointing in unit dir (dx,dy), length len function drawForceArrow(x, y, dx, dy, len, col) { push(); stroke(col); strokeWeight(2.4); strokeCap(ROUND); const ex = x + dx * len, ey = y + dy * len; line(x, y, ex, ey); noStroke(); fill(col); const px = -dy, py = dx; // perpendicular for the arrowhead triangle(ex + dx * 6, ey + dy * 6, ex + px * 4, ey + py * 4, ex - px * 4, ey - py * 4); pop(); } // =============================== READOUT PANEL =============================== function drawReadout(t, s) { push(); textAlign(LEFT, TOP); noStroke(); const x = panX + 12; let y = panY + 10; fill(COL.text); textStyle(BOLD); textSize(13); text("Readouts", x, y); y += 18; // operating point + regime const rl = regionInfo(t); fill(COL.op); textStyle(BOLD); textSize(12.5); text("Operating point", x, y); y += 16; textStyle(NORMAL); textSize(10.5); fill(COL.muted); text("speed omega* =", x, y); fill(COL.text); text(fmtSpeed(t.wStar) + " (" + fmtRpm(t.wStar) + ")", x + 96, y); y += 14; fill(COL.muted); text("now:", x, y); fill(COL.good); text(fmtSpeed(omega) + " (" + fmtRpm(omega) + ")", x + 40, y); fill(rl.c); textStyle(BOLD); text(rl.t, x + 170, y); textStyle(NORMAL); y += 17; // current + back-EMF fill(COL.cur); textStyle(BOLD); textSize(11.5); text("Current I_a = (V - E_b)/R_a", x, y); textStyle(NORMAL); textSize(10.5); y += 15; fill(COL.muted); text("I_a =", x, y); fill(COL.text); text(fmtAmp(s.iArm), x + 40, y); fill(COL.muted); text("E_b =", x + 130, y); fill(COL.text); text(fmtVolt(s.eb), x + 170, y); y += 14; fill(COL.muted); text("locked-rotor I = V/R_a =", x, y); fill(COL.warn); text(fmtAmp(s.iLock), x + 150, y); y += 16; // torque fill(COL.motorln); textStyle(BOLD); textSize(11.5); text("Torque tau_em = k*I_a", x, y); textStyle(NORMAL); textSize(10.5); y += 15; fill(COL.muted); text("tau_em =", x, y); fill(COL.text); text(fmtTorque(s.tauEm), x + 56, y); fill(COL.muted); text("tau_L =", x + 150, y); fill(COL.loadln); text(fmtTorque(tauLoad), x + 200, y); y += 16; // power + efficiency fill(COL.good); textStyle(BOLD); textSize(11.5); text("Power + efficiency", x, y); textStyle(NORMAL); textSize(10.5); y += 15; fill(COL.muted); text("P_elec =", x, y); fill(COL.text); text(fmtWatt(s.pElec), x + 56, y); fill(COL.muted); text("P_mech =", x + 150, y); fill(COL.text); text(fmtWatt(s.pMech), x + 210, y); y += 14; fill(COL.muted); text("P_loss(I^2R) =", x, y); fill(COL.text); text(fmtWatt(s.pLoss), x + 92, y); fill(COL.muted); text("eta =", x + 150, y); fill(COL.gauge); text(fmtPct(s.eff), x + 184, y); y += 17; // endpoints fill(COL.gauge); textStyle(BOLD); textSize(11.5); text("Characteristic endpoints", x, y); textStyle(NORMAL); textSize(10.5); y += 15; fill(COL.muted); text("stall tau_s = kV/R_a =", x, y); fill(COL.text); text(fmtTorque(t.tauStall), x + 138, y); y += 14; fill(COL.muted); text("no-load omega0 = V/k =", x, y); fill(COL.text); text(fmtSpeed(t.wNoLoad), x + 150, y); y += 14; fill(COL.muted); text("peak P @ omega0/2 =", x, y); fill(COL.power); text(fmtWatt(t.pPeak), x + 130, y); y += 16; fill(COL.muted); textSize(9.5); text("model: ideal PM DC (friction ~ 0; const field)", x, y); pop(); } // region label + colour from the targets function regionInfo(t) { if (t.stalled) return { t: "STALLED (tau_L >= tau_s)", c: COL.warn }; if (tauLoad <= 1e-9) return { t: "NO-LOAD (omega -> V/k)", c: COL.spole }; return { t: "RUNNING", c: COL.good }; } // =============================== HUD WATERMARK (drawn last) =============================== function drawHUD(t, s) { push(); noStroke(); // 1) title fill(COL.text); textAlign(LEFT, TOP); textStyle(BOLD); textSize(14); text(TITLE, devX, 12); textStyle(NORMAL); // 2) wiki URL fill(COL.muted); textSize(10.5); textAlign(RIGHT, TOP); text(WIKI, width - 12, 14); // 3) control hints + live slider value labels textAlign(LEFT, TOP); textSize(10.5); fill(COL.muted); text("Drag V, R_a, k, tau_L | key: r reset | load it down -> slower; past stall tau_s -> STALLED", devX, ctrlY - 14); fill(COL.text); textSize(11); text("V = " + fmtVolt(vSupply), cc1, ctrlY + 2); text("R_a = " + fmtOhm(rArm), cc2, ctrlY + 2); text("k = " + nf(kMot, 1, 3) + " N*m/A", cc3, ctrlY + 2); text("tau_L = " + fmtTorque(tauLoad), cc1, ctrlY + 38); // 4) live equation footer fill(COL.gauge); textSize(10.5); textAlign(LEFT, BOTTOM); text("tau=k*I_a | E_b=k*omega | V=I_a*R_a+E_b | I_a=" + fmtAmp(s.iArm) + " tau=" + fmtTorque(s.tauEm) + " omega=" + fmtRpm(omega) + " eta=" + fmtPct(s.eff), devX, height - 6); pop(); } // =============================== FORMAT HELPERS (ASCII units only) =============================== function fmtVolt(v) { const x = Math.abs(v); if (x >= 1e3) return nf(v / 1e3, 1, 2) + " kV"; if (x >= 1) return nf(v, 1, 2) + " V"; if (x >= 1e-3) return nf(v * 1e3, 1, 1) + " mV"; if (x > 0) return nf(v * 1e6, 1, 1) + " uV"; return "0 V"; } function fmtAmp(a) { const x = Math.abs(a); if (x >= 1) return nf(a, 1, 2) + " A"; if (x >= 1e-3) return nf(a * 1e3, 1, 1) + " mA"; if (x > 0) return nf(a * 1e6, 1, 1) + " uA"; return "0 A"; } function fmtOhm(r) { const a = Math.abs(r); if (a >= 1e3) return nf(r / 1e3, 1, 2) + " kohm"; if (a >= 1) return nf(r, 1, 2) + " ohm"; return nf(r, 1, 3) + " ohm"; } function fmtWatt(p) { const x = Math.abs(p); if (x >= 1e3) return nf(p / 1e3, 1, 2) + " kW"; if (x >= 1) return nf(p, 1, 2) + " W"; if (x >= 1e-3) return nf(p * 1e3, 1, 1) + " mW"; if (x > 0) return nf(p * 1e6, 1, 1) + " uW"; return "0 W"; } function fmtTorque(tq) { const x = Math.abs(tq); if (x >= 1) return nf(tq, 1, 3) + " N*m"; if (x >= 1e-3) return nf(tq * 1e3, 1, 1) + " mN*m"; if (x > 0) return nf(tq * 1e6, 1, 1) + " uN*m"; return "0 N*m"; } function fmtSpeed(w) { const x = Math.abs(w); if (x >= 1) return nf(w, 1, 1) + " rad/s"; if (x > 0) return nf(w, 1, 3) + " rad/s"; return "0 rad/s"; } function fmtSpeedShort(w) { if (Math.abs(w) >= 100) return nf(w, 1, 0); return nf(w, 1, 1); } function fmtRpm(w) { const rpm = w * RPM_K; if (Math.abs(rpm) >= 1e4) return nf(rpm / 1e3, 1, 1) + "k rpm"; return nf(rpm, 1, 0) + " rpm"; } function fmtPct(f) { return nf(100 * f, 1, 1) + " %"; } // =============================== INTERACTION =============================== function onCtrl() { vSupply = vSlider.value(); rArm = sliderToR(rSlider.value()); kMot = kSlider.value() / 1000; tauLoad = tlSlider.value() / 1000; // loop() keeps animating; the new targets take effect on the next frame } function resetAll() { vSlider.value(D_V); rSlider.value(Math.round(rToSlider(D_R))); kSlider.value(Math.round(D_K * 1000)); tlSlider.value(Math.round(D_TL * 1000)); onCtrl(); omega = 0; theta = 0; // reset ALL state incl. the rotor } function keyPressed() { if (key === 'r' || key === 'R') resetAll(); } ``` <!-- REAL-GENERATIVE-MEDIA:START --> ## MicroSim notes - **Pattern:** a **Pattern H/chart torque-speed characteristic** is the analytical star, composed with a **Pattern A animated schematic**. The chart plots the descending motor-torque line `tau_em(omega)` against a left **torque axis**, a horizontal **load line** at `tau_L`, the glowing **operating-point** dot where they cross, and the **mechanical-power parabola** `P(omega)` against a right **power axis** with its own peak marker at `omega_0/2`. Both vertical axes auto-scale (nice 1/2/5 ceilings) so the picture stays readable across the whole parameter range. The schematic draws the **permanent-magnet stator** (N pole top, S pole bottom, with a faint baked field gradient between them), the **rotating armature** (a disc + winding bars that actually spin at the live, legibility-scaled speed, with a motion-blur arc), the `F = BIL` **force arrows** on the two active conductors (length tracks the armature current), and the **commutator + brushes** at the shaft. A dense live **readout** panel carries every derived quantity. - **Model:** the closed-form ideal PM DC motor above -- `I_a = (V - k*omega)/R_a`, `tau_em = k*I_a`, `E_b = k*omega`, `tau_s = kV/R_a`, `omega_0 = V/k`, `omega* = V/k - tau_L R_a/k^2`, with `P_elec`, `P_mech`, `P_loss`, `eta`. The rotor speed is the one genuinely time-evolving quantity: a first-order ODE `J d(omega)/dt = tau_em - tau_L` advanced by the **exact exponential relaxation** `omega += (omega* - omega)*(1 - exp(-dt/tau_mech))` with `dt = min(deltaTime/1000, 0.05)` (Golden Rule 7: frame-rate-independent and unconditionally stable -- this is the `1 - exp(-lambda dt)` family, the exact discrete solution of the linear ODE, not forward-Euler). Stall is handled explicitly: if `tau_L >= tau_s` the target is clamped to `omega* = 0` and the regime flips to STALLED at the locked-rotor current `V/R_a`. SI units throughout (V, A, ohm, N*m, rad/s, W); `R_a` is log-mapped and RPM is derived for display only, converting [[Engineering|engineering]] units at the input/output edges only. - **Interaction & editor-safety:** this sim deliberately uses an **animating `loop()`** rather than the `noLoop()` default, because the rotor genuinely evolves in time (the spin-up transient is a core "aha") -- but it honors the *reason* behind the noLoop rule: `draw()` contains **no heavy per-frame inner loops** (only a ~90-point power-parabola polyline drawn as ONE path, a 2-point motor line, and a handful of rotor primitives), and **all static scenery** (panel frames, chart axes/gridlines/labels/titles, the legend, the stator poles, and the field gradient) is baked **once** into an offscreen `createGraphics` buffer in `setup()`. `setup()` is cheap (no heavy integral). `p5.disableFriendlyErrors = true`; ASCII-only strings (Golden Rule 6); top-level names avoid p5 globals AND p5 method names (no `map`, `scale`, `mag`, `pow`, `log`, `exp`, `sqrt`, `rotate`, `ratio`, `split`, ...): the model uses `Math.*`, bespoke `xForW`/`yForTau`/`yForP` mappers, and state names `vSupply`/`rArm`/`kMot`/`tauLoad`/`omega`/`theta`. Glowing single-path curves keep `shadowBlur` off any per-segment loop (the single-path rule). - **Why it earns the canvas:** a manipulable four-parameter space (`V`, `R_a`, `k`, `tau_L`) with several crisp visual "ahas" -- the torque-speed line **pivots and shifts** as you change voltage / resistance / `k`; the operating point **slides** as you load the motor and the **rotor visibly changes speed** to match; pushing the load past the stall torque **stops** the rotor (STALLED) and pins the current at locked-rotor; the **power parabola** peaks at half the no-load speed; and the back-EMF readout climbing with speed shows the current **self-regulating** down from its locked-rotor value -- all anchored to one crisp learning objective. ## Links (Wikipedia order) <!-- injected from _registry/childlinks/Electric_motor.json (2026-07-30T02:09:12Z) --> `AC-to-AC_converter` · `AC_motor` · `Acceleration` · `Actuator` · `Adaptive_control` · `Adolphe_Ganot` · `Air_gap_(magnetic)` · `Alcohol_fuel` · `Alessandro_Volta` · `Allen_Kent` · `Alloy_wheel` · [[Alternating_current]] · `Alternative_fuel_vehicle` · `Alternator` · `American_Institute_of_Electrical_Engineers` · `American_Petroleum_Institute` · `Amplidyne` · `Ampère's_force_law` · `Andrew_Gordon_(Benedictine)` · `André-Marie_Ampère` · [[Angular_frequency]] · `Antonio_Pacinotti` · `Arago's_rotations` · `Armature_(electrical)` · `Autogas` · `Automated_manual_transmission` · `Automatic_transmission` · `Automation_and_Remote_Control` · `Automotive_engine` · `Axial_flux_motor` · `Ball_bearing_motor` · `Barlow's_wheel` · `Battery_electric_bus` · `Battery_electric_multiple_unit` · `Battery_electric_vehicle` · `Bearing_(mechanical)` · `Benjamin_Franklin` · `Benjamin_G._Lamme` · `Bi-fuel_vehicle` · `Biodiesel` · `Biofuel` · `Biogas` · `Biogasoline` · [[Block_diagram]] · `Blocked_rotor_test` · `Bode_plot` · `Braking_chopper` · `Brine` · `Brush_(electric)` · `Brushed_DC_electric_motor` · `Brushless_DC_electric_motor` · `Butanol_fuel` · `Car` · [[Carbon]] · `Chain_drive` · `Charles-Augustin_de_Coulomb` · `Charles_Eugene_Lancelot_Brown` · `Charles_F._Scott_(engineer)` · `Charles_Proteus_Steinmetz` · `Chicago_"L"` · `Circle_diagram` · `Closed-loop_controller` · `Closed-loop_transfer_function` · `Clutch` · `Coefficient_diagram_method` · `Cogging_torque` · `Coil_winding_technology` · `Coilgun` · `Common_ethanol_fuel_mixtures` · `Commutator_(electric)` · `Compensation_winding` · `Compressed-air_car` · `Compressed-air_vehicle` · `Constant-velocity_joint` · `Continuously_variable_transmission` · `Control_reconfiguration` · [[Control_theory]] · `Controllability` · [[Coulomb's_law]] · `Counter-electromotive_force` · [[Coupling]] · `Cross_product` · `Current_source` · `Cycloconverter` · `DC_injection_braking` · `DC_motor` · `Dahlander_pole_changing_motor` · `Damper_winding` · `Dictionary_of_National_Biography` · `Diesel_engine` · `Differential_(mechanical_device)` · `Digital_control` · [[Digital_signal_processing]] · `Direct-drive_mechanism` · `Direct-shift_gearbox` · `Direct_current` · `Direct_torque_control` · `Distributed_control_system` · `Distributed_parameter_system` · `Donald_G._Fink` · `Doubly_fed_electric_machine` · `Drive_shaft` · `Drive_wheel` · `Dual-clutch_transmission` · `Dual-rotor_motor` · `Dynamo` · `E85` · `Eddy_current` · `Electric_aircraft` · `Electric_battery` · `Electric_bicycle` · `Electric_boat` · `Electric_bus` · `Electric_car` · [[Electric_current]] · `Electric_field` · `Electric_generator` · `Electric_locomotive` · `Electric_machine` · `Electric_motorcycles_and_scooters` · `Electric_platform_truck` · `Electric_power_distribution` · `Electric_truck` · `Electric_vehicle` · `Electric_watch` · `Electrical_energy` · `Electrical_steel` · `Electrodynamic_tether` · `Electromagnet` · `Electromagnetic_coil` · `Electromagnetic_induction` · `Electromagnetism` · `Electromotive_force` · `Electrorheological_clutch` · `Electrostatic_motor` · `Embedded_system` · `Emil_Lenz` · `Emily_Davenport` · `Energy-shaping_control` · `Energy_conversion_efficiency` · `Energy_recovery` · [[Energy_transformation]] · `Epicyclic_gearing` · `Eric_Laithwaite` · `Ethanol_fuel` · `Fan_(machine)` · `Ferromagnetism` · `Field_coil` · `Flexible-fuel_vehicle` · `Fluid_coupling` · `Flux_switching_alternator` · `Fourier_transform` · `Fractional-horsepower_motor` · `Fractional-order_control` · `Frank_J._Sprague` · `François_Arago` · [[Frequency_response]] · `Friction_drive` · `Friedrich_von_Hefner-Alteneck` · `Fuel_cell` · `Fuel_cell_vehicle` · [[Fuzzy_control_system]] · `Fuzzy_logic` · `Galileo_Ferraris` · `Gear_stick` · `General_Electric_Company` · `George_Westinghouse` · `Giubo` · `Giuseppe_Domenico_Botto` · `Goodness_factor` · `H-infinity_loop-shaping` · `Hall_effect` · `Hall_effect_sensor` · `Hammond_organ` · `Hamster_wheel` · `Hankel_singular_value` · `Hans_Christian_Ørsted` · `Hard_disk_drive` · `Henry_Cavendish` · `Hippolyte_Pixii` · `History_of_steam_road_vehicles` · `Homopolar_generator` · `Homopolar_motor` · `Horsepower` · `Hotchkiss_drive` · `Hubcap` · `Human_power` · `Hungary` · `Hybrid_computer` · `Hybrid_electric_vehicle` · `Hybrid_train` · `Hybrid_vehicle` · `Hybrid_vehicle_drivetrain` · [[Hydrogen]] · `Hydrogen-powered_aircraft` · `Hydrogen-powered_ship` · `Hydrogen_economy` · `Hydrogen_internal_combustion_engine_vehicle` · `Hydrogen_train` · `Hydrogen_vehicle` · `Imperial_units` · `Inchworm_motor` · `Inductance` · `Induction_generator` · `Induction_motor` · `Industrial_control_system` · `Institute_of_Electrical_and_Electronics_Engineers` · `Intelligent_control` · `Internal_combustion_engine` · `International_Electrotechnical_Commission` · `James_Clerk_Maxwell` · `Jonas_Wenström` · `Joseph_Henry` · `Joseph_Saxton` · `Joule_heating` · [[Kalman_filter]] · `Krener's_theorem` · [[Laplace_transform]] · `Lead–lag_compensator` · `Least_squares` · `Limited-slip_differential` · `Linear_alternator` · `Linear_induction_motor` · `Linear_motor` · `Liquid_nitrogen_engine` · `List_of_battery_electric_vehicles` · `List_of_prototype_solar-powered_cars` · `List_of_solar-powered_boats` · `Locking_differential` · `Lord_Kelvin` · `Lorentz_force` · `Losses_in_electrical_systems` · [[Loudspeaker]] · [[Lyapunov_stability]] · `Lynch_motor` · `MEMS` · `Machine` · `Maglev` · `Magnet` · `Magnet_motor` · `Magnetic_circuit` · `Magnetic_core` · `Magnetic_field` · `Magnetism` · `Magneto` · `Magnetosphere` · `Magnetostriction` · `Manual_transmission` · `Manumatic` · `Marine_propulsion` · `Maschinenfabrik_Oerlikon` · `Maxwell_stress_tensor` · `Mechanical_energy` · [[Mechatronics]] · `Mendocino_motor` · `Metadyne` · `Methanol_economy` · `Methanol_fuel` · `Michael_Faraday` · `Mikhail_Dolivo-Dobrovolsky` · `Minor_loop_feedback` · `Model_aircraft` · [[Model_predictive_control]] · `Moritz_von_Jacobi` · `Motion_control` · `Motor_(disambiguation)` · `Motor_capacitor` · `Motor_controller` · `Motor_drive` · `Motor_soft_starter` · `Mouse_mill_motor` · `Multifuel` · `National_Electrical_Manufacturers_Association` · `Natural_gas_vehicle` · `Nature_(journal)` · [[Negative_feedback]] · `Neighborhood_electric_vehicle` · [[Neodymium]] · `Nikola_Tesla` · `Nonlinear_control` · [[Observability]] · `Off-road_tire` · `Open-circuit_test` · `Open-loop_controller` · `Operating_temperature` · [[Optimal_control]] · [[PID_controller]] · `Parking_pawl` · `Pedelec` · [[Perceptual_control_theory]] · `Performance` · `Permanent_magnet_motor` · `Permanent_magnet_synchronous_generator` · `Peter_Barlow_(mathematician)` · `Petrol_engine` · `Physicist` · `Piezoelectric_motor` · `Piezoelectricity` · `Platen` · `Plug-in_electric_vehicle` · `Plug-in_hybrid` · `Pneumatic_motor` · [[Positive_feedback]] · `Potting_(electronics)` · `Power-to-weight_ratio` · `Power_inverter` · `Powertrain` · `Preselector_gearbox` · `Programmable_logic_controller` · `Propane` · `Pulse-width_modulation` · `Pumped-storage_hydroelectricity` · [[Quantization_(signal_processing)]] · `Quarterly_Journal_of_Science` · `Racing_slick` · `Radial_flux_motor` · `Radial_tire` · `Railgun` · `Rain_tyre` · `Ram_air_turbine` · [[Real-time_computing]] · `Reciprocating_electric_motor` · `Regenerative_braking` · `Reluctance_motor` · `Repulsion_motor` · `Resin` · `Revenge_of_the_Electric_Car` · `Revolutions_per_minute` · `Richmond,_Virginia` · `Rim_(wheel)` · `Robert_Davidson_(inventor)` · `Robert_H._Park` · [[Robotics]] · `Robust_control` · `Root_locus_analysis` · `Rotating_magnetic_field` · `Rotor_(electric)` · `Royal_Institution` · `Run-flat_tire` · `SCADA` · [[Samarium]] · `Saturation_(magnetic)` · `Scalar_(mathematics)` · `Scalar_control` · `Semi-automatic_transmission` · `Sentinel_Waggon_Works` · `Servomechanism` · `Servomotor` · `Shaded-pole_motor` · `Shading_coil` · `Shaft_(mechanical_engineering)` · `Shift-by-wire` · [[Signal-flow_graph]] · `Single-phase_electric_power` · `Single-phase_generator` · `Slip_ring` · `Snow_tire` · `Solar-powered_aircraft` · `Solar_bus` · `Solar_car` · `Solar_power` · `Solar_vehicle` · `Solenoid` · `South_Side_Elevated_Railroad` · `Spare_tire` · `Squirrel-cage_rotor` · `Stability_theory` · `Stall_torque` · `Starter_(engine)` · `State-space_representation` · `State_observer` · `Stator_(electric_machines)` · `Steady_state` · `Stepper_motor` · `Stochastic_control` · `Superconducting_electric_machine` · `Switch` · `Switched_reluctance_motor` · `Synchronous_motor` · [[System_dynamics]] · [[System_identification]] · `TEFC_motor` · `TRIAC` · `Tachometer` · `Telechron` · `Tesla_turbine` · `Thomas_Davenport_(inventor)` · `Thomas_Edison` · `Three-phase_electric_power` · `Time_constant` · `Timeline_of_the_electric_motor` · `Tire` · `Torque` · `Torque_converter` · `Torque_motor` · `Traction_motor` · `Transaxle` · [[Transfer_function]] · `Transformer` · `Transmission_(mechanical_device)` · `Transmission_control_unit` · `Tubeless_tire` · `Two-phase_electric_power` · `Ultrasonic_motor` · `Universal_joint` · `Universal_motor` · `University_of_Regensburg` · `Utility_frequency` · `Vactrain` · `Variable-frequency_drive` · `Vibrating_alert` · `Voltage_controller` · `Voltage_source` · `Ward_Leonard_control` · `Watt` · `Wave_power_ship` · `Werner_von_Siemens` · `What_Is_the_Electric_Car?` · `Wheel` · `Wheel_hub_assembly` · `Who_Killed_the_Electric_Car?` · `William_Sturgeon` · `Wind-powered_vehicle` · `Windmill_ship` · `Wood_gas` · `Wound_rotor_motor` · [[Z-transform]] · `Zero-emissions_vehicle` · `Zénobe_Gramme` · `Ányos_Jedlik` ## From the Real GENERATIVE library ![Electric motor](https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/VEM_motor_Wernigerode.png/220px-VEM_motor_Wernigerode.png) *Electric motor — placed from the Real G.E.N.E.R.A.T.I.V.E. course library (STEM and Music room). Source: Wikimedia Commons (via Wikipedia article media). [Details & license](https://commons.wikimedia.org/wiki/File:VEM_motor_Wernigerode.png).* > An electric motor is a machine that converts electrical energy into mechanical energy. Most electric motors operate through the interaction between the motor's magnetic field and electric current in a wire winding to generate force in the form of torque applied on the motor's shaft. ([Wikipedia](https://en.wikipedia.org/wiki/Electric_motor)) <!-- REAL-GENERATIVE-MEDIA:END --> ## Overview An **electric motor** is a machine that converts electrical [[Energy|energy]] into mechanical rotation. Almost every motor works the same way at heart: a current-carrying conductor sitting in a magnetic field feels a sideways **[[Force|force]]** (the motor, or Lorentz, force `F = B I L`), and when that conductor is mounted on a shaft the force becomes a **torque** that spins the rotor. Run the same machine backwards -- spin the shaft instead of feeding it current -- and the moving conductors generate a [[Voltage|voltage]] instead; a motor and a generator are the *same* device, which is the single most important fact about rotating electrical machines. Motors are how electricity does physical work, from the fan in a laptop to the traction motors of an electric train, and they consume a large fraction of all the electricity generated in the world. This MicroSim models the **brushed permanent-magnet DC motor** -- the textbook motor and the one whose behavior is captured exactly by a handful of equations. Permanent magnets in the **stator** supply a constant magnetic field; the **armature** (rotor) carries the current; a **commutator** and **brushes** reverse that current twice per revolution so the torque always pushes the rotor the same way. The key subtlety, and the thing the sim is built to show, is **back-EMF** (also called counter-EMF): because the armature is *also* a coil moving in a field, it generates its own voltage that *opposes* the supply (Lenz's law). The faster the motor spins, the larger the back-EMF, the smaller the net voltage across the armature resistance, and so the smaller the current and torque. A DC motor therefore **regulates its own current** -- it is not a short circuit that draws the full locked-rotor current forever; it speeds up until the back-EMF nearly balances the supply. The sim shows the motor through its two canonical pictures at once. On the left is the **animated schematic**: the N/S permanent-magnet poles, the spinning armature with its current-carrying conductors and the `F = BIL` force arrows that drive it, and the commutator + brushes at the shaft. The rotor actually turns, and its speed tracks the live operating point -- raise the voltage and it visibly spins faster; load it down and it slows; overload it past the stall torque and it stops. On the right and below is the **torque-speed characteristic**: the straight, descending motor-torque line (from the stall torque on the torque axis down to the no-load speed on the speed axis), a horizontal **load line**, the **operating point** where they cross, and -- on a second axis -- the **mechanical-power parabola**, which peaks at exactly half the no-load speed. Slide the four controls (supply voltage `V`, armature resistance `R_a`, motor constant `k`, and load torque `tau_L`) and watch the whole machine respond: the line pivots and shifts, the operating point slides along it, the rotor changes speed, and the power and efficiency readouts move with it. ## The physics / derivation **The motor principle (force -> torque).** A straight conductor of length `L` carrying current `I` in a magnetic field `B` feels a force `F = B I L` (the Lorentz force on the moving charges, perpendicular to both the current and the field). In a motor the conductors are the armature windings; the two sides of a loop carry current in opposite directions, so their forces form a couple -- a **torque** -- about the shaft. As the loop rotates, the torque from a fixed current would reverse every half turn; the **commutator** (a split ring) and the **brushes** flip the armature current at exactly the right moment so the torque stays unidirectional. Lumping the field, the conductor [[Geometry|geometry]], and the number of turns into one **motor constant** `k`, the electromagnetic torque is proportional to the armature current: ``` tau_em = k * I_a ``` **Back-EMF (counter-EMF).** The armature is a coil moving through the field, so by Faraday's law of induction it generates an EMF of its own. By Lenz's law this induced EMF *opposes* the applied voltage, hence "back" or "counter" EMF. For a constant field it is proportional to the angular speed: ``` E_b = k * omega ``` A remarkable fact follows from energy conservation: the **same constant `k`** appears in both the torque law and the back-EMF law. In SI units the **torque constant** (N*m/A) and the **back-EMF constant** (V*s/rad) are numerically *equal*, because 1 N*m/A = 1 V*s. The mechanical power delivered, `tau_em * omega = k I_a * omega`, exactly equals the electrical power converted, `E_b * I_a = k omega * I_a` -- the back-EMF is the bookkeeping for energy leaving the electrical side and entering the mechanical side. **The armature circuit (Kirchhoff's voltage law).** The supply voltage `V` drives current through the armature resistance `R_a` and against the back-EMF: ``` V = I_a * R_a + E_b = I_a * R_a + k * omega => I_a = (V - k*omega) / R_a ``` This single equation is the heart of the machine. At standstill (`omega = 0`) the back-EMF is zero and the current is the large **locked-rotor (stall) current** `I_a = V/R_a`. As the motor speeds up, `k*omega` climbs, the net driving voltage `V - k*omega` shrinks, and the current falls. **The torque-speed characteristic.** Substituting the current into the torque law gives torque as a linear, *decreasing* function of speed: ``` tau_em(omega) = k * (V - k*omega) / R_a = (k*V / R_a) - (k^2 / R_a) * omega ``` Two endpoints define the line: ``` stall torque tau_s = k*V / R_a (omega = 0, maximum torque, rotor held) no-load speed omega_0 = V / k (tau = 0, maximum speed, no load) ``` So the characteristic is a straight line from `(0, tau_s)` to `(omega_0, 0)` with slope `-k^2 / R_a`. Raising the **voltage** shifts the whole line outward (more stall torque *and* more no-load speed); raising the **resistance** steepens it (less stall torque, same no-load speed); raising the **motor constant** `k` raises the stall torque but *lowers* the no-load speed (`omega_0 = V/k`). **The operating point.** A real motor settles where the torque it produces equals the torque the load demands. For a constant (Coulomb) load torque `tau_L`, set `tau_em = tau_L`: ``` omega* = (k*V / R_a - tau_L) * (R_a / k^2) = V/k - tau_L * R_a / k^2 I_a* = (V - k*omega*) / R_a = tau_L / k ``` The operating point is the intersection of the descending motor line and the horizontal load line. If the load exceeds the stall torque (`tau_L >= tau_s`) the motor **cannot start** -- it sits stalled at `omega = 0` drawing the full locked-rotor current `V/R_a`, which is why a sustained stall overheats and destroys the windings. **Power and efficiency.** The electrical input, mechanical output, and the loss that separates them are: ``` P_elec = V * I_a (electrical power drawn from the supply) P_mech = tau_em * omega (mechanical power delivered to the shaft) P_loss = I_a^2 * R_a (copper/ohmic loss in the armature) eta = P_mech / P_elec ``` Because torque falls linearly with speed, the mechanical power `P_mech = tau_em(omega)*omega` is a downward **parabola** -- zero at `omega = 0` (all torque, no motion) and zero again at `omega = omega_0` (all speed, no torque) -- with its **maximum exactly at half the no-load speed**, `omega = omega_0/2`, where the torque is also half the stall torque. At that peak-power point the efficiency is only about **50%** (half the input is lost as `I^2 R_a`); efficient operation lives much closer to the no-load end, typically 70-90% of `omega_0`. This is why motors are geared rather than run at peak power. **Dynamics (how it reaches the operating point).** The rotor obeys Newton's law for rotation, `J * d(omega)/dt = tau_em - tau_L`, where `J` is the rotor inertia. Substituting the linear torque law turns this into a **first-order linear relaxation** toward the operating speed: ``` J * d(omega)/dt = (k*V/R_a - tau_L) - (k^2/R_a) * omega => omega(t) = omega* + (omega_initial - omega*) * exp(-t / tau_mech) with mechanical time constant tau_mech = J * R_a / k^2 ``` The [[Damping|damping]] that pulls the motor to its steady speed is the `k^2/R_a` term -- the **back-EMF acting as electrical friction**. The sim integrates this ODE with the exact exponential update each frame, so the rotor smoothly spins up (or down) to the new operating point whenever a control changes, and the transient is frame-rate-independent and unconditionally stable. ## Parameter table (control -> symbol -> range) | Control | Symbol | Meaning | Range (units) | Default | |---|---|---|---|---| | Supply voltage slider | `V` | DC voltage applied across the armature terminals | 0 - 24 V | 12 V | | Armature resistance slider | `R_a` | total armature-circuit resistance (log scale); sets the line slope and the stall current | 0.1 - 10 ohm | 1.0 ohm | | Motor constant slider | `k` | combined torque / back-EMF constant `k_t = k_e` (PM field folded in) | 0.005 - 0.2 N*m/A (= V*s/rad) | 0.05 | | Load torque slider | `tau_L` | constant (Coulomb) opposing load torque on the shaft | 0 - 1.0 N*m | 0.20 N*m | | Reset button / key `r` | -- | restore ALL controls to defaults and reset the rotor speed/angle | -- | -- | Fixed model constants: rotor inertia `J = 5e-4 kg*m^2` (sets the spin-up time constant `tau_mech = J R_a / k^2`; held constant so the four sliders stay the design space), bearing/viscous friction **neglected** (ideal: the only speed-dependent damping is the back-EMF term `k^2/R_a`), and a **constant PM stator field** (so `k` is constant). The on-screen rotor spin is shown at a reduced, legibility-scaled rate; the chart and the numeric readouts always carry the true SI values. Derived / read-out quantities: armature current `I_a = (V - k*omega)/R_a`; back-EMF `E_b = k*omega`; the operating speed `omega*` (rad/s and RPM); electromagnetic torque `tau_em = k*I_a`; stall torque `tau_s = kV/R_a` and no-load speed `omega_0 = V/k`; electrical power `P_elec = V I_a`, mechanical power `P_mech = tau_em*omega`, [[Copper|copper]] loss `I_a^2 R_a`, and efficiency `eta = P_mech/P_elec`; plus the running/stalled/no-load regime label. ## Learning objective From the two constitutive laws `tau_em = k I_a` and `E_b = k*omega` together with the armature KVL `V = I_a R_a + k*omega`, derive the **linear torque-speed characteristic** `tau_em(omega) = kV/R_a - (k^2/R_a)*omega`; read its two endpoints, the **stall torque** `kV/R_a` and the **no-load speed** `V/k`, straight off the controls; predict the **operating speed** where the motor line meets the load line; and explain from `P = tau*omega` why mechanical power peaks at **half the no-load speed** (at ~50% efficiency) and why **back-EMF self-regulates the current** so the running motor draws far less than its locked-rotor current. Distinguish the running, no-load (`tau_L = 0`), and stalled (`tau_L >= tau_s`) regimes from the values of the controls. <!-- CRAFT-LINK:START g12 --> *Built to the [[WT!P5_js_Microsim_Master_Class|p5.js Master Class]].* <!-- CRAFT-LINK:END --> <!-- SPINEPATH:BEGIN g20 — shortest chain of Wikipedia links between local articles to a Compendium Main article; do not hand-edit inside --> *Connected to the Apex Spine:* Electric motor → [[Fuel_cell|Fuel cell]] — [[WT!Thury_Hydrodynamics_Compendium|Compendium]] section 11, *Fuel cells: the same reaction without a flame*. <!-- SPINEPATH:END --> <!-- ELECSIM:BEGIN g28 — Electronics portal microsim (framework build, specs/sims/Electric_motor.json); do not hand-edit inside --> **Microsim — three.js (Wikitube framework):** *The brushed DC motor: torque, power and efficiency against speed* <div class="wt-sim" data-src="https://wikitube-3d-microsims.netlify.app/electronics/Electric_motor.html" data-title="Electric motor"></div> *Built from `MICROSIM_GUIDE/specs/sims/Electric_motor.json`; part of the [[Electronics]] set ([[PORTAL_Electronics]]).* <!-- ELECSIM:END --> ## Wikipedia : Wikitube **Strict pair:** [Wikipedia](https://en.wikipedia.org/wiki/Electric_motor) : [Wikitube](https://en.wikitube.io/wiki/Electric_motor) ## Previous hub tags Tree parent: [[Control_theory]]. Legacy hubs: `ENGINES`. --- *Sources: 1 legacy note. Minted wave 1, 2026-07-30 (v1.6 order).*