# 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 — 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).*