Brian Silverman | 36d0649 | 2018-05-12 11:52:35 -0700 | [diff] [blame] | 1 | #include "motors/big/motor_controls.h" |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 2 | |
| 3 | #include "motors/peripheral/configuration.h" |
| 4 | |
| 5 | namespace frc971 { |
Brian Silverman | a96c1a4 | 2018-05-12 12:11:31 -0700 | [diff] [blame] | 6 | namespace motors { |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 7 | namespace { |
| 8 | |
| 9 | template <int kRows, int kCols> |
| 10 | using ComplexMatrix = MotorControlsImplementation::ComplexMatrix<kRows, kCols>; |
| 11 | |
| 12 | using Complex = ::std::complex<float>; |
| 13 | |
Brian Silverman | 19ea60f | 2018-01-03 21:43:15 -0800 | [diff] [blame] | 14 | constexpr int kCountsPerRevolution = |
| 15 | MotorControlsImplementation::constant_counts_per_revolution(); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 16 | |
| 17 | #if 1 |
| 18 | constexpr double kMaxDutyCycle = 0.98; |
| 19 | #elif 1 |
| 20 | constexpr double kMaxDutyCycle = 0.6; |
| 21 | #elif 0 |
| 22 | constexpr double kMaxDutyCycle = 0.2; |
| 23 | #endif |
| 24 | |
| 25 | constexpr int kPhaseBOffset = kCountsPerRevolution / 3; |
| 26 | constexpr int kPhaseCOffset = 2 * kCountsPerRevolution / 3; |
| 27 | |
| 28 | constexpr double K1_unscaled = 1.81e04; |
| 29 | constexpr double K2_unscaled = -2.65e03; |
| 30 | |
| 31 | // Make the amplitude of the fundamental 1 for ease of math. |
| 32 | constexpr double K2 = K2_unscaled / K1_unscaled; |
| 33 | constexpr double K1 = 1; |
| 34 | |
| 35 | // volts |
| 36 | constexpr double vcc = 30.0; |
| 37 | |
| 38 | constexpr double Kv = 22000.0 * 2.0 * M_PI / 60.0 / 30.0 * 2.0; |
| 39 | |
| 40 | // Henries |
| 41 | constexpr double L = 1e-05; |
| 42 | |
| 43 | constexpr double M = 1e-06; |
| 44 | |
| 45 | // ohms for system |
| 46 | constexpr double R = 0.008; |
| 47 | |
| 48 | ::Eigen::Matrix<float, 3, 3> A_discrete() { |
| 49 | ::Eigen::Matrix<float, 3, 3> r; |
| 50 | static constexpr float kDiagonal = 0.960091f; |
| 51 | static constexpr float kOffDiagonal = 0.00356245f; |
| 52 | r << kDiagonal, kOffDiagonal, kOffDiagonal, kOffDiagonal, kDiagonal, |
| 53 | kOffDiagonal, kOffDiagonal, kOffDiagonal, kDiagonal; |
| 54 | return r; |
| 55 | } |
| 56 | |
| 57 | ::Eigen::Matrix<float, 3, 3> B_discrete_inverse() { |
| 58 | return ::Eigen::Matrix<float, 1, 3>::Constant(0.18403f).asDiagonal(); |
| 59 | } |
| 60 | |
| 61 | // The number to divide the product of the unit BEMF and the per phase current |
| 62 | // by to get motor current. |
| 63 | constexpr double kOneAmpScalar = 1.46785; |
| 64 | |
| 65 | constexpr double kMaxOneAmpDrivingVoltage = 0.0361525; |
| 66 | |
| 67 | // Use FluxLinkageTable() to access this with a const so you don't accidentally |
| 68 | // modify it. |
| 69 | float flux_linkage_table[kCountsPerRevolution]; |
| 70 | |
| 71 | void MakeFluxLinkageTable() { |
| 72 | for (int i = 0; i < kCountsPerRevolution; ++i) { |
| 73 | const double theta = static_cast<double>(i) / |
| 74 | static_cast<double>(kCountsPerRevolution) * 2.0 * M_PI; |
| 75 | flux_linkage_table[i] = K1 * sin(theta) - K2 * sin(theta * 5); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | // theta doesn't have to be less than kCountsPerRevolution. |
| 80 | ::Eigen::Matrix<float, 3, 1> FluxLinkageAt(uint32_t theta) { |
| 81 | ::Eigen::Matrix<float, 3, 1> r; |
| 82 | r(0) = flux_linkage_table[theta % kCountsPerRevolution]; |
| 83 | r(1) = flux_linkage_table[(theta + kPhaseBOffset) % kCountsPerRevolution]; |
| 84 | r(2) = flux_linkage_table[(theta + kPhaseCOffset) % kCountsPerRevolution]; |
| 85 | return r; |
| 86 | } |
| 87 | |
| 88 | ::Eigen::Matrix<float, 3, 3> MakeK() { |
| 89 | ::Eigen::Matrix<float, 3, 3> Vconv; |
| 90 | Vconv << 2.0f, -1.0f, -1.0f, -1.0f, 2.0f, -1.0f, -1.0f, -1.0f, 2.0f; |
| 91 | static constexpr float kControllerGain = 0.07f; |
| 92 | return kControllerGain * (Vconv / 3.0f); |
| 93 | } |
| 94 | |
| 95 | ComplexMatrix<3, 1> MakeE1Unrotated() { |
| 96 | ComplexMatrix<3, 1> rotation; |
| 97 | rotation << Complex(0, -1), Complex(::std::sqrt(3) / 2, 0.5), |
| 98 | Complex(-::std::sqrt(3) / 2, 0.5); |
| 99 | return K1 * rotation; |
| 100 | } |
| 101 | |
| 102 | ComplexMatrix<3, 1> MakeE2Unrotated() { |
| 103 | ComplexMatrix<3, 1> rotation; |
| 104 | rotation << Complex(0, -1), Complex(-::std::sqrt(3) / 2, 0.5), |
| 105 | Complex(::std::sqrt(3) / 2, 0.5); |
| 106 | return K2 * rotation; |
| 107 | } |
| 108 | |
| 109 | ComplexMatrix<3, 3> Hn(float omega, int scalar) { |
| 110 | const Complex a(static_cast<float>(R), |
| 111 | omega * static_cast<float>(scalar * L)); |
| 112 | const Complex b(0, omega * static_cast<float>(scalar * M)); |
| 113 | const Complex temp1 = a + b; |
| 114 | const Complex temp2 = -b; |
| 115 | ComplexMatrix<3, 3> matrix; |
| 116 | matrix << temp1, temp2, temp2, temp2, temp1, temp2, temp2, temp2, temp1; |
| 117 | return matrix * |
| 118 | -(omega / static_cast<float>(Kv) / (a * a + a * b - 2.0f * b * b)); |
| 119 | } |
| 120 | |
| 121 | } // namespace |
| 122 | |
| 123 | MotorControlsImplementation::MotorControlsImplementation() |
| 124 | : E1Unrotated_(MakeE1Unrotated()), E2Unrotated_(MakeE2Unrotated()) { |
| 125 | MakeFluxLinkageTable(); |
| 126 | } |
| 127 | |
James Kuszmaul | 998d303 | 2018-09-08 15:41:41 -0700 | [diff] [blame] | 128 | ::std::array<float, 3> MotorControlsImplementation::DoIteration( |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 129 | const float raw_currents[3], const uint32_t theta_in, |
| 130 | const float command_current) { |
| 131 | static constexpr float kCurrentSlewRate = 0.05f; |
| 132 | if (command_current > filtered_current_ + kCurrentSlewRate) { |
| 133 | filtered_current_ += kCurrentSlewRate; |
| 134 | } else if (command_current < filtered_current_ - kCurrentSlewRate) { |
| 135 | filtered_current_ -= kCurrentSlewRate; |
| 136 | } else { |
| 137 | filtered_current_ = command_current; |
| 138 | } |
| 139 | const float goal_current_in = filtered_current_; |
| 140 | const float max_current = |
| 141 | (static_cast<float>(vcc * kMaxDutyCycle) - |
| 142 | estimated_velocity_ / static_cast<float>(Kv / 2.0)) / |
| 143 | static_cast<float>(kMaxOneAmpDrivingVoltage); |
| 144 | const float min_current = |
| 145 | (-static_cast<float>(vcc * kMaxDutyCycle) - |
| 146 | estimated_velocity_ / static_cast<float>(Kv / 2.0)) / |
| 147 | static_cast<float>(kMaxOneAmpDrivingVoltage); |
| 148 | const float goal_current = |
| 149 | ::std::max(min_current, ::std::min(max_current, goal_current_in)); |
| 150 | |
| 151 | #if 0 |
| 152 | const uint32_t theta = |
| 153 | (theta_in + static_cast<uint32_t>(estimated_velocity_ * 1.0f)) % 1024; |
| 154 | #elif 0 |
| 155 | const uint32_t theta = |
| 156 | (theta_in + kCountsPerRevolution - 160u) % kCountsPerRevolution; |
| 157 | #elif 1 |
| 158 | const uint32_t theta = |
| 159 | (theta_in + kCountsPerRevolution + |
| 160 | ((estimated_velocity_ > 0) ? (kCountsPerRevolution - 10u) : 60u)) % |
| 161 | kCountsPerRevolution; |
| 162 | #elif 0 |
| 163 | const uint32_t theta = theta_in; |
| 164 | #endif |
| 165 | |
| 166 | const ::Eigen::Matrix<float, 3, 1> measured_current = |
| 167 | (::Eigen::Matrix<float, 3, 1>() << scale_current_reading(raw_currents[0]), |
| 168 | scale_current_reading(raw_currents[1]), |
| 169 | scale_current_reading(raw_currents[2])).finished(); |
| 170 | |
| 171 | const ComplexMatrix<3, 1> E1 = |
| 172 | E1Unrotated_ * |
Brian Silverman | 19ea60f | 2018-01-03 21:43:15 -0800 | [diff] [blame] | 173 | ImaginaryExpInt<::std::ratio<1, constant_counts_per_revolution()>>(theta); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 174 | const ComplexMatrix<3, 1> E2 = |
| 175 | E2Unrotated_ * |
Brian Silverman | 19ea60f | 2018-01-03 21:43:15 -0800 | [diff] [blame] | 176 | ImaginaryExpInt<::std::ratio<5, constant_counts_per_revolution()>>(theta); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 177 | |
| 178 | const float overall_measured_current = |
| 179 | ((E1 + E2).real().transpose() * measured_current / |
| 180 | static_cast<float>(kOneAmpScalar))(0); |
James Kuszmaul | 521eb65 | 2018-10-17 19:09:33 -0700 | [diff] [blame] | 181 | overall_measured_current_ = overall_measured_current; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 182 | const float current_error = goal_current - overall_measured_current; |
| 183 | estimated_velocity_ += current_error * 0.1f; |
| 184 | debug_[3] = theta; |
| 185 | const float omega = estimated_velocity_; |
| 186 | |
| 187 | debug_[4] = max_current * 10; |
| 188 | |
| 189 | const ::Eigen::Matrix<float, 3, 1> I_now = I_last_; |
| 190 | const ::Eigen::Matrix<float, 3, 1> I_next = |
| 191 | FluxLinkageAt(theta) * goal_current; |
| 192 | |
| 193 | const ComplexMatrix<3, 3> H1 = Hn(omega, 1); |
| 194 | const ComplexMatrix<3, 3> H2 = Hn(omega, 5); |
| 195 | |
Brian Silverman | 19ea60f | 2018-01-03 21:43:15 -0800 | [diff] [blame] | 196 | const ComplexMatrix<3, 1> H1E1 = H1 * E1; |
| 197 | const ComplexMatrix<3, 1> H2E2 = H2 * E2; |
| 198 | const ComplexMatrix<3, 1> p_imaginary = H1E1 + H2E2; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 199 | const ComplexMatrix<3, 1> p_next_imaginary = |
Brian Silverman | 19ea60f | 2018-01-03 21:43:15 -0800 | [diff] [blame] | 200 | ImaginaryExpFloat(omega / SWITCHING_FREQUENCY) * H1E1 + |
| 201 | ImaginaryExpFloat(omega * 5 / SWITCHING_FREQUENCY) * H2E2; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 202 | const ::Eigen::Matrix<float, 3, 1> p = p_imaginary.real(); |
| 203 | const ::Eigen::Matrix<float, 3, 1> p_next = p_next_imaginary.real(); |
| 204 | |
| 205 | const ::Eigen::Matrix<float, 3, 1> Vn_ff = |
| 206 | B_discrete_inverse() * (I_next - A_discrete() * (I_now - p) - p_next); |
| 207 | const ::Eigen::Matrix<float, 3, 1> Vn = |
| 208 | Vn_ff + MakeK() * (I_now - measured_current); |
| 209 | |
| 210 | debug_[0] = (I_next)(0) * 100; |
| 211 | debug_[1] = (I_next)(1) * 100; |
| 212 | debug_[2] = (I_next)(2) * 100; |
| 213 | |
| 214 | debug_[5] = Vn(0) * 100; |
| 215 | debug_[6] = Vn(1) * 100; |
| 216 | debug_[7] = Vn(2) * 100; |
| 217 | |
| 218 | ::Eigen::Matrix<float, 3, 1> times = Vn / vcc; |
| 219 | { |
| 220 | const float min_time = times.minCoeff(); |
| 221 | times -= ::Eigen::Matrix<float, 3, 1>::Constant(min_time); |
| 222 | } |
| 223 | { |
| 224 | const float max_time = times.maxCoeff(); |
| 225 | const float scalar = |
| 226 | static_cast<float>(kMaxDutyCycle) / |
| 227 | ::std::max(static_cast<float>(kMaxDutyCycle), max_time); |
| 228 | times *= scalar; |
| 229 | } |
| 230 | |
| 231 | I_last_ = I_next; |
| 232 | |
| 233 | // TODO(Austin): Figure out why we need the min here. |
James Kuszmaul | 998d303 | 2018-09-08 15:41:41 -0700 | [diff] [blame] | 234 | return {::std::max(0.0f, times(0)), ::std::max(0.0f, times(1)), |
| 235 | ::std::max(0.0f, times(2))}; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 236 | } |
| 237 | |
| 238 | int16_t MotorControlsImplementation::Debug(uint32_t theta) { |
| 239 | return debug_[theta]; |
| 240 | } |
| 241 | |
Brian Silverman | a96c1a4 | 2018-05-12 12:11:31 -0700 | [diff] [blame] | 242 | } // namespace motors |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 243 | } // namespace frc971 |