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...

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} \). More...
 
static void print_usage (const char *arg0, FILE *out)
 Print usage on the given stream. More...
 
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. More...
 

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

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 }
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 }
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 }