blob: f4a76a05a2259d64d37bb8473ee6e3a15d4530d7 [file] [log] [blame]
Austin Schuh1a387962015-01-31 16:36:20 -08001#!/usr/bin/python
2
3import control_loop
4import controls
5import polytope
6import polydrivetrain
7import numpy
Austin Schuhdbd6bfa2015-02-14 21:25:16 -08008import math
Austin Schuh1a387962015-01-31 16:36:20 -08009import sys
10import matplotlib
11from matplotlib import pylab
12
13class Arm(control_loop.ControlLoop):
14 def __init__(self, name="Arm", mass=None):
15 super(Arm, self).__init__(name)
16 # Stall Torque in N m
17 self.stall_torque = 0.476
18 # Stall Current in Amps
19 self.stall_current = 80.730
20 # Free Speed in RPM
21 self.free_speed = 13906.0
22 # Free Current in Amps
23 self.free_current = 5.820
24 # Mass of the arm
25 if mass is None:
26 self.mass = 13.0
27 else:
28 self.mass = mass
29
30 # Resistance of the motor
31 self.R = 12.0 / self.stall_current
32 # Motor velocity constant
33 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
34 (12.0 - self.R * self.free_current))
35 # Torque constant
36 self.Kt = self.stall_torque / self.stall_current
37 # Gear ratio
38 self.G = (44.0 / 12.0) * (54.0 / 14.0) * (54.0 / 14.0) * (44.0 / 20.0) * (72.0 / 16.0)
39 # Fridge arm length
40 self.r = 32 * 0.0254
41 # Control loop time step
42 self.dt = 0.005
43
44 # Arm moment of inertia
45 self.J = self.r * self.mass
46
47 # Arm left/right spring constant (N*m / radian)
Austin Schuhbfb8b242015-02-16 15:45:22 -080048 self.spring = 400.0
Austin Schuh1a387962015-01-31 16:36:20 -080049
50 # State is [average position, average velocity,
51 # position difference/2, velocity difference/2]
52 # Position difference is 1 - 2
53 # Input is [Voltage 1, Voltage 2]
54
55 C1 = self.spring / (self.J * 0.5)
56 C2 = self.Kt * self.G / (self.J * 0.5 * self.R)
57 C3 = self.G * self.G * self.Kt / (self.R * self.J * 0.5 * self.Kv)
58
59 self.A_continuous = numpy.matrix(
60 [[0, 1, 0, 0],
61 [0, -C3, 0, 0],
62 [0, 0, 0, 1],
63 [0, 0, -C1 * 2.0, -C3]])
64
65 print 'Full speed is', C2 / C3 * 12.0
66
Austin Schuhdbd6bfa2015-02-14 21:25:16 -080067 print 'Stall arm difference is', 12.0 * C2 / C1
68 print 'Stall arm difference first principles is', self.stall_torque * self.G / self.spring
69
70 print '5 degrees of arm error is', self.spring / self.r * (math.pi * 5.0 / 180.0)
71
Austin Schuh1a387962015-01-31 16:36:20 -080072 # Start with the unmodified input
73 self.B_continuous = numpy.matrix(
74 [[0, 0],
75 [C2 / 2.0, C2 / 2.0],
76 [0, 0],
77 [C2 / 2.0, -C2 / 2.0]])
78
79 self.C = numpy.matrix([[1, 0, 1, 0],
80 [1, 0, -1, 0]])
81 self.D = numpy.matrix([[0, 0],
82 [0, 0]])
83
84 self.A, self.B = self.ContinuousToDiscrete(
85 self.A_continuous, self.B_continuous, self.dt)
86
87 controlability = controls.ctrb(self.A, self.B);
88 print 'Rank of augmented controlability matrix.', numpy.linalg.matrix_rank(
89 controlability)
90
91 q_pos = 0.02
92 q_vel = 0.300
Austin Schuhbfb8b242015-02-16 15:45:22 -080093 q_pos_diff = 0.005
94 q_vel_diff = 0.13
Austin Schuh1a387962015-01-31 16:36:20 -080095 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0, 0.0, 0.0],
96 [0.0, (1.0 / (q_vel ** 2.0)), 0.0, 0.0],
97 [0.0, 0.0, (1.0 / (q_pos_diff ** 2.0)), 0.0],
98 [0.0, 0.0, 0.0, (1.0 / (q_vel_diff ** 2.0))]])
99
100 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0)), 0.0],
101 [0.0, 1.0 / (12.0 ** 2.0)]])
102 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
103 print 'Controller'
104 print self.K
105
106 print 'Controller Poles'
107 print numpy.linalg.eig(self.A - self.B * self.K)[0]
108
109 self.rpl = 0.20
110 self.ipl = 0.05
111 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
112 self.rpl + 1j * self.ipl,
113 self.rpl - 1j * self.ipl,
114 self.rpl - 1j * self.ipl])
115
116 # The box formed by U_min and U_max must encompass all possible values,
117 # or else Austin's code gets angry.
118 self.U_max = numpy.matrix([[12.0], [12.0]])
119 self.U_min = numpy.matrix([[-12.0], [-12.0]])
120
121 self.InitializeState()
122
123
124def CapU(U):
125 if U[0, 0] - U[1, 0] > 24:
126 return numpy.matrix([[12], [-12]])
127 elif U[0, 0] - U[1, 0] < -24:
128 return numpy.matrix([[-12], [12]])
129 else:
130 max_u = max(U[0, 0], U[1, 0])
131 min_u = min(U[0, 0], U[1, 0])
132 if max_u > 12:
133 return U - (max_u - 12)
134 if min_u < -12:
135 return U - (min_u + 12)
136 return U
137
138
139def run_test(arm, initial_X, goal, max_separation_error=0.01,
140 show_graph=True, iterations=200, controller_arm=None,
141 observer_arm=None):
142 """Runs the arm plant with an initial condition and goal.
143
144 The tests themselves are not terribly sophisticated; I just test for
145 whether the goal has been reached and whether the separation goes
146 outside of the initial and goal values by more than max_separation_error.
147 Prints out something for a failure of either condition and returns
148 False if tests fail.
149 Args:
150 arm: arm object to use.
151 initial_X: starting state.
152 goal: goal state.
153 show_graph: Whether or not to display a graph showing the changing
154 states and voltages.
155 iterations: Number of timesteps to run the model for.
156 controller_arm: arm object to get K from, or None if we should
157 use arm.
158 observer_arm: arm object to use for the observer, or None if we should
159 use the actual state.
160 """
161
162 arm.X = initial_X
163
164 if controller_arm is None:
165 controller_arm = arm
166
167 if observer_arm is not None:
168 observer_arm.X_hat = initial_X + 0.01
169 observer_arm.X_hat = initial_X
170
171 # Various lists for graphing things.
172 t = []
173 x_avg = []
174 x_sep = []
175 x_hat_avg = []
176 x_hat_sep = []
177 v_avg = []
178 v_sep = []
179 u_left = []
180 u_right = []
181
182 sep_plot_gain = 100.0
183
184 for i in xrange(iterations):
185 X_hat = arm.X
186 if observer_arm is not None:
187 X_hat = observer_arm.X_hat
188 x_hat_avg.append(observer_arm.X_hat[0, 0])
189 x_hat_sep.append(observer_arm.X_hat[2, 0] * sep_plot_gain)
190 U = controller_arm.K * (goal - X_hat)
191 U = CapU(U)
192 x_avg.append(arm.X[0, 0])
193 v_avg.append(arm.X[1, 0])
194 x_sep.append(arm.X[2, 0] * sep_plot_gain)
195 v_sep.append(arm.X[3, 0])
196 if observer_arm is not None:
197 observer_arm.PredictObserver(U)
198 arm.Update(U)
199 if observer_arm is not None:
200 observer_arm.Y = arm.Y
201 observer_arm.CorrectObserver(U)
202
203 t.append(i * arm.dt)
204 u_left.append(U[0, 0])
205 u_right.append(U[1, 0])
206
207 print numpy.linalg.inv(arm.A)
208 print "delta time is ", arm.dt
209 print "Velocity at t=0 is ", x_avg[0], v_avg[0], x_sep[0], v_sep[0]
210 print "Velocity at t=1+dt is ", x_avg[1], v_avg[1], x_sep[1], v_sep[1]
211
212 if show_graph:
213 pylab.subplot(2, 1, 1)
214 pylab.plot(t, x_avg, label='x avg')
215 pylab.plot(t, x_sep, label='x sep')
216 if observer_arm is not None:
217 pylab.plot(t, x_hat_avg, label='x_hat avg')
218 pylab.plot(t, x_hat_sep, label='x_hat sep')
219 pylab.legend()
220
221 pylab.subplot(2, 1, 2)
222 pylab.plot(t, u_left, label='u left')
223 pylab.plot(t, u_right, label='u right')
224 pylab.legend()
225 pylab.show()
226
227
228def main(argv):
229 loaded_mass = 25
230 #loaded_mass = 0
231 arm = Arm(mass=13 + loaded_mass)
232 arm_controller = Arm(mass=13 + 15)
233 observer_arm = Arm(mass=13 + 15)
234 #observer_arm = None
235
236 # Test moving the arm with constant separation.
237 initial_X = numpy.matrix([[0.0], [0.0], [0.01], [0.0]])
238 #initial_X = numpy.matrix([[0.0], [0.0], [0.00], [0.0]])
239 R = numpy.matrix([[1.0], [0.0], [0.0], [0.0]])
240 run_test(arm, initial_X, R, controller_arm=arm_controller,
241 observer_arm=observer_arm)
242
243 # Write the generated constants out to a file.
244 if len(argv) != 3:
245 print "Expected .h file name and .cc file name for the arm."
246 else:
Austin Schuhbfb8b242015-02-16 15:45:22 -0800247 arm = Arm("Arm", 2)
Austin Schuh1a387962015-01-31 16:36:20 -0800248 loop_writer = control_loop.ControlLoopWriter("Arm", [arm])
249 if argv[1][-3:] == '.cc':
250 loop_writer.Write(argv[2], argv[1])
251 else:
252 loop_writer.Write(argv[1], argv[2])
253
254if __name__ == '__main__':
255 sys.exit(main(sys.argv))