Maxwell Henderson | 3424299 | 2024-01-07 12:39:11 -0800 | [diff] [blame] | 1 | #include "frc971/control_loops/flywheel/flywheel_controller.h" |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 2 | |
| 3 | #include <chrono> |
| 4 | |
| 5 | #include "aos/logging/logging.h" |
Stephan Pleines | f63bde8 | 2024-01-13 15:59:33 -0800 | [diff] [blame^] | 6 | |
| 7 | namespace frc971::control_loops::flywheel { |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 8 | |
Austin Schuh | e8ca06a | 2020-03-07 22:27:39 -0800 | [diff] [blame] | 9 | // Class to current limit battery current for a flywheel controller. |
| 10 | class CurrentLimitedStateFeedbackController |
| 11 | : public StateFeedbackLoop<3, 1, 1, double, |
| 12 | StateFeedbackHybridPlant<3, 1, 1>, |
| 13 | HybridKalman<3, 1, 1>> { |
| 14 | public: |
| 15 | // Builds a CurrentLimitedStateFeedbackController given the coefficients, bemf |
| 16 | // coefficient (units of radians/sec / volt), and motor resistance in ohms. |
| 17 | CurrentLimitedStateFeedbackController( |
| 18 | StateFeedbackLoop<3, 1, 1, double, StateFeedbackHybridPlant<3, 1, 1>, |
| 19 | HybridKalman<3, 1, 1>> &&other, |
| 20 | double bemf, double resistance) |
| 21 | : StateFeedbackLoop(std::move(other)), |
| 22 | bemf_(bemf), |
| 23 | resistance_(resistance) {} |
| 24 | |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 25 | double resistance() const { return resistance_; } |
| 26 | double bemf() const { return bemf_; } |
| 27 | |
Austin Schuh | e8ca06a | 2020-03-07 22:27:39 -0800 | [diff] [blame] | 28 | void CapU() override { |
| 29 | const double bemf_voltage = X_hat(1) / bemf_; |
| 30 | // Solve the system of equations: |
| 31 | // |
| 32 | // motor_current = (u - bemf_voltage) / resistance |
| 33 | // battery_current = ((u - bemf_voltage) / resistance) * u / 12.0 |
| 34 | // 0.0 = u * u - u * bemf_voltage - max_current * 12.0 * resistance |
| 35 | // |
| 36 | // And we have a quadratic! |
| 37 | const double a = 1; |
| 38 | const double b = -bemf_voltage; |
Austin Schuh | 242d713 | 2021-11-07 17:39:48 -0800 | [diff] [blame] | 39 | const double c_positive = -70.0 * 12.0 * resistance_; |
Austin Schuh | c044dda | 2021-11-14 21:02:49 -0800 | [diff] [blame] | 40 | const double c_negative = -25.0 * 12.0 * resistance_; |
Austin Schuh | e8ca06a | 2020-03-07 22:27:39 -0800 | [diff] [blame] | 41 | |
| 42 | // Root is always positive. |
Austin Schuh | 242d713 | 2021-11-07 17:39:48 -0800 | [diff] [blame] | 43 | const double root_positive = std::sqrt(b * b - 4.0 * a * c_positive); |
| 44 | const double root_negative = std::sqrt(b * b - 4.0 * a * c_negative); |
| 45 | const double upper_limit = (-b + root_positive) / (2.0 * a); |
| 46 | const double lower_limit = (-b - root_negative) / (2.0 * a); |
Austin Schuh | e8ca06a | 2020-03-07 22:27:39 -0800 | [diff] [blame] | 47 | |
| 48 | // Limit to the battery voltage and the current limit voltage. |
| 49 | mutable_U(0, 0) = std::clamp(U(0, 0), lower_limit, upper_limit); |
Maxwell Henderson | 7a3c99b | 2024-01-08 15:41:09 -0800 | [diff] [blame] | 50 | |
| 51 | double lower_clamp = (std::abs(R(1)) > 50.0) ? 1.0 : 0.0; |
| 52 | double upper_clamp = 12.0; |
| 53 | |
| 54 | if (R(1) < 0.0) { |
| 55 | // If R(1) is negative, swap and invert. |
| 56 | std::swap(lower_clamp, upper_clamp); |
| 57 | lower_clamp *= -1.0; |
| 58 | upper_clamp *= -1.0; |
Austin Schuh | 242d713 | 2021-11-07 17:39:48 -0800 | [diff] [blame] | 59 | } |
Maxwell Henderson | 7a3c99b | 2024-01-08 15:41:09 -0800 | [diff] [blame] | 60 | |
| 61 | mutable_U(0, 0) = std::clamp(U(0, 0), lower_clamp, upper_clamp); |
Austin Schuh | e8ca06a | 2020-03-07 22:27:39 -0800 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | private: |
| 65 | double bemf_ = 0.0; |
| 66 | double resistance_ = 0.0; |
| 67 | }; |
| 68 | |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 69 | FlywheelController::FlywheelController( |
| 70 | StateFeedbackLoop<3, 1, 1, double, StateFeedbackHybridPlant<3, 1, 1>, |
Austin Schuh | e8ca06a | 2020-03-07 22:27:39 -0800 | [diff] [blame] | 71 | HybridKalman<3, 1, 1>> &&loop, |
| 72 | double bemf, double resistance) |
| 73 | : loop_(new CurrentLimitedStateFeedbackController(std::move(loop), bemf, |
| 74 | resistance)) { |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 75 | history_.fill(std::pair<double, ::aos::monotonic_clock::time_point>( |
| 76 | 0, ::aos::monotonic_clock::epoch())); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 77 | Y_.setZero(); |
| 78 | } |
| 79 | |
| 80 | void FlywheelController::set_goal(double angular_velocity_goal) { |
| 81 | loop_->mutable_next_R() << 0.0, angular_velocity_goal, 0.0; |
| 82 | last_goal_ = angular_velocity_goal; |
| 83 | } |
| 84 | |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 85 | void FlywheelController::set_position( |
| 86 | double current_position, |
| 87 | const aos::monotonic_clock::time_point position_timestamp) { |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 88 | // Project time forwards. |
| 89 | const int newest_history_position = |
| 90 | ((history_position_ == 0) ? kHistoryLength : history_position_) - 1; |
| 91 | |
| 92 | if (!first_) { |
| 93 | loop_->UpdateObserver( |
| 94 | loop_->U(), |
| 95 | position_timestamp - std::get<1>(history_[newest_history_position])); |
| 96 | } else { |
| 97 | first_ = false; |
| 98 | } |
| 99 | |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 100 | // Update position in the model. |
| 101 | Y_ << current_position; |
| 102 | |
| 103 | // Add the position to the history. |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 104 | history_[history_position_] = |
| 105 | std::pair<double, ::aos::monotonic_clock::time_point>(current_position, |
| 106 | position_timestamp); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 107 | history_position_ = (history_position_ + 1) % kHistoryLength; |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 108 | |
| 109 | loop_->Correct(Y_); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | double FlywheelController::voltage() const { return loop_->U(0, 0); } |
| 113 | |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 114 | double FlywheelController::current() const { |
| 115 | return ((voltage() - (velocity() / loop_->bemf())) / (loop_->resistance())) * |
| 116 | voltage() / 12.0; |
| 117 | } |
| 118 | |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 119 | void FlywheelController::Update(bool disabled) { |
| 120 | loop_->mutable_R() = loop_->next_R(); |
Maxwell Henderson | 7a3c99b | 2024-01-08 15:41:09 -0800 | [diff] [blame] | 121 | |
| 122 | if (std::abs(loop_->R(1, 0)) < 1.0) { |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 123 | // Kill power at low angular velocities. |
| 124 | disabled = true; |
| 125 | } |
| 126 | |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 127 | loop_->UpdateController(disabled); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 128 | } |
| 129 | |
| 130 | flatbuffers::Offset<FlywheelControllerStatus> FlywheelController::SetStatus( |
| 131 | flatbuffers::FlatBufferBuilder *fbb) { |
| 132 | // Compute the oldest point in the history. |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 133 | const int oldest_history_position = history_position_; |
| 134 | const int newest_history_position = |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 135 | ((history_position_ == 0) ? kHistoryLength : history_position_) - 1; |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 136 | const int second_newest_history_position = |
| 137 | ((newest_history_position == 0) ? kHistoryLength |
| 138 | : newest_history_position) - |
| 139 | 1; |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 140 | |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 141 | const double total_loop_time = ::aos::time::DurationInSeconds( |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 142 | std::get<1>(history_[newest_history_position]) - |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 143 | std::get<1>(history_[oldest_history_position])); |
| 144 | |
| 145 | const double distance_traveled = |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 146 | std::get<0>(history_[newest_history_position]) - |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 147 | std::get<0>(history_[oldest_history_position]); |
| 148 | |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 149 | const double last_loop_time = ::aos::time::DurationInSeconds( |
| 150 | std::get<1>(history_[newest_history_position]) - |
| 151 | std::get<1>(history_[second_newest_history_position])); |
| 152 | |
| 153 | const double last_distance_traveled = |
| 154 | std::get<0>(history_[newest_history_position]) - |
| 155 | std::get<0>(history_[second_newest_history_position]); |
| 156 | |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 157 | // Compute the distance moved over that time period. |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 158 | avg_angular_velocity_ = (distance_traveled) / (total_loop_time); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 159 | |
| 160 | FlywheelControllerStatusBuilder builder(*fbb); |
| 161 | |
Sabina Davis | 0f31d3f | 2020-02-20 20:41:00 -0800 | [diff] [blame] | 162 | builder.add_avg_angular_velocity(avg_angular_velocity_); |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 163 | builder.add_dt_angular_velocity(last_distance_traveled / last_loop_time); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 164 | builder.add_angular_velocity(loop_->X_hat(1, 0)); |
Austin Schuh | 43b9ae9 | 2020-02-29 23:08:38 -0800 | [diff] [blame] | 165 | builder.add_voltage_error(loop_->X_hat(2, 0)); |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 166 | builder.add_commanded_current(current()); |
Sabina Davis | edf8947 | 2020-02-17 15:27:37 -0800 | [diff] [blame] | 167 | builder.add_angular_velocity_goal(last_goal_); |
| 168 | return builder.Finish(); |
| 169 | } |
| 170 | |
Austin Schuh | 8047677 | 2021-03-06 20:17:36 -0800 | [diff] [blame] | 171 | FlywheelController::~FlywheelController() {} |
| 172 | |
| 173 | double FlywheelController::velocity() const { return loop_->X_hat(1, 0); } |
| 174 | |
Stephan Pleines | f63bde8 | 2024-01-13 15:59:33 -0800 | [diff] [blame^] | 175 | } // namespace frc971::control_loops::flywheel |