blob: 6f41a17e726451a70f01456abf2b40f804875ba1 [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
Austin Schuheb240f62021-11-07 19:57:06 -080073 glog.debug('K: %s', str(self.K))
74 glog.debug('Poles: %s',
75 str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
76
Sabina Davisedf89472020-02-17 15:27:37 -080077
78class Flywheel(VelocityFlywheel):
79 def __init__(self, params, name="Flywheel"):
80 super(Flywheel, self).__init__(params, name=name)
81
82 self.A_continuous_unaugmented = self.A_continuous
83 self.B_continuous_unaugmented = self.B_continuous
84
85 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
86 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
87 self.A_continuous[0, 1] = 1
88
89 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
90 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
91
92 # State feedback matrices
93 # [position, angular velocity]
94 self.C = numpy.matrix([[1, 0]])
95 self.D = numpy.matrix([[0]])
96
97 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
98 self.B_continuous, self.dt)
99
100 rpl = 0.45
101 ipl = 0.07
102 self.PlaceObserverPoles([rpl + 1j * ipl, rpl - 1j * ipl])
103
104 self.K_unaugmented = self.K
105 self.K = numpy.matrix(numpy.zeros((1, 2)))
106 self.K[0, 1:2] = self.K_unaugmented
107 self.Kff_unaugmented = self.Kff
108 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
109 self.Kff[0, 1:2] = self.Kff_unaugmented
110
111 self.InitializeState()
112
113
114class IntegralFlywheel(Flywheel):
115 def __init__(self, params, name="IntegralFlywheel"):
116 super(IntegralFlywheel, self).__init__(params, name=name)
117
118 self.A_continuous_unaugmented = self.A_continuous
119 self.B_continuous_unaugmented = self.B_continuous
120
121 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
122 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
123 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
124
125 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
126 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
127
Austin Schuh43b9ae92020-02-29 23:08:38 -0800128
Sabina Davisedf89472020-02-17 15:27:37 -0800129 # states
130 # [position, velocity, voltage_error]
131 self.C_unaugmented = self.C
132 self.C = numpy.matrix(numpy.zeros((1, 3)))
133 self.C[0:1, 0:2] = self.C_unaugmented
134
Austin Schuh43b9ae92020-02-29 23:08:38 -0800135 glog.debug('A_continuous %s' % str(self.A_continuous))
136 glog.debug('B_continuous %s' % str(self.B_continuous))
137 glog.debug('C %s' % str(self.C))
138
Sabina Davisedf89472020-02-17 15:27:37 -0800139 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
140 self.B_continuous, self.dt)
141
Austin Schuh43b9ae92020-02-29 23:08:38 -0800142 glog.debug('A %s' % str(self.A))
143 glog.debug('B %s' % str(self.B))
144
Sabina Davisedf89472020-02-17 15:27:37 -0800145 q_pos = self.params.q_pos
146 q_vel = self.params.q_vel
147 q_voltage = self.params.q_voltage
Austin Schuh43b9ae92020-02-29 23:08:38 -0800148 self.Q_continuous = numpy.matrix([[(q_pos**2.0), 0.0, 0.0],
Sabina Davisedf89472020-02-17 15:27:37 -0800149 [0.0, (q_vel**2.0), 0.0],
150 [0.0, 0.0, (q_voltage**2.0)]])
151
152 r_pos = self.params.r_pos
Austin Schuh43b9ae92020-02-29 23:08:38 -0800153 self.R_continuous = numpy.matrix([[(r_pos**2.0)]])
Sabina Davisedf89472020-02-17 15:27:37 -0800154
Austin Schuh43b9ae92020-02-29 23:08:38 -0800155 _, _, self.Q, self.R = controls.kalmd(
156 A_continuous=self.A_continuous,
157 B_continuous=self.B_continuous,
158 Q_continuous=self.Q_continuous,
159 R_continuous=self.R_continuous,
160 dt=self.dt)
161
162 glog.debug('Q_discrete %s' % (str(self.Q)))
163 glog.debug('R_discrete %s' % (str(self.R)))
164
165 self.KalmanGain, self.P_steady_state = controls.kalman(
Sabina Davisedf89472020-02-17 15:27:37 -0800166 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
167 self.L = self.A * self.KalmanGain
168
169 self.K_unaugmented = self.K
170 self.K = numpy.matrix(numpy.zeros((1, 3)))
171 self.K[0, 0:2] = self.K_unaugmented
172 self.K[0, 2] = 1
173 self.Kff_unaugmented = self.Kff
174 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
175 self.Kff[0, 0:2] = self.Kff_unaugmented
176
177 self.InitializeState()
178
179
Austin Schuh43b9ae92020-02-29 23:08:38 -0800180def PlotSpinup(params, goal, iterations=400):
Sabina Davisedf89472020-02-17 15:27:37 -0800181 """Runs the flywheel plant with an initial condition and goal.
182
183 Args:
184 flywheel: Flywheel object to use.
185 goal: goal state.
186 iterations: Number of timesteps to run the model for.
187 controller_flywheel: Flywheel object to get K from, or None if we should
188 use flywheel.
189 observer_flywheel: Flywheel object to use for the observer, or None if we
190 should use the actual state.
191 """
192
193 # Various lists for graphing things.
194 t = []
195 x = []
196 v = []
197 a = []
198 x_hat = []
199 u = []
200 offset = []
201
202 flywheel = Flywheel(params, params.name)
203 controller_flywheel = IntegralFlywheel(params, params.name)
204 observer_flywheel = IntegralFlywheel(params, params.name)
205 vbat = 12.0
206
207 if t:
208 initial_t = t[-1] + flywheel.dt
209 else:
210 initial_t = 0
211
Austin Schuh5ea48472021-02-02 20:46:41 -0800212 for i in range(iterations):
Sabina Davisedf89472020-02-17 15:27:37 -0800213 X_hat = flywheel.X
214
215 if observer_flywheel is not None:
216 X_hat = observer_flywheel.X_hat
217 x_hat.append(observer_flywheel.X_hat[1, 0])
218
219 ff_U = controller_flywheel.Kff * (goal - observer_flywheel.A * goal)
220
221 U = controller_flywheel.K * (goal - X_hat) + ff_U
222 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
223 x.append(flywheel.X[0, 0])
224
225 if v:
226 last_v = v[-1]
227 else:
228 last_v = 0
229
230 v.append(flywheel.X[1, 0])
231 a.append((v[-1] - last_v) / flywheel.dt)
232
233 if observer_flywheel is not None:
234 observer_flywheel.Y = flywheel.Y
Austin Schuh43b9ae92020-02-29 23:08:38 -0800235 observer_flywheel.CorrectHybridObserver(U)
Sabina Davisedf89472020-02-17 15:27:37 -0800236 offset.append(observer_flywheel.X_hat[2, 0])
237
238 applied_U = U.copy()
Austin Schuh43b9ae92020-02-29 23:08:38 -0800239 if i > 200:
Sabina Davisedf89472020-02-17 15:27:37 -0800240 applied_U += 2
241 flywheel.Update(applied_U)
242
243 if observer_flywheel is not None:
Austin Schuh43b9ae92020-02-29 23:08:38 -0800244 observer_flywheel.PredictHybridObserver(U, flywheel.dt)
Sabina Davisedf89472020-02-17 15:27:37 -0800245
246 t.append(initial_t + i * flywheel.dt)
247 u.append(U[0, 0])
248
Sabina Davisedf89472020-02-17 15:27:37 -0800249 pylab.subplot(3, 1, 1)
250 pylab.plot(t, v, label='x')
251 pylab.plot(t, x_hat, label='x_hat')
252 pylab.legend()
253
254 pylab.subplot(3, 1, 2)
255 pylab.plot(t, u, label='u')
256 pylab.plot(t, offset, label='voltage_offset')
257 pylab.legend()
258
259 pylab.subplot(3, 1, 3)
260 pylab.plot(t, a, label='a')
261 pylab.legend()
262
263 pylab.show()
264
265
266def WriteFlywheel(params, plant_files, controller_files, namespace):
267 """Writes out the constants for a flywheel to a file.
268
269 Args:
270 params: list of Flywheel Params, the
271 parameters defining the system.
272 plant_files: list of strings, the cc and h files for the plant.
273 controller_files: list of strings, the cc and h files for the integral
274 controller.
275 namespaces: list of strings, the namespace list to use.
276 """
277 # Write the generated constants out to a file.
278 flywheels = []
279 integral_flywheels = []
280
281 if type(params) is list:
282 name = params[0].name
283 for index, param in enumerate(params):
284 flywheels.append(Flywheel(param, name=param.name + str(index)))
285 integral_flywheels.append(
286 IntegralFlywheel(
287 param, name='Integral' + param.name + str(index)))
288 else:
289 name = params.name
290 flywheels.append(Flywheel(params, params.name))
291 integral_flywheels.append(
292 IntegralFlywheel(params, name='Integral' + params.name))
293
294 loop_writer = control_loop.ControlLoopWriter(
295 name, flywheels, namespaces=namespace)
296 loop_writer.AddConstant(
Austin Schuh9dcd5202020-02-20 20:06:04 -0800297 control_loop.Constant('kOutputRatio', '%f',
Sabina Davisedf89472020-02-17 15:27:37 -0800298 flywheels[0].G))
299 loop_writer.AddConstant(
Austin Schuh9dcd5202020-02-20 20:06:04 -0800300 control_loop.Constant('kFreeSpeed', '%f',
Sabina Davisedf89472020-02-17 15:27:37 -0800301 flywheels[0].motor.free_speed))
Austin Schuhe8ca06a2020-03-07 22:27:39 -0800302 loop_writer.AddConstant(
303 control_loop.Constant(
304 'kBemf',
305 '%f',
306 flywheels[0].motor.Kv * flywheels[0].G,
307 comment="// Radians/sec / volt"))
308 loop_writer.AddConstant(
309 control_loop.Constant(
310 'kResistance',
311 '%f',
312 flywheels[0].motor.resistance,
313 comment="// Ohms"))
Sabina Davisedf89472020-02-17 15:27:37 -0800314 loop_writer.Write(plant_files[0], plant_files[1])
315
316 integral_loop_writer = control_loop.ControlLoopWriter(
Austin Schuh43b9ae92020-02-29 23:08:38 -0800317 'Integral' + name,
318 integral_flywheels,
319 namespaces=namespace,
320 plant_type='StateFeedbackHybridPlant',
321 observer_type='HybridKalman')
Sabina Davisedf89472020-02-17 15:27:37 -0800322 integral_loop_writer.Write(controller_files[0], controller_files[1])