PID Objects & Algo

These are the PID controller implementations for fans,
and thermals.  This also includes the PID algorithm used.

Change-Id: I30471fbf7a8a7ed65f78bf105970d62815fedc56
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/pid/ec/pid.cpp b/pid/ec/pid.cpp
new file mode 100644
index 0000000..a1c4e41
--- /dev/null
+++ b/pid/ec/pid.cpp
@@ -0,0 +1,118 @@
+/**
+ * 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"
+
+namespace ec
+{
+
+/********************************
+ *  clamp
+ *
+ */
+static float clamp(float x, float min, float 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
+ */
+float pid(pid_info_t* pidinfoptr, float input, float setpoint)
+{
+    float error;
+
+    float p_term = 0.0f;
+    float i_term = 0.0f;
+    float ff_term = 0.0f;
+
+    float output;
+
+    // calculate P, I, D, FF
+
+    // Pid
+    error   = setpoint - input;
+    p_term  = pidinfoptr->p_c * error;
+
+    // pId
+    if (0.0f != pidinfoptr->i_c)
+    {
+        i_term  = pidinfoptr->integral;
+        i_term  += error * pidinfoptr->i_c * pidinfoptr->ts;
+        i_term  = clamp(i_term, pidinfoptr->i_lim.min, pidinfoptr->i_lim.max);
+    }
+
+    // FF
+    ff_term = (setpoint + pidinfoptr->ff_off) * pidinfoptr->ff_gain;
+
+    output = p_term + i_term + ff_term;
+    output = clamp(output, pidinfoptr->out_lim.min, pidinfoptr->out_lim.max);
+
+    // slew rate
+    // TODO(aarena) - Simplify logic as Andy suggested by creating dynamic
+    // out_lim_min/max that are affected by slew rate control and just clamping
+    // to those instead of effectively clamping twice.
+    if (pidinfoptr->initialized)
+    {
+        if (pidinfoptr->slew_neg != 0.0f)
+        {
+            // Don't decrease too fast
+            float min_out = pidinfoptr->last_output + pidinfoptr->slew_neg *
+                            pidinfoptr->ts;
+            if (output < min_out)
+            {
+                output = min_out;
+            }
+        }
+        if (pidinfoptr->slew_pos != 0.0f)
+        {
+            // Don't increase too fast
+            float max_out = pidinfoptr->last_output + pidinfoptr->slew_pos *
+                            pidinfoptr->ts;
+            if (output > max_out)
+            {
+                output = max_out;
+            }
+        }
+
+        if (pidinfoptr->slew_neg != 0.0f || pidinfoptr->slew_pos != 0.0f)
+        {
+            // Back calculate integral term for the cases where we limited the
+            // output
+            i_term = output - p_term;
+        }
+    }
+
+    // Clamp again because having limited the output may result in a
+    // larger integral term
+    i_term  = clamp(i_term, pidinfoptr->i_lim.min, pidinfoptr->i_lim.max);
+    pidinfoptr->integral = i_term;
+    pidinfoptr->initialized    = true;
+    pidinfoptr->last_output    = output;
+
+    return output;
+}
+
+}
diff --git a/pid/ec/pid.hpp b/pid/ec/pid.hpp
new file mode 100644
index 0000000..e138933
--- /dev/null
+++ b/pid/ec/pid.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <cstdint>
+
+namespace ec
+{
+
+typedef struct
+{
+    float       min;
+    float       max;
+} limits_t;
+
+/* Note: If you update these structs you need to update the copy code in
+ * pid/util.cpp.
+ */
+typedef struct
+{
+    bool        initialized;        // has pid been initialized
+
+    float       ts;                 // sample time in seconds
+    float       integral;           // intergal of error
+    float       last_output;        // value of last output
+
+    float       p_c;                // coeff for P
+    float       i_c;                // coeff for I
+    float       ff_off;             // offset coeff for feed-forward term
+    float       ff_gain;            // gain for feed-forward term
+
+    limits_t    i_lim;              // clamp of integral
+    limits_t    out_lim;            // clamp of output
+    float       slew_neg;
+    float       slew_pos;
+} pid_info_t;
+
+float pid(pid_info_t* pidinfoptr, float input, float setpoint);
+
+/* Condensed version for use by the configuration. */
+struct pidinfo
+{
+    float        ts;                 // sample time in seconds
+    float        p_c;                // coeff for P
+    float        i_c;                // coeff for I
+    float        ff_off;             // offset coeff for feed-forward term
+    float        ff_gain;            // gain for feed-forward term
+    ec::limits_t i_lim;              // clamp of integral
+    ec::limits_t out_lim;            // clamp of output
+    float        slew_neg;
+    float        slew_pos;
+};
+
+
+}