mirror of https://github.com/arendst/Tasmota.git
235 lines
6.7 KiB
C++
235 lines
6.7 KiB
C++
/**
|
|
* Copyright 2018 Colin Law
|
|
*
|
|
* 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.
|
|
*
|
|
* See Timeprop.h for Usage
|
|
*
|
|
**/
|
|
|
|
|
|
#include "PID.h"
|
|
|
|
PID::PID() {
|
|
m_initialised = 0;
|
|
m_last_sample_time = 0;
|
|
m_last_pv_update_time = 0;
|
|
m_last_power = 0.0;
|
|
}
|
|
|
|
void PID::initialise( double setpoint, double prop_band, double t_integral, double t_derivative,
|
|
double integral_default, int max_interval, double smooth_factor, unsigned char mode_auto, double manual_op ) {
|
|
|
|
m_setpoint = setpoint;
|
|
m_prop_band = prop_band;
|
|
m_t_integral = t_integral;
|
|
m_t_derivative = t_derivative;
|
|
m_integral_default = integral_default;
|
|
m_max_interval = max_interval;
|
|
m_smooth_factor= smooth_factor;
|
|
m_mode_auto= mode_auto;
|
|
m_manual_op = manual_op;
|
|
|
|
m_initialised = 1;
|
|
|
|
}
|
|
|
|
|
|
/* called regularly to calculate and return new power value */
|
|
double PID::tick( unsigned long nowSecs ) {
|
|
double power;
|
|
double factor;
|
|
if (m_initialised && m_last_pv_update_time) {
|
|
// we have been initialised and have been given a pv value
|
|
// check whether too long has elapsed since pv was last updated
|
|
if (m_max_interval > 0 && nowSecs - m_last_pv_update_time > m_max_interval) {
|
|
// yes, too long has elapsed since last PV update so go to fallback power
|
|
power = m_manual_op;
|
|
} else {
|
|
// is this the first time through here?
|
|
if (m_last_sample_time) {
|
|
// not first time
|
|
unsigned long delta_t = nowSecs - m_last_sample_time; // seconds
|
|
if (delta_t <= 0 || delta_t > m_max_interval) {
|
|
// too long since last sample so leave integral as is and set deriv to zero
|
|
m_derivative = 0;
|
|
} else {
|
|
if (m_smooth_factor > 0) {
|
|
// A derivative smoothing factor has been supplied
|
|
// smoothing time constant is td/factor but with a min of delta_t to stop overflows
|
|
int ts = m_t_derivative/m_smooth_factor > delta_t ? m_t_derivative/m_smooth_factor : delta_t;
|
|
factor = 1.0/(ts/delta_t);
|
|
} else {
|
|
// no integral smoothing so factor is 1, this makes smoothed_value the previous pv
|
|
factor = 1.0;
|
|
}
|
|
double delta_v = (m_pv - m_smoothed_value) * factor;
|
|
m_smoothed_value = m_smoothed_value + delta_v;
|
|
m_derivative = m_t_derivative * delta_v/delta_t;
|
|
// lock the integral if abs(previous integral + error) > prop_band/2
|
|
// as this means that P + I is outside the linear region so power will be 0 or full
|
|
// also lock if control is disabled
|
|
double error = m_pv - m_setpoint;
|
|
double pbo2 = m_prop_band/2.0;
|
|
double epi = error + m_integral;
|
|
if (epi < 0.0) epi = -epi; // abs value of error + m_integral
|
|
if (epi < pbo2 && m_mode_auto) {
|
|
if (m_t_integral <= 0) {
|
|
// t_integral is zero (or silly), set integral to one end or the other
|
|
// or half way if exactly on sp
|
|
if (error > 0.0) {
|
|
m_integral = pbo2;
|
|
} else if (error < 0) {
|
|
m_integral = -pbo2;
|
|
} else {
|
|
m_integral = 0.0;
|
|
}
|
|
} else {
|
|
m_integral = m_integral + error * delta_t/m_t_integral;
|
|
}
|
|
}
|
|
// clamp to +- 0.5 prop band widths so that it cannot push the zero power point outside the pb
|
|
// do this here rather than when integral is updated to allow for the fact that the pb may change dynamically
|
|
if ( m_integral < -pbo2 ) {
|
|
m_integral = -pbo2;
|
|
} else if (m_integral > pbo2) {
|
|
m_integral = pbo2;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// first time through, initialise context data
|
|
m_smoothed_value = m_pv;
|
|
// setup the integral term so that the power out would be integral_default if pv=setpoint
|
|
m_integral = (0.5 - m_integral_default)*m_prop_band;
|
|
m_derivative = 0.0;
|
|
}
|
|
|
|
double proportional = m_pv - m_setpoint;
|
|
if (m_prop_band == 0) {
|
|
// prop band is zero so drop back to on/off control with zero hysteresis
|
|
if (proportional > 0.0) {
|
|
power = 0.0;
|
|
} else if (proportional < 0.0) {
|
|
power = 1.0;
|
|
} else {
|
|
// exactly on sp so leave power as it was last time round
|
|
power = m_last_power;
|
|
}
|
|
}
|
|
else {
|
|
power = -1.0/m_prop_band * (proportional + m_integral + m_derivative) + 0.5;
|
|
}
|
|
// set power to disabled value if the loop is not enabled
|
|
if (!m_mode_auto) {
|
|
power = m_manual_op;
|
|
}
|
|
m_last_sample_time = nowSecs;
|
|
}
|
|
} else {
|
|
// not yet initialised or no pv value yet so set power to disabled value
|
|
power = m_manual_op;
|
|
}
|
|
if (power < 0.0) {
|
|
power = 0.0;
|
|
} else if (power > 1.0) {
|
|
power = 1.0;
|
|
}
|
|
m_last_power = power;
|
|
return power;
|
|
}
|
|
|
|
// call to pass in new process value
|
|
void PID::setPv( double pv, unsigned long nowSecs ){
|
|
m_pv = pv;
|
|
m_last_pv_update_time = nowSecs;
|
|
}
|
|
|
|
// methods to modify configuration data
|
|
void PID::setSp( double setpoint ) {
|
|
m_setpoint = setpoint;
|
|
}
|
|
|
|
void PID::setPb( double prop_band ) {
|
|
m_prop_band = prop_band;
|
|
}
|
|
|
|
void PID::setTi( double t_integral ) {
|
|
m_t_integral = t_integral;
|
|
}
|
|
|
|
void PID::setTd( double t_derivative ) {
|
|
m_t_derivative = t_derivative;
|
|
}
|
|
|
|
void PID::setInitialInt( double integral_default ) {
|
|
m_integral_default = integral_default;
|
|
}
|
|
|
|
void PID::setDSmooth( double smooth_factor ) {
|
|
m_smooth_factor = smooth_factor;
|
|
}
|
|
|
|
void PID::setAuto( unsigned char mode_auto ) {
|
|
m_mode_auto = mode_auto;
|
|
}
|
|
|
|
void PID::setManualPower( double manual_op ) {
|
|
m_manual_op = manual_op;
|
|
}
|
|
|
|
void PID::setMaxInterval( int max_interval ) {
|
|
m_max_interval = max_interval;
|
|
}
|
|
|
|
|
|
double PID::getPv() {
|
|
return(m_pv);
|
|
}
|
|
|
|
double PID::getSp() {
|
|
return(m_setpoint);
|
|
}
|
|
|
|
double PID::getPb() {
|
|
return(m_prop_band);
|
|
}
|
|
|
|
double PID::getTi() {
|
|
return(m_t_integral);
|
|
}
|
|
|
|
double PID::getTd() {
|
|
return(m_t_derivative);
|
|
}
|
|
|
|
double PID::getInitialInt() {
|
|
return(m_integral_default);
|
|
}
|
|
|
|
double PID::getDSmooth() {
|
|
return(m_smooth_factor);
|
|
}
|
|
|
|
unsigned char PID::getAuto() {
|
|
return(m_mode_auto);
|
|
}
|
|
|
|
double PID::getManualPower() {
|
|
return(m_manual_op);
|
|
}
|
|
|
|
int PID::getMaxInterval() {
|
|
return(m_max_interval);
|
|
}
|