Example: geared DC motor
The TT gear motor drives a wheel through a 48:1 gearbox. Neither half contains the word “motor”. The circuit logic is a winding, and the component logic is a torque and a friction on a hinge.
What it is made of
Section titled “What it is made of”- Connections: two pins,
RedandBlack. - Rendering: a
Wheelmesh with a part body and a hinge joint on the axle. - Circuit logic properties:
R,L,Ke,BrushDrop,MaxAmpsandFailTime. - Component logic properties:
GearRatio,Efficiency,DirectionandRotorInertia.
The two halves share four numbers:
UserData |
Direction | Meaning |
|---|---|---|
[0] |
in | Rotor speed at the motor shaft, rad/s (wheel speed × gear ratio) |
[1] |
out | Winding current from the last settled tick, A |
[2] |
out | Charge through the winding since the run began, A·s |
[3] |
out | Time over which that charge was counted, s |
The last two let the physics side average the current over its own step. With PWM at a few hundred hertz the current swings every cycle, and a single sample would land at a random point on the wave.
Circuit logic: the winding
Section titled “Circuit logic: the winding”A brushed motor seen from its leads is V = I·R + L·dI/dt + Ke·ω. It is one backward-Euler branch between the two pins, a conductance of 1 / (R + L/dt) in parallel with a current source that carries the inductor’s history minus the back-EMF. A stalled rotor makes the back-EMF zero, so it draws (V − brush drop) / R with no special case.
This block is the complete circuit logic, exactly as you type it into the editor (see What you write).
private double _geff, _isrc, _i, _iPrev;private double _overTime; // seconds over MaxAmps; survives Reset, clears on PowerCycleprivate double _charge, _counted;
public override bool ConnectionsAreConnected(int l, int r) { return true; }
public override void Reset(){ base.Reset(); _i = 0.0; _iPrev = 0.0; _charge = 0.0; _counted = 0.0;}
public override void PowerCycle(){ base.PowerCycle(); _overTime = 0.0;}
public override void StampInitial(){ Simulation.StampRightSideDynamic(ConnectionNodes[Connections.Red]); Simulation.StampRightSideDynamic(ConnectionNodes[Connections.Black]);}
public override void PreStep(){ // Published before the solve, so the physics thread only ever sees a settled current. UserData[1] = _iPrev; UserData[2] = _charge; UserData[3] = _counted;
double dt = Simulation.TimeStep; double r = Properties.R > 1e-6 ? Properties.R : 1e-6; double l = Properties.L > 0.0 ? Properties.L : 0.0; double emf = Properties.Ke * UserData[0] + Properties.BrushDrop * Math.Tanh(_iPrev / 0.02); _geff = 1.0 / (r + l / dt); _isrc = _geff * ((l / dt) * _iPrev - emf);}
public override void Step(){ Simulation.StampConductanceDynamic(ConnectionNodes[Connections.Red], ConnectionNodes[Connections.Black], _geff); Simulation.StampCurrentSourceDynamic(ConnectionNodes[Connections.Red], ConnectionNodes[Connections.Black], _isrc);}
public override void CalculateCurrent(){ double v = ConnectionVoltages[Connections.Red] - ConnectionVoltages[Connections.Black]; _i = _geff * v + _isrc; _iPrev = _i; double dt = Simulation.TimeStep; _charge += _i * dt; _counted += dt; SetCurrent(Connections.Red, _i);}
public override bool CheckFailure(){ // Heat over real time above the limit, leaking back down below it. double dt = Simulation.TimeStep; if (Math.Abs(_i) > Properties.MaxAmps) { _overTime += dt; if (_overTime >= Properties.FailTime) { Fail("winding burnt out, the rotor was held stalled too long"); return true; } } else { _overTime = Math.Max(0.0, _overTime - dt); } return false;}Component logic: the gearbox
Section titled “Component logic: the gearbox”Every physics step, this reads the mean current, turns it into torque on the wheel’s hinge, and hands the rotor speed back so the winding can make its back-EMF. That loop makes the wheel settle at a speed and makes the current rise when the wheel is held.
The gearbox is two numbers: a ratio and an efficiency. The push on the wheel is the motor torque times the ratio. The loss is friction on the hinge, because tooth friction grows with the torque through the teeth and always opposes the turn, so a brake can never make energy.
This block is the complete component logic, as you type it.
private double _ke = 0.00421; // Kt equals Ke in SI units; read from the circuit logicprivate double _ratio = 48.0;private double _eff = 0.267;private double _dir = 1.0;private double _lastCharge, _lastCounted;private bool _haveLast;
public override void Initialize() => ApplyProperties();public override void OnPropertyUpdated(string propertyName) => ApplyProperties();
public override void OnCircuitPropertyUpdated(string propertyName){ double ke = CircuitProperties.Ke; if (ke > 0.0) _ke = ke;}
private void ApplyProperties(){ _ratio = Properties.GearRatio > 0.0 ? Properties.GearRatio : 1.0; _eff = Math.Max(0.0, Math.Min(1.0, Properties.Efficiency)); _dir = Properties.Direction < 0.0 ? -1.0 : 1.0; // The rotor's inertia as the wheel feels it, through the ratio squared. double rotor = Properties.RotorInertia > 0.0 ? Properties.RotorInertia : 0.0; Rendering.Wheel.Rigidbody.ReflectedInertia = rotor * _ratio * _ratio;}
public override void Update(double delta) { }
public override void PhysicsUpdate(double delta){ if (Circuit == null) return;
// Mean current since the last step, from the winding's running totals. double charge = Circuit.UserData[2]; double counted = Circuit.UserData[3]; double amps = Circuit.UserData[1]; if (_haveLast && counted > _lastCounted) amps = (charge - _lastCharge) / (counted - _lastCounted); _lastCharge = charge; _lastCounted = counted; _haveLast = true;
// The rotor's speed, not the wheel's: the winding owns Ke and only needs how fast it spins. double rotorSpeed = Rendering.Wheel.Rigidbody.SpinRate * _ratio * _dir;
double push = _ke * amps * _ratio; bool motorDrives = amps * rotorSpeed >= 0.0 || Math.Abs(rotorSpeed) < 1.0; double loss = Math.Abs(push) * (motorDrives ? 1.0 - _eff : 1.0 / Math.Max(_eff, 1e-3) - 1.0);
Rendering.Wheel.Rigidbody.ApplyTorque(push * _dir); Rendering.Wheel.Rigidbody.ApplyJointFriction(loss); Circuit.UserData[0] = rotorSpeed;}The torque acts about the wheel’s hinge, and the same torque pushes back on whatever the motor body is fixed to, so a robot rocks as it pulls away just as a real one does.

