James Kuszmaul | c68b350 | 2024-10-09 07:56:18 -0700 | [diff] [blame] | 1 | #ifndef FRC971_CONTROL_LOOPS_SWERVE_INVERSE_KINEMATICS_H_ |
| 2 | #define FRC971_CONTROL_LOOPS_SWERVE_INVERSE_KINEMATICS_H_ |
| 3 | #include "aos/util/math.h" |
| 4 | #include "frc971/control_loops/swerve/simplified_dynamics.h" |
| 5 | namespace frc971::control_loops::swerve { |
| 6 | // Class to do straightforwards inverse kinematics of a swerve drivebase. This |
| 7 | // is meant largely as a sanity-check/initializer for more sophisticated |
| 8 | // methods. This calculates which directions the modules must be pointed to |
| 9 | // cause them to be pointed directly along the direction of motion of the |
| 10 | // drivebase. Accounting for slip angles and the such must be done as part of |
| 11 | // more sophisticated inverse dynamics. |
| 12 | template <typename Scalar> |
| 13 | class InverseKinematics { |
| 14 | public: |
| 15 | using ModuleParams = SimplifiedDynamics<Scalar>::ModuleParams; |
| 16 | using Parameters = SimplifiedDynamics<Scalar>::Parameters; |
| 17 | using States = SimplifiedDynamics<Scalar>::States; |
| 18 | using State = SimplifiedDynamics<Scalar>::template VelocityState<Scalar>; |
| 19 | InverseKinematics(const Parameters ¶ms) : params_(params) {} |
| 20 | |
| 21 | // Uses kVx, kVy, kTheta, and kOmega from the input goal state for the |
| 22 | // absolute kinematics. Also uses the specified theta values to bias theta |
| 23 | // output values towards the current state (i.e., if the module 0 theta is |
| 24 | // currently 0 and we are asked to drive straight backwards, this will prefer |
| 25 | // a theta of zero rather than a theta of pi). |
| 26 | State Solve(const State &goal) { |
| 27 | State result = goal; |
| 28 | for (size_t module_index = 0; module_index < params_.modules.size(); |
| 29 | ++module_index) { |
| 30 | SolveModule(goal, params_.modules[module_index].position, |
| 31 | &result(States::kThetas0 + 2 * module_index), |
| 32 | &result(States::kOmegas0 + 2 * module_index)); |
| 33 | } |
| 34 | return result; |
| 35 | } |
| 36 | |
| 37 | void SolveModule(const State &goal, |
| 38 | const Eigen::Matrix<Scalar, 2, 1> &module_position, |
| 39 | Scalar *module_theta, Scalar *module_omega) { |
| 40 | const Scalar vx = goal(States::kVx); |
| 41 | const Scalar vy = goal(States::kVy); |
| 42 | const Scalar omega = goal(States::kOmega); |
| 43 | // module_velocity_in_robot_frame = R(-theta) * robot_vel + |
| 44 | // omega.cross(module_position); |
| 45 | // module_vel_x = (cos(-theta) * vx - sin(-theta) * vy) - omega * module_y |
| 46 | // module_vel_y = (sin(-theta) * vx + cos(-theta) * vy) + omega * module_x |
| 47 | // module_theta = atan2(module_vel_y, module_vel_x) |
| 48 | // module_omega = datan2(module_vel_y, module_vel_x) / dt |
| 49 | // datan2(y, x) / dt = (x * dy/dt - y * dx / dt) / (x^2 + y^2) |
| 50 | // robot accelerations are assumed to be zero. |
| 51 | // dmodule_vel_x / dt = (sin(-theta) * vx + cos(-theta) * vy) * omega |
| 52 | // dmodule_vel_y / dt = (-cos(-theta) * vx + sin(-theta) * vy) * omega |
| 53 | const Scalar ctheta = std::cos(-goal(States::kTheta)); |
| 54 | const Scalar stheta = std::sin(-goal(States::kTheta)); |
| 55 | const Scalar module_vel_x = |
| 56 | (ctheta * vx - stheta * vy) - omega * module_position.y(); |
| 57 | const Scalar module_vel_y = |
| 58 | (stheta * vx + ctheta * vy) + omega * module_position.x(); |
| 59 | const Scalar nominal_module_theta = atan2(module_vel_y, module_vel_x); |
| 60 | // If the current module theta is more than 90 deg from the desired theta, |
| 61 | // flip the desired theta by 180 deg. |
| 62 | if (std::abs(aos::math::DiffAngle(nominal_module_theta, *module_theta)) > |
| 63 | M_PI_2) { |
| 64 | *module_theta = aos::math::NormalizeAngle(nominal_module_theta + M_PI); |
| 65 | } else { |
| 66 | *module_theta = nominal_module_theta; |
| 67 | } |
| 68 | const Scalar module_accel_x = (stheta * vx + ctheta * vy) * omega; |
| 69 | const Scalar module_accel_y = (-ctheta * vx + stheta * vy) * omega; |
| 70 | const Scalar module_vel_norm_squared = |
| 71 | (module_vel_x * module_vel_x + module_vel_y * module_vel_y); |
| 72 | if (module_vel_norm_squared < 1e-5) { |
| 73 | // Prevent poor conditioning of module velocities at near-zero speeds. |
| 74 | *module_omega = 0.0; |
| 75 | } else { |
| 76 | *module_omega = |
| 77 | (module_vel_x * module_accel_y - module_vel_y * module_accel_x) / |
| 78 | module_vel_norm_squared; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | private: |
| 83 | Parameters params_; |
| 84 | }; |
| 85 | } // namespace frc971::control_loops::swerve |
| 86 | #endif // FRC971_CONTROL_LOOPS_SWERVE_INVERSE_KINEMATICS_H_ |