All insights

Robotics

Real-Time Motion Control: Servo Drives, EtherCAT, and the Pursuit of Determinism

Behind every smooth robotic motion is a control loop that must close on time, every time. A look at the servo drives, fieldbuses, and real-time discipline that separate precise machines from jittery ones — no machine learning involved.

Root Digit Robotics · Motion & Controls7 min read

Real-time motion control is one of the few areas of software engineering where the average case is irrelevant. A control loop that meets its deadline 99.9% of the time is not 99.9% correct — it is a machine that produces a defect, or a collision, several times per shift. The engineering discipline here is entirely about bounding the worst case.

The cascade, and why bandwidth separation is not optional

Servo control is conventionally three nested loops: an innermost current (torque) loop, a velocity loop around it, and an outermost position loop. Each loop treats the one inside it as an ideal actuator, and that approximation only holds if the inner loop is substantially faster than the outer.

LoopRateBandwidthRuns on
Current / torque8–20 kHz1–2 kHzDrive DSP
Velocity2–8 kHz150–400 HzDrive DSP
Position1–4 kHz20–60 HzDrive or controller
Trajectory / interpolation250 Hz–1 kHzMotion controller
Supervisory / sequencing10–100 HzPLC or Linux host
Typical loop structure for an industrial servo axis. The 5–10× separation between tiers is what allows each loop to be designed independently; violate it and the loops interact, producing oscillation that no amount of gain tuning will resolve.

Sampling introduces its own constraint. A zero-order hold contributes a phase lag equal to half a sample period, which at the closed-loop bandwidth directly consumes phase margin. This is why the sample rate cannot simply be set to whatever the CPU can sustain.

φZOH  ≈  − ω Ts / 2    →    fs  ≳  20 · fbw

At f_s = 10·f_bw the hold alone costs about 18° of phase margin. At 20× it costs roughly 9°, which is generally affordable alongside filter and transport delays.

Following error is a design output, not a tuning result

A position loop with proportional gain tracking a constant velocity exhibits a steady-state lag that is fully determined by the gain. No amount of careful tuning removes it, because it is structural.

ess  =  v  /  Kv

v — commanded velocity · K_v — velocity constant of the position loop (s⁻¹). At v = 500 mm/s and K_v = 50 s⁻¹, the axis lags its command by 10 mm — every time, predictably.

On a single axis this lag is invisible in the finished part. On coordinated axes it is not: two axes with different K<sub>v</sub> lag by different amounts, and a commanded straight line comes out as a curve. Contour error on interpolated motion is dominated by K<sub>v</sub> mismatch, which is why matching velocity constants across axes matters more than optimising any one of them.

The correct fix is velocity feedforward. Injecting the commanded velocity directly into the velocity loop cancels the lag term without raising the gain, so tracking improves without moving the system toward instability. Adding acceleration feedforward through an inverse-dynamics model handles the remaining inertial term.

Jerk limiting: paying a little time to avoid exciting the machine

A trapezoidal velocity profile has discontinuous acceleration, and a step in acceleration is a broadband excitation containing energy at every frequency the structure has a mode at. The result is residual vibration after each move — settling time that shows up as reduced throughput and surface finish defects.

An S-curve profile bounds jerk, rolling the acceleration on and off. This lengthens the nominal move slightly and shortens the settling substantially, and on lightly damped structures the net cycle time almost always improves.

Tmove  =  d/vmax  +  vmax/amax  +  amax/jmax

Each term is a tier of the profile. The jerk term is the price of smoothness; when it is smaller than the settling time it removes, limiting jerk is strictly faster end to end.

The fieldbus: determinism you can actually specify

EtherCAT dominates industrial motion because of how it handles frames. Rather than addressing slaves individually, a single frame passes through every node, which reads its data and inserts its response as the frame moves through — processing on the fly, in hardware, at wire speed. One frame therefore services an entire axis group within a cycle.

Synchronisation comes from Distributed Clocks. One slave's clock is designated the reference, propagation delays between nodes are measured during initialisation, and every node compensates. Well-configured systems hold inter-node synchronisation under a microsecond, and this is what allows physically separate drives to apply their setpoints simultaneously — the property that coordinated motion depends on.

struct timespec next;
clock_gettime(CLOCK_MONOTONIC, &next);

for (;;) {
    next.tv_nsec += CYCLE_NS;                    /* 1 ms */
    while (next.tv_nsec >= NSEC_PER_SEC) {
        next.tv_nsec -= NSEC_PER_SEC; next.tv_sec++;
    }
    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);

    ecrt_master_receive(master);                 /* inbound process data */
    ecrt_domain_process(domain);

    control_step();                              /* no malloc, no I/O, no locks */

    ecrt_domain_queue(domain);
    ecrt_master_send(master);                    /* outbound, same cycle */
}
A cyclic task under PREEMPT_RT. clock_nanosleep with TIMER_ABSTIME against a monotonically advancing deadline prevents the drift that accumulates when each cycle sleeps for a relative interval.

The comment on the control step is the whole discipline. Inside a cyclic task there must be no dynamic allocation, no file or network I/O, no unbounded lock, and no page fault. Each of these has an unbounded worst case, and one unbounded term makes the whole loop unbounded regardless of how good the average looks.

Configuring the host so the kernel stops being the problem

A stock Linux kernel will schedule a motion task when convenient. Convenient is not a specification. The configuration below is what turns a general-purpose machine into one with a defensible latency bound:

  • PREEMPT_RT kernel, so kernel code paths become preemptible and priority inversion is handled by inheritance.
  • SCHED_FIFO for the cyclic thread, at a priority above every driver thread it does not depend on.
  • mlockall(MCL_CURRENT | MCL_FUTURE) plus a pre-faulted stack — a single page fault mid-loop dwarfs the entire cycle budget.
  • isolcpus and IRQ affinity, so the control core runs the control task and the NIC interrupt and nothing else.
  • Disable frequency scaling and deep C-states; the wake-up latency from a deep sleep state is measured in the same units as the cycle time.
  • Measure with cyclictest under the actual production load, not idle. Idle latency figures are marketing.

What you are producing is a number: the maximum observed latency over a long run under worst-case load, with margin. On tuned industrial hardware, sub-50 µs worst case at a 1 ms cycle is achievable and verifiable. Without this work the figure is unbounded, and an unbounded figure cannot go in a specification.

The honest summary

Motion control rewards conservatism in a way most software does not. Bandwidth separation, feedforward instead of gain, bounded jerk, hardware synchronisation, and a scheduler configured to make promises it can keep. Every one of these is a decision to accept slightly less nominal performance in exchange for a worst case you can write down — and on a machine that runs three shifts, the worst case is the only number that ever matters.

Explore how Root Digit can support your team

From discovery workshops to production deployment, our engineers and consultants partner with you across the lifecycle of your AI, robotics, and IoT initiatives.

Cookie Policy

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies. You can also choose "Necessary Only" to limit cookies to essential website functions only. Learn more