Skip to content

easyPID 1.1.0 — autotuner repair, anti-windup and timing fixes

Latest

Choose a tag to compare

@Kronbii Kronbii released this 09 Aug 15:50
9183354

Repairs every defect found in an audit of 1.0.0 and adds the tuner API needed to
use autotuning on real unipolar hardware.

Anyone running 1.0.0 should upgrade. Three defects made advertised features
non-functional rather than merely degraded.

Fixed — the serious ones

The relay autotuner could never complete. cyclesDetected_ was incremented
inside a guard that required it to already be non-zero, so it stayed pinned at
zero forever. isComplete() never returned true, getProgress() never rose
above 0, and the timeout never fired either because its reference was refreshed
on every relay switch — the tuner simply relayed indefinitely. Oscillation
amplitude was also measured as roughly twice the noise band instead of the
limit-cycle swing, because the extremes were sampled at the switching instant
and the process peak lags the switch.

ANTIWINDUP_CLAMP could pin the output at a limit indefinitely. The
integral rollback fired on saturation alone, without checking whether the
accumulation was making saturation better or worse, so it cancelled the very
accumulation that would have freed the output. Once the I term alone exceeded
the limit, the integrator froze. This is the default anti-windup mode.

update() fabricated a timestep. When millis() had not advanced, it
substituted a full sampleTime_ — 100 ms by default — and integrated as though
that time had passed. Since the documented usage calls update() unconditionally
from loop(), on a fast board the integral accrued at up to 100x its true rate.

Fixed — the rest

  • ANTIWINDUP_BACKCALC divided by Ki with no zero guard, latching the output
    to NaN permanently on any P-only or PD controller
  • A derivative kick proportional to the entire error on the first sample after
    begin()/reset()
  • getError() returned the direction-flipped value under REVERSE, contradicting
    its documentation
  • The manual-dt overload and reset() left the timing reference stale
  • getIterm() reported the pre-anti-windup value, so it appeared to keep
    climbing while the integrator was in fact being held
  • The autotuner shared relay-edge state across instances via a function-local
    static, contradicting the library's multi-instance guarantee
  • #define PI collided with Arduino.h, emitting a warning on every build
  • The stall timeout silently capped the tunable limit-cycle period at 60 s,
    aborting on exactly the lag-dominant thermal processes autotuning suits best
  • Ziegler-Nichols open-loop formulas in the tuning guide were wrong by 100x
  • The tuning guide's autotune snippet called reset() every loop iteration
  • Inverted output/integral limits and a filter alpha of exactly 1.0 were
    accepted, each of which silently disables part of the controller

Fixed — examples

All three sketches were broken. Two chased setpoints above their simulated
plant's ceiling
, so they demonstrated integral windup rather than control.
AutoTunePID could not induce a limit cycle at all: the relay swings symmetrically
about zero, and the plant only accepts 0–255.

Added

  • start() takes an optional outputBias, the operating point the relay
    swings around. Required for unipolar actuators — a heater, a PWM pin — where a
    relay centred on zero spends half of each period commanding a negative drive
    the hardware clips to zero. Defaults to 0.0, reproducing the previous
    symmetric swing exactly.
  • TUNER_FAILED, so a run that produced no usable result is distinguishable
    from one never started. Appended to the enum, so existing enumerator values
    are unchanged.
  • applyTunings(rule), applying computed gains to the attached controller
    and resetting it in one call.

Upgrading

Existing sketches compile and behave identically, with two intentional
exceptions:

  • getError() under REVERSE now returns setpoint - measurement, as
    always documented. DIRECT controllers — the default — are unaffected. If you
    were compensating for the old sign when logging or plotting, remove that.
  • Saturating controllers using the default anti-windup respond differently,
    because the integrator is no longer frozen at the rail. Better, but different:
    a loop tuned around the old behaviour is worth re-checking.

Verification

This release also adds the testing the library never had. arduino-lint was the
only CI step and never invokes a compiler; it was green throughout 1.0.0.

  • 68 host assertions under extras/test/, run against a mock Arduino.h with a
    virtual clock, one group per defect fixed here
  • All examples compile warning-free on Uno, Mega 2560 and ESP32
  • arduino-lint --compliance strict --library-manager update clean
  • Both now run in CI on every push

Full detail in CHANGELOG.md.