/** * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pid.hpp" #include "../tuning.hpp" #include "logging.hpp" namespace pid_control { namespace ec { /******************************** * clamp * */ static double clamp(double x, double min, double max) { if (x < min) { return min; } else if (x > max) { return max; } return x; } /******************************** * pid code * Note: Codes assumes the ts field is non-zero */ double pid(pid_info_t* pidinfoptr, double input, double setpoint, const std::string* nameptr) { if (nameptr) { if (!(pidinfoptr->initialized)) { LogInit(*nameptr, pidinfoptr); } } auto logPtr = nameptr ? LogPeek(*nameptr) : nullptr; PidCoreContext coreContext; std::chrono::milliseconds msNow; if (logPtr) { msNow = LogTimestamp(); } coreContext.input = input; coreContext.setpoint = setpoint; double error; double proportionalTerm; double integralTerm = 0.0f; double derivativeTerm = 0.0f; double feedFwdTerm = 0.0f; double output; // calculate P, I, D, FF // Pid error = setpoint - input; proportionalTerm = pidinfoptr->proportionalCoeff * error; coreContext.error = error; coreContext.proportionalTerm = proportionalTerm; coreContext.integralTerm1 = 0.0; // pId if (0.0f != pidinfoptr->integralCoeff) { integralTerm = pidinfoptr->integral; integralTerm += error * pidinfoptr->integralCoeff * pidinfoptr->ts; coreContext.integralTerm1 = integralTerm; integralTerm = clamp(integralTerm, pidinfoptr->integralLimit.min, pidinfoptr->integralLimit.max); } coreContext.integralTerm2 = integralTerm; // piD derivativeTerm = pidinfoptr->derivativeCoeff * ((error - pidinfoptr->lastError) / pidinfoptr->ts); coreContext.derivativeTerm = derivativeTerm; // FF feedFwdTerm = (setpoint + pidinfoptr->feedFwdOffset) * pidinfoptr->feedFwdGain; coreContext.feedFwdTerm = feedFwdTerm; output = proportionalTerm + integralTerm + derivativeTerm + feedFwdTerm; coreContext.output1 = output; output = clamp(output, pidinfoptr->outLim.min, pidinfoptr->outLim.max); coreContext.output2 = output; coreContext.minOut = 0.0; coreContext.maxOut = 0.0; // slew rate // TODO(aarena) - Simplify logic as Andy suggested by creating dynamic // outLim_min/max that are affected by slew rate control and just clamping // to those instead of effectively clamping twice. if (pidinfoptr->initialized) { if (pidinfoptr->slewNeg != 0.0f) { // Don't decrease too fast double minOut = pidinfoptr->lastOutput + pidinfoptr->slewNeg * pidinfoptr->ts; coreContext.minOut = minOut; if (output < minOut) { output = minOut; } } if (pidinfoptr->slewPos != 0.0f) { // Don't increase too fast double maxOut = pidinfoptr->lastOutput + pidinfoptr->slewPos * pidinfoptr->ts; coreContext.maxOut = maxOut; if (output > maxOut) { output = maxOut; } } if (pidinfoptr->slewNeg != 0.0f || pidinfoptr->slewPos != 0.0f) { // Back calculate integral term for the cases where we limited the // output integralTerm = output - proportionalTerm; } } coreContext.output3 = output; coreContext.integralTerm3 = integralTerm; // Clamp again because having limited the output may result in a // larger integral term integralTerm = clamp(integralTerm, pidinfoptr->integralLimit.min, pidinfoptr->integralLimit.max); pidinfoptr->integral = integralTerm; pidinfoptr->initialized = true; pidinfoptr->lastError = error; pidinfoptr->lastOutput = output; coreContext.integralTerm = pidinfoptr->integral; coreContext.output = pidinfoptr->lastOutput; if (logPtr) { LogContext(*logPtr, msNow, coreContext); } return output; } } // namespace ec } // namespace pid_control