fome-fw/firmware/util/math/pid.cpp

296 lines
7.8 KiB
C++
Raw Normal View History

2015-07-10 06:01:56 -07:00
/**
* @file pid.cpp
*
2017-05-25 09:09:07 -07:00
* https://en.wikipedia.org/wiki/Feedback
2015-07-10 06:01:56 -07:00
* http://en.wikipedia.org/wiki/PID_controller
*
* @date Sep 16, 2014
2020-01-13 18:57:43 -08:00
* @author Andrey Belomutskiy, (c) 2012-2020
2015-07-10 06:01:56 -07:00
*/
2019-07-05 17:03:32 -07:00
#include "global.h"
#include "os_access.h"
2019-07-05 17:03:32 -07:00
#include "pid.h"
2016-02-06 09:02:24 -08:00
#include "math.h"
2019-11-22 12:55:38 -08:00
#include "engine_configuration_generated_structures.h"
2016-02-06 09:02:24 -08:00
Pid::Pid() {
2018-07-29 13:30:23 -07:00
initPidClass(NULL);
2016-02-06 09:02:24 -08:00
}
2015-07-10 06:01:56 -07:00
2019-09-01 15:41:51 -07:00
Pid::Pid(pid_s *parameters) {
initPidClass(parameters);
2016-02-06 09:02:24 -08:00
}
2019-09-01 15:41:51 -07:00
void Pid::initPidClass(pid_s *parameters) {
this->parameters = parameters;
2017-06-02 18:34:00 -07:00
resetCounter = 0;
2015-07-10 06:01:56 -07:00
2017-06-02 18:34:00 -07:00
reset();
2015-07-10 06:01:56 -07:00
}
2019-11-22 12:55:38 -08:00
bool Pid::isSame(const pid_s *parameters) const {
2020-03-24 13:46:36 -07:00
if (!this->parameters) {
// this 'null' could happen on first execution during initialization
2020-03-23 23:33:20 -07:00
return false;
}
efiAssert(OBD_PCM_Processor_Fault, parameters != NULL, "PID::isSame NULL", false);
2019-09-01 15:41:51 -07:00
return this->parameters->pFactor == parameters->pFactor
&& this->parameters->iFactor == parameters->iFactor
&& this->parameters->dFactor == parameters->dFactor
&& this->parameters->offset == parameters->offset
&& this->parameters->periodMs == parameters->periodMs;
2016-09-15 20:01:48 -07:00
}
/**
* @param Controller input / process output
* @returns Output from the PID controller / the input to the process
*/
float Pid::getOutput(float target, float input) {
2019-09-01 15:41:51 -07:00
float dTime = MS2SEC(GET_PERIOD_LIMITED(parameters));
2019-04-25 17:50:28 -07:00
return getOutput(target, input, dTime);
2017-01-22 14:03:31 -08:00
}
float Pid::getUnclampedOutput(float target, float input, float dTime) {
float error = (target - input) * errorAmplificationCoef;
this->target = target;
this->input = input;
2015-07-10 06:01:56 -07:00
2019-09-01 15:41:51 -07:00
float pTerm = parameters->pFactor * error;
updateITerm(parameters->iFactor * dTime * error);
dTerm = parameters->dFactor / dTime * (error - previousError);
2015-07-10 06:01:56 -07:00
previousError = error;
2015-07-10 06:01:56 -07:00
return pTerm + iTerm + dTerm + getOffset();
2017-12-24 18:17:10 -08:00
}
2017-02-13 08:03:16 -08:00
2019-04-24 20:46:49 -07:00
/**
* @param dTime seconds probably? :)
*/
float Pid::getOutput(float target, float input, float dTime) {
float output = getUnclampedOutput(target, input, dTime);
2017-02-13 08:03:16 -08:00
2019-09-01 15:41:51 -07:00
if (output > parameters->maxValue) {
output = parameters->maxValue;
} else if (output < getMinValue()) {
output = getMinValue();
2015-07-10 06:01:56 -07:00
}
this->output = output;
return output;
2015-07-10 06:01:56 -07:00
}
void Pid::updateFactors(float pFactor, float iFactor, float dFactor) {
2019-09-01 15:41:51 -07:00
parameters->pFactor = pFactor;
parameters->iFactor = iFactor;
parameters->dFactor = dFactor;
2015-07-10 06:01:56 -07:00
reset();
}
void Pid::reset(void) {
2017-06-02 18:34:00 -07:00
dTerm = iTerm = 0;
output = input = target = previousError = 0;
errorAmplificationCoef = 1.0f;
2017-06-02 18:34:00 -07:00
resetCounter++;
2015-07-10 06:01:56 -07:00
}
float Pid::getP(void) const {
2019-09-01 15:41:51 -07:00
return parameters->pFactor;
2015-07-10 06:01:56 -07:00
}
float Pid::getI(void) const {
2019-09-01 15:41:51 -07:00
return parameters->iFactor;
2015-07-10 06:01:56 -07:00
}
float Pid::getPrevError(void) const {
return previousError;
2016-01-24 13:01:28 -08:00
}
float Pid::getIntegration(void) const {
2015-07-10 06:01:56 -07:00
return iTerm;
}
float Pid::getD(void) const {
2019-09-01 15:41:51 -07:00
return parameters->dFactor;
2015-07-10 06:01:56 -07:00
}
float Pid::getOffset(void) const {
2019-09-01 15:41:51 -07:00
return parameters->offset;
2016-02-06 09:02:24 -08:00
}
2015-07-10 06:01:56 -07:00
float Pid::getMinValue(void) const {
return parameters->minValue;
}
void Pid::setErrorAmplification(float coef) {
errorAmplificationCoef = coef;
}
2019-04-12 19:10:57 -07:00
#if EFI_TUNER_STUDIO
2019-12-16 17:36:40 -08:00
void Pid::postState(TunerStudioOutputChannels *tsOutputChannels) const {
2017-07-23 09:12:35 -07:00
postState(tsOutputChannels, 1);
}
2018-01-28 10:14:18 -08:00
/**
* see https://rusefi.com/wiki/index.php?title=Manual:Debug_fields
*/
2019-12-16 17:36:40 -08:00
void Pid::postState(TunerStudioOutputChannels *tsOutputChannels, int pMult) const {
tsOutputChannels->debugFloatField1 = output;
2016-09-20 18:02:46 -07:00
tsOutputChannels->debugFloatField2 = iTerm;
2016-02-06 09:02:24 -08:00
tsOutputChannels->debugFloatField3 = getPrevError();
tsOutputChannels->debugFloatField4 = getI();
tsOutputChannels->debugFloatField5 = getD();
2018-07-29 13:30:23 -07:00
tsOutputChannels->debugFloatField6 = dTerm;
2019-09-01 15:41:51 -07:00
// tsOutputChannels->debugFloatField6 = parameters->minValue;
tsOutputChannels->debugFloatField7 = parameters->maxValue;
2017-07-23 09:12:35 -07:00
tsOutputChannels->debugIntField1 = getP() * pMult;
2016-02-06 09:02:24 -08:00
tsOutputChannels->debugIntField2 = getOffset();
2017-06-03 07:37:26 -07:00
tsOutputChannels->debugIntField3 = resetCounter;
2019-09-01 15:41:51 -07:00
tsOutputChannels->debugIntField4 = parameters->periodMs;
2016-02-06 09:02:24 -08:00
}
2018-11-16 05:08:20 -08:00
#endif /* EFI_TUNER_STUDIO */
2017-05-25 05:49:04 -07:00
2017-05-28 19:10:35 -07:00
void Pid::sleep() {
2019-04-12 19:10:57 -07:00
#if !EFI_UNIT_TEST
2019-09-01 15:41:51 -07:00
int periodMs = maxI(10, parameters->periodMs);
2019-02-10 19:47:49 -08:00
chThdSleepMilliseconds(periodMs);
#endif /* EFI_UNIT_TEST */
2017-05-28 19:10:35 -07:00
}
2019-11-22 12:55:38 -08:00
void Pid::showPidStatus(Logging *logging, const char*msg) const {
2019-09-27 20:37:57 -07:00
scheduleMsg(logging, "%s settings: offset=%f P=%.5f I=%.5f D=%.5f period=%dms",
2017-05-25 05:49:04 -07:00
msg,
getOffset(),
2019-09-01 15:41:51 -07:00
parameters->pFactor,
parameters->iFactor,
parameters->dFactor,
parameters->periodMs);
2017-05-25 05:56:36 -07:00
2018-01-23 09:05:14 -08:00
scheduleMsg(logging, "%s status: value=%.2f input=%.2f/target=%.2f iTerm=%.5f dTerm=%.5f",
2017-05-29 19:51:14 -07:00
msg,
output,
input,
target,
2017-05-25 05:56:36 -07:00
iTerm, dTerm);
2017-05-25 05:49:04 -07:00
}
2017-12-24 18:17:10 -08:00
void Pid::updateITerm(float value) {
iTerm += value;
/**
* If we have exceeded the ability of the controlled device to hit target, the I factor will keep accumulating and approach infinity.
* Here we limit the I-term #353
*/
2019-09-01 15:41:51 -07:00
if (iTerm > parameters->maxValue * 100) {
iTerm = parameters->maxValue * 100;
2019-03-02 11:00:32 -08:00
}
if (iTerm > iTermMax) {
iTerm = iTermMax;
}
2017-12-24 18:17:10 -08:00
// this is kind of a hack. a proper fix would be having separate additional settings 'maxIValue' and 'minIValye'
2019-09-01 15:41:51 -07:00
if (iTerm < -parameters->maxValue * 100)
iTerm = -parameters->maxValue * 100;
2019-03-02 11:00:32 -08:00
if (iTerm < iTermMin) {
iTerm = iTermMin;
}
2017-12-24 18:17:10 -08:00
}
PidCic::PidCic() {
// call our derived reset()
reset();
}
2019-09-01 15:41:51 -07:00
PidCic::PidCic(pid_s *parameters) : Pid(parameters) {
2017-12-24 18:17:10 -08:00
// call our derived reset()
reset();
}
void PidCic::reset(void) {
Pid::reset();
totalItermCnt = 0;
for (int i = 0; i < PID_AVG_BUF_SIZE; i++)
iTermBuf[i] = 0;
iTermInvNum = 1.0f / (float)PID_AVG_BUF_SIZE;
}
float PidCic::getOutput(float target, float input, float dTime) {
return getUnclampedOutput(target, input, dTime);
2017-12-24 18:17:10 -08:00
}
void PidCic::updateITerm(float value) {
// use a variation of cascaded integrator-comb (CIC) filtering to get non-overflow iTerm
totalItermCnt++;
int localBufPos = (totalItermCnt >> PID_AVG_BUF_SIZE_SHIFT) % PID_AVG_BUF_SIZE;
int localPrevBufPos = ((totalItermCnt - 1) >> PID_AVG_BUF_SIZE_SHIFT) % PID_AVG_BUF_SIZE;
// reset old buffer cell
if (localPrevBufPos != localBufPos)
iTermBuf[localBufPos] = 0;
// integrator stage
iTermBuf[localBufPos] += value;
// return moving average of all sums, to smoothen the result
float iTermSum = 0;
for (int i = 0; i < PID_AVG_BUF_SIZE; i++) {
iTermSum += iTermBuf[i];
}
iTerm = iTermSum * iTermInvNum;
}
PidIndustrial::PidIndustrial() : Pid() {
}
PidIndustrial::PidIndustrial(pid_s *parameters) : Pid(parameters) {
}
float PidIndustrial::getOutput(float target, float input, float dTime) {
float ad, bd;
float error = (target - input) * errorAmplificationCoef;
float pTerm = parameters->pFactor * error;
// calculate dTerm coefficients
if (fabsf(derivativeFilterLoss) > DBL_EPSILON) {
// restore Td in the Standard form from the Parallel form: Td = Kd / Kc
float Td = parameters->dFactor / parameters->pFactor;
// calculate the backward differences approximation of the derivative term
ad = Td / (Td + dTime / derivativeFilterLoss);
bd = parameters->pFactor * ad / derivativeFilterLoss;
} else {
// According to the Theory of limits, if p.derivativeFilterLoss -> 0, then
// lim(ad) = 0; lim(bd) = p.pFactor * Td / dTime = p.dFactor / dTime
// i.e. dTerm becomes equal to Pid's
ad = 0.0f;
bd = parameters->dFactor / dTime;
}
// (error - previousError) = (target-input) - (target-prevousInput) = -(input - prevousInput)
dTerm = dTerm * ad + (error - previousError) * bd;
// update the I-term
iTerm += parameters->iFactor * dTime * error;
// calculate output and apply the limits
float output = pTerm + iTerm + dTerm + getOffset();
float limitedOutput = limitOutput(output);
// apply the integrator anti-windup on top of the "normal" iTerm change above
// If p.antiwindupFreq = 0, then iTerm is equal to PidParallelController's
iTerm += dTime * antiwindupFreq * (limitedOutput - output);
// update the state
previousError = error;
return limitedOutput;
}
float PidIndustrial::limitOutput(float v) const {
if (v < parameters->minValue)
v = parameters->minValue;
if (v > parameters->maxValue)
v = parameters->maxValue;
return v;
}