From 2d1a044ebaedbc9bcf9e95d5b60420ed9e18a2d6 Mon Sep 17 00:00:00 2001 From: Raptor Engineering Development Team Date: Sun, 29 Apr 2018 07:31:49 -0500 Subject: Add PID control option to fan controller --- control/actions.hpp | 42 ++++++++++++++++++++++-- control/main.cpp | 1 + control/zone.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ control/zone.hpp | 50 +++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 2 deletions(-) diff --git a/control/actions.hpp b/control/actions.hpp index c7356ab..653364d 100644 --- a/control/actions.hpp +++ b/control/actions.hpp @@ -1,3 +1,6 @@ +// PID controls (c) 2018 Raptor Engineering, LLC +// Licensed for use with Raptor Computing Systems machines only + #pragma once #include @@ -183,7 +186,7 @@ auto set_net_increase_speed(T&& state, T&& factor, uint64_t speedDelta) std::get(entry.second)); // TODO openbmc/phosphor-fan-presence#7 - Support possible // state types for comparison - if (value >= state) + if ((value >= state) && (factor != 0)) { // Increase by at least a single delta(factor) // to attempt bringing under 'state' @@ -240,7 +243,7 @@ auto set_net_decrease_speed(T&& state, T&& factor, uint64_t speedDelta) std::get(entry.second)); // TODO openbmc/phosphor-fan-presence#7 - Support possible // state types for comparison - if (value < state) + if ((value < state) && (factor != 0)) { if (netDelta == 0) { @@ -274,6 +277,41 @@ auto set_net_decrease_speed(T&& state, T&& factor, uint64_t speedDelta) }; } +template +auto run_pid_control(T&& state, T&& integrator_timestep, T&& kp, T&& ki, T&& kd) +{ + return [integrator_timestep = std::forward(integrator_timestep), + kp = std::forward(kp), + ki = std::forward(ki), + kd = std::forward(kd), + state = std::forward(state)](auto& zone, auto& group) + { + auto error_accum = 0; + auto error_items = 0; + for (auto& entry : group) + { + try + { + T value = zone.template getPropertyValue( + entry.first, + std::get(entry.second), + std::get(entry.second)); + error_accum += (value - state); + error_items++; + } + catch (const std::out_of_range& oore) + { + // Property value not found + } + } + + if (error_items > 0) + { + zone.runPidLoop((error_accum / error_items), integrator_timestep, kp, ki, kd); + } + }; +} + } // namespace action } // namespace control } // namespace fan diff --git a/control/main.cpp b/control/main.cpp index dd49c93..9d12781 100644 --- a/control/main.cpp +++ b/control/main.cpp @@ -85,6 +85,7 @@ int main(int argc, char* argv[]) } else { + printf("Setup complete. Starting event loop...\n"); r = sd_event_loop(eventPtr.get()); if (r < 0) { diff --git a/control/zone.cpp b/control/zone.cpp index 8e665f0..4ce62ea 100644 --- a/control/zone.cpp +++ b/control/zone.cpp @@ -13,6 +13,9 @@ * 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. + * + * PID controls (c) 2018 Raptor Engineering, LLC + * Licensed for use with Raptor Computing Systems machines only */ #include #include @@ -49,6 +52,7 @@ Zone::Zone(Mode mode, _decInterval(std::get(def)), _incTimer(events, [this](){ this->incTimerExpired(); }), _decTimer(events, [this](){ this->decTimerExpired(); }), + _pidTimer(events, [this](){ this->pidTimerExpired(); }), _sdEvents(events) { auto& fanDefs = std::get(def); @@ -77,6 +81,12 @@ Zone::Zone(Mode mode, _decTimer.start(_decInterval, util::Timer::TimerType::repeating); } + // Start timer for PID loops + if (!_pidTimer.running()) + { + _pidTimer.start(std::chrono::seconds(1), + util::Timer::TimerType::repeating); + } } } @@ -102,6 +112,9 @@ void Zone::setFullSpeed() fan->setSpeed(_targetSpeed); } } + + _pidIntegrator = 0; + _pidPrevError = 0; } void Zone::disableRotors() @@ -110,6 +123,9 @@ void Zone::disableRotors() { fan->disableRotor(); } + + _pidIntegrator = 0; + _pidPrevError = 0; } void Zone::setActiveAllow(const Group* group, bool isActiveAllow) @@ -127,6 +143,9 @@ void Zone::setActiveAllow(const Group* group, bool isActiveAllow) _active.end(), actPred); } + + _pidIntegrator = 0; + _pidPrevError = 0; } void Zone::removeService(const Group* group, @@ -275,6 +294,16 @@ void Zone::requestSpeedDecrease(uint64_t targetDelta) } } +void Zone::runPidLoop(int64_t error, uint64_t integrator_timestep, int64_t kp, int64_t ki, int64_t kd) +{ + _pidLastError = error; + _pidLastIntegratorTimestep = integrator_timestep; + _pidLastKp = kp; + _pidLastKi = ki; + _pidLastKd = kd; + _pidActive = true; +} + void Zone::decTimerExpired() { // Check all entries are set to allow a decrease @@ -312,6 +341,67 @@ void Zone::decTimerExpired() // Decrease timer is restarted since its repeating } +void Zone::pidTimerExpired() +{ + if (!_pidActive) + { + return; + } + + /* If error is positive on startup, set fans to full by forcing integrator to maximum value */ + if (!_pidActivePrev) { + if (_pidLastError > 0) { + _pidIntegrator = ((_ceilingSpeed - _floorSpeed) * 100000) / _pidLastKi; + } + } + + auto pidIntegrator_orig = _pidIntegrator; + _pidIntegrator += _pidLastError * _pidLastIntegratorTimestep; + int64_t derivative = (_pidLastError - _pidPrevError) / _pidLastIntegratorTimestep; + int64_t pid_output = ((_pidLastError * _pidLastKp) + (_pidIntegrator * _pidLastKi) + (derivative * _pidLastKd)) / 100000; + _pidPrevError = _pidLastError; + + uint64_t requestTarget = _floorSpeed; + if (pid_output > 0) + { + requestTarget = _floorSpeed + pid_output; + } + else { + _pidIntegrator = pidIntegrator_orig; + } + // Target speed can not go above a defined ceiling speed + if (requestTarget > _ceilingSpeed) + { + requestTarget = _ceilingSpeed; + _pidIntegrator = pidIntegrator_orig; + } + // Target speed can not go below a defined floor speed + if (requestTarget < _floorSpeed) + { + requestTarget = _floorSpeed; + _pidIntegrator = pidIntegrator_orig; + } + + /* Cap integrator wind-up */ + if (_pidLastKi != 0) { + if (_pidIntegrator > (int64_t)(((_ceilingSpeed - _floorSpeed) * 100000) / _pidLastKi)) + { + _pidIntegrator = ((_ceilingSpeed - _floorSpeed) * 100000) / _pidLastKi; + } + if (_pidIntegrator < ((int64_t)((_ceilingSpeed - _floorSpeed) * -100000) / _pidLastKi)) + { + _pidIntegrator = ((_ceilingSpeed - _floorSpeed) * -100000) / _pidLastKi; + } + } + else { + _pidIntegrator = 0; + } + + setRequestSpeedBase(requestTarget); + + _pidActivePrev = true; +} + void Zone::initEvent(const SetSpeedEvent& event) { sdbusplus::message::message nullMsg{nullptr}; @@ -319,6 +409,8 @@ void Zone::initEvent(const SetSpeedEvent& event) for (auto& sig : std::get(event)) { // Initialize the event signal using handler + // FIXME + // This next line is very slow, delaying fan control startup significantly std::get(sig)(_bus, nullMsg, *this); // Setup signal matches of the property for event std::unique_ptr eventData = diff --git a/control/zone.hpp b/control/zone.hpp index 2185afa..fad2ae6 100644 --- a/control/zone.hpp +++ b/control/zone.hpp @@ -1,3 +1,6 @@ +// PID controls (c) 2018 Raptor Engineering, LLC +// Licensed for use with Raptor Computing Systems machines only + #pragma once #include #include @@ -327,6 +330,18 @@ class Zone */ void requestSpeedDecrease(uint64_t targetDelta); + /** + * @brief Run PID algorithm based on provided error, Kp, Ki, and Kd + * values. + * + * @param[in] error - Current error value + * @param[in] integrator_timestep - Integrator time step + * @param[in] kp - PID proportional constant + * @param[in] ki - PID integral constant + * @param[in] kd - PID derivative constant + */ + void runPidLoop(int64_t error, uint64_t integrator_timestep, int64_t kp, int64_t ki, int64_t kd); + /** * @brief Callback function for the increase timer that delays * processing of requested speed increases while fans are increasing @@ -339,6 +354,11 @@ class Zone */ void decTimerExpired(); + /** + * @brief Callback function for the PID timer + */ + void pidTimerExpired(); + /** * @brief Get the event pointer used with this zone's timers * @@ -501,6 +521,31 @@ class Zone */ uint64_t _requestSpeedBase = 0; + /** + * PID active + */ + bool _pidActive = false; + bool _pidActivePrev = false; + + /** + * PID integrator + */ + int64_t _pidIntegrator = 0; + + /** + * PID values + */ + int64_t _pidLastError = 0; + uint64_t _pidLastIntegratorTimestep = 0; + int64_t _pidLastKp = 0; + int64_t _pidLastKi = 0; + int64_t _pidLastKd = 0; + + /** + * PID prior state tracker + */ + int64_t _pidPrevError = 0; + /** * Speed increase delay in seconds */ @@ -521,6 +566,11 @@ class Zone */ phosphor::fan::util::Timer _decTimer; + /** + * The PID timer object + */ + phosphor::fan::util::Timer _pidTimer; + /** * Dbus event used on set speed event timers */ -- cgit v1.2.1