|
42 | 42 | "source": [
|
43 | 43 | "## System dynamics\n",
|
44 | 44 | "\n",
|
45 |
| - "Consider a simple mechanism for positioning a mechanical arm whose equations of motion are given by\n", |
| 45 | + "Consider a simple mechanism consisting of a spring loaded arm that is driven by a motor, as shown below:\n", |
| 46 | + "\n", |
| 47 | + "<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/servomech-diagram.png\" width=200 alt=\"servomech-diagram\"></center>\n", |
| 48 | + "\n", |
| 49 | + "The motor applies a torque that twists the arm against a linear spring and moves the end of the arm across a rotating platter. The input to the system is the motor torque $\\tau_\\text{m}$. The force exerted by the spring is a nonlinear function of the head position due to the way it is attached.\n", |
| 50 | + "\n", |
| 51 | + "The equations of motion for the system are given by\n", |
46 | 52 | "\n",
|
47 | 53 | "$$\n",
|
48 | 54 | "J \\ddot \\theta = -b \\dot\\theta - k r\\sin\\theta + \\tau_\\text{m},\n",
|
|
56 | 62 | " + \\begin{bmatrix} 0 \\\\ 1/J \\end{bmatrix} \\tau_\\text{m}.\n",
|
57 | 63 | "$$\n",
|
58 | 64 | "\n",
|
59 |
| - "The system consists of a spring loaded arm that is driven by a motor, as shown below.\n", |
60 |
| - "\n", |
61 |
| - "<center><img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/servomech-diagram.png\" alt=\"servomech-diagram\" height=\"240\"></center>\n", |
62 |
| - "\n", |
63 |
| - "The motor applies a torque that twists the arm against a linear spring and moves the end of the arm across a rotating platter. The input to the system is the motor torque $\\tau_\\text{m}$. The force exerted by the spring is a nonlinear function of the head position due to the way it is attached.\n", |
64 |
| - "\n", |
65 | 65 | "The system parameters are given by\n",
|
66 | 66 | "\n",
|
67 | 67 | "$$\n",
|
68 | 68 | "k = 1,\\quad J = 100,\\quad b = 10,\n",
|
69 | 69 | "\\quad r = 1,\\quad l = 2,\\quad \\epsilon = 0.01.\n",
|
70 | 70 | "$$\n",
|
71 | 71 | "\n",
|
72 |
| - "and we assume that time is measured in msec and distance in cm. (The constants here are made up and don't necessarily reflect a real disk drive, though the units and time constants are motivated by computer disk drives.)" |
| 72 | + "and we assume that time is measured in milliseconds (ms) and distance in centimeters (cm). (The constants here are made up and don't necessarily reflect a real disk drive, though the units and time constants are motivated by computer disk drives.)" |
73 | 73 | ]
|
74 | 74 | },
|
75 | 75 | {
|
|
170 | 170 | "source": [
|
171 | 171 | "### Linearization\n",
|
172 | 172 | "\n",
|
173 |
| - "To study the open loop dynamicsof the system, we compute the linearization of the dynamics about the equilibrium point corresponding to $\\theta_\\text{e} = 15^\\circ$." |
| 173 | + "To study the open loop dynamics of the system, we compute the linearization of the dynamics about the equilibrium point corresponding to $\\theta_\\text{e} = 15^\\circ$." |
174 | 174 | ]
|
175 | 175 | },
|
176 | 176 | {
|
|
195 | 195 | "ct.bode_plot(P)"
|
196 | 196 | ]
|
197 | 197 | },
|
198 |
| - { |
199 |
| - "cell_type": "code", |
200 |
| - "execution_count": null, |
201 |
| - "metadata": {}, |
202 |
| - "outputs": [], |
203 |
| - "source": [ |
204 |
| - "# Colab: create a simplified form to get rid of warning messages\n", |
205 |
| - "if len(P.num[0][0]) > 1:\n", |
206 |
| - " P = ct.tf(P.num[0][0][2], P.den, name='P') # Fix up conditioning\n", |
207 |
| - "print(P)\n", |
208 |
| - "ct.bode_plot(P)" |
209 |
| - ] |
210 |
| - }, |
211 | 198 | {
|
212 | 199 | "cell_type": "markdown",
|
213 | 200 | "metadata": {
|
|
216 | 203 | "source": [
|
217 | 204 | "## Ziegler-Nichols tuning\n",
|
218 | 205 | "\n",
|
| 206 | + "Ziegler-Nichols tuning provides a method for choosing the gains of a PID controller that give reasonable closed loop response. More information can be found in [Feedback Systems](https://fbswiki.org/wiki/index.php/Feedback_Systems:_An_Introduction_for_Scientists_and_Engineers) (FBS2e), Section 11.3.\n", |
| 207 | + "\n", |
| 208 | + "We show here the figures and tables that we will use (from FBS2e):\n", |
| 209 | + "\n", |
219 | 210 | "<center>\n",
|
220 | 211 | "<table>\n",
|
221 | 212 | "<tr>\n",
|
|
226 | 217 | "<img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/zn-step-table.png\" width=300>\n",
|
227 | 218 | "</td>\n",
|
228 | 219 | "</tr>\n",
|
229 |
| - "</center>" |
| 220 | + "</center>\n", |
| 221 | + "\n", |
| 222 | + "To use the Ziegler-Nichols turning rules, we plot the step response, compute the parameters (shown in the figure), and then apply the formulas in the table:" |
230 | 223 | ]
|
231 | 224 | },
|
232 | 225 | {
|
|
257 | 250 | "print(f\"{a=}, {tau=}\")"
|
258 | 251 | ]
|
259 | 252 | },
|
| 253 | + { |
| 254 | + "cell_type": "markdown", |
| 255 | + "metadata": {}, |
| 256 | + "source": [ |
| 257 | + "We can then construct a controller using the parameters:" |
| 258 | + ] |
| 259 | + }, |
260 | 260 | {
|
261 | 261 | "cell_type": "code",
|
262 | 262 | "execution_count": null,
|
|
325 | 325 | "id": "6iZwB2WEeg8S"
|
326 | 326 | },
|
327 | 327 | "source": [
|
328 |
| - "## Loop shaping" |
| 328 | + "## Loop shaping\n", |
| 329 | + "\n", |
| 330 | + "A better design can be obtained by looking at the loop transfer function and adjusting the controller parameters to give a loop shape that will give closed loop properties. We show the steps for such a design here:" |
329 | 331 | ]
|
330 | 332 | },
|
331 | 333 | {
|
|
390 | 392 | "ct.nyquist([ltf_shape])"
|
391 | 393 | ]
|
392 | 394 | },
|
| 395 | + { |
| 396 | + "cell_type": "markdown", |
| 397 | + "metadata": {}, |
| 398 | + "source": [ |
| 399 | + "We see that the loop shaping controller has better step response (faster rise and settling time, less overshoot)." |
| 400 | + ] |
| 401 | + }, |
393 | 402 | {
|
394 | 403 | "cell_type": "markdown",
|
395 | 404 | "metadata": {
|
396 | 405 | "id": "GyXQXykafzWs"
|
397 | 406 | },
|
398 | 407 | "source": [
|
399 |
| - "### Gang of Four" |
| 408 | + "### Gang of Four\n", |
| 409 | + "\n", |
| 410 | + "When designing a controller, it is important to look at all of the input/output responses, not just the response from reference to output (which is what the step response above focuses on). \n", |
| 411 | + "\n", |
| 412 | + "In the frequency domain, the Gang of 4 plots provide useful information on all (important) input/output pairs:" |
400 | 413 | ]
|
401 | 414 | },
|
402 | 415 | {
|
|
408 | 421 | "ct.gangof4(P, ctrl_shape)"
|
409 | 422 | ]
|
410 | 423 | },
|
| 424 | + { |
| 425 | + "cell_type": "markdown", |
| 426 | + "metadata": {}, |
| 427 | + "source": [ |
| 428 | + "These all look pretty resonable, except that the transfer function from the reference $r$ to the system input $u$ is getting large at high frequency. This occurs because we did not filter the derivative on the PID controller, so high frequency components of the reference signal (or the measurement noise!) get amplified. We will fix this in the more advanced controller below." |
| 429 | + ] |
| 430 | + }, |
411 | 431 | {
|
412 | 432 | "cell_type": "markdown",
|
413 | 433 | "metadata": {
|
414 | 434 | "id": "uFO3wiWXhBAK"
|
415 | 435 | },
|
416 | 436 | "source": [
|
417 |
| - "## Anti-windup\n", |
| 437 | + "## Anti-windup + derivative filtering\n", |
| 438 | + "\n", |
| 439 | + "In addition to the amplification of high frequency signals due to the derivative term, another practical consideration in the use of PID controllers is integrator windup. Integrator windup occurs when there are limits on the control inputs so that the error signal may not descrease quickly. This causes the integral term in the PID controller to see an error for a long period of time, and the resulting integration of the error must be offset by making the error have opposite sign for some period of time. This is often undesireable.\n", |
418 | 440 | "\n",
|
419 |
| - "We now implement the full PID controller with anti-windup and derivative filtering:\n", |
| 441 | + "To see how to address both amplification of noise due to the derivative term and integrator windup effects in the presence of input constraints, we now implement PID controller with anti-windup and derivative filtering, as shown in the following figure (see also Figure 11.11 in [FBS2e](https://fbswiki.org/wiki/index.php/Feedback_Systems:_An_Introduction_for_Scientists_and_Engineers)):\n", |
420 | 442 | "\n",
|
421 | 443 | "<center>\n",
|
422 | 444 | "<img src=\"https://www.cds.caltech.edu/~murray/courses/cds110/sp2024/pid-aw-diagram.png\"</img>\n",
|
|
529 | 551 | " clsys, timepts, stepsize, params={'umax': 5, 'kaw': 2*ki, 'a': 100})\n",
|
530 | 552 | "\n",
|
531 | 553 | "# Plot the time responses in a single plot\n",
|
532 |
| - "out = ct.time_response_plot(resp_ln, color='b', plot_inputs=False)\n", |
533 |
| - "ct.time_response_plot(resp_cl, color='r', plot_inputs=False)\n", |
534 |
| - "ct.time_response_plot(resp_aw, color='g', plot_inputs=False)\n", |
535 |
| - "\n", |
536 |
| - "# Annotations\n", |
537 |
| - "axs = ct.get_plot_axes(out)\n", |
538 |
| - "axs[0, 0].legend(['linear', 'clipped', 'anti-windup'])\n", |
539 |
| - "axs[0, 0].plot([0, timepts[-1]], [stepsize, stepsize], 'k--')\n", |
540 |
| - "axs[1, 0].plot([0, timepts[-1]], [0, 0], 'k--')\n", |
541 |
| - "axs[1, 0].set_ylim([-5, 15])\n", |
542 |
| - "axs[1, 0].legend([]);" |
| 554 | + "ct.time_response_plot(resp_ln, color='b', plot_inputs=False, label=\"linear\")\n", |
| 555 | + "ct.time_response_plot(resp_cl, color='r', plot_inputs=False, label=\"clipped\")\n", |
| 556 | + "ct.time_response_plot(resp_aw, color='g', plot_inputs=False, label=\"anti-windup\");" |
543 | 557 | ]
|
544 | 558 | },
|
545 | 559 | {
|
|
558 | 572 | "outputs": [],
|
559 | 573 | "source": [
|
560 | 574 | "resp_aw = ct.input_output_response(\n",
|
561 |
| - "# clsys, timepts, stepsize, params={'umax': 5, 'kaw': 1, 'a': 100})\n", |
562 | 575 | " clsys, timepts, stepsize, params={'umax': 5, 'kaw': 0.05 * ki, 'a': 100})\n",
|
563 | 576 | "\n",
|
564 | 577 | "# Plot the time responses in a single plot\n",
|
565 |
| - "out = ct.time_response_plot(resp_ln, color='b', plot_inputs=False)\n", |
566 |
| - "ct.time_response_plot(resp_cl, color='r', plot_inputs=False)\n", |
567 |
| - "ct.time_response_plot(resp_aw, color='g', plot_inputs=False)\n", |
568 |
| - "\n", |
569 |
| - "# Annotations\n", |
570 |
| - "axs = ct.get_plot_axes(out)\n", |
571 |
| - "axs[0, 0].legend(['linear', 'clipped', 'anti-windup'])\n", |
572 |
| - "axs[0, 0].plot([0, timepts[-1]], [stepsize, stepsize], 'k--')\n", |
573 |
| - "axs[1, 0].plot([0, timepts[-1]], [0, 0], 'k--')\n", |
574 |
| - "axs[1, 0].set_ylim([-5, 15])\n", |
575 |
| - "axs[1, 0].legend([]);" |
| 578 | + "ct.time_response_plot(resp_ln, color='b', plot_inputs=False, label=\"linear\")\n", |
| 579 | + "ct.time_response_plot(resp_cl, color='r', plot_inputs=False, label=\"clipped\")\n", |
| 580 | + "ct.time_response_plot(resp_aw, color='g', plot_inputs=False, label=\"anti-windup\");" |
576 | 581 | ]
|
577 | 582 | },
|
578 | 583 | {
|
|
0 commit comments