blob: 278fa223173bd3ec850b7cfcf520f56e7815d8a1 [file] [log] [blame]
Alexander Hansen46a755f2025-10-27 16:31:08 +01001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2017 Google Inc
Patrick Ventured8012182018-03-08 08:21:38 -08003
4#include "pid.hpp"
5
Josh Lehande745422020-11-07 02:14:09 -08006#include "logging.hpp"
7
Ed Tanousf8b6e552025-06-27 13:27:50 -07008#include <chrono>
9#include <string>
10
Patrick Venturea0764872020-08-08 07:48:43 -070011namespace pid_control
12{
Patrick Ventured8012182018-03-08 08:21:38 -080013namespace ec
14{
15
16/********************************
17 * clamp
18 *
19 */
Patrick Venture5f59c0f2018-11-11 12:55:14 -080020static double clamp(double x, double min, double max)
Patrick Ventured8012182018-03-08 08:21:38 -080021{
22 if (x < min)
23 {
24 return min;
25 }
Ed Tanousd2768c52025-06-26 11:42:57 -070026 if (x > max)
Patrick Ventured8012182018-03-08 08:21:38 -080027 {
28 return max;
29 }
30 return x;
31}
32
33/********************************
34 * pid code
35 * Note: Codes assumes the ts field is non-zero
36 */
Josh Lehande745422020-11-07 02:14:09 -080037double pid(pid_info_t* pidinfoptr, double input, double setpoint,
38 const std::string* nameptr)
Patrick Ventured8012182018-03-08 08:21:38 -080039{
Josh Lehande745422020-11-07 02:14:09 -080040 if (nameptr)
41 {
42 if (!(pidinfoptr->initialized))
43 {
44 LogInit(*nameptr, pidinfoptr);
45 }
46 }
47
48 auto logPtr = nameptr ? LogPeek(*nameptr) : nullptr;
49
50 PidCoreContext coreContext;
51 std::chrono::milliseconds msNow;
52
53 if (logPtr)
54 {
55 msNow = LogTimestamp();
56 }
57
58 coreContext.input = input;
59 coreContext.setpoint = setpoint;
60
Patrick Venture5f59c0f2018-11-11 12:55:14 -080061 double error;
Patrick Ventured8012182018-03-08 08:21:38 -080062
Patrick Venturea23468e2019-02-11 09:25:56 -080063 double proportionalTerm;
64 double integralTerm = 0.0f;
Bonnie Lo0e8fc392022-10-05 10:20:55 +080065 double derivativeTerm = 0.0f;
Patrick Venturea23468e2019-02-11 09:25:56 -080066 double feedFwdTerm = 0.0f;
Patrick Ventured8012182018-03-08 08:21:38 -080067
Patrick Venture5f59c0f2018-11-11 12:55:14 -080068 double output;
Patrick Ventured8012182018-03-08 08:21:38 -080069
70 // calculate P, I, D, FF
71
72 // Pid
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070073 error = setpoint - input;
Patrick Venturea23468e2019-02-11 09:25:56 -080074 proportionalTerm = pidinfoptr->proportionalCoeff * error;
Patrick Ventured8012182018-03-08 08:21:38 -080075
Josh Lehande745422020-11-07 02:14:09 -080076 coreContext.error = error;
77 coreContext.proportionalTerm = proportionalTerm;
78 coreContext.integralTerm1 = 0.0;
79
Patrick Ventured8012182018-03-08 08:21:38 -080080 // pId
Patrick Venture4b0df322019-02-11 09:04:57 -080081 if (0.0f != pidinfoptr->integralCoeff)
Patrick Ventured8012182018-03-08 08:21:38 -080082 {
Patrick Venturea23468e2019-02-11 09:25:56 -080083 integralTerm = pidinfoptr->integral;
84 integralTerm += error * pidinfoptr->integralCoeff * pidinfoptr->ts;
Josh Lehande745422020-11-07 02:14:09 -080085
86 coreContext.integralTerm1 = integralTerm;
87
Patrick Venturea23468e2019-02-11 09:25:56 -080088 integralTerm = clamp(integralTerm, pidinfoptr->integralLimit.min,
89 pidinfoptr->integralLimit.max);
Patrick Ventured8012182018-03-08 08:21:38 -080090 }
91
Josh Lehande745422020-11-07 02:14:09 -080092 coreContext.integralTerm2 = integralTerm;
93
Bonnie Lo0e8fc392022-10-05 10:20:55 +080094 // piD
95 derivativeTerm = pidinfoptr->derivativeCoeff *
96 ((error - pidinfoptr->lastError) / pidinfoptr->ts);
97
Josh Lehande745422020-11-07 02:14:09 -080098 coreContext.derivativeTerm = derivativeTerm;
99
Patrick Ventured8012182018-03-08 08:21:38 -0800100 // FF
Patrick Williams8c051122023-05-10 07:50:59 -0500101 feedFwdTerm = (setpoint + pidinfoptr->feedFwdOffset) *
102 pidinfoptr->feedFwdGain;
Patrick Ventured8012182018-03-08 08:21:38 -0800103
Josh Lehande745422020-11-07 02:14:09 -0800104 coreContext.feedFwdTerm = feedFwdTerm;
105
Bonnie Lo0e8fc392022-10-05 10:20:55 +0800106 output = proportionalTerm + integralTerm + derivativeTerm + feedFwdTerm;
Josh Lehande745422020-11-07 02:14:09 -0800107
108 coreContext.output1 = output;
109
Patrick Venture4b0df322019-02-11 09:04:57 -0800110 output = clamp(output, pidinfoptr->outLim.min, pidinfoptr->outLim.max);
Patrick Ventured8012182018-03-08 08:21:38 -0800111
Josh Lehande745422020-11-07 02:14:09 -0800112 coreContext.output2 = output;
113
114 coreContext.minOut = 0.0;
115 coreContext.maxOut = 0.0;
116
Patrick Ventured8012182018-03-08 08:21:38 -0800117 // slew rate
118 // TODO(aarena) - Simplify logic as Andy suggested by creating dynamic
Patrick Venture7442c372019-02-11 10:21:05 -0800119 // outLim_min/max that are affected by slew rate control and just clamping
Patrick Ventured8012182018-03-08 08:21:38 -0800120 // to those instead of effectively clamping twice.
121 if (pidinfoptr->initialized)
122 {
Patrick Venture4b0df322019-02-11 09:04:57 -0800123 if (pidinfoptr->slewNeg != 0.0f)
Patrick Ventured8012182018-03-08 08:21:38 -0800124 {
125 // Don't decrease too fast
Patrick Williams8c051122023-05-10 07:50:59 -0500126 double minOut = pidinfoptr->lastOutput +
127 pidinfoptr->slewNeg * pidinfoptr->ts;
Josh Lehande745422020-11-07 02:14:09 -0800128
129 coreContext.minOut = minOut;
130
Patrick Venturea23468e2019-02-11 09:25:56 -0800131 if (output < minOut)
Patrick Ventured8012182018-03-08 08:21:38 -0800132 {
Patrick Venturea23468e2019-02-11 09:25:56 -0800133 output = minOut;
Patrick Ventured8012182018-03-08 08:21:38 -0800134 }
135 }
Patrick Venture4b0df322019-02-11 09:04:57 -0800136 if (pidinfoptr->slewPos != 0.0f)
Patrick Ventured8012182018-03-08 08:21:38 -0800137 {
138 // Don't increase too fast
Patrick Williams8c051122023-05-10 07:50:59 -0500139 double maxOut = pidinfoptr->lastOutput +
140 pidinfoptr->slewPos * pidinfoptr->ts;
Josh Lehande745422020-11-07 02:14:09 -0800141
142 coreContext.maxOut = maxOut;
143
Patrick Venturea23468e2019-02-11 09:25:56 -0800144 if (output > maxOut)
Patrick Ventured8012182018-03-08 08:21:38 -0800145 {
Patrick Venturea23468e2019-02-11 09:25:56 -0800146 output = maxOut;
Patrick Ventured8012182018-03-08 08:21:38 -0800147 }
148 }
149
Patrick Venture4b0df322019-02-11 09:04:57 -0800150 if (pidinfoptr->slewNeg != 0.0f || pidinfoptr->slewPos != 0.0f)
Patrick Ventured8012182018-03-08 08:21:38 -0800151 {
152 // Back calculate integral term for the cases where we limited the
153 // output
Patrick Venturea23468e2019-02-11 09:25:56 -0800154 integralTerm = output - proportionalTerm;
Patrick Ventured8012182018-03-08 08:21:38 -0800155 }
156 }
157
Josh Lehande745422020-11-07 02:14:09 -0800158 coreContext.output3 = output;
159 coreContext.integralTerm3 = integralTerm;
160
Patrick Ventured8012182018-03-08 08:21:38 -0800161 // Clamp again because having limited the output may result in a
162 // larger integral term
Patrick Venturea23468e2019-02-11 09:25:56 -0800163 integralTerm = clamp(integralTerm, pidinfoptr->integralLimit.min,
164 pidinfoptr->integralLimit.max);
165 pidinfoptr->integral = integralTerm;
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700166 pidinfoptr->initialized = true;
Bonnie Lo0e8fc392022-10-05 10:20:55 +0800167 pidinfoptr->lastError = error;
Patrick Venture4b0df322019-02-11 09:04:57 -0800168 pidinfoptr->lastOutput = output;
Patrick Ventured8012182018-03-08 08:21:38 -0800169
Josh Lehande745422020-11-07 02:14:09 -0800170 coreContext.integralTerm = pidinfoptr->integral;
171 coreContext.output = pidinfoptr->lastOutput;
172
173 if (logPtr)
174 {
175 LogContext(*logPtr, msNow, coreContext);
176 }
177
Patrick Ventured8012182018-03-08 08:21:38 -0800178 return output;
179}
180
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700181} // namespace ec
Patrick Venturea0764872020-08-08 07:48:43 -0700182} // namespace pid_control