forked from arendst/Tasmota
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from arendst/development
update dev branch
- Loading branch information
Showing
17 changed files
with
1,277 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
/** | ||
* 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** | ||
* 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. | ||
**/ | ||
|
||
/** | ||
* A PID control class | ||
* | ||
* Github repository https://github.com/colinl/process-control.git | ||
* | ||
* Given ... | ||
* | ||
* Usage: | ||
* First call initialise(), see below for parameters then | ||
* ... | ||
* The functions require a parameter nowSecs which is a representation of the | ||
* current time in seconds. The absolute value of this is immaterial, it is | ||
* used for relative timing only. | ||
* | ||
**/ | ||
|
||
|
||
#ifndef PID_h | ||
#define PID_h | ||
|
||
class PID { | ||
public: | ||
|
||
PID(); | ||
|
||
/* | ||
Initialiser given | ||
current time in seconds | ||
*/ | ||
void 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 ); | ||
|
||
|
||
/* called regularly to calculate and return new power value */ | ||
double tick(unsigned long nowSecs); | ||
|
||
// call to pass in new process value | ||
void setPv( double pv, unsigned long nowSecs ); | ||
|
||
// methods to modify configuration data | ||
void setSp( double setpoint ); | ||
void setPb( double prop_band ); | ||
void setTi( double t_integral ); | ||
void setTd( double t_derivative ); | ||
void setInitialInt( double integral_default ); | ||
void setDSmooth( double smooth_factor ); | ||
void setAuto( unsigned char mode_auto ); | ||
void setManualPower( double manual_op ); | ||
void setMaxInterval( int max_interval ); | ||
|
||
double getPv(); | ||
double getSp(); | ||
double getPb(); | ||
double getTi(); | ||
double getTd(); | ||
double getInitialInt(); | ||
double getDSmooth(); | ||
unsigned char getAuto(); | ||
double getManualPower(); | ||
int getMaxInterval(); | ||
|
||
private: | ||
double m_pv; | ||
double m_setpoint; | ||
double m_prop_band; | ||
double m_t_integral; | ||
double m_t_derivative; | ||
double m_integral_default; | ||
double m_smooth_factor; | ||
unsigned char m_mode_auto; | ||
double m_manual_op; | ||
int m_max_interval; | ||
double m_last_power; | ||
|
||
|
||
unsigned char m_initialised; | ||
unsigned long m_last_pv_update_time; // the time of last pv update secs | ||
unsigned long m_last_sample_time; // the time of the last tick() run | ||
double m_smoothed_value; | ||
double m_integral; | ||
double m_derivative ; | ||
}; | ||
|
||
#endif // Timeprop_h |
Oops, something went wrong.