rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
efi_pid.cpp
Go to the documentation of this file.
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
17 initPidClass(nullptr);
18}
19
20Pid::Pid(pid_s *p_parameters) {
21 initPidClass(p_parameters);
22}
23
24void Pid::initPidClass(pid_s *p_parameters) {
25 parameters = p_parameters;
26 resetCounter = 0;
27
28 Pid::reset();
29}
30
31static bool isClose(float a, float b) {
32 bool result = (a == b);
33 if (!result) {
34 efiPrintf("PID: not same %f %f", a, b);
35 }
36 return result;
37}
38
39bool Pid::isSame(const pid_s *p_parameters) const {
40 if (!parameters) {
41 // this 'null' could happen on first execution during initialization
42 return false;
43 }
44 efiAssert(ObdCode::OBD_PCM_Processor_Fault, p_parameters != nullptr, "PID::isSame nullptr", false);
45 return isClose(parameters->pFactor, p_parameters->pFactor)
46 && isClose(parameters->iFactor, p_parameters->iFactor)
47 && isClose(parameters->dFactor, p_parameters->dFactor)
48 && isClose(parameters->offset, p_parameters->offset)
49 && 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 */
56float Pid::getOutput(float p_target, float p_input) {
57 efiAssert(ObdCode::OBD_PCM_Processor_Fault, parameters != nullptr, "PID::getOutput nullptr", 0);
58 float dTime = MS2SEC(GET_PERIOD_LIMITED(parameters));
59 return getOutput(p_target, p_input, dTime);
60}
61
62float Pid::getUnclampedOutput(float p_target, float p_input, float dTime) {
63 target = p_target;
64 input = p_input;
65 float error = (target - input) * errorAmplificationCoef;
66
67 float pTerm = parameters->pFactor * error;
68 updateITerm(parameters->iFactor * dTime * error);
69 dTerm = parameters->dFactor / dTime * (error - previousError);
70
71 previousError = error;
72
73 if (dTime <=0) {
74 warning(ObdCode::CUSTOM_PID_DTERM, "PID: unexpected dTime");
75 return pTerm + getOffset();
76 }
77
78 return pTerm + iTerm + dTerm + getOffset();
79}
80
81/**
82 * @param dTime seconds probably? :)
83 */
84float Pid::getOutput(float p_target, float p_input, float dTime) {
85 float l_output = getUnclampedOutput(p_target, p_input, dTime);
86
87 if (l_output > parameters->maxValue) {
88 l_output = parameters->maxValue;
89 } else if (l_output < getMinValue()) {
90 l_output = getMinValue();
91 }
92 output = l_output;
93 return output;
94}
95
96void Pid::updateFactors(float pFactor, float iFactor, float dFactor) {
97 parameters->pFactor = pFactor;
98 parameters->iFactor = iFactor;
99 parameters->dFactor = dFactor;
100 reset();
101}
102
104 dTerm = iTerm = 0;
107 resetCounter++;
108}
109
110float Pid::getP() const {
111 return parameters->pFactor;
112}
113
114float Pid::getI() const {
115 return parameters->iFactor;
116}
117
118float Pid::getPrevError() const {
119 return previousError;
120}
121
122float Pid::getIntegration() const {
123 return iTerm;
124}
125
126float Pid::getD() const {
127 return parameters->dFactor;
128}
129
130float Pid::getOffset(void) const {
131 return parameters->offset;
132}
133
134float Pid::getMinValue(void) const {
135 return parameters->minValue;
136}
137
140}
141
142#if EFI_TUNER_STUDIO
143
144void Pid::postState(pid_status_s& pidStatus) const {
145 pidStatus.output = output;
146 pidStatus.error = previousError;
147 pidStatus.pTerm = parameters == nullptr ? 0 : parameters->pFactor * previousError;
148 pidStatus.iTerm = iTerm;
149 pidStatus.dTerm = dTerm;
150}
151#endif /* EFI_TUNER_STUDIO */
152
154#if !EFI_UNIT_TEST
155 int periodMs = maxI(10, parameters->periodMs);
156 chThdSleepMilliseconds(periodMs);
157#endif /* EFI_UNIT_TEST */
158}
159
160void Pid::showPidStatus(const char*msg) const {
161 efiPrintf("%s settings: offset=%f P=%.5f I=%.5f D=%.5f period=%dms",
162 msg,
163 getOffset(),
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
178void Pid::updateITerm(float value) {
179 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 if (iTerm > parameters->maxValue * 100) {
185 iTerm = parameters->maxValue * 100;
186 }
187 if (iTerm > iTermMax) {
188 iTerm = iTermMax;
189 }
190
191 // this is kind of a hack. a proper fix would be having separate additional settings 'maxIValue' and 'minIValye'
192 if (iTerm < -parameters->maxValue * 100)
193 iTerm = -parameters->maxValue * 100;
194 if (iTerm < iTermMin) {
195 iTerm = iTermMin;
196 }
197}
198
199
201 // call our derived reset()
203}
204
205PidCic::PidCic(pid_s *p_parameters) : Pid(p_parameters) {
206 // call our derived reset()
208}
209
210void 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
219float PidCic::getOutput(float p_target, float p_input, float dTime) {
220 return getUnclampedOutput(p_target, p_input, dTime);
221}
222
223void PidCic::updateITerm(float value) {
224 // use a variation of cascaded integrator-comb (CIC) filtering to get non-overflow iTerm
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
245
246PidIndustrial::PidIndustrial(pid_s *p_parameters) : Pid(p_parameters) {
247}
248
249float PidIndustrial::getOutput(float p_target, float p_input, float dTime) {
250 float ad, bd;
251 float error = (p_target - p_input) * errorAmplificationCoef;
252 float pTerm = parameters->pFactor * error;
253
254 // calculate dTerm coefficients
255 if (fabsf(derivativeFilterLoss) > DBL_EPSILON) {
256 // restore Td in the Standard form from the Parallel form: Td = Kd / Kc
257 float Td = parameters->dFactor / parameters->pFactor;
258 // calculate the backward differences approximation of the derivative term
259 ad = Td / (Td + dTime / 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 ad = 0.0f;
266 bd = parameters->dFactor / dTime;
267 }
268
269 // (error - previousError) = (target-input) - (target-prevousInput) = -(input - prevousInput)
270 dTerm = dTerm * ad + (error - previousError) * bd;
271
272 updateITerm(parameters->iFactor * dTime * error);
273
274 // calculate output and apply the limits
275 float l_output = pTerm + iTerm + dTerm + getOffset();
276 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 iTerm += dTime * antiwindupFreq * (limitedOutput - l_output);
281
282 // update the state
283 previousError = error;
284
285 return limitedOutput;
286}
287
288float PidIndustrial::limitOutput(float v) const {
289 if (v < getMinValue())
290 v = getMinValue();
291 if (v > parameters->maxValue)
292 v = parameters->maxValue;
293 return v;
294}
float iTermInvNum
Definition efi_pid.h:97
float getOutput(float target, float input, float dTime) override
Definition efi_pid.cpp:219
int totalItermCnt
Definition efi_pid.h:99
float iTermBuf[PID_AVG_BUF_SIZE]
Definition efi_pid.h:95
void reset(void) override
Definition efi_pid.cpp:210
void updateITerm(float value) override
Definition efi_pid.cpp:223
Definition efi_pid.h:34
void sleep()
Definition efi_pid.cpp:153
float iTermMax
Definition efi_pid.h:69
void updateFactors(float pFactor, float iFactor, float dFactor)
Definition efi_pid.cpp:96
float iTermMin
Definition efi_pid.h:68
bool isSame(const pid_s *parameters) const
Definition efi_pid.cpp:39
virtual void updateITerm(float value)
Definition efi_pid.cpp:178
void setErrorAmplification(float coef)
Definition efi_pid.cpp:138
void postState(pid_status_s &pidStatus) const
Definition efi_pid.cpp:144
float getP() const
Definition efi_pid.cpp:110
float getOffset() const
Definition efi_pid.cpp:130
void showPidStatus(const char *msg) const
Definition efi_pid.cpp:160
float getD() const
Definition efi_pid.cpp:126
float getI() const
Definition efi_pid.cpp:114
Pid()
Definition efi_pid.cpp:16
virtual void reset()
Definition efi_pid.cpp:103
float getUnclampedOutput(float target, float input, float dTime)
Definition efi_pid.cpp:62
pid_s * parameters
Definition efi_pid.h:71
float getPrevError(void) const
Definition efi_pid.cpp:118
void initPidClass(pid_s *parameters)
Definition efi_pid.cpp:24
int resetCounter
Definition efi_pid.h:66
float getIntegration(void) const
Definition efi_pid.cpp:122
float getMinValue() const
Definition efi_pid.cpp:134
float getOutput(float target, float input)
Definition efi_pid.cpp:56
float getOutput(float target, float input, float dTime) override
Definition efi_pid.cpp:249
float antiwindupFreq
Definition efi_pid.h:123
float derivativeFilterLoss
Definition efi_pid.h:124
float limitOutput(float v) const
Definition efi_pid.cpp:288
static bool isClose(float a, float b)
Definition efi_pid.cpp:31
bool warning(ObdCode code, const char *fmt,...)
@ OBD_PCM_Processor_Fault
@ CUSTOM_PID_DTERM
scaled_channel< int16_t, 100, 1 > dTerm
scaled_channel< int16_t, 100, 1 > error
scaled_channel< int16_t, 100, 1 > output
scaled_channel< int16_t, 100, 1 > iTerm