blob: d16e8a13f735e538fccd59122a87d5b3f509010b [file] [log] [blame]
Comran Morshed5323ecb2015-12-26 20:50:55 +00001#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_POLYDRIVETRAIN_H_
2#define FRC971_CONTROL_LOOPS_DRIVETRAIN_POLYDRIVETRAIN_H_
3
4#include "aos/common/controls/polytope.h"
5
Austin Schuhbcce26a2018-03-26 23:41:24 -07006#include "aos/common/commonmath.h"
7#include "frc971/control_loops/coerce_goal.h"
Austin Schuh093535c2016-03-05 23:21:00 -08008#include "frc971/control_loops/drivetrain/gear.h"
Austin Schuhbcce26a2018-03-26 23:41:24 -07009#ifdef __linux__
Comran Morshed5323ecb2015-12-26 20:50:55 +000010#include "frc971/control_loops/drivetrain/drivetrain.q.h"
Austin Schuhbcce26a2018-03-26 23:41:24 -070011#include "aos/common/logging/logging.h"
12#include "aos/common/logging/matrix_logging.h"
13#include "aos/common/logging/queue_logging.h"
14#include "aos/common/messages/robot_state.q.h"
15#else
16#include "frc971/control_loops/drivetrain/drivetrain_uc.q.h"
17#endif // __linux__
Comran Morshed5323ecb2015-12-26 20:50:55 +000018#include "frc971/control_loops/state_feedback_loop.h"
19#include "frc971/control_loops/drivetrain/drivetrain_config.h"
20
21namespace frc971 {
22namespace control_loops {
23namespace drivetrain {
24
Austin Schuhbcce26a2018-03-26 23:41:24 -070025template <typename Scalar = double>
Comran Morshed5323ecb2015-12-26 20:50:55 +000026class PolyDrivetrain {
27 public:
Austin Schuhbcce26a2018-03-26 23:41:24 -070028 PolyDrivetrain(const DrivetrainConfig<Scalar> &dt_config,
29 StateFeedbackLoop<7, 2, 4, Scalar> *kf);
Comran Morshed5323ecb2015-12-26 20:50:55 +000030
Austin Schuhc5fceb82017-02-25 16:24:12 -080031 int controller_index() const { return loop_->index(); }
Comran Morshed5323ecb2015-12-26 20:50:55 +000032
Comran Morshed5323ecb2015-12-26 20:50:55 +000033 // Computes the speed of the motor given the hall effect position and the
34 // speed of the robot.
Austin Schuhbcce26a2018-03-26 23:41:24 -070035 Scalar MotorSpeed(const constants::ShifterHallEffect &hall_effect,
36 Scalar shifter_position, Scalar velocity, Gear gear);
Comran Morshed5323ecb2015-12-26 20:50:55 +000037
Austin Schuh093535c2016-03-05 23:21:00 -080038 void SetGoal(const ::frc971::control_loops::DrivetrainQueue::Goal &goal);
Comran Morshed5323ecb2015-12-26 20:50:55 +000039
40 void SetPosition(
Austin Schuh093535c2016-03-05 23:21:00 -080041 const ::frc971::control_loops::DrivetrainQueue::Position *position,
42 Gear left_gear, Gear right_gear);
Comran Morshed5323ecb2015-12-26 20:50:55 +000043
Austin Schuhbcce26a2018-03-26 23:41:24 -070044 Scalar FilterVelocity(Scalar throttle) const;
Comran Morshed5323ecb2015-12-26 20:50:55 +000045
Austin Schuhbcce26a2018-03-26 23:41:24 -070046 Scalar MaxVelocity();
Comran Morshed5323ecb2015-12-26 20:50:55 +000047
48 void Update();
49
Austin Schuh093535c2016-03-05 23:21:00 -080050 void SetOutput(::frc971::control_loops::DrivetrainQueue::Output *output);
Comran Morshed5323ecb2015-12-26 20:50:55 +000051
Austin Schuh41565602016-02-28 20:10:49 -080052 void PopulateStatus(::frc971::control_loops::DrivetrainQueue::Status *status);
53
Austin Schuh093535c2016-03-05 23:21:00 -080054 // Computes the next state of a shifter given the current state and the
55 // requested state.
56 Gear UpdateSingleGear(Gear requested_gear, Gear current_gear);
57
Austin Schuhbcce26a2018-03-26 23:41:24 -070058 // Returns the current estimated velocity in m/s.
59 Scalar velocity() const {
60 return (loop_->mutable_X_hat()(0) + loop_->mutable_X_hat()(1)) / 2.0;
61 }
62
Comran Morshed5323ecb2015-12-26 20:50:55 +000063 private:
Austin Schuhbcce26a2018-03-26 23:41:24 -070064 StateFeedbackLoop<7, 2, 4, Scalar> *kf_;
Comran Morshed5323ecb2015-12-26 20:50:55 +000065
Austin Schuhbcce26a2018-03-26 23:41:24 -070066 const ::aos::controls::HVPolytope<2, 4, 4, Scalar> U_Poly_;
Comran Morshed5323ecb2015-12-26 20:50:55 +000067
Austin Schuhbcce26a2018-03-26 23:41:24 -070068 ::std::unique_ptr<StateFeedbackLoop<2, 2, 2, Scalar>> loop_;
Comran Morshed5323ecb2015-12-26 20:50:55 +000069
Austin Schuhbcce26a2018-03-26 23:41:24 -070070 const Scalar ttrust_;
71 Scalar wheel_;
72 Scalar throttle_;
Comran Morshed5323ecb2015-12-26 20:50:55 +000073 bool quickturn_;
Austin Schuh093535c2016-03-05 23:21:00 -080074
Comran Morshed5323ecb2015-12-26 20:50:55 +000075 Gear left_gear_;
76 Gear right_gear_;
Austin Schuh093535c2016-03-05 23:21:00 -080077
Comran Morshed5323ecb2015-12-26 20:50:55 +000078 ::frc971::control_loops::DrivetrainQueue::Position last_position_;
79 ::frc971::control_loops::DrivetrainQueue::Position position_;
80 int counter_;
Austin Schuhbcce26a2018-03-26 23:41:24 -070081 DrivetrainConfig<Scalar> dt_config_;
Austin Schuh41565602016-02-28 20:10:49 -080082
Austin Schuhbcce26a2018-03-26 23:41:24 -070083 Scalar goal_left_velocity_ = 0.0;
84 Scalar goal_right_velocity_ = 0.0;
Kyle Stachowicz2f3c20f2017-07-13 16:04:05 -070085
86 // Stored from the last iteration, for logging shifting logic.
Austin Schuhbcce26a2018-03-26 23:41:24 -070087 Scalar left_motor_speed_ = 0.0;
88 Scalar right_motor_speed_ = 0.0;
89 Scalar current_left_velocity_ = 0.0;
90 Scalar current_right_velocity_ = 0.0;
Comran Morshed5323ecb2015-12-26 20:50:55 +000091};
92
Austin Schuhbcce26a2018-03-26 23:41:24 -070093template <typename Scalar>
94PolyDrivetrain<Scalar>::PolyDrivetrain(
95 const DrivetrainConfig<Scalar> &dt_config,
96 StateFeedbackLoop<7, 2, 4, Scalar> *kf)
97 : kf_(kf),
98 U_Poly_((Eigen::Matrix<Scalar, 4, 2>() << /*[[*/ 1, 0 /*]*/,
99 /*[*/ -1, 0 /*]*/,
100 /*[*/ 0, 1 /*]*/,
101 /*[*/ 0, -1 /*]]*/)
102 .finished(),
103 (Eigen::Matrix<Scalar, 4, 1>() << /*[[*/ 12 /*]*/,
104 /*[*/ 12 /*]*/,
105 /*[*/ 12 /*]*/,
106 /*[*/ 12 /*]]*/)
107 .finished(),
108 (Eigen::Matrix<Scalar, 2, 4>() << /*[[*/ 12, 12, -12, -12 /*]*/,
109 /*[*/ -12, 12, 12, -12 /*]*/)
110 .finished()),
111 loop_(new StateFeedbackLoop<2, 2, 2, Scalar>(
112 dt_config.make_v_drivetrain_loop())),
113 ttrust_(1.1),
114 wheel_(0.0),
115 throttle_(0.0),
116 quickturn_(false),
117 left_gear_(dt_config.default_high_gear ? Gear::HIGH : Gear::LOW),
118 right_gear_(dt_config.default_high_gear ? Gear::HIGH : Gear::LOW),
119 counter_(0),
120 dt_config_(dt_config) {
121 last_position_.Zero();
122 position_.Zero();
123}
124
125template <typename Scalar>
126Scalar PolyDrivetrain<Scalar>::MotorSpeed(
127 const constants::ShifterHallEffect &hall_effect, Scalar shifter_position,
128 Scalar velocity, Gear gear) {
129 const Scalar high_gear_speed =
130 velocity / dt_config_.high_gear_ratio / dt_config_.wheel_radius;
131 const Scalar low_gear_speed =
132 velocity / dt_config_.low_gear_ratio / dt_config_.wheel_radius;
133
134 if (shifter_position < hall_effect.clear_low) {
135 // We're in low gear, so return speed for that gear.
136 return low_gear_speed;
137 } else if (shifter_position > hall_effect.clear_high) {
138 // We're in high gear, so return speed for that gear.
139 return high_gear_speed;
140 }
141
142 // Not in gear, so speed-match to destination gear.
143 switch (gear) {
144 case Gear::HIGH:
145 case Gear::SHIFTING_UP:
146 return high_gear_speed;
147 case Gear::LOW:
148 case Gear::SHIFTING_DOWN:
149 default:
150 return low_gear_speed;
151 break;
152 }
153}
154
155template <typename Scalar>
156Gear PolyDrivetrain<Scalar>::UpdateSingleGear(Gear requested_gear,
157 Gear current_gear) {
158 const Gear shift_up =
159 (dt_config_.shifter_type == ShifterType::HALL_EFFECT_SHIFTER)
160 ? Gear::SHIFTING_UP
161 : Gear::HIGH;
162 const Gear shift_down =
163 (dt_config_.shifter_type == ShifterType::HALL_EFFECT_SHIFTER)
164 ? Gear::SHIFTING_DOWN
165 : Gear::LOW;
166 if (current_gear != requested_gear) {
167 if (IsInGear(current_gear)) {
168 if (requested_gear == Gear::HIGH) {
169 if (current_gear != Gear::HIGH) {
170 current_gear = shift_up;
171 }
172 } else {
173 if (current_gear != Gear::LOW) {
174 current_gear = shift_down;
175 }
176 }
177 } else {
178 if (requested_gear == Gear::HIGH && current_gear == Gear::SHIFTING_DOWN) {
179 current_gear = Gear::SHIFTING_UP;
180 } else if (requested_gear == Gear::LOW &&
181 current_gear == Gear::SHIFTING_UP) {
182 current_gear = Gear::SHIFTING_DOWN;
183 }
184 }
185 }
186 return current_gear;
187}
188
189template <typename Scalar>
190void PolyDrivetrain<Scalar>::SetGoal(
191 const ::frc971::control_loops::DrivetrainQueue::Goal &goal) {
192 const Scalar wheel = goal.wheel;
193 const Scalar throttle = goal.throttle;
194 const bool quickturn = goal.quickturn;
195 const bool highgear = goal.highgear;
196
197 // Apply a sin function that's scaled to make it feel better.
198 const Scalar angular_range = M_PI_2 * dt_config_.wheel_non_linearity;
199
200 wheel_ = sin(angular_range * wheel) / sin(angular_range);
201 wheel_ = sin(angular_range * wheel_) / sin(angular_range);
202 wheel_ = 2.0 * wheel - wheel_;
203 quickturn_ = quickturn;
204
205 if (quickturn_) {
206 wheel_ *= dt_config_.quickturn_wheel_multiplier;
207 } else {
208 wheel_ *= dt_config_.wheel_multiplier;
209 }
210
211 static const Scalar kThrottleDeadband = 0.05;
212 if (::std::abs(throttle) < kThrottleDeadband) {
213 throttle_ = 0;
214 } else {
215 throttle_ = copysign(
216 (::std::abs(throttle) - kThrottleDeadband) / (1.0 - kThrottleDeadband),
217 throttle);
218 }
219
220 Gear requested_gear = highgear ? Gear::HIGH : Gear::LOW;
221
222 left_gear_ = UpdateSingleGear(requested_gear, left_gear_);
223 right_gear_ = UpdateSingleGear(requested_gear, right_gear_);
224}
225
226template <typename Scalar>
227void PolyDrivetrain<Scalar>::SetPosition(
228 const ::frc971::control_loops::DrivetrainQueue::Position *position,
229 Gear left_gear, Gear right_gear) {
230 left_gear_ = left_gear;
231 right_gear_ = right_gear;
232 last_position_ = position_;
233 position_ = *position;
234}
235
236template <typename Scalar>
237Scalar PolyDrivetrain<Scalar>::FilterVelocity(Scalar throttle) const {
238 const Eigen::Matrix<Scalar, 2, 2> FF =
239 loop_->plant().B().inverse() *
240 (Eigen::Matrix<Scalar, 2, 2>::Identity() - loop_->plant().A());
241
242 constexpr int kHighGearController = 3;
243 const Eigen::Matrix<Scalar, 2, 2> FF_high =
244 loop_->plant().coefficients(kHighGearController).B.inverse() *
245 (Eigen::Matrix<Scalar, 2, 2>::Identity() -
246 loop_->plant().coefficients(kHighGearController).A);
247
248 ::Eigen::Matrix<Scalar, 1, 2> FF_sum = FF.colwise().sum();
249 int min_FF_sum_index;
250 const Scalar min_FF_sum = FF_sum.minCoeff(&min_FF_sum_index);
251 const Scalar min_K_sum = loop_->controller().K().col(min_FF_sum_index).sum();
252 const Scalar high_min_FF_sum = FF_high.col(0).sum();
253
254 const Scalar adjusted_ff_voltage =
255 ::aos::Clip(throttle * 12.0 * min_FF_sum / high_min_FF_sum, -12.0, 12.0);
256 return (adjusted_ff_voltage +
257 ttrust_ * min_K_sum * (loop_->X_hat(0, 0) + loop_->X_hat(1, 0)) /
258 2.0) /
259 (ttrust_ * min_K_sum + min_FF_sum);
260}
261
262template <typename Scalar>
263Scalar PolyDrivetrain<Scalar>::MaxVelocity() {
264 const Eigen::Matrix<Scalar, 2, 2> FF =
265 loop_->plant().B().inverse() *
266 (Eigen::Matrix<Scalar, 2, 2>::Identity() - loop_->plant().A());
267
268 constexpr int kHighGearController = 3;
269 const Eigen::Matrix<Scalar, 2, 2> FF_high =
270 loop_->plant().coefficients(kHighGearController).B.inverse() *
271 (Eigen::Matrix<Scalar, 2, 2>::Identity() -
272 loop_->plant().coefficients(kHighGearController).A);
273
274 ::Eigen::Matrix<Scalar, 1, 2> FF_sum = FF.colwise().sum();
275 int min_FF_sum_index;
276 const Scalar min_FF_sum = FF_sum.minCoeff(&min_FF_sum_index);
277 // const Scalar min_K_sum = loop_->K().col(min_FF_sum_index).sum();
278 const Scalar high_min_FF_sum = FF_high.col(0).sum();
279
280 const Scalar adjusted_ff_voltage =
281 ::aos::Clip(12.0 * min_FF_sum / high_min_FF_sum, -12.0, 12.0);
282 return adjusted_ff_voltage / min_FF_sum;
283}
284
285template <typename Scalar>
286void PolyDrivetrain<Scalar>::Update() {
287 if (dt_config_.loop_type == LoopType::CLOSED_LOOP) {
288 loop_->mutable_X_hat()(0, 0) = kf_->X_hat()(1, 0);
289 loop_->mutable_X_hat()(1, 0) = kf_->X_hat()(3, 0);
290 }
291
292 // TODO(austin): Observer for the current velocity instead of difference
293 // calculations.
294 ++counter_;
295
296 if (IsInGear(left_gear_) && IsInGear(right_gear_)) {
297 // FF * X = U (steady state)
298 const Eigen::Matrix<Scalar, 2, 2> FF =
299 loop_->plant().B().inverse() *
300 (Eigen::Matrix<Scalar, 2, 2>::Identity() - loop_->plant().A());
301
302 // Invert the plant to figure out how the velocity filter would have to
303 // work
304 // out in order to filter out the forwards negative inertia.
305 // This math assumes that the left and right power and velocity are
306 // equals,
307 // and that the plant is the same on the left and right.
308 const Scalar fvel = FilterVelocity(throttle_);
309
310 const Scalar sign_svel = wheel_ * ((fvel > 0.0) ? 1.0 : -1.0);
311 Scalar steering_velocity;
312 if (quickturn_) {
313 steering_velocity = wheel_ * MaxVelocity();
314 } else {
315 steering_velocity = ::std::abs(fvel) * wheel_;
316 }
317 const Scalar left_velocity = fvel - steering_velocity;
318 const Scalar right_velocity = fvel + steering_velocity;
319 goal_left_velocity_ = left_velocity;
320 goal_right_velocity_ = right_velocity;
321
322 // Integrate velocity to get the position.
323 // This position is used to get integral control.
324 loop_->mutable_R() << left_velocity, right_velocity;
325
326 if (!quickturn_) {
327 // K * R = w
328 Eigen::Matrix<Scalar, 1, 2> equality_k;
329 equality_k << 1 + sign_svel, -(1 - sign_svel);
330 const Scalar equality_w = 0.0;
331
332 // Construct a constraint on R by manipulating the constraint on U
333 ::aos::controls::HVPolytope<2, 4, 4, Scalar> R_poly_hv(
334 U_Poly_.static_H() * (loop_->controller().K() + FF),
335 U_Poly_.static_k() +
336 U_Poly_.static_H() * loop_->controller().K() * loop_->X_hat(),
337 (loop_->controller().K() + FF).inverse() *
338 ::aos::controls::ShiftPoints<2, 4, Scalar>(
339 U_Poly_.StaticVertices(),
340 loop_->controller().K() * loop_->X_hat()));
341
342 // Limit R back inside the box.
343 loop_->mutable_R() =
344 CoerceGoal<Scalar>(R_poly_hv, equality_k, equality_w, loop_->R());
345 }
346
347 const Eigen::Matrix<Scalar, 2, 1> FF_volts = FF * loop_->R();
348 const Eigen::Matrix<Scalar, 2, 1> U_ideal =
349 loop_->controller().K() * (loop_->R() - loop_->X_hat()) + FF_volts;
350
351 for (int i = 0; i < 2; i++) {
352 loop_->mutable_U()[i] = ::aos::Clip(U_ideal[i], -12, 12);
353 }
354
355 if (dt_config_.loop_type == LoopType::OPEN_LOOP) {
356 loop_->mutable_X_hat() =
357 loop_->plant().A() * loop_->X_hat() + loop_->plant().B() * loop_->U();
358 }
359
360 // Housekeeping: set the shifting logging values to zero, because we're not shifting
361 left_motor_speed_ = 0.0;
362 right_motor_speed_ = 0.0;
363 current_left_velocity_ = 0.0;
364 current_right_velocity_ = 0.0;
365 } else {
366 current_left_velocity_ =
367 (position_.left_encoder - last_position_.left_encoder) / dt_config_.dt;
368 current_right_velocity_ =
369 (position_.right_encoder - last_position_.right_encoder) /
370 dt_config_.dt;
371 left_motor_speed_ =
372 MotorSpeed(dt_config_.left_drive, position_.left_shifter_position,
373 current_left_velocity_, left_gear_);
374 right_motor_speed_ =
375 MotorSpeed(dt_config_.right_drive, position_.right_shifter_position,
376 current_right_velocity_, right_gear_);
377
378 goal_left_velocity_ = current_left_velocity_;
379 goal_right_velocity_ = current_right_velocity_;
380
381 // Any motor is not in gear. Speed match.
382 ::Eigen::Matrix<Scalar, 1, 1> R_left;
383 ::Eigen::Matrix<Scalar, 1, 1> R_right;
384 R_left(0, 0) = left_motor_speed_;
385 R_right(0, 0) = right_motor_speed_;
386
387 const Scalar wiggle =
388 (static_cast<Scalar>((counter_ % 30) / 15) - 0.5) * 8.0;
389
390 loop_->mutable_U(0, 0) = ::aos::Clip(
391 (R_left / dt_config_.v)(0, 0) + (IsInGear(left_gear_) ? 0 : wiggle),
392 -12.0, 12.0);
393 loop_->mutable_U(1, 0) = ::aos::Clip(
394 (R_right / dt_config_.v)(0, 0) + (IsInGear(right_gear_) ? 0 : wiggle),
395 -12.0, 12.0);
396#ifdef __linux__
397 loop_->mutable_U() *= 12.0 / ::aos::robot_state->voltage_battery;
398#endif // __linux__
399 }
400}
401
402template <typename Scalar>
403void PolyDrivetrain<Scalar>::SetOutput(
404 ::frc971::control_loops::DrivetrainQueue::Output *output) {
405 if (output != NULL) {
406 output->left_voltage = loop_->U(0, 0);
407 output->right_voltage = loop_->U(1, 0);
408 output->left_high = MaybeHigh(left_gear_);
409 output->right_high = MaybeHigh(right_gear_);
410 }
411}
412
413template <typename Scalar>
414void PolyDrivetrain<Scalar>::PopulateStatus(
415 ::frc971::control_loops::DrivetrainQueue::Status *status) {
416 status->left_velocity_goal = goal_left_velocity_;
417 status->right_velocity_goal = goal_right_velocity_;
418
419 status->cim_logging.left_in_gear = IsInGear(left_gear_);
420 status->cim_logging.left_motor_speed = left_motor_speed_;
421 status->cim_logging.left_velocity = current_left_velocity_;
422
423 status->cim_logging.right_in_gear = IsInGear(right_gear_);
424 status->cim_logging.right_motor_speed = right_motor_speed_;
425 status->cim_logging.right_velocity = current_right_velocity_;
426}
427
428
Comran Morshed5323ecb2015-12-26 20:50:55 +0000429} // namespace drivetrain
430} // namespace control_loops
431} // namespace frc971
432
433#endif // FRC971_CONTROL_LOOPS_DRIVETRAIN_POLYDRIVETRAIN_H_