-
Notifications
You must be signed in to change notification settings - Fork 4
/
PID.cpp
193 lines (172 loc) · 6.16 KB
/
PID.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
* 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;
}