TinyRobotics
Loading...
Searching...
No Matches
PIDAutoTuner.h
1#pragma once
2#include <math.h>
3#include <stdint.h>
4
5namespace tinyrobotics {
6
7/**
8 * @class PIDAutoTuner
9 * @ingroup control
10 * @brief Automatic PID tuning using the relay (Åström-Hägglund) method.
11 *
12 * This class implements an automatic PID tuning algorithm based on the relay
13 * (bang-bang) method, also known as the Åström-Hägglund relay auto-tuning
14 * technique. It applies a relay (on/off) control to the system and analyzes the
15 * resulting oscillations to estimate the ultimate gain (Ku) and period (Tu).
16 * These values are then used to compute PID parameters using the
17 * Ziegler-Nichols tuning rules.
18 *
19 * - The user specifies a target setpoint, relay amplitude, and sample time
20 * (dt).
21 * - The tuner applies a relay output and detects peaks in the process variable.
22 * - After sufficient oscillations, it calculates Ku, Tu, and recommended PID
23 * gains.
24 * - The process can be monitored with isFinished() and the results retrieved
25 * with getResult().
26 * - Hysteresis can be adjusted to avoid chattering.
27 *
28 * **Usage Example:**
29 * @code
30 * PIDAutoTuner tuner(target, relayAmp, dt);
31 * while (!tuner.isFinished()) {
32 * float control = tuner.update(measuredValue);
33 * // Apply 'control' to actuator
34 * }
35 * auto result = tuner.getResult();
36 * if (result.success) {
37 * // Use result.Kp, result.Ki, result.Kd
38 * }
39 * @endcode
40 *
41 * @see setHysteresis, getResult, isFinished, update, reset
42 *
43 * @author Phil Schatzmann
44 */
45
46class PIDAutoTuner {
47 public:
48 struct Result {
49 float Kp;
50 float Ki;
51 float Kd;
52 float Ku;
53 float Tu;
54 bool success;
55 };
56
57 PIDAutoTuner(float target, float relayAmp, float dt)
58 : target(target), relayAmp(relayAmp), dt(dt) {}
59
60 void reset() {
61 state = State::INIT;
62 peakCount = 0;
63 lastY = 0;
64 lastPeakTime = 0;
65 maxY = -1e9;
66 minY = 1e9;
67 time = 0;
68 output = relayAmp;
69 success = false;
70 }
71
72 float update(float y) {
73 time += dt;
74
75 // Relay control (bang-bang)
76 if (y > target + hysteresis)
77 output = -relayAmp;
78 else if (y < target - hysteresis)
79 output = relayAmp;
80
81 detectPeaks(y);
82
83 return output;
84 }
85
86 bool isFinished() const { return success; }
87
88 Result getResult() const {
89 Result r{};
90 r.success = success;
91
92 if (!success) return r;
93
94 float amplitude = (avgMax - avgMin) / 2.0f;
95
96 // Ultimate gain (relay method)
97 float Ku = (4.0f * relayAmp) / (static_cast<float>(M_PI) * amplitude);
98
99 float Tu = avgPeriod;
100
101 r.Ku = Ku;
102 r.Tu = Tu;
103
104 // Ziegler-Nichols PID tuning
105 r.Kp = 0.6f * Ku;
106 r.Ki = 2.0f * r.Kp / Tu;
107 r.Kd = r.Kp * Tu / 8.0f;
108
109 return r;
110 }
111
112 void setHysteresis(float h) { hysteresis = h; }
113
114 protected:
115 enum class State { INIT, RUNNING };
116
117 float target;
118 float relayAmp;
119 float dt;
120 float hysteresis = 0.01f;
121
122 float output = 0;
123 float time = 0;
124
125 float lastY = 0;
126
127 float maxY = -1e9;
128 float minY = 1e9;
129
130 float avgMax = 0;
131 float avgMin = 0;
132 float avgPeriod = 0;
133
134 float lastPeakTime = 0;
135
136 int peakCount = 0;
137 bool lastWasMax = false;
138
139 bool success = false;
140
141 State state = State::INIT;
142
143 void detectPeaks(float y) {
144 // Track extrema
145 if (y > maxY) maxY = y;
146 if (y < minY) minY = y;
147
148 // Detect zero crossing slope change
149 if ((lastY < y) && (lastWasMax == false)) {
150 // rising
151 } else if ((lastY > y) && (lastWasMax == false)) {
152 // max peak
153 registerPeak(true);
154 lastWasMax = true;
155 } else if ((lastY > y) && (lastWasMax == true)) {
156 // falling
157 } else if ((lastY < y) && (lastWasMax == true)) {
158 // min peak
159 registerPeak(false);
160 lastWasMax = false;
161 }
162
163 lastY = y;
164 }
165
166 void registerPeak(bool isMax) {
167 peakCount++;
168
169 float period = time - lastPeakTime;
170 lastPeakTime = time;
171
172 // Ignore first few peaks
173 if (peakCount < 4) return;
174
175 // Running averages
176 if (isMax)
177 avgMax = 0.8f * avgMax + 0.2f * maxY;
178 else
179 avgMin = 0.8f * avgMin + 0.2f * minY;
180
181 avgPeriod = 0.8f * avgPeriod + 0.2f * period;
182
183 // Reset extrema
184 maxY = -1e9;
185 minY = 1e9;
186
187 // Finish after enough stable oscillations
188 if (peakCount > 12) {
189 success = true;
190 }
191 }
192};
193
194} // namespace tinyrobotics
Automatic PID tuning using the relay (Åström-Hägglund) method.
Definition: PIDAutoTuner.h:46
Definition: PIDAutoTuner.h:48