Repository navigation
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_BACKCALCdivided byKiwith 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 underREVERSE, contradicting
its documentation- The manual-
dtoverload andreset()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 PIcollided withArduino.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
alphaof 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 optionaloutputBias, 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 to0.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()underREVERSEnow returnssetpoint - measurement, as
always documented.DIRECTcontrollers — 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 mockArduino.hwith 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 updateclean- Both now run in CI on every push
Full detail in CHANGELOG.md.