blob: 045798595a2bfc6285897120561b9ceb91f8ef7e [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
33class VelocityFlywheel(control_loop.ControlLoop):
34 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
124 # states
125 # [position, velocity, voltage_error]
126 self.C_unaugmented = self.C
127 self.C = numpy.matrix(numpy.zeros((1, 3)))
128 self.C[0:1, 0:2] = self.C_unaugmented
129
130 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
131 self.B_continuous, self.dt)
132
133 q_pos = self.params.q_pos
134 q_vel = self.params.q_vel
135 q_voltage = self.params.q_voltage
136 self.Q = numpy.matrix([[(q_pos**2.0), 0.0, 0.0],
137 [0.0, (q_vel**2.0), 0.0],
138 [0.0, 0.0, (q_voltage**2.0)]])
139
140 r_pos = self.params.r_pos
141 self.R = numpy.matrix([[(r_pos**2.0)]])
142
143 self.KalmanGain, self.Q_steady = controls.kalman(
144 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
145 self.L = self.A * self.KalmanGain
146
147 self.K_unaugmented = self.K
148 self.K = numpy.matrix(numpy.zeros((1, 3)))
149 self.K[0, 0:2] = self.K_unaugmented
150 self.K[0, 2] = 1
151 self.Kff_unaugmented = self.Kff
152 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
153 self.Kff[0, 0:2] = self.Kff_unaugmented
154
155 self.InitializeState()
156
157
158def PlotSpinup(params, goal, iterations=200):
159 """Runs the flywheel plant with an initial condition and goal.
160
161 Args:
162 flywheel: Flywheel object to use.
163 goal: goal state.
164 iterations: Number of timesteps to run the model for.
165 controller_flywheel: Flywheel object to get K from, or None if we should
166 use flywheel.
167 observer_flywheel: Flywheel object to use for the observer, or None if we
168 should use the actual state.
169 """
170
171 # Various lists for graphing things.
172 t = []
173 x = []
174 v = []
175 a = []
176 x_hat = []
177 u = []
178 offset = []
179
180 flywheel = Flywheel(params, params.name)
181 controller_flywheel = IntegralFlywheel(params, params.name)
182 observer_flywheel = IntegralFlywheel(params, params.name)
183 vbat = 12.0
184
185 if t:
186 initial_t = t[-1] + flywheel.dt
187 else:
188 initial_t = 0
189
190 for i in xrange(iterations):
191 X_hat = flywheel.X
192
193 if observer_flywheel is not None:
194 X_hat = observer_flywheel.X_hat
195 x_hat.append(observer_flywheel.X_hat[1, 0])
196
197 ff_U = controller_flywheel.Kff * (goal - observer_flywheel.A * goal)
198
199 U = controller_flywheel.K * (goal - X_hat) + ff_U
200 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
201 x.append(flywheel.X[0, 0])
202
203 if v:
204 last_v = v[-1]
205 else:
206 last_v = 0
207
208 v.append(flywheel.X[1, 0])
209 a.append((v[-1] - last_v) / flywheel.dt)
210
211 if observer_flywheel is not None:
212 observer_flywheel.Y = flywheel.Y
213 observer_flywheel.CorrectObserver(U)
214 offset.append(observer_flywheel.X_hat[2, 0])
215
216 applied_U = U.copy()
Austin Schuh989a3132020-02-20 18:20:06 -0800217 if i > 100:
Sabina Davisedf89472020-02-17 15:27:37 -0800218 applied_U += 2
219 flywheel.Update(applied_U)
220
221 if observer_flywheel is not None:
222 observer_flywheel.PredictObserver(U)
223
224 t.append(initial_t + i * flywheel.dt)
225 u.append(U[0, 0])
226
227 glog.debug('Time: %f', t[-1])
228 pylab.subplot(3, 1, 1)
229 pylab.plot(t, v, label='x')
230 pylab.plot(t, x_hat, label='x_hat')
231 pylab.legend()
232
233 pylab.subplot(3, 1, 2)
234 pylab.plot(t, u, label='u')
235 pylab.plot(t, offset, label='voltage_offset')
236 pylab.legend()
237
238 pylab.subplot(3, 1, 3)
239 pylab.plot(t, a, label='a')
240 pylab.legend()
241
242 pylab.show()
243
244
245def WriteFlywheel(params, plant_files, controller_files, namespace):
246 """Writes out the constants for a flywheel to a file.
247
248 Args:
249 params: list of Flywheel Params, the
250 parameters defining the system.
251 plant_files: list of strings, the cc and h files for the plant.
252 controller_files: list of strings, the cc and h files for the integral
253 controller.
254 namespaces: list of strings, the namespace list to use.
255 """
256 # Write the generated constants out to a file.
257 flywheels = []
258 integral_flywheels = []
259
260 if type(params) is list:
261 name = params[0].name
262 for index, param in enumerate(params):
263 flywheels.append(Flywheel(param, name=param.name + str(index)))
264 integral_flywheels.append(
265 IntegralFlywheel(
266 param, name='Integral' + param.name + str(index)))
267 else:
268 name = params.name
269 flywheels.append(Flywheel(params, params.name))
270 integral_flywheels.append(
271 IntegralFlywheel(params, name='Integral' + params.name))
272
273 loop_writer = control_loop.ControlLoopWriter(
274 name, flywheels, namespaces=namespace)
275 loop_writer.AddConstant(
276 control_loop.Constant('kOutputRatio' + params.name, '%f',
277 flywheels[0].G))
278 loop_writer.AddConstant(
279 control_loop.Constant('kFreeSpeed' + params.name, '%f',
280 flywheels[0].motor.free_speed))
281 loop_writer.Write(plant_files[0], plant_files[1])
282
283 integral_loop_writer = control_loop.ControlLoopWriter(
284 'Integral' + name, integral_flywheels, namespaces=namespace)
285 integral_loop_writer.Write(controller_files[0], controller_files[1])