blob: caaee96cbc94e60494fdef92a354b6c194a20d01 [file] [log] [blame]
Sabina Davisedf89472020-02-17 15:27:37 -08001from frc971.control_loops.python import control_loop
2from frc971.control_loops.python import controls
3import numpy
4from matplotlib import pylab
5
6import glog
7
8
9class FlywheelParams(object):
10 def __init__(self,
11 name,
12 motor,
13 G,
14 J,
15 q_pos,
16 q_vel,
17 q_voltage,
18 r_pos,
19 controller_poles,
20 dt=0.00505):
21 self.name = name
22 self.motor = motor
23 self.G = G
24 self.J = J
25 self.q_pos = q_pos
26 self.q_vel = q_vel
27 self.q_voltage = q_voltage
28 self.r_pos = r_pos
29 self.dt = dt
30 self.controller_poles = controller_poles
31
32
Austin Schuh43b9ae92020-02-29 23:08:38 -080033class VelocityFlywheel(control_loop.HybridControlLoop):
Sabina Davisedf89472020-02-17 15:27:37 -080034 def __init__(self, params, name="Flywheel"):
35 super(VelocityFlywheel, self).__init__(name=name)
36 self.params = params
37 # Set Motor
38 self.motor = self.params.motor
39 # Moment of inertia of the flywheel wheel in kg m^2
40 self.J = self.params.J
41 # Gear ratio
42 self.G = self.params.G
43 # Control loop time step
44 self.dt = self.params.dt
45
46 # State feedback matrices
47 # [angular velocity]
48 self.A_continuous = numpy.matrix([[
49 -self.motor.Kt / self.motor.Kv /
50 (self.J * self.G * self.G * self.motor.resistance)
51 ]])
52 self.B_continuous = numpy.matrix(
53 [[self.motor.Kt / (self.J * self.G * self.motor.resistance)]])
54 self.C = numpy.matrix([[1]])
55 self.D = numpy.matrix([[0]])
56
57 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
58 self.B_continuous, self.dt)
59
60 self.PlaceControllerPoles(self.params.controller_poles)
61
62 # Generated controller not used.
63 self.PlaceObserverPoles([0.3])
64
65 self.U_max = numpy.matrix([[12.0]])
66 self.U_min = numpy.matrix([[-12.0]])
67
68 qff_vel = 8.0
69 self.Qff = numpy.matrix([[1.0 / (qff_vel**2.0)]])
70
71 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
72
73
74class Flywheel(VelocityFlywheel):
75 def __init__(self, params, name="Flywheel"):
76 super(Flywheel, self).__init__(params, name=name)
77
78 self.A_continuous_unaugmented = self.A_continuous
79 self.B_continuous_unaugmented = self.B_continuous
80
81 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
82 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
83 self.A_continuous[0, 1] = 1
84
85 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
86 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
87
88 # State feedback matrices
89 # [position, angular velocity]
90 self.C = numpy.matrix([[1, 0]])
91 self.D = numpy.matrix([[0]])
92
93 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
94 self.B_continuous, self.dt)
95
96 rpl = 0.45
97 ipl = 0.07
98 self.PlaceObserverPoles([rpl + 1j * ipl, rpl - 1j * ipl])
99
100 self.K_unaugmented = self.K
101 self.K = numpy.matrix(numpy.zeros((1, 2)))
102 self.K[0, 1:2] = self.K_unaugmented
103 self.Kff_unaugmented = self.Kff
104 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
105 self.Kff[0, 1:2] = self.Kff_unaugmented
106
107 self.InitializeState()
108
109
110class IntegralFlywheel(Flywheel):
111 def __init__(self, params, name="IntegralFlywheel"):
112 super(IntegralFlywheel, self).__init__(params, name=name)
113
114 self.A_continuous_unaugmented = self.A_continuous
115 self.B_continuous_unaugmented = self.B_continuous
116
117 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
118 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
119 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
120
121 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
122 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
123
Austin Schuh43b9ae92020-02-29 23:08:38 -0800124
Sabina Davisedf89472020-02-17 15:27:37 -0800125 # states
126 # [position, velocity, voltage_error]
127 self.C_unaugmented = self.C
128 self.C = numpy.matrix(numpy.zeros((1, 3)))
129 self.C[0:1, 0:2] = self.C_unaugmented
130
Austin Schuh43b9ae92020-02-29 23:08:38 -0800131 glog.debug('A_continuous %s' % str(self.A_continuous))
132 glog.debug('B_continuous %s' % str(self.B_continuous))
133 glog.debug('C %s' % str(self.C))
134
Sabina Davisedf89472020-02-17 15:27:37 -0800135 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
136 self.B_continuous, self.dt)
137
Austin Schuh43b9ae92020-02-29 23:08:38 -0800138 glog.debug('A %s' % str(self.A))
139 glog.debug('B %s' % str(self.B))
140
Sabina Davisedf89472020-02-17 15:27:37 -0800141 q_pos = self.params.q_pos
142 q_vel = self.params.q_vel
143 q_voltage = self.params.q_voltage
Austin Schuh43b9ae92020-02-29 23:08:38 -0800144 self.Q_continuous = numpy.matrix([[(q_pos**2.0), 0.0, 0.0],
Sabina Davisedf89472020-02-17 15:27:37 -0800145 [0.0, (q_vel**2.0), 0.0],
146 [0.0, 0.0, (q_voltage**2.0)]])
147
148 r_pos = self.params.r_pos
Austin Schuh43b9ae92020-02-29 23:08:38 -0800149 self.R_continuous = numpy.matrix([[(r_pos**2.0)]])
Sabina Davisedf89472020-02-17 15:27:37 -0800150
Austin Schuh43b9ae92020-02-29 23:08:38 -0800151 _, _, self.Q, self.R = controls.kalmd(
152 A_continuous=self.A_continuous,
153 B_continuous=self.B_continuous,
154 Q_continuous=self.Q_continuous,
155 R_continuous=self.R_continuous,
156 dt=self.dt)
157
158 glog.debug('Q_discrete %s' % (str(self.Q)))
159 glog.debug('R_discrete %s' % (str(self.R)))
160
161 self.KalmanGain, self.P_steady_state = controls.kalman(
Sabina Davisedf89472020-02-17 15:27:37 -0800162 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
163 self.L = self.A * self.KalmanGain
164
165 self.K_unaugmented = self.K
166 self.K = numpy.matrix(numpy.zeros((1, 3)))
167 self.K[0, 0:2] = self.K_unaugmented
168 self.K[0, 2] = 1
169 self.Kff_unaugmented = self.Kff
170 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
171 self.Kff[0, 0:2] = self.Kff_unaugmented
172
173 self.InitializeState()
174
175
Austin Schuh43b9ae92020-02-29 23:08:38 -0800176def PlotSpinup(params, goal, iterations=400):
Sabina Davisedf89472020-02-17 15:27:37 -0800177 """Runs the flywheel plant with an initial condition and goal.
178
179 Args:
180 flywheel: Flywheel object to use.
181 goal: goal state.
182 iterations: Number of timesteps to run the model for.
183 controller_flywheel: Flywheel object to get K from, or None if we should
184 use flywheel.
185 observer_flywheel: Flywheel object to use for the observer, or None if we
186 should use the actual state.
187 """
188
189 # Various lists for graphing things.
190 t = []
191 x = []
192 v = []
193 a = []
194 x_hat = []
195 u = []
196 offset = []
197
198 flywheel = Flywheel(params, params.name)
199 controller_flywheel = IntegralFlywheel(params, params.name)
200 observer_flywheel = IntegralFlywheel(params, params.name)
201 vbat = 12.0
202
203 if t:
204 initial_t = t[-1] + flywheel.dt
205 else:
206 initial_t = 0
207
Austin Schuh5ea48472021-02-02 20:46:41 -0800208 for i in range(iterations):
Sabina Davisedf89472020-02-17 15:27:37 -0800209 X_hat = flywheel.X
210
211 if observer_flywheel is not None:
212 X_hat = observer_flywheel.X_hat
213 x_hat.append(observer_flywheel.X_hat[1, 0])
214
215 ff_U = controller_flywheel.Kff * (goal - observer_flywheel.A * goal)
216
217 U = controller_flywheel.K * (goal - X_hat) + ff_U
218 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
219 x.append(flywheel.X[0, 0])
220
221 if v:
222 last_v = v[-1]
223 else:
224 last_v = 0
225
226 v.append(flywheel.X[1, 0])
227 a.append((v[-1] - last_v) / flywheel.dt)
228
229 if observer_flywheel is not None:
230 observer_flywheel.Y = flywheel.Y
Austin Schuh43b9ae92020-02-29 23:08:38 -0800231 observer_flywheel.CorrectHybridObserver(U)
Sabina Davisedf89472020-02-17 15:27:37 -0800232 offset.append(observer_flywheel.X_hat[2, 0])
233
234 applied_U = U.copy()
Austin Schuh43b9ae92020-02-29 23:08:38 -0800235 if i > 200:
Sabina Davisedf89472020-02-17 15:27:37 -0800236 applied_U += 2
237 flywheel.Update(applied_U)
238
239 if observer_flywheel is not None:
Austin Schuh43b9ae92020-02-29 23:08:38 -0800240 observer_flywheel.PredictHybridObserver(U, flywheel.dt)
Sabina Davisedf89472020-02-17 15:27:37 -0800241
242 t.append(initial_t + i * flywheel.dt)
243 u.append(U[0, 0])
244
Sabina Davisedf89472020-02-17 15:27:37 -0800245 pylab.subplot(3, 1, 1)
246 pylab.plot(t, v, label='x')
247 pylab.plot(t, x_hat, label='x_hat')
248 pylab.legend()
249
250 pylab.subplot(3, 1, 2)
251 pylab.plot(t, u, label='u')
252 pylab.plot(t, offset, label='voltage_offset')
253 pylab.legend()
254
255 pylab.subplot(3, 1, 3)
256 pylab.plot(t, a, label='a')
257 pylab.legend()
258
259 pylab.show()
260
261
262def WriteFlywheel(params, plant_files, controller_files, namespace):
263 """Writes out the constants for a flywheel to a file.
264
265 Args:
266 params: list of Flywheel Params, the
267 parameters defining the system.
268 plant_files: list of strings, the cc and h files for the plant.
269 controller_files: list of strings, the cc and h files for the integral
270 controller.
271 namespaces: list of strings, the namespace list to use.
272 """
273 # Write the generated constants out to a file.
274 flywheels = []
275 integral_flywheels = []
276
277 if type(params) is list:
278 name = params[0].name
279 for index, param in enumerate(params):
280 flywheels.append(Flywheel(param, name=param.name + str(index)))
281 integral_flywheels.append(
282 IntegralFlywheel(
283 param, name='Integral' + param.name + str(index)))
284 else:
285 name = params.name
286 flywheels.append(Flywheel(params, params.name))
287 integral_flywheels.append(
288 IntegralFlywheel(params, name='Integral' + params.name))
289
290 loop_writer = control_loop.ControlLoopWriter(
291 name, flywheels, namespaces=namespace)
292 loop_writer.AddConstant(
Austin Schuh9dcd5202020-02-20 20:06:04 -0800293 control_loop.Constant('kOutputRatio', '%f',
Sabina Davisedf89472020-02-17 15:27:37 -0800294 flywheels[0].G))
295 loop_writer.AddConstant(
Austin Schuh9dcd5202020-02-20 20:06:04 -0800296 control_loop.Constant('kFreeSpeed', '%f',
Sabina Davisedf89472020-02-17 15:27:37 -0800297 flywheels[0].motor.free_speed))
Austin Schuhe8ca06a2020-03-07 22:27:39 -0800298 loop_writer.AddConstant(
299 control_loop.Constant(
300 'kBemf',
301 '%f',
302 flywheels[0].motor.Kv * flywheels[0].G,
303 comment="// Radians/sec / volt"))
304 loop_writer.AddConstant(
305 control_loop.Constant(
306 'kResistance',
307 '%f',
308 flywheels[0].motor.resistance,
309 comment="// Ohms"))
Sabina Davisedf89472020-02-17 15:27:37 -0800310 loop_writer.Write(plant_files[0], plant_files[1])
311
312 integral_loop_writer = control_loop.ControlLoopWriter(
Austin Schuh43b9ae92020-02-29 23:08:38 -0800313 'Integral' + name,
314 integral_flywheels,
315 namespaces=namespace,
316 plant_type='StateFeedbackHybridPlant',
317 observer_type='HybridKalman')
Sabina Davisedf89472020-02-17 15:27:37 -0800318 integral_loop_writer.Write(controller_files[0], controller_files[1])