blob: 1535432a09f5eacd09e16662c22cc11da14aaf0b [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
Austin Schuh566c82b2023-01-27 20:49:08 -08006import math
Campbell Crowley33e0e3d2017-12-27 17:55:40 -08007import sys
8from matplotlib import pylab
James Kuszmaul57754812024-03-16 19:26:24 -07009import matplotlib
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080010import glog
11
Tyler Chatow6738c362019-02-16 14:12:30 -080012
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080013class DrivetrainParams(object):
Ravago Jones5127ccc2022-07-31 16:32:45 -070014
Diana Burgessd0180f12018-03-21 21:24:17 -070015 def __init__(self,
16 J,
17 mass,
18 robot_radius,
19 wheel_radius,
Austin Schuhecc92a02019-01-20 17:42:19 -080020 G=None,
21 G_high=None,
22 G_low=None,
23 q_pos=None,
24 q_pos_low=None,
25 q_pos_high=None,
26 q_vel=None,
27 q_vel_low=None,
28 q_vel_high=None,
Diana Burgessd0180f12018-03-21 21:24:17 -070029 efficiency=0.60,
30 has_imu=False,
31 force=False,
32 kf_q_voltage=10.0,
33 motor_type=control_loop.CIM(),
34 num_motors=2,
35 dt=0.00505,
36 controller_poles=[0.90, 0.90],
Michael Schuh95fbcc52018-03-10 20:57:20 -080037 observer_poles=[0.02, 0.02],
Austin Schuh566c82b2023-01-27 20:49:08 -080038 robot_cg_offset=0.0,
39 coefficient_of_friction=1.0):
Diana Burgessd0180f12018-03-21 21:24:17 -070040 """Defines all constants of a drivetrain.
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080041
Diana Burgessd0180f12018-03-21 21:24:17 -070042 Args:
43 J: float, Moment of inertia of drivetrain in kg m^2
44 mass: float, Mass of the robot in kg.
45 robot_radius: float, Radius of the robot, in meters (requires tuning by
46 hand).
47 wheel_radius: float, Radius of the wheels, in meters.
Austin Schuhecc92a02019-01-20 17:42:19 -080048 G: float, Gear ratio for a single speed.
Diana Burgessd0180f12018-03-21 21:24:17 -070049 G_high: float, Gear ratio for high gear.
50 G_low: float, Gear ratio for low gear.
51 dt: float, Control loop time step.
Austin Schuh1542ea32021-01-23 20:19:50 -080052 q_pos: float, q position for a single speed LQR controller.
53 q_pos_low: float, q position low gear LQR controller.
54 q_pos_high: float, q position high gear LQR controller.
55 q_vel: float, q velocity for a single speed LQR controller.
56 q_vel_low: float, q velocity low gear LQR controller.
57 q_vel_high: float, q velocity high gear LQR controller.
Diana Burgessd0180f12018-03-21 21:24:17 -070058 efficiency: float, gear box effiency.
59 has_imu: bool, true if imu is present.
60 force: bool, true if force.
61 kf_q_voltage: float
62 motor_type: object, class of values defining the motor in drivetrain.
63 num_motors: int, number of motors on one side of drivetrain.
Austin Schuh1542ea32021-01-23 20:19:50 -080064 controller_poles: array, An array of poles for the polydrivetrain
65 controller. (See control_loop.py)
Diana Burgessd0180f12018-03-21 21:24:17 -070066 observer_poles: array, An array of poles. (See control_loop.py)
Michael Schuh95fbcc52018-03-10 20:57:20 -080067 robot_cg_offset: offset in meters of CG from robot center to left side
Diana Burgessd0180f12018-03-21 21:24:17 -070068 """
Austin Schuhecc92a02019-01-20 17:42:19 -080069 if G is not None:
70 assert (G_high is None)
71 assert (G_low is None)
72 G_high = G
73 G_low = G
74 assert (G_high is not None)
75 assert (G_low is not None)
76
77 if q_pos is not None:
78 assert (q_pos_low is None)
79 assert (q_pos_high is None)
80 q_pos_low = q_pos
81 q_pos_high = q_pos
82 assert (q_pos_low is not None)
83 assert (q_pos_high is not None)
84
85 if q_vel is not None:
86 assert (q_vel_low is None)
87 assert (q_vel_high is None)
88 q_vel_low = q_vel
89 q_vel_high = q_vel
90 assert (q_vel_low is not None)
91 assert (q_vel_high is not None)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -080092
Diana Burgessd0180f12018-03-21 21:24:17 -070093 self.J = J
94 self.mass = mass
95 self.robot_radius = robot_radius
Michael Schuh95fbcc52018-03-10 20:57:20 -080096 self.robot_cg_offset = robot_cg_offset
Diana Burgessd0180f12018-03-21 21:24:17 -070097 self.wheel_radius = wheel_radius
98 self.G_high = G_high
99 self.G_low = G_low
100 self.dt = dt
101 self.q_pos_low = q_pos_low
102 self.q_pos_high = q_pos_high
103 self.q_vel_low = q_vel_low
104 self.q_vel_high = q_vel_high
105 self.efficiency = efficiency
106 self.has_imu = has_imu
107 self.kf_q_voltage = kf_q_voltage
108 self.motor_type = motor_type
109 self.force = force
110 self.num_motors = num_motors
111 self.controller_poles = controller_poles
112 self.observer_poles = observer_poles
Austin Schuh566c82b2023-01-27 20:49:08 -0800113 self.coefficient_of_friction = coefficient_of_friction
Diana Burgessd0180f12018-03-21 21:24:17 -0700114
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800115
116class Drivetrain(control_loop.ControlLoop):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700117
Diana Burgessd0180f12018-03-21 21:24:17 -0700118 def __init__(self,
119 drivetrain_params,
120 name="Drivetrain",
121 left_low=True,
122 right_low=True):
123 """Defines a base drivetrain for a robot.
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800124
Diana Burgessd0180f12018-03-21 21:24:17 -0700125 Args:
126 drivetrain_params: DrivetrainParams, class of values defining the drivetrain.
127 name: string, Name of this drivetrain.
128 left_low: bool, Whether the left is in high gear.
129 right_low: bool, Whether the right is in high gear.
130 """
131 super(Drivetrain, self).__init__(name)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800132
Diana Burgessd0180f12018-03-21 21:24:17 -0700133 # Moment of inertia of the drivetrain in kg m^2
134 self.J = drivetrain_params.J
135 # Mass of the robot, in kg.
136 self.mass = drivetrain_params.mass
137 # Radius of the robot, in meters (requires tuning by hand)
138 self.robot_radius = drivetrain_params.robot_radius
139 # Radius of the wheels, in meters.
140 self.r = drivetrain_params.wheel_radius
141 self.has_imu = drivetrain_params.has_imu
Michael Schuh95fbcc52018-03-10 20:57:20 -0800142 # Offset in meters of the CG from the center of the robot to the left side
143 # of the robot. Since the arm is on the right side, the offset will
144 # likely be a negative number.
145 self.robot_cg_offset = drivetrain_params.robot_cg_offset
146 # Distance from the left side of the robot to the Center of Gravity
147 self.robot_radius_l = drivetrain_params.robot_radius - self.robot_cg_offset
148 # Distance from the right side of the robot to the Center of Gravity
149 self.robot_radius_r = drivetrain_params.robot_radius + self.robot_cg_offset
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800150
Diana Burgessd0180f12018-03-21 21:24:17 -0700151 # Gear ratios
152 self.G_low = drivetrain_params.G_low
153 self.G_high = drivetrain_params.G_high
154 if left_low:
155 self.Gl = self.G_low
156 else:
157 self.Gl = self.G_high
158 if right_low:
159 self.Gr = self.G_low
160 else:
161 self.Gr = self.G_high
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800162
Diana Burgessd0180f12018-03-21 21:24:17 -0700163 # Control loop time step
164 self.dt = drivetrain_params.dt
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800165
Diana Burgessd0180f12018-03-21 21:24:17 -0700166 self.efficiency = drivetrain_params.efficiency
167 self.force = drivetrain_params.force
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800168
Diana Burgessd0180f12018-03-21 21:24:17 -0700169 self.BuildDrivetrain(drivetrain_params.motor_type,
170 drivetrain_params.num_motors)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800171
Diana Burgessd0180f12018-03-21 21:24:17 -0700172 if left_low or right_low:
173 q_pos = drivetrain_params.q_pos_low
174 q_vel = drivetrain_params.q_vel_low
175 else:
176 q_pos = drivetrain_params.q_pos_high
177 q_vel = drivetrain_params.q_vel_high
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800178
Diana Burgessd0180f12018-03-21 21:24:17 -0700179 self.BuildDrivetrainController(q_pos, q_vel)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800180
Diana Burgessd0180f12018-03-21 21:24:17 -0700181 self.InitializeState()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800182
Diana Burgessd0180f12018-03-21 21:24:17 -0700183 def BuildDrivetrain(self, motor, num_motors_per_side):
184 self.motor = motor
185 # Number of motors per side
186 self.num_motors = num_motors_per_side
187 # Stall Torque in N m
188 self.stall_torque = motor.stall_torque * self.num_motors * self.efficiency
189 # Stall Current in Amps
190 self.stall_current = motor.stall_current * self.num_motors
191 # Free Speed in rad/s
192 self.free_speed = motor.free_speed
193 # Free Current in Amps
194 self.free_current = motor.free_current * self.num_motors
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800195
Diana Burgessd0180f12018-03-21 21:24:17 -0700196 # Effective motor resistance in ohms.
197 self.resistance = 12.0 / self.stall_current
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800198
Diana Burgessd0180f12018-03-21 21:24:17 -0700199 # Resistance of the motor, divided by the number of motors.
200 # Motor velocity constant
Ravago Jones5127ccc2022-07-31 16:32:45 -0700201 self.Kv = (self.free_speed /
202 (12.0 - self.resistance * self.free_current))
Diana Burgessd0180f12018-03-21 21:24:17 -0700203 # Torque constant
204 self.Kt = self.stall_torque / self.stall_current
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800205
Diana Burgessd0180f12018-03-21 21:24:17 -0700206 # These describe the way that a given side of a robot will be influenced
207 # by the other side. Units of 1 / kg.
Michael Schuh95fbcc52018-03-10 20:57:20 -0800208 self.mspl = 1.0 / self.mass + self.robot_radius_l * self.robot_radius_l / self.J
209 self.mspr = 1.0 / self.mass + self.robot_radius_r * self.robot_radius_r / self.J
210 self.msnl = self.robot_radius_r / ( self.robot_radius_l * self.mass ) - \
211 self.robot_radius_l * self.robot_radius_r / self.J
212 self.msnr = self.robot_radius_l / ( self.robot_radius_r * self.mass ) - \
213 self.robot_radius_l * self.robot_radius_r / self.J
Diana Burgessd0180f12018-03-21 21:24:17 -0700214 # The calculations which we will need for A and B.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700215 self.tcl = self.Kt / self.Kv / (self.Gl * self.Gl * self.resistance *
216 self.r * self.r)
217 self.tcr = self.Kt / self.Kv / (self.Gr * self.Gr * self.resistance *
218 self.r * self.r)
Diana Burgessd0180f12018-03-21 21:24:17 -0700219 self.mpl = self.Kt / (self.Gl * self.resistance * self.r)
220 self.mpr = self.Kt / (self.Gr * self.resistance * self.r)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800221
Diana Burgessd0180f12018-03-21 21:24:17 -0700222 # State feedback matrices
223 # X will be of the format
Austin Schuhd77c0642020-12-30 21:38:54 -0800224 # [[positionl], [velocityl], [positionr], [velocityr]]
Diana Burgessd0180f12018-03-21 21:24:17 -0700225 self.A_continuous = numpy.matrix(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800226 [[0, 1, 0,
227 0], [0, -self.mspl * self.tcl, 0, -self.msnr * self.tcr],
228 [0, 0, 0, 1],
229 [0, -self.msnl * self.tcl, 0, -self.mspr * self.tcr]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700230 self.B_continuous = numpy.matrix(
Tyler Chatow6738c362019-02-16 14:12:30 -0800231 [[0, 0], [self.mspl * self.mpl, self.msnr * self.mpr], [0, 0],
Michael Schuh95fbcc52018-03-10 20:57:20 -0800232 [self.msnl * self.mpl, self.mspr * self.mpr]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700233 self.C = numpy.matrix([[1, 0, 0, 0], [0, 0, 1, 0]])
234 self.D = numpy.matrix([[0, 0], [0, 0]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800235
Diana Burgessd0180f12018-03-21 21:24:17 -0700236 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
237 self.B_continuous, self.dt)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800238
Diana Burgessd0180f12018-03-21 21:24:17 -0700239 def BuildDrivetrainController(self, q_pos, q_vel):
Austin Schuhd77c0642020-12-30 21:38:54 -0800240 # We can solve for the max velocity by setting \dot(x) = Ax + Bu to 0
241 max_voltage = 12
242 glog.debug(
243 "Max speed %f m/s",
244 -(self.B_continuous[1, 1] + self.B_continuous[1, 0]) /
245 (self.A_continuous[1, 1] + self.A_continuous[1, 3]) * max_voltage)
246
Diana Burgessd0180f12018-03-21 21:24:17 -0700247 # Tune the LQR controller
Tyler Chatow6738c362019-02-16 14:12:30 -0800248 self.Q = numpy.matrix([[(1.0 / (q_pos**2.0)), 0.0, 0.0, 0.0],
249 [0.0, (1.0 / (q_vel**2.0)), 0.0, 0.0],
250 [0.0, 0.0, (1.0 / (q_pos**2.0)), 0.0],
251 [0.0, 0.0, 0.0, (1.0 / (q_vel**2.0))]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800252
Diana Burgessd0180f12018-03-21 21:24:17 -0700253 self.R = numpy.matrix([[(1.0 / (12.0**2.0)), 0.0],
254 [0.0, (1.0 / (12.0**2.0))]])
255 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800256
Diana Burgessd0180f12018-03-21 21:24:17 -0700257 glog.debug('DT q_pos %f q_vel %s %s', q_pos, q_vel, self._name)
Ravago Jones26f7ad02021-02-05 15:45:59 -0800258 glog.debug('Poles: %s',
259 str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
260 glog.debug(
261 'Time constants: %s hz',
262 str([
263 numpy.log(x) / -self.dt
264 for x in numpy.linalg.eig(self.A - self.B * self.K)[0]
265 ]))
Diana Burgessd0180f12018-03-21 21:24:17 -0700266 glog.debug('K %s', repr(self.K))
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800267
Diana Burgessd0180f12018-03-21 21:24:17 -0700268 self.hlp = 0.3
269 self.llp = 0.4
270 self.PlaceObserverPoles([self.hlp, self.hlp, self.llp, self.llp])
271
272 self.U_max = numpy.matrix([[12.0], [12.0]])
273 self.U_min = numpy.matrix([[-12.0], [-12.0]])
274
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800275
276class KFDrivetrain(Drivetrain):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700277
Diana Burgessd0180f12018-03-21 21:24:17 -0700278 def __init__(self,
279 drivetrain_params,
280 name="KFDrivetrain",
281 left_low=True,
282 right_low=True):
283 """Kalman filter values of a drivetrain.
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800284
Diana Burgessd0180f12018-03-21 21:24:17 -0700285 Args:
286 drivetrain_params: DrivetrainParams, class of values defining the drivetrain.
287 name: string, Name of this drivetrain.
288 left_low: bool, Whether the left is in high gear.
289 right_low: bool, Whether the right is in high gear.
290 """
291 super(KFDrivetrain, self).__init__(drivetrain_params, name, left_low,
292 right_low)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800293
Diana Burgessd0180f12018-03-21 21:24:17 -0700294 self.unaugmented_A_continuous = self.A_continuous
295 self.unaugmented_B_continuous = self.B_continuous
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800296
Diana Burgessd0180f12018-03-21 21:24:17 -0700297 # The practical voltage applied to the wheels is
298 # V_left = U_left + left_voltage_error
299 #
300 # The states are
301 # [left position, left velocity, right position, right velocity,
302 # left voltage error, right voltage error, angular_error]
303 #
304 # The left and right positions are filtered encoder positions and are not
305 # adjusted for heading error.
306 # The turn velocity as computed by the left and right velocities is
307 # adjusted by the gyro velocity.
308 # The angular_error is the angular velocity error between the wheel speed
309 # and the gyro speed.
310 self.A_continuous = numpy.matrix(numpy.zeros((7, 7)))
311 self.B_continuous = numpy.matrix(numpy.zeros((7, 2)))
312 self.A_continuous[0:4, 0:4] = self.unaugmented_A_continuous
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800313
Diana Burgessd0180f12018-03-21 21:24:17 -0700314 if self.force:
Tyler Chatow6738c362019-02-16 14:12:30 -0800315 self.A_continuous[0:4, 4:6] = numpy.matrix([[0.0, 0.0],
316 [self.mspl, self.msnl],
317 [0.0, 0.0],
Ravago Jones26f7ad02021-02-05 15:45:59 -0800318 [self.msnr,
319 self.mspr]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700320 q_voltage = drivetrain_params.kf_q_voltage * self.mpl
321 else:
322 self.A_continuous[0:4, 4:6] = self.unaugmented_B_continuous
323 q_voltage = drivetrain_params.kf_q_voltage
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800324
Diana Burgessd0180f12018-03-21 21:24:17 -0700325 self.B_continuous[0:4, 0:2] = self.unaugmented_B_continuous
326 self.A_continuous[0, 6] = 1
327 self.A_continuous[2, 6] = -1
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800328
Diana Burgessd0180f12018-03-21 21:24:17 -0700329 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
330 self.B_continuous, self.dt)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800331
Diana Burgessd0180f12018-03-21 21:24:17 -0700332 if self.has_imu:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700333 self.C = numpy.matrix([[1, 0, 0, 0, 0, 0, 0],
334 [0, 0, 1, 0, 0, 0, 0],
335 [
336 0,
337 -0.5 / drivetrain_params.robot_radius,
338 0, 0.5 / drivetrain_params.robot_radius,
339 0, 0, 0
340 ], [0, 0, 0, 0, 0, 0, 0]])
Diana Burgessd0180f12018-03-21 21:24:17 -0700341 gravity = 9.8
Ravago Jones26f7ad02021-02-05 15:45:59 -0800342 self.C[3, 0:6] = 0.5 * (self.A_continuous[1, 0:6] +
343 self.A_continuous[3, 0:6]) / gravity
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800344
Ravago Jones5127ccc2022-07-31 16:32:45 -0700345 self.D = numpy.matrix([
346 [0, 0], [0, 0], [0, 0],
347 [
348 0.5 * (self.B_continuous[1, 0] + self.B_continuous[3, 0]) /
349 gravity,
350 0.5 * (self.B_continuous[1, 1] + self.B_continuous[3, 1]) /
351 gravity
352 ]
353 ])
Diana Burgessd0180f12018-03-21 21:24:17 -0700354 else:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700355 self.C = numpy.matrix([[1, 0, 0, 0, 0, 0, 0],
356 [0, 0, 1, 0, 0, 0, 0],
357 [
358 0,
359 -0.5 / drivetrain_params.robot_radius,
360 0, 0.5 / drivetrain_params.robot_radius,
361 0, 0, 0
362 ]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800363
Diana Burgessd0180f12018-03-21 21:24:17 -0700364 self.D = numpy.matrix([[0, 0], [0, 0], [0, 0]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800365
Diana Burgessd0180f12018-03-21 21:24:17 -0700366 q_pos = 0.05
367 q_vel = 1.00
368 q_encoder_uncertainty = 2.00
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800369
Diana Burgessd0180f12018-03-21 21:24:17 -0700370 self.Q = numpy.matrix(
Tyler Chatow6738c362019-02-16 14:12:30 -0800371 [[(q_pos**2.0), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
372 [0.0, (q_vel**2.0), 0.0, 0.0, 0.0, 0.0, 0.0],
373 [0.0, 0.0, (q_pos**2.0), 0.0, 0.0, 0.0, 0.0],
374 [0.0, 0.0, 0.0, (q_vel**2.0), 0.0, 0.0, 0.0],
375 [0.0, 0.0, 0.0, 0.0, (q_voltage**2.0), 0.0, 0.0],
376 [0.0, 0.0, 0.0, 0.0, 0.0, (q_voltage**2.0), 0.0],
Diana Burgessd0180f12018-03-21 21:24:17 -0700377 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, (q_encoder_uncertainty**2.0)]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800378
Diana Burgessd0180f12018-03-21 21:24:17 -0700379 r_pos = 0.0001
380 r_gyro = 0.000001
381 if self.has_imu:
382 r_accelerometer = 7.0
383 self.R = numpy.matrix([[(r_pos**2.0), 0.0, 0.0, 0.0],
384 [0.0, (r_pos**2.0), 0.0, 0.0],
385 [0.0, 0.0, (r_gyro**2.0), 0.0],
386 [0.0, 0.0, 0.0, (r_accelerometer**2.0)]])
387 else:
388 self.R = numpy.matrix([[(r_pos**2.0), 0.0, 0.0],
389 [0.0, (r_pos**2.0), 0.0],
390 [0.0, 0.0, (r_gyro**2.0)]])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800391
Diana Burgessd0180f12018-03-21 21:24:17 -0700392 # Solving for kf gains.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700393 self.KalmanGain, self.Q_steady = controls.kalman(A=self.A,
394 B=self.B,
395 C=self.C,
396 Q=self.Q,
397 R=self.R)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800398
James Kuszmaul4d752d52019-02-09 17:27:55 -0800399 # If we don't have an IMU, pad various matrices with zeros so that
400 # we can still have 4 measurement outputs.
Diana Burgessd0180f12018-03-21 21:24:17 -0700401 if not self.has_imu:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700402 self.KalmanGain = numpy.hstack(
403 (self.KalmanGain, numpy.matrix(numpy.zeros((7, 1)))))
Austin Schuhecc92a02019-01-20 17:42:19 -0800404 self.C = numpy.vstack((self.C, numpy.matrix(numpy.zeros((1, 7)))))
405 self.D = numpy.vstack((self.D, numpy.matrix(numpy.zeros((1, 2)))))
James Kuszmaul4d752d52019-02-09 17:27:55 -0800406 Rtmp = numpy.zeros((4, 4))
407 Rtmp[0:3, 0:3] = self.R
408 self.R = Rtmp
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800409
Diana Burgessd0180f12018-03-21 21:24:17 -0700410 self.L = self.A * self.KalmanGain
411
412 unaug_K = self.K
413
414 # Implement a nice closed loop controller for use by the closed loop
415 # controller.
416 self.K = numpy.matrix(numpy.zeros((self.B.shape[1], self.A.shape[0])))
417 self.K[0:2, 0:4] = unaug_K
418 if self.force:
419 self.K[0, 4] = 1.0 / self.mpl
420 self.K[1, 5] = 1.0 / self.mpr
421 else:
422 self.K[0, 4] = 1.0
423 self.K[1, 5] = 1.0
424
425 self.Qff = numpy.matrix(numpy.zeros((4, 4)))
426 qff_pos = 0.005
427 qff_vel = 1.00
428 self.Qff[0, 0] = 1.0 / qff_pos**2.0
429 self.Qff[1, 1] = 1.0 / qff_vel**2.0
430 self.Qff[2, 2] = 1.0 / qff_pos**2.0
431 self.Qff[3, 3] = 1.0 / qff_vel**2.0
432 self.Kff = numpy.matrix(numpy.zeros((2, 7)))
Ravago Jones5127ccc2022-07-31 16:32:45 -0700433 self.Kff[0:2,
434 0:4] = controls.TwoStateFeedForwards(self.B[0:4, :], self.Qff)
Diana Burgessd0180f12018-03-21 21:24:17 -0700435
436 self.InitializeState()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800437
438
Tyler Chatow6738c362019-02-16 14:12:30 -0800439def WriteDrivetrain(drivetrain_files,
440 kf_drivetrain_files,
441 year_namespace,
442 drivetrain_params,
443 scalar_type='double'):
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800444
Diana Burgessd0180f12018-03-21 21:24:17 -0700445 # Write the generated constants out to a file.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700446 drivetrain_low_low = Drivetrain(name="DrivetrainLowLow",
447 left_low=True,
448 right_low=True,
449 drivetrain_params=drivetrain_params)
450 drivetrain_low_high = Drivetrain(name="DrivetrainLowHigh",
451 left_low=True,
452 right_low=False,
453 drivetrain_params=drivetrain_params)
454 drivetrain_high_low = Drivetrain(name="DrivetrainHighLow",
455 left_low=False,
456 right_low=True,
457 drivetrain_params=drivetrain_params)
458 drivetrain_high_high = Drivetrain(name="DrivetrainHighHigh",
459 left_low=False,
460 right_low=False,
461 drivetrain_params=drivetrain_params)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800462
Ravago Jones5127ccc2022-07-31 16:32:45 -0700463 kf_drivetrain_low_low = KFDrivetrain(name="KFDrivetrainLowLow",
464 left_low=True,
465 right_low=True,
466 drivetrain_params=drivetrain_params)
467 kf_drivetrain_low_high = KFDrivetrain(name="KFDrivetrainLowHigh",
468 left_low=True,
469 right_low=False,
470 drivetrain_params=drivetrain_params)
471 kf_drivetrain_high_low = KFDrivetrain(name="KFDrivetrainHighLow",
472 left_low=False,
473 right_low=True,
474 drivetrain_params=drivetrain_params)
475 kf_drivetrain_high_high = KFDrivetrain(name="KFDrivetrainHighHigh",
476 left_low=False,
477 right_low=False,
478 drivetrain_params=drivetrain_params)
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800479
Austin Schuhbcce26a2018-03-26 23:41:24 -0700480 if isinstance(year_namespace, list):
Austin Schuhecc92a02019-01-20 17:42:19 -0800481 namespaces = year_namespace
Austin Schuhbcce26a2018-03-26 23:41:24 -0700482 else:
Austin Schuhecc92a02019-01-20 17:42:19 -0800483 namespaces = [year_namespace, 'control_loops', 'drivetrain']
Ravago Jones5127ccc2022-07-31 16:32:45 -0700484 dog_loop_writer = control_loop.ControlLoopWriter("Drivetrain", [
485 drivetrain_low_low, drivetrain_low_high, drivetrain_high_low,
486 drivetrain_high_high
487 ],
488 namespaces=namespaces,
489 scalar_type=scalar_type)
Diana Burgessd0180f12018-03-21 21:24:17 -0700490 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800491 control_loop.Constant("kDt",
492 "%f",
493 drivetrain_low_low.dt,
494 json_name="dt",
495 json_scale=1e9,
496 json_type=int))
Diana Burgessd0180f12018-03-21 21:24:17 -0700497 dog_loop_writer.AddConstant(
498 control_loop.Constant("kStallTorque", "%f",
499 drivetrain_low_low.stall_torque))
500 dog_loop_writer.AddConstant(
501 control_loop.Constant("kStallCurrent", "%f",
502 drivetrain_low_low.stall_current))
503 dog_loop_writer.AddConstant(
504 control_loop.Constant("kFreeSpeed", "%f",
505 drivetrain_low_low.free_speed))
506 dog_loop_writer.AddConstant(
507 control_loop.Constant("kFreeCurrent", "%f",
508 drivetrain_low_low.free_current))
509 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800510 control_loop.Constant("kJ",
511 "%f",
512 drivetrain_low_low.J,
513 json_name="moment_of_inertia"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700514 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800515 control_loop.Constant("kMass",
516 "%f",
517 drivetrain_low_low.mass,
518 json_name="mass"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700519 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800520 control_loop.Constant("kRobotRadius",
521 "%f",
522 drivetrain_low_low.robot_radius,
523 json_name="robot_radius"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700524 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800525 control_loop.Constant("kWheelRadius",
526 "%f",
527 drivetrain_low_low.r,
528 json_name="wheel_radius"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700529 dog_loop_writer.AddConstant(
530 control_loop.Constant("kR", "%f", drivetrain_low_low.resistance))
531 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800532 control_loop.Constant("kV",
533 "%f",
534 drivetrain_low_low.Kv,
535 json_name="motor_kv"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700536 dog_loop_writer.AddConstant(
537 control_loop.Constant("kT", "%f", drivetrain_low_low.Kt))
538 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800539 control_loop.Constant("kLowGearRatio",
540 "%f",
541 drivetrain_low_low.G_low,
542 json_name="low_gear_ratio"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700543 dog_loop_writer.AddConstant(
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800544 control_loop.Constant("kHighGearRatio",
545 "%f",
546 drivetrain_high_high.G_high,
547 json_name="high_gear_ratio"))
Diana Burgessd0180f12018-03-21 21:24:17 -0700548 dog_loop_writer.AddConstant(
549 control_loop.Constant(
550 "kHighOutputRatio", "%f",
551 drivetrain_high_high.G_high * drivetrain_high_high.r))
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800552
James Kuszmauleeb98e92024-01-14 22:15:32 -0800553 dog_loop_writer.Write(drivetrain_files[0], drivetrain_files[1],
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800554 drivetrain_files[2], "drivetrain_loop")
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800555
Ravago Jones5127ccc2022-07-31 16:32:45 -0700556 kf_loop_writer = control_loop.ControlLoopWriter("KFDrivetrain", [
557 kf_drivetrain_low_low, kf_drivetrain_low_high, kf_drivetrain_high_low,
558 kf_drivetrain_high_high
559 ],
560 namespaces=namespaces,
561 scalar_type=scalar_type)
James Kuszmauleeb98e92024-01-14 22:15:32 -0800562 kf_loop_writer.Write(kf_drivetrain_files[0], kf_drivetrain_files[1],
James Kuszmaul62c3bd82024-01-17 20:03:05 -0800563 kf_drivetrain_files[2], "kalman_drivetrain_loop")
Diana Burgessd0180f12018-03-21 21:24:17 -0700564
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800565
Austin Schuh566c82b2023-01-27 20:49:08 -0800566def PlotDrivetrainSprint(drivetrain_params):
James Kuszmaul57754812024-03-16 19:26:24 -0700567 # Set up the gtk backend before running matplotlib.
568 matplotlib.use("GTK3Agg")
569
Austin Schuh566c82b2023-01-27 20:49:08 -0800570 # Simulate the response of the system to a step input.
571 drivetrain = KFDrivetrain(left_low=False,
572 right_low=False,
573 drivetrain_params=drivetrain_params)
574 simulated_left_position = []
575 simulated_right_position = []
576 simulated_left_velocity = []
577 simulated_right_velocity = []
578
579 simulated_left_motor_currents = []
580 simulated_left_breaker_currents = []
581 simulated_right_motor_currents = []
582 simulated_right_breaker_currents = []
583
584 simulated_battery_heat_wattages = []
585 simulated_wattage = []
586 motor_inverter_voltages = []
587 voltage_left = []
588 voltage_right = []
589 simulated_motor_heat_wattages = []
590 simulated_motor_wattage = []
591
592 max_motor_currents = []
593 overall_currents = []
594 simulated_battery_wattage = []
595
596 # Distance in meters to call 1/2 field.
597 kSprintDistance = 8.0
598
Austin Schuhcaed7362024-01-13 15:29:05 -0800599 kMaxBreakerCurrent = 220
600
Austin Schuh566c82b2023-01-27 20:49:08 -0800601 vbat = 12.6
602 # Measured resistance of the battery, pd board, and breakers.
603 Rw = 0.023
604 top_speed = drivetrain.free_speed * (drivetrain.Gr +
605 drivetrain.Gl) / 2.0 * drivetrain.r
606
607 passed_distance = False
608 max_breaker_current = 0
609 heat_energy_usage = 0.0
610 for index in range(800):
611 # Current per side
612 left_traction_current = (drivetrain.mass / 2.0 *
613 drivetrain_params.coefficient_of_friction *
614 9.81 * drivetrain.r * drivetrain.Gl /
615 drivetrain.Kt)
616 right_traction_current = (drivetrain.mass / 2.0 *
617 drivetrain_params.coefficient_of_friction *
618 9.81 * drivetrain.r * drivetrain.Gr /
619 drivetrain.Kt)
620
621 # Detect if we've traveled over the sprint distance and report stats.
622 if (drivetrain.X[0, 0] + drivetrain.X[2, 0]) / 2.0 > kSprintDistance:
623 if not passed_distance:
624 velocity = (drivetrain.X[1, 0] + drivetrain.X[3, 0]) / 2.0
625 print("Took", index * drivetrain.dt,
626 "to pass 1/2 field, going", velocity, "m/s,",
627 velocity / 0.0254 / 12.0, "Traction limit current",
628 left_traction_current / drivetrain_params.num_motors,
629 "max breaker current", max_breaker_current, "top speed",
630 top_speed, "m/s", top_speed / 0.0254 / 12.0,
631 "fps, gear ratio", drivetrain.Gl, "heat energy",
632 heat_energy_usage)
633 passed_distance = True
634
635 bemf_left = drivetrain.X[
636 1, 0] / drivetrain.r / drivetrain.Gl / drivetrain.Kv
637 bemf_right = drivetrain.X[
638 3, 0] / drivetrain.r / drivetrain.Gr / drivetrain.Kv
639
640 # Max current we could push through the motors is what we would get if
641 # we short the battery through the battery resistance into the motor.
Austin Schuhcaed7362024-01-13 15:29:05 -0800642 bemf = (bemf_left + bemf_right) / 2.0
643 max_motor_current = (vbat - bemf) / (Rw + drivetrain.resistance / 2.0)
Austin Schuh566c82b2023-01-27 20:49:08 -0800644
645 max_motor_currents.append(max_motor_current /
646 (drivetrain_params.num_motors * 2))
647
648 # From this current, we can compute the voltage we can apply.
649 # This is either the traction limit or the current limit.
Austin Schuhcaed7362024-01-13 15:29:05 -0800650 max_current_request_left = min(max_motor_current / 2,
651 left_traction_current)
652 max_current_request_right = min(max_motor_current / 2,
653 right_traction_current)
654 max_voltage_left = (bemf_left +
655 max_current_request_left * drivetrain.resistance)
656 max_voltage_right = (bemf_right +
657 max_current_request_right * drivetrain.resistance)
658
659 # Now, make sure we don't pull more power out of the battery than the
660 # breakers will let us pull. Do this by comparing the max power we can
661 # pull out of the battery with the requested power.
662 #
663 # TODO(austin): This all assumes the robot is symetric...
664 max_battery_wattage = kMaxBreakerCurrent * (vbat -
665 kMaxBreakerCurrent * Rw)
666 if (max_current_request_left * max_voltage_left +
667 max_current_request_right * max_voltage_right >
668 max_battery_wattage):
669 # Now solve the quadratic equation to figure out what the overall
670 # motor current can be which puts us at the max battery wattage.
671 max_motor_current = (
672 -bemf + math.sqrt(bemf * bemf + 4 * drivetrain.resistance /
673 2.0 * max_battery_wattage)) / (
674 2.0 * drivetrain.resistance / 2.0)
675 # Clip each side's currents to 1/2 of the max motor current since
676 # we know we are limited.
677 max_current_request_left = min(max_motor_current / 2.0,
678 max_current_request_left)
679 max_current_request_right = min(max_motor_current / 2.0,
680 max_current_request_right)
681 # And then update the voltages.
682 max_voltage_left = (
683 bemf_left + max_current_request_left * drivetrain.resistance)
684 max_voltage_right = (
685 bemf_right + max_current_request_right * drivetrain.resistance)
Austin Schuh566c82b2023-01-27 20:49:08 -0800686
687 simulated_left_position.append(drivetrain.X[0, 0])
688 simulated_left_velocity.append(drivetrain.X[1, 0])
689 simulated_right_position.append(drivetrain.X[2, 0])
690 simulated_right_velocity.append(drivetrain.X[3, 0])
691
692 U = numpy.matrix([[min(max_voltage_left, vbat)],
693 [min(max_voltage_right, vbat)]])
694
695 # Stator current
696 simulated_left_motor_current = (U[0, 0] -
697 bemf_left) / drivetrain.resistance
698 simulated_right_motor_current = (U[1, 0] -
699 bemf_right) / drivetrain.resistance
700
701 # And this gives us the power pushed into the motors.
702 power = (U[0, 0] * simulated_left_motor_current +
703 U[1, 0] * simulated_right_motor_current)
704
705 simulated_wattage.append(power)
706
707 # Solve for the voltage we'd have to supply to the input of the motor
708 # controller to generate the power required.
709 motor_inverter_voltage = (
710 vbat + numpy.sqrt(vbat * vbat - 4.0 * power * Rw)) / 2.0
711
712 overall_current = (vbat - motor_inverter_voltage) / Rw
713 overall_currents.append(overall_current)
714
715 motor_inverter_voltages.append(motor_inverter_voltage)
716
717 # Overall left and right currents at the breaker
718 simulated_left_breaker_current = (
719 simulated_left_motor_current /
720 drivetrain_params.num_motors) * U[0, 0] / motor_inverter_voltage
721 simulated_right_breaker_current = (
722 simulated_right_motor_current /
723 drivetrain_params.num_motors) * U[1, 0] / motor_inverter_voltage
724
725 simulated_left_motor_currents.append(simulated_left_motor_current /
726 drivetrain_params.num_motors)
727 simulated_left_breaker_currents.append(simulated_left_breaker_current)
728 simulated_right_motor_currents.append(simulated_right_motor_current /
729 drivetrain_params.num_motors)
730 simulated_right_breaker_currents.append(
731 simulated_right_breaker_current)
732
733 # Save out the peak battery current observed.
734 max_breaker_current = max(
735 max_breaker_current,
736 max(simulated_left_breaker_current,
737 simulated_right_breaker_current))
738
739 # Compute the heat burned in the battery
740 simulated_battery_heat_wattage = math.pow(
741 vbat - motor_inverter_voltage, 2.0) / Rw
742 simulated_battery_heat_wattages.append(simulated_battery_heat_wattage)
743
744 motor_heat_wattage = (math.pow(simulated_left_motor_current, 2.0) *
745 drivetrain.resistance +
746 math.pow(simulated_right_motor_current, 2.0) *
747 drivetrain.resistance)
748 simulated_motor_heat_wattages.append(motor_heat_wattage)
749
750 simulated_motor_wattage.append(simulated_left_motor_current * U[0, 0] +
751 simulated_right_motor_current * U[1, 0])
752
753 simulated_battery_wattage.append(vbat * overall_current)
754
755 # And then the overall energy outputted by the battery.
756 heat_energy_usage += (motor_heat_wattage +
757 simulated_battery_heat_wattage) * drivetrain.dt
758
759 voltage_left.append(U[0, 0])
760 voltage_right.append(U[1, 0])
761
762 drivetrain.Update(U)
763
764 t = [drivetrain.dt * x for x in range(len(simulated_left_position))]
765 pylab.rc('lines', linewidth=4)
766 pylab.subplot(3, 1, 1)
767 pylab.plot(t, simulated_left_position, label='left position')
768 pylab.plot(t, simulated_right_position, 'r--', label='right position')
769 pylab.plot(t, simulated_left_velocity, label='left velocity')
770 pylab.plot(t, simulated_right_velocity, label='right velocity')
771
772 pylab.suptitle('Acceleration Test\n12 Volt Step Input')
773 pylab.legend(loc='lower right')
774
775 pylab.subplot(3, 1, 2)
776
777 pylab.plot(t, simulated_left_motor_currents, label='left rotor current')
778 pylab.plot(t,
779 simulated_right_motor_currents,
780 'r--',
781 label='right rotor current')
782 pylab.plot(t,
783 simulated_left_breaker_currents,
784 label='left breaker current')
785 pylab.plot(t,
786 simulated_right_breaker_currents,
787 'r--',
788 label='right breaker current')
789 pylab.plot(t, motor_inverter_voltages, label='motor inverter voltage')
790 pylab.plot(t, voltage_left, label='left voltage')
791 pylab.plot(t, voltage_right, label='right voltage')
792 pylab.plot(t, max_motor_currents, label='max_currents')
793 pylab.legend(loc='lower right')
794
795 wattage_axis = pylab.subplot(3, 1, 3)
796 wattage_axis.plot(t, simulated_wattage, label='wattage')
797 wattage_axis.plot(t,
798 simulated_battery_heat_wattages,
799 label='battery wattage')
800 wattage_axis.plot(t,
801 simulated_motor_heat_wattages,
802 label='motor heat wattage')
803 wattage_axis.plot(t, simulated_motor_wattage, label='motor wattage')
804 wattage_axis.plot(t, simulated_battery_wattage, label='overall wattage')
805 pylab.legend(loc='upper left')
806 overall_current_axis = wattage_axis.twinx()
807 overall_current_axis.plot(t, overall_currents, 'c--', label='current')
808
809 pylab.legend(loc='lower right')
810
811 pylab.suptitle('Acceleration Test\n12 Volt Step Input\n%f fps' %
812 (top_speed / 0.0254 / 12.0, ))
813 pylab.show()
814
815
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800816def PlotDrivetrainMotions(drivetrain_params):
James Kuszmaul57754812024-03-16 19:26:24 -0700817 # Set up the gtk backend before running matplotlib.
818 matplotlib.use("GTK3Agg")
819
Austin Schuh1542ea32021-01-23 20:19:50 -0800820 # Test out the voltage error.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700821 drivetrain = KFDrivetrain(left_low=False,
822 right_low=False,
823 drivetrain_params=drivetrain_params)
Austin Schuh1542ea32021-01-23 20:19:50 -0800824 close_loop_left = []
825 close_loop_right = []
826 left_power = []
827 right_power = []
828 R = numpy.matrix([[0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800829 for _ in range(300):
Austin Schuh1542ea32021-01-23 20:19:50 -0800830 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
831 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800832 drivetrain.CorrectObserver(U)
833 drivetrain.PredictObserver(U)
Austin Schuh1542ea32021-01-23 20:19:50 -0800834 drivetrain.Update(U + numpy.matrix([[1.0], [1.0]]))
835 close_loop_left.append(drivetrain.X[0, 0])
836 close_loop_right.append(drivetrain.X[2, 0])
837 left_power.append(U[0, 0])
838 right_power.append(U[1, 0])
839
840 t = [drivetrain.dt * x for x in range(300)]
841 pylab.plot(t, close_loop_left, label='left position')
842 pylab.plot(t, close_loop_right, 'm--', label='right position')
843 pylab.plot(t, left_power, label='left power')
844 pylab.plot(t, right_power, '--', label='right power')
845 pylab.suptitle('Voltage error')
846 pylab.legend()
847 pylab.show()
848
Diana Burgessd0180f12018-03-21 21:24:17 -0700849 # Simulate the response of the system to a step input.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700850 drivetrain = KFDrivetrain(left_low=False,
851 right_low=False,
852 drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700853 simulated_left = []
854 simulated_right = []
Austin Schuh5ea48472021-02-02 20:46:41 -0800855 for _ in range(100):
Diana Burgessd0180f12018-03-21 21:24:17 -0700856 drivetrain.Update(numpy.matrix([[12.0], [12.0]]))
857 simulated_left.append(drivetrain.X[0, 0])
858 simulated_right.append(drivetrain.X[2, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800859
Diana Burgessd0180f12018-03-21 21:24:17 -0700860 pylab.rc('lines', linewidth=4)
861 pylab.plot(range(100), simulated_left, label='left position')
862 pylab.plot(range(100), simulated_right, 'r--', label='right position')
863 pylab.suptitle('Acceleration Test\n12 Volt Step Input')
864 pylab.legend(loc='lower right')
865 pylab.show()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800866
Diana Burgessd0180f12018-03-21 21:24:17 -0700867 # Simulate forwards motion.
Ravago Jones5127ccc2022-07-31 16:32:45 -0700868 drivetrain = KFDrivetrain(left_low=False,
869 right_low=False,
870 drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700871 close_loop_left = []
872 close_loop_right = []
873 left_power = []
874 right_power = []
Austin Schuh1542ea32021-01-23 20:19:50 -0800875 R = numpy.matrix([[1.0], [0.0], [1.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800876 for _ in range(300):
Diana Burgessd0180f12018-03-21 21:24:17 -0700877 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
878 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800879 drivetrain.CorrectObserver(U)
880 drivetrain.PredictObserver(U)
Diana Burgessd0180f12018-03-21 21:24:17 -0700881 drivetrain.Update(U)
882 close_loop_left.append(drivetrain.X[0, 0])
883 close_loop_right.append(drivetrain.X[2, 0])
884 left_power.append(U[0, 0])
885 right_power.append(U[1, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800886
Austin Schuh1542ea32021-01-23 20:19:50 -0800887 t = [drivetrain.dt * x for x in range(300)]
888 pylab.plot(t, close_loop_left, label='left position')
889 pylab.plot(t, close_loop_right, 'm--', label='right position')
890 pylab.plot(t, left_power, label='left power')
891 pylab.plot(t, right_power, '--', label='right power')
Diana Burgessd0180f12018-03-21 21:24:17 -0700892 pylab.suptitle('Linear Move\nLeft and Right Position going to 1')
893 pylab.legend()
894 pylab.show()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800895
Diana Burgessd0180f12018-03-21 21:24:17 -0700896 # Try turning in place
Austin Schuh1542ea32021-01-23 20:19:50 -0800897 drivetrain = KFDrivetrain(drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700898 close_loop_left = []
899 close_loop_right = []
Austin Schuh1542ea32021-01-23 20:19:50 -0800900 R = numpy.matrix([[-1.0], [0.0], [1.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800901 for _ in range(200):
Diana Burgessd0180f12018-03-21 21:24:17 -0700902 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
903 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800904 drivetrain.CorrectObserver(U)
905 drivetrain.PredictObserver(U)
Diana Burgessd0180f12018-03-21 21:24:17 -0700906 drivetrain.Update(U)
907 close_loop_left.append(drivetrain.X[0, 0])
908 close_loop_right.append(drivetrain.X[2, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800909
Diana Burgessd0180f12018-03-21 21:24:17 -0700910 pylab.plot(range(200), close_loop_left, label='left position')
911 pylab.plot(range(200), close_loop_right, label='right position')
912 pylab.suptitle(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800913 'Angular Move\nLeft position going to -1 and right position going to 1'
914 )
Diana Burgessd0180f12018-03-21 21:24:17 -0700915 pylab.legend(loc='center right')
916 pylab.show()
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800917
Diana Burgessd0180f12018-03-21 21:24:17 -0700918 # Try turning just one side.
Austin Schuh1542ea32021-01-23 20:19:50 -0800919 drivetrain = KFDrivetrain(drivetrain_params=drivetrain_params)
Diana Burgessd0180f12018-03-21 21:24:17 -0700920 close_loop_left = []
921 close_loop_right = []
Austin Schuh1542ea32021-01-23 20:19:50 -0800922 R = numpy.matrix([[0.0], [0.0], [1.0], [0.0], [0.0], [0.0], [0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800923 for _ in range(300):
Diana Burgessd0180f12018-03-21 21:24:17 -0700924 U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat), drivetrain.U_min,
925 drivetrain.U_max)
Austin Schuh64433f12022-02-21 19:40:38 -0800926 drivetrain.CorrectObserver(U)
927 drivetrain.PredictObserver(U)
Diana Burgessd0180f12018-03-21 21:24:17 -0700928 drivetrain.Update(U)
929 close_loop_left.append(drivetrain.X[0, 0])
930 close_loop_right.append(drivetrain.X[2, 0])
Campbell Crowley33e0e3d2017-12-27 17:55:40 -0800931
Diana Burgessd0180f12018-03-21 21:24:17 -0700932 pylab.plot(range(300), close_loop_left, label='left position')
933 pylab.plot(range(300), close_loop_right, label='right position')
934 pylab.suptitle(
935 'Pivot\nLeft position not changing and right position going to 1')
936 pylab.legend(loc='center right')
937 pylab.show()