Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file pid.cpp | |||
3 | * | |||
4 | * https://en.wikipedia.org/wiki/Feedback | |||
5 | * http://en.wikipedia.org/wiki/PID_controller | |||
6 | * | |||
7 | * @date Sep 16, 2014 | |||
8 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
9 | */ | |||
10 | ||||
11 | #include "pch.h" | |||
12 | ||||
13 | #include "efi_pid.h" | |||
14 | #include "math.h" | |||
15 | ||||
16 | 2808 | Pid::Pid() { | ||
17 | 2808 | initPidClass(nullptr); | ||
18 | 2808 | } | ||
19 | ||||
20 | 6 | Pid::Pid(pid_s *p_parameters) { | ||
21 | 6 | initPidClass(p_parameters); | ||
22 | 6 | } | ||
23 | ||||
24 | 5319 | void Pid::initPidClass(pid_s *p_parameters) { | ||
25 | 5319 | parameters = p_parameters; | ||
26 | 5319 | resetCounter = 0; | ||
27 | ||||
28 | 5319 | Pid::reset(); | ||
29 | 5319 | } | ||
30 | ||||
31 | 2206 | static bool isClose(float a, float b) { | ||
32 | 2206 | bool result = (a == b); | ||
33 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2190 times.
|
2/2✓ Decision 'true' taken 16 times.
✓ Decision 'false' taken 2190 times.
|
2206 | if (!result) { |
34 | 16 | efiPrintf("PID: not same %f %f", a, b); | ||
35 | } | |||
36 | 2206 | return result; | ||
37 | } | |||
38 | ||||
39 | 454 | bool Pid::isSame(const pid_s *p_parameters) const { | ||
40 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 454 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 454 times.
|
454 | if (!parameters) { |
41 | // this 'null' could happen on first execution during initialization | |||
42 | ✗ | return false; | ||
43 | } | |||
44 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 454 times.
|
454 | efiAssert(ObdCode::OBD_PCM_Processor_Fault, p_parameters != nullptr, "PID::isSame nullptr", false); | |
45 | 454 | return isClose(parameters->pFactor, p_parameters->pFactor) | ||
46 |
1/2✓ Branch 1 taken 438 times.
✗ Branch 2 not taken.
|
438 | && isClose(parameters->iFactor, p_parameters->iFactor) | |
47 |
1/2✓ Branch 1 taken 438 times.
✗ Branch 2 not taken.
|
438 | && isClose(parameters->dFactor, p_parameters->dFactor) | |
48 |
1/2✓ Branch 1 taken 438 times.
✗ Branch 2 not taken.
|
438 | && isClose(parameters->offset, p_parameters->offset) | |
49 |
3/4✓ Branch 0 taken 438 times.
✓ Branch 1 taken 16 times.
✓ Branch 3 taken 438 times.
✗ Branch 4 not taken.
|
892 | && isClose(parameters->periodMs, p_parameters->periodMs); | |
50 | } | |||
51 | ||||
52 | /** | |||
53 | * @param Controller input / process output | |||
54 | * @returns Output from the PID controller / the input to the process | |||
55 | */ | |||
56 | 44 | float Pid::getOutput(float p_target, float p_input) { | ||
57 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | efiAssert(ObdCode::OBD_PCM_Processor_Fault, parameters != nullptr, "PID::getOutput nullptr", 0); | |
58 | 44 | float dTime = MS2SEC(GET_PERIOD_LIMITED(parameters)); | ||
59 | 44 | return getOutput(p_target, p_input, dTime); | ||
60 | } | |||
61 | ||||
62 | 200 | float Pid::getUnclampedOutput(float p_target, float p_input, float dTime) { | ||
63 | 200 | target = p_target; | ||
64 | 200 | input = p_input; | ||
65 | 200 | float error = (target - input) * errorAmplificationCoef; | ||
66 | ||||
67 | 200 | float pTerm = parameters->pFactor * error; | ||
68 | 200 | updateITerm(parameters->iFactor * dTime * error); | ||
69 | 200 | dTerm = parameters->dFactor / dTime * (error - previousError); | ||
70 | ||||
71 | 200 | previousError = error; | ||
72 | ||||
73 |
2/2✓ Branch 0 taken 154 times.
✓ Branch 1 taken 46 times.
|
2/2✓ Decision 'true' taken 154 times.
✓ Decision 'false' taken 46 times.
|
200 | if (dTime <=0) { |
74 | 154 | warning(ObdCode::CUSTOM_PID_DTERM, "PID: unexpected dTime"); | ||
75 | 154 | return pTerm + getOffset(); | ||
76 | } | |||
77 | ||||
78 | 46 | return pTerm + iTerm + dTerm + getOffset(); | ||
79 | } | |||
80 | ||||
81 | /** | |||
82 | * @param dTime seconds probably? :) | |||
83 | */ | |||
84 | 200 | float Pid::getOutput(float p_target, float p_input, float dTime) { | ||
85 | 200 | float l_output = getUnclampedOutput(p_target, p_input, dTime); | ||
86 | ||||
87 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 194 times.
|
2/2✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 194 times.
|
200 | if (l_output > parameters->maxValue) { |
88 | 6 | l_output = parameters->maxValue; | ||
89 |
2/2✓ Branch 1 taken 11 times.
✓ Branch 2 taken 183 times.
|
2/2✓ Decision 'true' taken 11 times.
✓ Decision 'false' taken 183 times.
|
194 | } else if (l_output < getMinValue()) { |
90 | 11 | l_output = getMinValue(); | ||
91 | } | |||
92 | 200 | output = l_output; | ||
93 | 200 | return output; | ||
94 | } | |||
95 | ||||
96 | 1 | void Pid::updateFactors(float pFactor, float iFactor, float dFactor) { | ||
97 | 1 | parameters->pFactor = pFactor; | ||
98 | 1 | parameters->iFactor = iFactor; | ||
99 | 1 | parameters->dFactor = dFactor; | ||
100 | 1 | reset(); | ||
101 | 1 | } | ||
102 | ||||
103 | 5479 | void Pid::reset() { | ||
104 | 5479 | dTerm = iTerm = 0; | ||
105 | 5479 | output = input = target = previousError = 0; | ||
106 | 5479 | errorAmplificationCoef = 1.0f; | ||
107 | 5479 | resetCounter++; | ||
108 | 5479 | } | ||
109 | ||||
110 | ✗ | float Pid::getP() const { | ||
111 | ✗ | return parameters->pFactor; | ||
112 | } | |||
113 | ||||
114 | ✗ | float Pid::getI() const { | ||
115 | ✗ | return parameters->iFactor; | ||
116 | } | |||
117 | ||||
118 | ✗ | float Pid::getPrevError() const { | ||
119 | ✗ | return previousError; | ||
120 | } | |||
121 | ||||
122 | 1132 | float Pid::getIntegration() const { | ||
123 | 1132 | return iTerm; | ||
124 | } | |||
125 | ||||
126 | ✗ | float Pid::getD() const { | ||
127 | ✗ | return parameters->dFactor; | ||
128 | } | |||
129 | ||||
130 | 229 | float Pid::getOffset(void) const { | ||
131 | 229 | return parameters->offset; | ||
132 | } | |||
133 | ||||
134 | 234 | float Pid::getMinValue(void) const { | ||
135 | 234 | return parameters->minValue; | ||
136 | } | |||
137 | ||||
138 | 9 | void Pid::setErrorAmplification(float coef) { | ||
139 | 9 | errorAmplificationCoef = coef; | ||
140 | 9 | } | ||
141 | ||||
142 | #if EFI_TUNER_STUDIO | |||
143 | ||||
144 | 224 | void Pid::postState(pid_status_s& pidStatus) const { | ||
145 | 224 | pidStatus.output = output; | ||
146 | 224 | pidStatus.error = previousError; | ||
147 |
1/2✓ Branch 0 taken 224 times.
✗ Branch 1 not taken.
|
224 | pidStatus.pTerm = parameters == nullptr ? 0 : parameters->pFactor * previousError; | |
148 | 224 | pidStatus.iTerm = iTerm; | ||
149 | 224 | pidStatus.dTerm = dTerm; | ||
150 | 224 | } | ||
151 | #endif /* EFI_TUNER_STUDIO */ | |||
152 | ||||
153 | ✗ | void Pid::sleep() { | ||
154 | #if !EFI_UNIT_TEST | |||
155 | int periodMs = maxI(10, parameters->periodMs); | |||
156 | chThdSleepMilliseconds(periodMs); | |||
157 | #endif /* EFI_UNIT_TEST */ | |||
158 | ✗ | } | ||
159 | ||||
160 | ✗ | void Pid::showPidStatus(const char*msg) const { | ||
161 | ✗ | efiPrintf("%s settings: offset=%f P=%.5f I=%.5f D=%.5f period=%dms", | ||
162 | msg, | |||
163 | getOffset(), | |||
164 | parameters->pFactor, | |||
165 | parameters->iFactor, | |||
166 | parameters->dFactor, | |||
167 | parameters->periodMs); | |||
168 | ||||
169 | ✗ | efiPrintf("%s status: value=%.2f input=%.2f/target=%.2f iTerm=%.5f dTerm=%.5f", | ||
170 | msg, | |||
171 | output, | |||
172 | input, | |||
173 | target, | |||
174 | iTerm, dTerm); | |||
175 | ||||
176 | ✗ | } | ||
177 | ||||
178 | 229 | void Pid::updateITerm(float value) { | ||
179 | 229 | iTerm += value; | ||
180 | /** | |||
181 | * If we have exceeded the ability of the controlled device to hit target, the I factor will keep accumulating and approach infinity. | |||
182 | * Here we limit the I-term #353 | |||
183 | */ | |||
184 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 229 times.
|
229 | if (iTerm > parameters->maxValue * 100) { |
185 | ✗ | iTerm = parameters->maxValue * 100; | ||
186 | } | |||
187 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 227 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 227 times.
|
229 | if (iTerm > iTermMax) { |
188 | 2 | iTerm = iTermMax; | ||
189 | } | |||
190 | ||||
191 | // this is kind of a hack. a proper fix would be having separate additional settings 'maxIValue' and 'minIValye' | |||
192 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 229 times.
|
229 | if (iTerm < -parameters->maxValue * 100) |
193 | ✗ | iTerm = -parameters->maxValue * 100; | ||
194 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 229 times.
|
229 | if (iTerm < iTermMin) { |
195 | ✗ | iTerm = iTermMin; | ||
196 | } | |||
197 | 229 | } | ||
198 | ||||
199 | ||||
200 | ✗ | PidCic::PidCic() { | ||
201 | // call our derived reset() | |||
202 | ✗ | PidCic::reset(); | ||
203 | ✗ | } | ||
204 | ||||
205 | ✗ | PidCic::PidCic(pid_s *p_parameters) : Pid(p_parameters) { | ||
206 | // call our derived reset() | |||
207 | ✗ | PidCic::reset(); | ||
208 | ✗ | } | ||
209 | ||||
210 | ✗ | void PidCic::reset(void) { | ||
211 | ✗ | Pid::reset(); | ||
212 | ||||
213 | ✗ | totalItermCnt = 0; | ||
214 | ✗ | for (int i = 0; i < PID_AVG_BUF_SIZE; i++) | ||
215 | ✗ | iTermBuf[i] = 0; | ||
216 | ✗ | iTermInvNum = 1.0f / (float)PID_AVG_BUF_SIZE; | ||
217 | ✗ | } | ||
218 | ||||
219 | ✗ | float PidCic::getOutput(float p_target, float p_input, float dTime) { | ||
220 | ✗ | return getUnclampedOutput(p_target, p_input, dTime); | ||
221 | } | |||
222 | ||||
223 | ✗ | void PidCic::updateITerm(float value) { | ||
224 | // use a variation of cascaded integrator-comb (CIC) filtering to get non-overflow iTerm | |||
225 | ✗ | totalItermCnt++; | ||
226 | ✗ | int localBufPos = (totalItermCnt >> PID_AVG_BUF_SIZE_SHIFT) % PID_AVG_BUF_SIZE; | ||
227 | ✗ | int localPrevBufPos = ((totalItermCnt - 1) >> PID_AVG_BUF_SIZE_SHIFT) % PID_AVG_BUF_SIZE; | ||
228 | ||||
229 | // reset old buffer cell | |||
230 | ✗ | if (localPrevBufPos != localBufPos) | ||
231 | ✗ | iTermBuf[localBufPos] = 0; | ||
232 | // integrator stage | |||
233 | ✗ | iTermBuf[localBufPos] += value; | ||
234 | ||||
235 | // return moving average of all sums, to smoothen the result | |||
236 | ✗ | float iTermSum = 0; | ||
237 | ✗ | for (int i = 0; i < PID_AVG_BUF_SIZE; i++) { | ||
238 | ✗ | iTermSum += iTermBuf[i]; | ||
239 | } | |||
240 | ✗ | iTerm = iTermSum * iTermInvNum; | ||
241 | ✗ | } | ||
242 | ||||
243 | 703 | PidIndustrial::PidIndustrial() : Pid() { | ||
244 | 703 | } | ||
245 | ||||
246 | 1 | PidIndustrial::PidIndustrial(pid_s *p_parameters) : Pid(p_parameters) { | ||
247 | 1 | } | ||
248 | ||||
249 | 29 | float PidIndustrial::getOutput(float p_target, float p_input, float dTime) { | ||
250 | float ad, bd; | |||
251 | 29 | float error = (p_target - p_input) * errorAmplificationCoef; | ||
252 | 29 | float pTerm = parameters->pFactor * error; | ||
253 | ||||
254 | // calculate dTerm coefficients | |||
255 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 23 times.
|
2/2✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 23 times.
|
29 | if (fabsf(derivativeFilterLoss) > DBL_EPSILON) { |
256 | // restore Td in the Standard form from the Parallel form: Td = Kd / Kc | |||
257 | 6 | float Td = parameters->dFactor / parameters->pFactor; | ||
258 | // calculate the backward differences approximation of the derivative term | |||
259 | 6 | ad = Td / (Td + dTime / derivativeFilterLoss); | ||
260 | 6 | bd = parameters->pFactor * ad / derivativeFilterLoss; | ||
261 | } else { | |||
262 | // According to the Theory of limits, if p.derivativeFilterLoss -> 0, then | |||
263 | // lim(ad) = 0; lim(bd) = p.pFactor * Td / dTime = p.dFactor / dTime | |||
264 | // i.e. dTerm becomes equal to Pid's | |||
265 | 23 | ad = 0.0f; | ||
266 | 23 | bd = parameters->dFactor / dTime; | ||
267 | } | |||
268 | ||||
269 | // (error - previousError) = (target-input) - (target-prevousInput) = -(input - prevousInput) | |||
270 | 29 | dTerm = dTerm * ad + (error - previousError) * bd; | ||
271 | ||||
272 | 29 | updateITerm(parameters->iFactor * dTime * error); | ||
273 | ||||
274 | // calculate output and apply the limits | |||
275 | 29 | float l_output = pTerm + iTerm + dTerm + getOffset(); | ||
276 | 29 | float limitedOutput = limitOutput(l_output); | ||
277 | ||||
278 | // apply the integrator anti-windup on top of the "normal" iTerm change above | |||
279 | // If p.antiwindupFreq = 0, then iTerm is equal to PidParallelController's | |||
280 | 29 | iTerm += dTime * antiwindupFreq * (limitedOutput - l_output); | ||
281 | ||||
282 | // update the state | |||
283 | 29 | previousError = error; | ||
284 | ||||
285 | 29 | return limitedOutput; | ||
286 | } | |||
287 | ||||
288 | 29 | float PidIndustrial::limitOutput(float v) const { | ||
289 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 29 times.
|
29 | if (v < getMinValue()) |
290 | ✗ | v = getMinValue(); | ||
291 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 26 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 26 times.
|
29 | if (v > parameters->maxValue) |
292 | 3 | v = parameters->maxValue; | ||
293 | 29 | return v; | ||
294 | } | |||
295 |