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