github.com/RhysU/helm
A header-only PID controller.
step3.c File Reference

Sample controlling a 3rd order system across a unit step in the setpoint. More...

Include dependency graph for step3.c:

Functions

static void advance (const double h, const double a[restrict static 3], const double b[restrict static 1], const double u[restrict static 1], double y[restrict static 3])
 Advance the temporal state of a model given by transfer function \( \frac{y(s)}{u(s)} = \frac{b_0}{s^3 + a_2 s^2 + a_1 s + a_0} \).
 
static void print_usage (const char *arg0, FILE *out)
 Print usage on the given stream.
 
int main (int argc, char *argv[])
 Control the process with transfer function \( \frac{y(s)}{u(s)} = \frac{b_0}{s^3 + a_2 s^2 + a_1 s + a_0} \) across a unit step change in setpoint value.
 

Variables

static const double default_a [3] = {1, 3, 3}
 Default process parameters.
 
static const double default_b [1] = {1}
 Default process parameters.
 
static const double default_f = 0.01
 Default filter time scale.
 
static const double default_kd = 1
 Default derivative gain.
 
static const double default_ki = 1
 Default integration gain.
 
static const double default_kp = 1
 Default proportional gain.
 
static const double default_r = 1
 Default reference value.
 
static const double default_t = 1
 Default time step size.
 
static const double default_T = 25
 Default final time.
 

Detailed Description

Sample controlling a 3rd order system across a unit step in the setpoint.

This sample can be used to test controller behavior against known good results. For example, those presented in Figure 10.2 within Chapter 10 of Astrom and Murray.

Function Documentation

◆ advance()

static void advance ( const double  h,
const double  a[restrict static 3],
const double  b[restrict static 1],
const double  u[restrict static 1],
double  y[restrict static 3] 
)
static

Advance the temporal state of a model given by transfer function \( \frac{y(s)}{u(s)} = \frac{b_0}{s^3 + a_2 s^2 + a_1 s + a_0} \).

Given the process transfer function

\begin{align} \frac{y(s)}{u(s)} = \frac{b_0}{s^3 + a_2 s^2 + a_1 s + a_0} \end{align}

a matching state space model consisting of 1st order differential equations can be derived with the form

\begin{align} \frac{\mathrm{d}}{\mathrm{d}t} \begin{bmatrix} y_0(t) \\ y_1(t) \\ y_2(t) \end{bmatrix} &= \begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ -a_0 & -a_1 & -a_2 \end{bmatrix} \begin{bmatrix} y_0(t) \\ y_1(t) \\ y_2(t) \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ b_0 \end{bmatrix} u(t) \end{align}

for constants \(a_0\), \(a_1\), \(a_2\), and \(b_0\) and time-varying input data \(u(t)\). Using a semi-implicit Euler integration scheme,

\begin{align} \vec{y}\left(t_{i+1}\right) &= \vec{y}\left(t_i\right) + h \vec{f}\left(\vec{y}\left(t_{i+1}\right), u\left(t_i\right)\right) , \end{align}

yields a constant-coefficient linear problem for advancing by time \(h\):

\begin{align} \begin{bmatrix} 1 & -h & 0 \\ 0 & 1 & -h \\ ha_0 & ha_1 & 1+ha_2 \end{bmatrix} \begin{bmatrix} y_0(t_{i+1})\\y_1(t_{i+1})\\y_2(t_{i+1}) \end{bmatrix} &= \begin{bmatrix} y_0(t_i) \\ y_1(t_i) \\ y_2(t_i) \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ b_0 \end{bmatrix} u(t_i) \end{align}

Left multiplying by the matrix cofactor and dividing by the determinant gives a form amenable to computation,

\begin{align} \begin{bmatrix} y_0(t_{i+1})\\y_1(t_{i+1})\\y_2(t_{i+1}) \end{bmatrix} &= \frac{ \begin{bmatrix} h (a_2+a_1 h)+1 & h (a_2 h+1) & h^2 \\ -a_0 h^2 & a_2 h+1 & h \\ -a_0 h & -h (a_1+a_0 h) & 1 \end{bmatrix} \left( \begin{bmatrix} y_0(t_i) \\ y_1(t_i) \\ y_2(t_i) \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ b_0 \end{bmatrix} u(t_i) \right) }{h (h (a_0 h+a_1)+a_2)+1} . \end{align}

This routine advances \(\vec{y}(t)\) to \(\vec{y}(t + h)\) using the above result.

Parameters
[in]hTime step \(h\) to be taken.
[in]aCoefficients \(a_0\), \(a_1\), and \(a_2\).
[in]bCoefficient \(b_0\).
[in]uInput \(u(t)\).
[in,out]yOn input, state \(y_0(t)\), \(y_1(t)\), and \(y_2(t)\). On output, state \(y_0(t+h)\), \(y_1(t+h)\), and \(y_2(t+h)\).
112{
113 const double rhs[3] = {
114 y[0],
115 y[1],
116 y[2] + b[0]*u[0]
117 };
118 const double cof[3][3] = {
119 { h*(a[2] + a[1]*h) + 1 , h*(a[2]*h + 1) , h*h },
120 { -a[0]*h*h , a[2]*h + 1 , h },
121 { -a[0]*h , -h*(a[1] + a[0]*h) , 1 }
122 };
123 const double det = h*(h*(a[0]*h + a[1]) + a[2]) + 1;
124
125 for (int i = 0; i < 3; ++i) {
126 y[i] = 0;
127 for (int j = 0; j < 3; ++j) {
128 y[i] += cof[i][j]*rhs[j];
129 }
130 y[i] /= det;
131 }
132}

◆ print_usage()

static void print_usage ( const char *  arg0,
FILE *  out 
)
static

Print usage on the given stream.

148{
149 fprintf(out, "Usage: %s [OPTION...]\n", arg0);
150 fprintf(out, "Control 3rd-order system across a setpoint step change.\n");
151 fprintf(out, "Output is tab-delimited t, u, y[0], y[1], y[2].\n");
152 fputc('\n', out);
153 fprintf(out, "Process transfer function "
154 "y(s)/u(s) = b0 / (s^3 + a2 s^2 + a1 s + a0):\n");
155 fprintf(out, " -0 a0\t\tSet coefficient a0 (default %g)\n", default_a[0]);
156 fprintf(out, " -1 a1\t\tSet coefficient a1 (default %g)\n", default_a[1]);
157 fprintf(out, " -2 a2\t\tSet coefficient a2 (default %g)\n", default_a[2]);
158 fprintf(out, " -b b0\t\tSet coefficient b0 (default %g)\n", default_b[0]);
159 fputc('\n', out);
160 fprintf(out, "Term-by-term, parallel-form PID settings\n");
161 fprintf(out, " -p kp\t\tProportional gain (default %g)\n", default_kp);
162 fprintf(out, " -i ki\t\tIntegral gain (default %g)\n", default_ki);
163 fprintf(out, " -d kd\t\tDerivative gain (default %g)\n", default_kd);
164 fprintf(out, " -f Tf\t\tFilter time scale (default %g)\n", default_f);
165 fprintf(out, " -r sp\t\tReference value (default %g)\n", default_r);
166 fputc('\n', out);
167 fprintf(out, "Miscellaneous:\n");
168 fprintf(out, " -r r \t\tAdjust setpoint (default %g)\n", default_r);
169 fprintf(out, " -t dt\t\tSet time step size (default %g)\n", default_t);
170 fprintf(out, " -T Tf\t\tSet final time (default %g)\n", default_T);
171 fprintf(out, " -h\t\tDisplay this help and exit\n");
172}
static const double default_r
Default reference value.
Definition step3.c:140
static const double default_f
Default filter time scale.
Definition step3.c:136
static const double default_kd
Default derivative gain.
Definition step3.c:137
static const double default_b[1]
Default process parameters.
Definition step3.c:135
static const double default_kp
Default proportional gain.
Definition step3.c:139
static const double default_T
Default final time.
Definition step3.c:142
static const double default_t
Default time step size.
Definition step3.c:141
static const double default_ki
Default integration gain.
Definition step3.c:138
static const double default_a[3]
Default process parameters.
Definition step3.c:134

◆ main()

int main ( int  argc,
char *  argv[] 
)

Control the process with transfer function \( \frac{y(s)}{u(s)} = \frac{b_0}{s^3 + a_2 s^2 + a_1 s + a_0} \) across a unit step change in setpoint value.

That is, just prior to time zero process state \(y(t)\), reference value \(r(t)\), actuator signal \(u(t)\), and all of their derivatives are zero. At time zero, step change \(r(t) = 1\) is introduced. The transfer function, in conjunction with the controller dynamics, determines the controlled system response.

185{
186 // Establish mutable settings
187 double a[3] = {default_a[0], default_a[1], default_a[2]};
188 double b[1] = {default_b[0]};
189 double f = default_f;
190 double kd = default_kd;
191 double ki = default_ki;
192 double kp = default_kp;
193 double r = default_r;
194 double t = default_t;
195 double T = default_T;
196
197 // Process incoming arguments
198 static const char optstring[] = "0:1:2:b:d:f:i:p:r:t:T:h";
199 for (int opt; -1 != (opt = getopt(argc, argv, optstring));) {
200 switch (opt) {
201 case '0': a[0] = atof(optarg); break;
202 case '1': a[1] = atof(optarg); break;
203 case '2': a[2] = atof(optarg); break;
204 case 'b': b[0] = atof(optarg); break;
205 case 'd': kd = atof(optarg); break;
206 case 'f': f = atof(optarg); break;
207 case 'i': ki = atof(optarg); break;
208 case 'p': kp = atof(optarg); break;
209 case 'r': r = atof(optarg); break;
210 case 't': t = atof(optarg); break;
211 case 'T': T = atof(optarg); break;
212 case 'h': print_usage(argv[0], stdout); return EXIT_SUCCESS;
213 default: print_usage(argv[0], stderr); return EXIT_FAILURE;
214 }
215 }
216
217 // Avoid infinite loops by sanitizing inputs
218 if (t <= 0) {
219 fprintf(stderr, "Step size t must be strictly positive\n");
220 return EXIT_FAILURE;
221 }
222 if (T <= 0) {
223 fprintf(stderr, "Final time T must be strictly positive\n");
224 return EXIT_FAILURE;
225 }
226
227 // Initialize state
228 double u[1] = {0}; // Actuator signal
229 double v[1] = {0}; // Control signal
230 double y[3] = {0, 0, 0}; // Model state
231
232 // Initialize controller setting PID parameters from kp, ki, and kd
233 struct helm_state h;
234 helm_reset(&h);
235 h.kp = kp; // Unified gain
236 h.Td = kd / h.kp; // Convert to derivative time scale
237 h.Tf = f; // Astrom and Murray p.308 suggests (h.Td / 2--20)
238 h.Ti = h.kp / ki; // Convert to integral time scale
239
240 // Simulate controlled model, outputting status after each step
241 helm_approach(&h);
242 for (size_t i = 0; i*t < T+t;) {
243 advance((++i*t > T ? T - (i-1)*t : t), a, b, u, y); // Advance
244 printf("%-22.16g\t%-22.16g\t%-22.16g\t%-22.16g\t%-22.16g\n",
245 i*t > T ? T : i*t, u[0], y[0], y[1], y[2]); // Output
246 v[0] += helm_steady(&h, t, r, u[0], v[0], y[0]); // Control
247 u[0] = v[0]; // Ideal
248 }
249
250 return EXIT_SUCCESS;
251}
static struct helm_state * helm_reset(struct helm_state *const h)
Reset all tuning parameters, but not transient state.
Definition helm.h:201
double kp
Proportional gain modifying P, I, and D terms.
Definition helm.h:177
double f
Internal tracking the filtered process.
Definition helm.h:187
static double helm_steady(struct helm_state *const h, const double dt, const double r, const double u, const double v, const double y)
Find the control signal necessary to steady unsteady process y(t).
Definition helm.h:249
static struct helm_state * helm_approach(struct helm_state *const h)
Reset any transient state, but not tuning parameters.
Definition helm.h:223
double y
Internal tracking of the process observable.
Definition helm.h:186
Tuning parameters and internal state for an incremental PID controller.
Definition helm.h:176
static void print_usage(const char *arg0, FILE *out)
Print usage on the given stream.
Definition step3.c:147
static void advance(const double h, const double a[restrict static 3], const double b[restrict static 1], const double u[restrict static 1], double y[restrict static 3])
Advance the temporal state of a model given by transfer function .
Definition step3.c:107

Variable Documentation

◆ default_a

const double default_a[3] = {1, 3, 3}
static

Default process parameters.

134{1, 3, 3};

◆ default_b

const double default_b[1] = {1}
static

Default process parameters.

135{1};