blob: d2e7bbfc360c4688e3b3c6754afc06ce6dba3009 [file] [log] [blame]
Ben Fredrickson6b5ba792015-01-25 17:14:40 -08001#include "frc971/control_loops/fridge/fridge.h"
2
Austin Schuh703b8d42015-02-01 14:56:34 -08003#include <cmath>
4
Ben Fredrickson6b5ba792015-01-25 17:14:40 -08005#include "aos/common/controls/control_loops.q.h"
6#include "aos/common/logging/logging.h"
7
8#include "frc971/control_loops/fridge/elevator_motor_plant.h"
9#include "frc971/control_loops/fridge/arm_motor_plant.h"
Austin Schuh703b8d42015-02-01 14:56:34 -080010#include "frc971/zeroing/zeroing.h"
11
12#include "frc971/constants.h"
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080013
14namespace frc971 {
15namespace control_loops {
16
Austin Schuh703b8d42015-02-01 14:56:34 -080017constexpr double Fridge::dt;
18
19namespace {
20constexpr double kZeroingVoltage = 1.0;
21constexpr double kElevatorZeroingVelocity = 0.1;
22constexpr double kArmZeroingVelocity = 0.1;
23} // namespace
24
25
26void CappedStateFeedbackLoop::CapU() {
27 // TODO(austin): Use Campbell's code.
28 if (mutable_U(0, 0) > max_voltage_) {
29 mutable_U(0, 0) = max_voltage_;
30 }
31 if (mutable_U(1, 0) > max_voltage_) {
32 mutable_U(1, 0) = max_voltage_;
33 }
34 if (mutable_U(0, 0) < -max_voltage_) {
35 mutable_U(0, 0) = -max_voltage_;
36 }
37 if (mutable_U(1, 0) < -max_voltage_) {
38 mutable_U(1, 0) = -max_voltage_;
39 }
40}
41
42Eigen::Matrix<double, 2, 1>
43CappedStateFeedbackLoop::UnsaturateOutputGoalChange() {
44 // Compute the K matrix used to compensate for position errors.
45 Eigen::Matrix<double, 2, 2> Kp;
46 Kp.setZero();
47 Kp.col(0) = K().col(0);
48 Kp.col(1) = K().col(2);
49
50 Eigen::Matrix<double, 2, 2> Kp_inv = Kp.inverse();
51
52 // Compute how much we need to change R in order to achieve the change in U
53 // that was observed.
54 Eigen::Matrix<double, 2, 1> deltaR = -Kp_inv * (U_uncapped() - U());
55 return deltaR;
56}
57
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080058Fridge::Fridge(control_loops::FridgeQueue *fridge)
59 : aos::controls::ControlLoop<control_loops::FridgeQueue>(fridge),
Austin Schuh703b8d42015-02-01 14:56:34 -080060 arm_loop_(new CappedStateFeedbackLoop(
61 StateFeedbackLoop<4, 2, 2>(MakeArmLoop()))),
62 elevator_loop_(new CappedStateFeedbackLoop(
63 StateFeedbackLoop<4, 2, 2>(MakeElevatorLoop()))),
64 left_arm_estimator_(constants::GetValues().left_arm_zeroing_constants),
65 right_arm_estimator_(constants::GetValues().right_arm_zeroing_constants),
66 left_elevator_estimator_(
67 constants::GetValues().left_elevator_zeroing_constants),
68 right_elevator_estimator_(
69 constants::GetValues().right_elevator_zeroing_constants) {}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080070
Austin Schuh703b8d42015-02-01 14:56:34 -080071void Fridge::UpdateZeroingState() {
72 if (left_elevator_estimator_.offset_ratio_ready() < 0.5 ||
73 right_elevator_estimator_.offset_ratio_ready() < 0.5 ||
74 left_arm_estimator_.offset_ratio_ready() < 0.5 ||
75 right_arm_estimator_.offset_ratio_ready() < 0.5) {
76 state_ = INITIALIZING;
77 } else if (!left_elevator_estimator_.zeroed() ||
78 !right_elevator_estimator_.zeroed()) {
79 state_ = ZEROING_ELEVATOR;
80 } else if (!left_arm_estimator_.zeroed() ||
81 !right_arm_estimator_.zeroed()) {
82 state_ = ZEROING_ARM;
83 } else {
84 state_ = RUNNING;
85 }
86}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080087
Austin Schuh703b8d42015-02-01 14:56:34 -080088void Fridge::Correct() {
89 {
90 Eigen::Matrix<double, 2, 1> Y;
91 Y << left_elevator(), right_elevator();
92 elevator_loop_->Correct(Y);
93 }
94
95 {
96 Eigen::Matrix<double, 2, 1> Y;
97 Y << left_arm(), right_arm();
98 arm_loop_->Correct(Y);
99 }
100}
101
102void Fridge::SetElevatorOffset(double left_offset, double right_offset) {
103 double left_doffset = left_offset - left_elevator_offset_;
104 double right_doffset = right_offset - right_elevator_offset_;
105
106 // Adjust the average height and height difference between the two sides.
107 // The derivatives of both should not need to be updated since the speeds
108 // haven't changed.
109 // The height difference is calculated as left - right, not right - left.
110 elevator_loop_->mutable_X_hat(0, 0) += (left_doffset + right_doffset) / 2;
111 elevator_loop_->mutable_X_hat(2, 0) += (left_doffset - right_doffset) / 2;
112
113 // Modify the zeroing goal.
114 elevator_goal_ += (left_doffset + right_doffset) / 2;
115
116 // Update the cached offset values to the actual values.
117 left_elevator_offset_ = left_offset;
118 right_elevator_offset_ = right_offset;
119}
120
121void Fridge::SetArmOffset(double left_offset, double right_offset) {
122 double left_doffset = left_offset - left_arm_offset_;
123 double right_doffset = right_offset - right_arm_offset_;
124
125 // Adjust the average angle and angle difference between the two sides.
126 // The derivatives of both should not need to be updated since the speeds
127 // haven't changed.
128 arm_loop_->mutable_X_hat(0, 0) += (left_doffset + right_doffset) / 2;
129 arm_loop_->mutable_X_hat(2, 0) += (left_doffset - right_doffset) / 2;
130
131 // Modify the zeroing goal.
132 arm_goal_ += (left_doffset + right_doffset) / 2;
133
134 // Update the cached offset values to the actual values.
135 left_arm_offset_ = left_offset;
136 right_arm_offset_ = right_offset;
137}
138
139double Fridge::estimated_left_elevator() {
140 return current_position_.elevator.left.encoder +
141 left_elevator_estimator_.offset();
142}
143double Fridge::estimated_right_elevator() {
144 return current_position_.elevator.right.encoder +
145 right_elevator_estimator_.offset();
146}
147
148double Fridge::estimated_elevator() {
149 return (estimated_left_elevator() + estimated_right_elevator()) / 2.0;
150}
151
152double Fridge::estimated_left_arm() {
153 return current_position_.elevator.left.encoder + left_arm_estimator_.offset();
154}
155double Fridge::estimated_right_arm() {
156 return current_position_.elevator.right.encoder +
157 right_arm_estimator_.offset();
158}
159double Fridge::estimated_arm() {
160 return (estimated_left_arm() + estimated_right_arm()) / 2.0;
161}
162
163double Fridge::left_elevator() {
164 return current_position_.elevator.left.encoder + left_elevator_offset_;
165}
166double Fridge::right_elevator() {
167 return current_position_.elevator.right.encoder + right_elevator_offset_;
168}
169
170double Fridge::elevator() { return (left_elevator() + right_elevator()) / 2.0; }
171
172double Fridge::left_arm() {
173 return current_position_.arm.left.encoder + left_arm_offset_;
174}
175double Fridge::right_arm() {
176 return current_position_.arm.right.encoder + right_arm_offset_;
177}
178double Fridge::arm() { return (left_arm() + right_arm()) / 2.0; }
179
180double Fridge::elevator_zeroing_velocity() {
181 double average_elevator =
182 (constants::GetValues().fridge.elevator.lower_limit +
183 constants::GetValues().fridge.elevator.upper_limit) /
184 2.0;
185
186 const double pulse_width = ::std::max(
187 constants::GetValues().left_elevator_zeroing_constants.index_difference,
188 constants::GetValues().right_elevator_zeroing_constants.index_difference);
189
190 if (elevator_zeroing_velocity_ == 0) {
191 if (estimated_elevator() > average_elevator) {
192 elevator_zeroing_velocity_ = -kElevatorZeroingVelocity;
193 } else {
194 elevator_zeroing_velocity_ = kElevatorZeroingVelocity;
195 }
196 } else if (elevator_zeroing_velocity_ > 0 &&
197 estimated_elevator() > average_elevator + 2 * pulse_width) {
198 elevator_zeroing_velocity_ = -kElevatorZeroingVelocity;
199 } else if (elevator_zeroing_velocity_ < 0 &&
200 estimated_elevator() < average_elevator - 2 * pulse_width) {
201 elevator_zeroing_velocity_ = kElevatorZeroingVelocity;
202 }
203 return elevator_zeroing_velocity_;
204}
205
206double Fridge::arm_zeroing_velocity() {
207 const double average_arm = (constants::GetValues().fridge.arm.lower_limit +
208 constants::GetValues().fridge.arm.upper_limit) /
209 2.0;
210 const double pulse_width = ::std::max(
211 constants::GetValues().right_arm_zeroing_constants.index_difference,
212 constants::GetValues().left_arm_zeroing_constants.index_difference);
213
214 if (arm_zeroing_velocity_ == 0) {
215 if (estimated_arm() > average_arm) {
216 arm_zeroing_velocity_ = -kArmZeroingVelocity;
217 } else {
218 arm_zeroing_velocity_ = kArmZeroingVelocity;
219 }
220 } else if (arm_zeroing_velocity_ > 0.0 &&
221 estimated_arm() > average_arm + 2.0 * pulse_width) {
222 arm_zeroing_velocity_ = -kArmZeroingVelocity;
223 } else if (arm_zeroing_velocity_ < 0.0 &&
224 estimated_arm() < average_arm - 2.0 * pulse_width) {
225 arm_zeroing_velocity_ = kArmZeroingVelocity;
226 }
227 return arm_zeroing_velocity_;
228}
229
230void Fridge::RunIteration(const control_loops::FridgeQueue::Goal *goal,
231 const control_loops::FridgeQueue::Position *position,
232 control_loops::FridgeQueue::Output *output,
233 control_loops::FridgeQueue::Status *status) {
234 // Get a reference to the constants struct since we use it so often in this
235 // code.
236 auto &values = constants::GetValues();
237
238 // Bool to track if we should turn the motors on or not.
239 bool disable = output == nullptr;
240 double elevator_goal_velocity = 0.0;
241 double arm_goal_velocity = 0.0;
242
243 // Save the current position so it can be used easily in the class.
244 current_position_ = *position;
245
246 left_elevator_estimator_.UpdateEstimate(position->elevator.left);
247 right_elevator_estimator_.UpdateEstimate(position->elevator.right);
248 left_arm_estimator_.UpdateEstimate(position->arm.left);
249 right_arm_estimator_.UpdateEstimate(position->arm.right);
250
251 if (state_ != UNINITIALIZED) {
252 Correct();
253 }
254
255 // Zeroing will work as follows:
256 // At startup, record the offset of the two halves of the two subsystems.
257 // Then, start moving the elevator towards the center until both halves are
258 // zeroed.
259 // Then, start moving the claw towards the center until both halves are
260 // zeroed.
261 // Then, done!
262
263 // We'll then need code to do sanity checking on values.
264
265 // Now, we need to figure out which way to go.
266
267 switch (state_) {
268 case UNINITIALIZED:
269 LOG(DEBUG, "Uninitialized\n");
270 // Startup. Assume that we are at the origin everywhere.
271 // This records the encoder offset between the two sides of the elevator.
272 left_elevator_offset_ = -position->elevator.left.encoder;
273 right_elevator_offset_ = -position->elevator.right.encoder;
274 left_arm_offset_ = -position->arm.left.encoder;
275 right_arm_offset_ = -position->arm.right.encoder;
276 Correct();
277 state_ = INITIALIZING;
278 disable = true;
279 break;
280
281 case INITIALIZING:
282 LOG(DEBUG, "Waiting for accurate initial position.\n");
283 disable = true;
284 // Update state_ to accurately represent the state of the zeroing
285 // estimators.
286 UpdateZeroingState();
287 if (state_ != INITIALIZING) {
288 // Set the goals to where we are now.
289 elevator_goal_ = elevator();
290 arm_goal_ = arm();
291 }
292 break;
293
294 case ZEROING_ELEVATOR:
295 LOG(DEBUG, "Zeroing elevator\n");
296 elevator_goal_velocity = elevator_zeroing_velocity();
297 elevator_goal_ += elevator_goal_velocity * dt;
298
299 // Update state_ to accurately represent the state of the zeroing
300 // estimators.
301 UpdateZeroingState();
302 if (left_elevator_estimator_.zeroed() &&
303 right_elevator_estimator_.zeroed()) {
304 SetElevatorOffset(left_elevator_estimator_.offset(),
305 right_elevator_estimator_.offset());
306 LOG(DEBUG, "Zeroed the elevator!\n");
307 }
308 break;
309
310 case ZEROING_ARM:
311 LOG(DEBUG, "Zeroing the arm\n");
312 arm_goal_velocity = arm_zeroing_velocity();
313 arm_goal_ += arm_goal_velocity * dt;
314
315 // Update state_ to accurately represent the state of the zeroing
316 // estimators.
317 UpdateZeroingState();
318 if (left_arm_estimator_.zeroed() && right_arm_estimator_.zeroed()) {
319 SetArmOffset(left_arm_estimator_.offset(),
320 right_arm_estimator_.offset());
321 LOG(DEBUG, "Zeroed the arm!\n");
322 }
323 break;
324
325 case RUNNING:
326 LOG(DEBUG, "Running!\n");
327 arm_goal_velocity = goal->angular_velocity;
328 elevator_goal_velocity = goal->velocity;
329
330 // Update state_ to accurately represent the state of the zeroing
331 // estimators.
332 UpdateZeroingState();
333 arm_goal_ = goal->angle;
334 elevator_goal_ = goal->height;
335
336 if (state_ != RUNNING && state_ != ESTOP) {
337 state_ = UNINITIALIZED;
338 }
339 break;
340
341 case ESTOP:
342 LOG(ERROR, "Estop\n");
343 disable = true;
344 break;
345 }
346
347 // Commence death if either left/right tracking error gets too big. This
348 // should run immediately after the SetArmOffset and SetElevatorOffset
349 // functions to double-check that the hardware is in a sane state.
350 if (::std::abs(left_arm() - right_arm()) >=
351 values.max_allowed_left_right_arm_difference ||
352 ::std::abs(left_elevator() - right_elevator()) >=
353 values.max_allowed_left_right_elevator_difference) {
354 LOG(ERROR, "The two sides went too far apart\n");
355
356 // Indicate an ESTOP condition and stop the motors.
357 state_ = ESTOP;
358 disable = true;
359 }
360
361 // Limit the goals so we can't exceed the hardware limits if we are RUNNING.
362 if (state_ == RUNNING) {
363 // Limit the arm goal to min/max allowable angles.
364 if (arm_goal_ >= values.fridge.arm.upper_limit) {
365 LOG(WARNING, "Arm goal above limit, %f > %f\n", arm_goal_,
366 values.fridge.arm.upper_limit);
367 arm_goal_ = values.fridge.arm.upper_limit;
368 }
369 if (arm_goal_ <= values.fridge.arm.lower_limit) {
370 LOG(WARNING, "Arm goal below limit, %f < %f\n", arm_goal_,
371 values.fridge.arm.lower_limit);
372 arm_goal_ = values.fridge.arm.lower_limit;
373 }
374
375 // Limit the elevator goal to min/max allowable heights.
376 if (elevator_goal_ >= values.fridge.elevator.upper_limit) {
377 LOG(WARNING, "Elevator goal above limit, %f > %f\n", elevator_goal_,
378 values.fridge.elevator.upper_limit);
379 elevator_goal_ = values.fridge.elevator.upper_limit;
380 }
381 if (elevator_goal_ <= values.fridge.elevator.lower_limit) {
382 LOG(WARNING, "Elevator goal below limit, %f < %f\n", elevator_goal_,
383 values.fridge.elevator.lower_limit);
384 elevator_goal_ = values.fridge.elevator.lower_limit;
385 }
386 }
387
388 // Check the lower level hardware limit as well.
389 if (state_ == RUNNING) {
390 if (left_arm() >= values.fridge.arm.upper_hard_limit ||
391 left_arm() <= values.fridge.arm.lower_hard_limit) {
392 LOG(ERROR, "Left arm at %f out of bounds [%f, %f], ESTOPing\n",
393 left_arm(), values.fridge.arm.lower_hard_limit,
394 values.fridge.arm.upper_hard_limit);
395 state_ = ESTOP;
396 }
397
398 if (right_arm() >= values.fridge.arm.upper_hard_limit ||
399 right_arm() <= values.fridge.arm.lower_hard_limit) {
400 LOG(ERROR, "Right arm at %f out of bounds [%f, %f], ESTOPing\n",
401 right_arm(), values.fridge.arm.lower_hard_limit,
402 values.fridge.arm.upper_hard_limit);
403 state_ = ESTOP;
404 }
405
406 if (left_elevator() >= values.fridge.elevator.upper_hard_limit ||
407 left_elevator() <= values.fridge.elevator.lower_hard_limit) {
408 LOG(ERROR, "Left elevator at %f out of bounds [%f, %f], ESTOPing\n",
409 left_elevator(), values.fridge.elevator.lower_hard_limit,
410 values.fridge.elevator.upper_hard_limit);
411 state_ = ESTOP;
412 }
413
414 if (right_elevator() >= values.fridge.elevator.upper_hard_limit ||
415 right_elevator() <= values.fridge.elevator.lower_hard_limit) {
416 LOG(ERROR, "Right elevator at %f out of bounds [%f, %f], ESTOPing\n",
417 right_elevator(), values.fridge.elevator.lower_hard_limit,
418 values.fridge.elevator.upper_hard_limit);
419 state_ = ESTOP;
420 }
421 }
422
423 // Set the goals.
424 arm_loop_->mutable_R() << arm_goal_, arm_goal_velocity, 0.0, 0.0;
425 elevator_loop_->mutable_R() << elevator_goal_, elevator_goal_velocity, 0.0,
426 0.0;
427
428 const double max_voltage = state_ == RUNNING ? 12.0 : kZeroingVoltage;
429 arm_loop_->set_max_voltage(max_voltage);
430 elevator_loop_->set_max_voltage(max_voltage);
431
432 if (state_ == ESTOP) {
433 disable = true;
434 }
435 arm_loop_->Update(disable);
436 elevator_loop_->Update(disable);
437
438 if (state_ == INITIALIZING || state_ == ZEROING_ELEVATOR ||
439 state_ == ZEROING_ARM) {
440 if (arm_loop_->U() != arm_loop_->U_uncapped()) {
441 Eigen::Matrix<double, 2, 1> deltaR =
442 arm_loop_->UnsaturateOutputGoalChange();
443
444 // Move the average arm goal by the amount observed.
445 LOG(WARNING, "Moving arm goal by %f to handle saturation\n",
446 deltaR(0, 0));
447 arm_goal_ += deltaR(0, 0);
448 }
449
450 if (elevator_loop_->U() != elevator_loop_->U_uncapped()) {
451 Eigen::Matrix<double, 2, 1> deltaR =
452 elevator_loop_->UnsaturateOutputGoalChange();
453
454 // Move the average elevator goal by the amount observed.
455 LOG(WARNING, "Moving elevator goal by %f to handle saturation\n",
456 deltaR(0, 0));
457 elevator_goal_ += deltaR(0, 0);
458 }
459 }
460
461 if (output) {
462 output->left_arm = arm_loop_->U(0, 0);
463 output->right_arm = arm_loop_->U(1, 0);
464 output->left_elevator = elevator_loop_->U(0, 0);
465 output->right_elevator = elevator_loop_->U(1, 0);
466 output->grabbers = goal->grabbers;
467 }
468
469 // TODO(austin): Populate these fully.
470 status->zeroed = false;
471 status->done = false;
472 status->angle = arm_loop_->X_hat(0, 0);
473 status->height = elevator_loop_->X_hat(0, 0);
474 status->grabbers = goal->grabbers;
475 status->estopped = (state_ == ESTOP);
476 last_state_ = state_;
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800477}
478
479} // namespace control_loops
480} // namespace frc971