blob: 80a4a535c842f28684e35e1950fd8260728916f8 [file] [log] [blame]
Austin Schuh085eab92020-11-26 13:54:51 -08001#!/usr/bin/python3
Campbell Crowley33e0e3d2017-12-27 17:55:40 -08002
3from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
5import numpy
6import sys
7from matplotlib import pylab
8import glog
9
Tyler Chatow6738c362019-02-16 14:12:30 -080010
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080011class DrivetrainParams(object):
Ravago Jones5127ccc2022-07-31 16:32:45 -070012
Diana Burgessd0180f12018-03-21 21:24:17 -070013 def __init__(self,
14 J,
15 mass,
16 robot_radius,
17 wheel_radius,
Austin Schuhecc92a02019-01-20 17:42:19 -080018 G=None,
19 G_high=None,
20 G_low=None,
21 q_pos=None,
22 q_pos_low=None,
23 q_pos_high=None,
24 q_vel=None,
25 q_vel_low=None,
26 q_vel_high=None,
Diana Burgessd0180f12018-03-21 21:24:17 -070027 efficiency=0.60,
28 has_imu=False,
29 force=False,
30 kf_q_voltage=10.0,
31 motor_type=control_loop.CIM(),
32 num_motors=2,
33 dt=0.00505,
34 controller_poles=[0.90, 0.90],
Michael Schuh95fbcc52018-03-10 20:57:20 -080035 observer_poles=[0.02, 0.02],
36 robot_cg_offset=0.0):
Diana Burgessd0180f12018-03-21 21:24:17 -070037 """Defines all constants of a drivetrain.
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080038
Diana Burgessd0180f12018-03-21 21:24:17 -070039 Args:
40 J: float, Moment of inertia of drivetrain in kg m^2
41 mass: float, Mass of the robot in kg.
42 robot_radius: float, Radius of the robot, in meters (requires tuning by
43 hand).
44 wheel_radius: float, Radius of the wheels, in meters.
Austin Schuhecc92a02019-01-20 17:42:19 -080045 G: float, Gear ratio for a single speed.
Diana Burgessd0180f12018-03-21 21:24:17 -070046 G_high: float, Gear ratio for high gear.
47 G_low: float, Gear ratio for low gear.
48 dt: float, Control loop time step.
Austin Schuh1542ea32021-01-23 20:19:50 -080049 q_pos: float, q position for a single speed LQR controller.
50 q_pos_low: float, q position low gear LQR controller.
51 q_pos_high: float, q position high gear LQR controller.
52 q_vel: float, q velocity for a single speed LQR controller.
53 q_vel_low: float, q velocity low gear LQR controller.
54 q_vel_high: float, q velocity high gear LQR controller.
Diana Burgessd0180f12018-03-21 21:24:17 -070055 efficiency: float, gear box effiency.
56 has_imu: bool, true if imu is present.
57 force: bool, true if force.
58 kf_q_voltage: float
59 motor_type: object, class of values defining the motor in drivetrain.
60 num_motors: int, number of motors on one side of drivetrain.
Austin Schuh1542ea32021-01-23 20:19:50 -080061 controller_poles: array, An array of poles for the polydrivetrain
62 controller. (See control_loop.py)
Diana Burgessd0180f12018-03-21 21:24:17 -070063 observer_poles: array, An array of poles. (See control_loop.py)
Michael Schuh95fbcc52018-03-10 20:57:20 -080064 robot_cg_offset: offset in meters of CG from robot center to left side
Diana Burgessd0180f12018-03-21 21:24:17 -070065 """
Austin Schuhecc92a02019-01-20 17:42:19 -080066 if G is not None:
67 assert (G_high is None)
68 assert (G_low is None)
69 G_high = G
70 G_low = G
71 assert (G_high is not None)
72 assert (G_low is not None)
73
74 if q_pos is not None:
75 assert (q_pos_low is None)
76 assert (q_pos_high is None)
77 q_pos_low = q_pos
78 q_pos_high = q_pos
79 assert (q_pos_low is not None)
80 assert (q_pos_high is not None)
81
82 if q_vel is not None:
83 assert (q_vel_low is None)
84 assert (q_vel_high is None)
85 q_vel_low = q_vel
86 q_vel_high = q_vel
87 assert (q_vel_low is not None)
88 assert (q_vel_high is not None)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080089
Diana Burgessd0180f12018-03-21 21:24:17 -070090 self.J = J
91 self.mass = mass
92 self.robot_radius = robot_radius
Michael Schuh95fbcc52018-03-10 20:57:20 -080093 self.robot_cg_offset = robot_cg_offset
Diana Burgessd0180f12018-03-21 21:24:17 -070094 self.wheel_radius = wheel_radius
95 self.G_high = G_high
96 self.G_low = G_low
97 self.dt = dt
98 self.q_pos_low = q_pos_low
99 self.q_pos_high = q_pos_high
100 self.q_vel_low = q_vel_low
101 self.q_vel_high = q_vel_high
102 self.efficiency = efficiency
103 self.has_imu = has_imu
104 self.kf_q_voltage = kf_q_voltage
105 self.motor_type = motor_type
106 self.force = force
107 self.num_motors = num_motors
108 self.controller_poles = controller_poles
109 self.observer_poles = observer_poles
110
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800111
112class Drivetrain(control_loop.ControlLoop):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700113
Diana Burgessd0180f12018-03-21 21:24:17 -0700114 def __init__(self,
115 drivetrain_params,
116 name="Drivetrain",
117 left_low=True,
118 right_low=True):
119 """Defines a base drivetrain for a robot.
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800120
Diana Burgessd0180f12018-03-21 21:24:17 -0700121 Args:
122 drivetrain_params: DrivetrainParams, class of values defining the drivetrain.
123 name: string, Name of this drivetrain.
124 left_low: bool, Whether the left is in high gear.
125 right_low: bool, Whether the right is in high gear.
126 """
127 super(Drivetrain, self).__init__(name)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800128
Diana Burgessd0180f12018-03-21 21:24:17 -0700129 # Moment of inertia of the drivetrain in kg m^2
130 self.J = drivetrain_params.J
131 # Mass of the robot, in kg.
132 self.mass = drivetrain_params.mass
133 # Radius of the robot, in meters (requires tuning by hand)
134 self.robot_radius = drivetrain_params.robot_radius
135 # Radius of the wheels, in meters.
136 self.r = drivetrain_params.wheel_radius
137 self.has_imu = drivetrain_params.has_imu
Michael Schuh95fbcc52018-03-10 20:57:20 -0800138 # Offset in meters of the CG from the center of the robot to the left side
139 # of the robot. Since the arm is on the right side, the offset will
140 # likely be a negative number.
141 self.robot_cg_offset = drivetrain_params.robot_cg_offset
142 # Distance from the left side of the robot to the Center of Gravity
143 self.robot_radius_l = drivetrain_params.robot_radius - self.robot_cg_offset
144 # Distance from the right side of the robot to the Center of Gravity
145 self.robot_radius_r = drivetrain_params.robot_radius + self.robot_cg_offset
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800146
Diana Burgessd0180f12018-03-21 21:24:17 -0700147 # Gear ratios
148 self.G_low = drivetrain_params.G_low
149 self.G_high = drivetrain_params.G_high
150 if left_low:
151 self.Gl = self.G_low
152 else:
153 self.Gl = self.G_high
154 if right_low:
155 self.Gr = self.G_low
156 else:
157 self.Gr = self.G_high
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800158
Diana Burgessd0180f12018-03-21 21:24:17 -0700159 # Control loop time step
160 self.dt = drivetrain_params.dt
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800161
Diana Burgessd0180f12018-03-21 21:24:17 -0700162 self.efficiency = drivetrain_params.efficiency
163 self.force = drivetrain_params.force
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800164
Diana Burgessd0180f12018-03-21 21:24:17 -0700165 self.BuildDrivetrain(drivetrain_params.motor_type,
166 drivetrain_params.num_motors)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800167
Diana Burgessd0180f12018-03-21 21:24:17 -0700168 if left_low or right_low:
169 q_pos = drivetrain_params.q_pos_low
170 q_vel = drivetrain_params.q_vel_low
171 else:
172 q_pos = drivetrain_params.q_pos_high
173 q_vel = drivetrain_params.q_vel_high
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800174
Diana Burgessd0180f12018-03-21 21:24:17 -0700175 self.BuildDrivetrainController(q_pos, q_vel)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800176
Diana Burgessd0180f12018-03-21 21:24:17 -0700177 self.InitializeState()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800178
Diana Burgessd0180f12018-03-21 21:24:17 -0700179 def BuildDrivetrain(self, motor, num_motors_per_side):
180 self.motor = motor
181 # Number of motors per side
182 self.num_motors = num_motors_per_side
183 # Stall Torque in N m
184 self.stall_torque = motor.stall_torque * self.num_motors * self.efficiency
185 # Stall Current in Amps
186 self.stall_current = motor.stall_current * self.num_motors
187 # Free Speed in rad/s
188 self.free_speed = motor.free_speed
189 # Free Current in Amps
190 self.free_current = motor.free_current * self.num_motors
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800191
Diana Burgessd0180f12018-03-21 21:24:17 -0700192 # Effective motor resistance in ohms.
193 self.resistance = 12.0 / self.stall_current
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800194
Diana Burgessd0180f12018-03-21 21:24:17 -0700195 # Resistance of the motor, divided by the number of motors.
196 # Motor velocity constant
Ravago Jones5127ccc2022-07-31 16:32:45 -0700197 self.Kv = (self.free_speed /
198 (12.0 - self.resistance * self.free_current))
Diana Burgessd0180f12018-03-21 21:24:17 -0700199 # Torque constant
200 self.Kt = self.stall_torque / self.stall_current
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800201
Diana Burgessd0180f12018-03-21 21:24:17 -0700202 # These describe the way that a given side of a robot will be influenced
203 # by the other side. Units of 1 / kg.
Michael Schuh95fbcc52018-03-10 20:57:20 -0800204 self.mspl = 1.0 / self.mass + self.robot_radius_l * self.robot_radius_l / self.J
205 self.mspr = 1.0 / self.mass + self.robot_radius_r * self.robot_radius_r / self.J
206 self.msnl = self.robot_radius_r / ( self.robot_radius_l * self.mass ) - \
207 self.robot_radius_l * self.robot_radius_r / self.J
208 self.msnr = self.robot_radius_l / ( self.robot_radius_r * self.mass ) - \
209 self.robot_radius_l * self.robot_radius_r / self.J
Diana Burgessd0180f12018-03-21 21:24:17 -0700210 # The calculations which we will need for A and B.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700211 self.tcl = self.Kt / self.Kv / (self.Gl * self.Gl * self.resistance *
212 self.r * self.r)
213 self.tcr = self.Kt / self.Kv / (self.Gr * self.Gr * self.resistance *
214 self.r * self.r)
Diana Burgessd0180f12018-03-21 21:24:17 -0700215 self.mpl = self.Kt / (self.Gl * self.resistance * self.r)
216 self.mpr = self.Kt / (self.Gr * self.resistance * self.r)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800217
Diana Burgessd0180f12018-03-21 21:24:17 -0700218 # State feedback matrices
219 # X will be of the format
Austin Schuhd77c0642020-12-30 21:38:54 -0800220 # [[positionl], [velocityl], [positionr], [velocityr]]
Diana Burgessd0180f12018-03-21 21:24:17 -0700221 self.A_continuous = numpy.matrix(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800222 [[0, 1, 0,
223 0], [0, -self.mspl * self.tcl, 0, -self.msnr * self.tcr],
224 [0, 0, 0, 1],
225 [0, -self.msnl * self.tcl, 0, -self.mspr * self.tcr]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700226 self.B_continuous = numpy.matrix(
Tyler Chatow6738c362019-02-16 14:12:30 -0800227 [[0, 0], [self.mspl * self.mpl, self.msnr * self.mpr], [0, 0],
Michael Schuh95fbcc52018-03-10 20:57:20 -0800228 [self.msnl * self.mpl, self.mspr * self.mpr]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700229 self.C = numpy.matrix([[1, 0, 0, 0], [0, 0, 1, 0]])
230 self.D = numpy.matrix([[0, 0], [0, 0]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800231
Diana Burgessd0180f12018-03-21 21:24:17 -0700232 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
233 self.B_continuous, self.dt)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800234
Diana Burgessd0180f12018-03-21 21:24:17 -0700235 def BuildDrivetrainController(self, q_pos, q_vel):
Austin Schuhd77c0642020-12-30 21:38:54 -0800236 # We can solve for the max velocity by setting \dot(x) = Ax + Bu to 0
237 max_voltage = 12
238 glog.debug(
239 "Max speed %f m/s",
240 -(self.B_continuous[1, 1] + self.B_continuous[1, 0]) /
241 (self.A_continuous[1, 1] + self.A_continuous[1, 3]) * max_voltage)
242
Diana Burgessd0180f12018-03-21 21:24:17 -0700243 # Tune the LQR controller
Tyler Chatow6738c362019-02-16 14:12:30 -0800244 self.Q = numpy.matrix([[(1.0 / (q_pos**2.0)), 0.0, 0.0, 0.0],
245 [0.0, (1.0 / (q_vel**2.0)), 0.0, 0.0],
246 [0.0, 0.0, (1.0 / (q_pos**2.0)), 0.0],
247 [0.0, 0.0, 0.0, (1.0 / (q_vel**2.0))]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800248
Diana Burgessd0180f12018-03-21 21:24:17 -0700249 self.R = numpy.matrix([[(1.0 / (12.0**2.0)), 0.0],
250 [0.0, (1.0 / (12.0**2.0))]])
251 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800252
Diana Burgessd0180f12018-03-21 21:24:17 -0700253 glog.debug('DT q_pos %f q_vel %s %s', q_pos, q_vel, self._name)
Ravago Jones26f7ad02021-02-05 15:45:59 -0800254 glog.debug('Poles: %s',
255 str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
256 glog.debug(
257 'Time constants: %s hz',
258 str([
259 numpy.log(x) / -self.dt
260 for x in numpy.linalg.eig(self.A - self.B * self.K)[0]
261 ]))
Diana Burgessd0180f12018-03-21 21:24:17 -0700262 glog.debug('K %s', repr(self.K))
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800263
Diana Burgessd0180f12018-03-21 21:24:17 -0700264 self.hlp = 0.3
265 self.llp = 0.4
266 self.PlaceObserverPoles([self.hlp, self.hlp, self.llp, self.llp])
267
268 self.U_max = numpy.matrix([[12.0], [12.0]])
269 self.U_min = numpy.matrix([[-12.0], [-12.0]])
270
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800271
272class KFDrivetrain(Drivetrain):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700273
Diana Burgessd0180f12018-03-21 21:24:17 -0700274 def __init__(self,
275 drivetrain_params,
276 name="KFDrivetrain",
277 left_low=True,
278 right_low=True):
279 """Kalman filter values of a drivetrain.
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800280
Diana Burgessd0180f12018-03-21 21:24:17 -0700281 Args:
282 drivetrain_params: DrivetrainParams, class of values defining the drivetrain.
283 name: string, Name of this drivetrain.
284 left_low: bool, Whether the left is in high gear.
285 right_low: bool, Whether the right is in high gear.
286 """
287 super(KFDrivetrain, self).__init__(drivetrain_params, name, left_low,
288 right_low)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800289
Diana Burgessd0180f12018-03-21 21:24:17 -0700290 self.unaugmented_A_continuous = self.A_continuous
291 self.unaugmented_B_continuous = self.B_continuous
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800292
Diana Burgessd0180f12018-03-21 21:24:17 -0700293 # The practical voltage applied to the wheels is
294 # V_left = U_left + left_voltage_error
295 #
296 # The states are
297 # [left position, left velocity, right position, right velocity,
298 # left voltage error, right voltage error, angular_error]
299 #
300 # The left and right positions are filtered encoder positions and are not
301 # adjusted for heading error.
302 # The turn velocity as computed by the left and right velocities is
303 # adjusted by the gyro velocity.
304 # The angular_error is the angular velocity error between the wheel speed
305 # and the gyro speed.
306 self.A_continuous = numpy.matrix(numpy.zeros((7, 7)))
307 self.B_continuous = numpy.matrix(numpy.zeros((7, 2)))
308 self.A_continuous[0:4, 0:4] = self.unaugmented_A_continuous
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800309
Diana Burgessd0180f12018-03-21 21:24:17 -0700310 if self.force:
Tyler Chatow6738c362019-02-16 14:12:30 -0800311 self.A_continuous[0:4, 4:6] = numpy.matrix([[0.0, 0.0],
312 [self.mspl, self.msnl],
313 [0.0, 0.0],
Ravago Jones26f7ad02021-02-05 15:45:59 -0800314 [self.msnr,
315 self.mspr]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700316 q_voltage = drivetrain_params.kf_q_voltage * self.mpl
317 else:
318 self.A_continuous[0:4, 4:6] = self.unaugmented_B_continuous
319 q_voltage = drivetrain_params.kf_q_voltage
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800320
Diana Burgessd0180f12018-03-21 21:24:17 -0700321 self.B_continuous[0:4, 0:2] = self.unaugmented_B_continuous
322 self.A_continuous[0, 6] = 1
323 self.A_continuous[2, 6] = -1
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800324
Diana Burgessd0180f12018-03-21 21:24:17 -0700325 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
326 self.B_continuous, self.dt)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800327
Diana Burgessd0180f12018-03-21 21:24:17 -0700328 if self.has_imu:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700329 self.C = numpy.matrix([[1, 0, 0, 0, 0, 0, 0],
330 [0, 0, 1, 0, 0, 0, 0],
331 [
332 0,
333 -0.5 / drivetrain_params.robot_radius,
334 0, 0.5 / drivetrain_params.robot_radius,
335 0, 0, 0
336 ], [0, 0, 0, 0, 0, 0, 0]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700337 gravity = 9.8
Ravago Jones26f7ad02021-02-05 15:45:59 -0800338 self.C[3, 0:6] = 0.5 * (self.A_continuous[1, 0:6] +
339 self.A_continuous[3, 0:6]) / gravity
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800340
Ravago Jones5127ccc2022-07-31 16:32:45 -0700341 self.D = numpy.matrix([
342 [0, 0], [0, 0], [0, 0],
343 [
344 0.5 * (self.B_continuous[1, 0] + self.B_continuous[3, 0]) /
345 gravity,
346 0.5 * (self.B_continuous[1, 1] + self.B_continuous[3, 1]) /
347 gravity
348 ]
349 ])
Diana Burgessd0180f12018-03-21 21:24:17 -0700350 else:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700351 self.C = numpy.matrix([[1, 0, 0, 0, 0, 0, 0],
352 [0, 0, 1, 0, 0, 0, 0],
353 [
354 0,
355 -0.5 / drivetrain_params.robot_radius,
356 0, 0.5 / drivetrain_params.robot_radius,
357 0, 0, 0
358 ]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800359
Diana Burgessd0180f12018-03-21 21:24:17 -0700360 self.D = numpy.matrix([[0, 0], [0, 0], [0, 0]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800361
Diana Burgessd0180f12018-03-21 21:24:17 -0700362 q_pos = 0.05
363 q_vel = 1.00
364 q_encoder_uncertainty = 2.00
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800365
Diana Burgessd0180f12018-03-21 21:24:17 -0700366 self.Q = numpy.matrix(
Tyler Chatow6738c362019-02-16 14:12:30 -0800367 [[(q_pos**2.0), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
368 [0.0, (q_vel**2.0), 0.0, 0.0, 0.0, 0.0, 0.0],
369 [0.0, 0.0, (q_pos**2.0), 0.0, 0.0, 0.0, 0.0],
370 [0.0, 0.0, 0.0, (q_vel**2.0), 0.0, 0.0, 0.0],
371 [0.0, 0.0, 0.0, 0.0, (q_voltage**2.0), 0.0, 0.0],
372 [0.0, 0.0, 0.0, 0.0, 0.0, (q_voltage**2.0), 0.0],
Diana Burgessd0180f12018-03-21 21:24:17 -0700373 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, (q_encoder_uncertainty**2.0)]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800374
Diana Burgessd0180f12018-03-21 21:24:17 -0700375 r_pos = 0.0001
376 r_gyro = 0.000001
377 if self.has_imu:
378 r_accelerometer = 7.0
379 self.R = numpy.matrix([[(r_pos**2.0), 0.0, 0.0, 0.0],
380 [0.0, (r_pos**2.0), 0.0, 0.0],
381 [0.0, 0.0, (r_gyro**2.0), 0.0],
382 [0.0, 0.0, 0.0, (r_accelerometer**2.0)]])
383 else:
384 self.R = numpy.matrix([[(r_pos**2.0), 0.0, 0.0],
385 [0.0, (r_pos**2.0), 0.0],
386 [0.0, 0.0, (r_gyro**2.0)]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800387
Diana Burgessd0180f12018-03-21 21:24:17 -0700388 # Solving for kf gains.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700389 self.KalmanGain, self.Q_steady = controls.kalman(A=self.A,
390 B=self.B,
391 C=self.C,
392 Q=self.Q,
393 R=self.R)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800394
James Kuszmaul4d752d52019-02-09 17:27:55 -0800395 # If we don't have an IMU, pad various matrices with zeros so that
396 # we can still have 4 measurement outputs.
Diana Burgessd0180f12018-03-21 21:24:17 -0700397 if not self.has_imu:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700398 self.KalmanGain = numpy.hstack(
399 (self.KalmanGain, numpy.matrix(numpy.zeros((7, 1)))))
Austin Schuhecc92a02019-01-20 17:42:19 -0800400 self.C = numpy.vstack((self.C, numpy.matrix(numpy.zeros((1, 7)))))
401 self.D = numpy.vstack((self.D, numpy.matrix(numpy.zeros((1, 2)))))
James Kuszmaul4d752d52019-02-09 17:27:55 -0800402 Rtmp = numpy.zeros((4, 4))
403 Rtmp[0:3, 0:3] = self.R
404 self.R = Rtmp
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800405
Diana Burgessd0180f12018-03-21 21:24:17 -0700406 self.L = self.A * self.KalmanGain
407
408 unaug_K = self.K
409
410 # Implement a nice closed loop controller for use by the closed loop
411 # controller.
412 self.K = numpy.matrix(numpy.zeros((self.B.shape[1], self.A.shape[0])))
413 self.K[0:2, 0:4] = unaug_K
414 if self.force:
415 self.K[0, 4] = 1.0 / self.mpl
416 self.K[1, 5] = 1.0 / self.mpr
417 else:
418 self.K[0, 4] = 1.0
419 self.K[1, 5] = 1.0
420
421 self.Qff = numpy.matrix(numpy.zeros((4, 4)))
422 qff_pos = 0.005
423 qff_vel = 1.00
424 self.Qff[0, 0] = 1.0 / qff_pos**2.0
425 self.Qff[1, 1] = 1.0 / qff_vel**2.0
426 self.Qff[2, 2] = 1.0 / qff_pos**2.0
427 self.Qff[3, 3] = 1.0 / qff_vel**2.0
428 self.Kff = numpy.matrix(numpy.zeros((2, 7)))
Ravago Jones5127ccc2022-07-31 16:32:45 -0700429 self.Kff[0:2,
430 0:4] = controls.TwoStateFeedForwards(self.B[0:4, :], self.Qff)
Diana Burgessd0180f12018-03-21 21:24:17 -0700431
432 self.InitializeState()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800433
434
Tyler Chatow6738c362019-02-16 14:12:30 -0800435def WriteDrivetrain(drivetrain_files,
436 kf_drivetrain_files,
437 year_namespace,
438 drivetrain_params,
439 scalar_type='double'):
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800440
Diana Burgessd0180f12018-03-21 21:24:17 -0700441 # Write the generated constants out to a file.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700442 drivetrain_low_low = Drivetrain(name="DrivetrainLowLow",
443 left_low=True,
444 right_low=True,
445 drivetrain_params=drivetrain_params)
446 drivetrain_low_high = Drivetrain(name="DrivetrainLowHigh",
447 left_low=True,
448 right_low=False,
449 drivetrain_params=drivetrain_params)
450 drivetrain_high_low = Drivetrain(name="DrivetrainHighLow",
451 left_low=False,
452 right_low=True,
453 drivetrain_params=drivetrain_params)
454 drivetrain_high_high = Drivetrain(name="DrivetrainHighHigh",
455 left_low=False,
456 right_low=False,
457 drivetrain_params=drivetrain_params)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800458
Ravago Jones5127ccc2022-07-31 16:32:45 -0700459 kf_drivetrain_low_low = KFDrivetrain(name="KFDrivetrainLowLow",
460 left_low=True,
461 right_low=True,
462 drivetrain_params=drivetrain_params)
463 kf_drivetrain_low_high = KFDrivetrain(name="KFDrivetrainLowHigh",
464 left_low=True,
465 right_low=False,
466 drivetrain_params=drivetrain_params)
467 kf_drivetrain_high_low = KFDrivetrain(name="KFDrivetrainHighLow",
468 left_low=False,
469 right_low=True,
470 drivetrain_params=drivetrain_params)
471 kf_drivetrain_high_high = KFDrivetrain(name="KFDrivetrainHighHigh",
472 left_low=False,
473 right_low=False,
474 drivetrain_params=drivetrain_params)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800475
Austin Schuhbcce26a2018-03-26 23:41:24 -0700476 if isinstance(year_namespace, list):
Austin Schuhecc92a02019-01-20 17:42:19 -0800477 namespaces = year_namespace
Austin Schuhbcce26a2018-03-26 23:41:24 -0700478 else:
Austin Schuhecc92a02019-01-20 17:42:19 -0800479 namespaces = [year_namespace, 'control_loops', 'drivetrain']
Ravago Jones5127ccc2022-07-31 16:32:45 -0700480 dog_loop_writer = control_loop.ControlLoopWriter("Drivetrain", [
481 drivetrain_low_low, drivetrain_low_high, drivetrain_high_low,
482 drivetrain_high_high
483 ],
484 namespaces=namespaces,
485 scalar_type=scalar_type)
Diana Burgessd0180f12018-03-21 21:24:17 -0700486 dog_loop_writer.AddConstant(
487 control_loop.Constant("kDt", "%f", drivetrain_low_low.dt))
488 dog_loop_writer.AddConstant(
489 control_loop.Constant("kStallTorque", "%f",
490 drivetrain_low_low.stall_torque))
491 dog_loop_writer.AddConstant(
492 control_loop.Constant("kStallCurrent", "%f",
493 drivetrain_low_low.stall_current))
494 dog_loop_writer.AddConstant(
495 control_loop.Constant("kFreeSpeed", "%f",
496 drivetrain_low_low.free_speed))
497 dog_loop_writer.AddConstant(
498 control_loop.Constant("kFreeCurrent", "%f",
499 drivetrain_low_low.free_current))
500 dog_loop_writer.AddConstant(
501 control_loop.Constant("kJ", "%f", drivetrain_low_low.J))
502 dog_loop_writer.AddConstant(
503 control_loop.Constant("kMass", "%f", drivetrain_low_low.mass))
504 dog_loop_writer.AddConstant(
505 control_loop.Constant("kRobotRadius", "%f",
506 drivetrain_low_low.robot_radius))
507 dog_loop_writer.AddConstant(
508 control_loop.Constant("kWheelRadius", "%f", drivetrain_low_low.r))
509 dog_loop_writer.AddConstant(
510 control_loop.Constant("kR", "%f", drivetrain_low_low.resistance))
511 dog_loop_writer.AddConstant(
512 control_loop.Constant("kV", "%f", drivetrain_low_low.Kv))
513 dog_loop_writer.AddConstant(
514 control_loop.Constant("kT", "%f", drivetrain_low_low.Kt))
515 dog_loop_writer.AddConstant(
516 control_loop.Constant("kLowGearRatio", "%f", drivetrain_low_low.G_low))
517 dog_loop_writer.AddConstant(
518 control_loop.Constant("kHighGearRatio", "%f",
519 drivetrain_high_high.G_high))
520 dog_loop_writer.AddConstant(
521 control_loop.Constant(
522 "kHighOutputRatio", "%f",
523 drivetrain_high_high.G_high * drivetrain_high_high.r))
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800524
Diana Burgessd0180f12018-03-21 21:24:17 -0700525 dog_loop_writer.Write(drivetrain_files[0], drivetrain_files[1])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800526
Ravago Jones5127ccc2022-07-31 16:32:45 -0700527 kf_loop_writer = control_loop.ControlLoopWriter("KFDrivetrain", [
528 kf_drivetrain_low_low, kf_drivetrain_low_high, kf_drivetrain_high_low,
529 kf_drivetrain_high_high
530 ],
531 namespaces=namespaces,
532 scalar_type=scalar_type)
Diana Burgessd0180f12018-03-21 21:24:17 -0700533 kf_loop_writer.Write(kf_drivetrain_files[0], kf_drivetrain_files[1])
534
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800535
536def PlotDrivetrainMotions(drivetrain_params):
Austin Schuh1542ea32021-01-23 20:19:50 -0800537 # Test out the voltage error.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700538 drivetrain = KFDrivetrain(left_low=False,
539 right_low=False,
540 drivetrain_params=drivetrain_params)
Austin Schuh1542ea32021-01-23 20:19:50 -0800541 close_loop_left = []
542 close_loop_right = []
543 left_power = []
544 right_power = []
545 R = numpy.matrix([[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800546 for _ in range(300):
Austin Schuh1542ea32021-01-23 20:19:50 -0800547 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
548 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800549 drivetrain.CorrectObserver(U)
550 drivetrain.PredictObserver(U)
Austin Schuh1542ea32021-01-23 20:19:50 -0800551 drivetrain.Update(U + numpy.matrix([[1.0], [1.0]]))
552 close_loop_left.append(drivetrain.X[0, 0])
553 close_loop_right.append(drivetrain.X[2, 0])
554 left_power.append(U[0, 0])
555 right_power.append(U[1, 0])
556
557 t = [drivetrain.dt * x for x in range(300)]
558 pylab.plot(t, close_loop_left, label='left position')
559 pylab.plot(t, close_loop_right, 'm--', label='right position')
560 pylab.plot(t, left_power, label='left power')
561 pylab.plot(t, right_power, '--', label='right power')
562 pylab.suptitle('Voltage error')
563 pylab.legend()
564 pylab.show()
565
Diana Burgessd0180f12018-03-21 21:24:17 -0700566 # Simulate the response of the system to a step input.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700567 drivetrain = KFDrivetrain(left_low=False,
568 right_low=False,
569 drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700570 simulated_left = []
571 simulated_right = []
Austin Schuh5ea48472021-02-02 20:46:41 -0800572 for _ in range(100):
Diana Burgessd0180f12018-03-21 21:24:17 -0700573 drivetrain.Update(numpy.matrix([[12.0], [12.0]]))
574 simulated_left.append(drivetrain.X[0, 0])
575 simulated_right.append(drivetrain.X[2, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800576
Diana Burgessd0180f12018-03-21 21:24:17 -0700577 pylab.rc('lines', linewidth=4)
578 pylab.plot(range(100), simulated_left, label='left position')
579 pylab.plot(range(100), simulated_right, 'r--', label='right position')
580 pylab.suptitle('Acceleration Test\n12 Volt Step Input')
581 pylab.legend(loc='lower right')
582 pylab.show()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800583
Diana Burgessd0180f12018-03-21 21:24:17 -0700584 # Simulate forwards motion.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700585 drivetrain = KFDrivetrain(left_low=False,
586 right_low=False,
587 drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700588 close_loop_left = []
589 close_loop_right = []
590 left_power = []
591 right_power = []
Austin Schuh1542ea32021-01-23 20:19:50 -0800592 R = numpy.matrix([[1.0], [0.0], [1.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800593 for _ in range(300):
Diana Burgessd0180f12018-03-21 21:24:17 -0700594 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
595 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800596 drivetrain.CorrectObserver(U)
597 drivetrain.PredictObserver(U)
Diana Burgessd0180f12018-03-21 21:24:17 -0700598 drivetrain.Update(U)
599 close_loop_left.append(drivetrain.X[0, 0])
600 close_loop_right.append(drivetrain.X[2, 0])
601 left_power.append(U[0, 0])
602 right_power.append(U[1, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800603
Austin Schuh1542ea32021-01-23 20:19:50 -0800604 t = [drivetrain.dt * x for x in range(300)]
605 pylab.plot(t, close_loop_left, label='left position')
606 pylab.plot(t, close_loop_right, 'm--', label='right position')
607 pylab.plot(t, left_power, label='left power')
608 pylab.plot(t, right_power, '--', label='right power')
Diana Burgessd0180f12018-03-21 21:24:17 -0700609 pylab.suptitle('Linear Move\nLeft and Right Position going to 1')
610 pylab.legend()
611 pylab.show()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800612
Diana Burgessd0180f12018-03-21 21:24:17 -0700613 # Try turning in place
Austin Schuh1542ea32021-01-23 20:19:50 -0800614 drivetrain = KFDrivetrain(drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700615 close_loop_left = []
616 close_loop_right = []
Austin Schuh1542ea32021-01-23 20:19:50 -0800617 R = numpy.matrix([[-1.0], [0.0], [1.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800618 for _ in range(200):
Diana Burgessd0180f12018-03-21 21:24:17 -0700619 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
620 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800621 drivetrain.CorrectObserver(U)
622 drivetrain.PredictObserver(U)
Diana Burgessd0180f12018-03-21 21:24:17 -0700623 drivetrain.Update(U)
624 close_loop_left.append(drivetrain.X[0, 0])
625 close_loop_right.append(drivetrain.X[2, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800626
Diana Burgessd0180f12018-03-21 21:24:17 -0700627 pylab.plot(range(200), close_loop_left, label='left position')
628 pylab.plot(range(200), close_loop_right, label='right position')
629 pylab.suptitle(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800630 'Angular Move\nLeft position going to -1 and right position going to 1'
631 )
Diana Burgessd0180f12018-03-21 21:24:17 -0700632 pylab.legend(loc='center right')
633 pylab.show()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800634
Diana Burgessd0180f12018-03-21 21:24:17 -0700635 # Try turning just one side.
Austin Schuh1542ea32021-01-23 20:19:50 -0800636 drivetrain = KFDrivetrain(drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700637 close_loop_left = []
638 close_loop_right = []
Austin Schuh1542ea32021-01-23 20:19:50 -0800639 R = numpy.matrix([[0.0], [0.0], [1.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800640 for _ in range(300):
Diana Burgessd0180f12018-03-21 21:24:17 -0700641 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
642 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800643 drivetrain.CorrectObserver(U)
644 drivetrain.PredictObserver(U)
Diana Burgessd0180f12018-03-21 21:24:17 -0700645 drivetrain.Update(U)
646 close_loop_left.append(drivetrain.X[0, 0])
647 close_loop_right.append(drivetrain.X[2, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800648
Diana Burgessd0180f12018-03-21 21:24:17 -0700649 pylab.plot(range(300), close_loop_left, label='left position')
650 pylab.plot(range(300), close_loop_right, label='right position')
651 pylab.suptitle(
652 'Pivot\nLeft position not changing and right position going to 1')
653 pylab.legend(loc='center right')
654 pylab.show()