Swerve Simulation

Important

Swerve simulation is only supported for FRC users.

The API supports a functionality focused simulation. This means that the simulation API assumes that the swerve drive is perfect (no scrub and no wheel slip). Additionally, it assumes a constant drive motor inertia regardless of the type of motion.

To update the simulated swerve robot state, ensure drivetrain.updateSimState(...) (Java, C++, Python) is called in simulationPeriodic(). The typical update rate of a robot project is 20 ms (0.020 seconds), and RobotController.getBatteryVoltage() (Java, C++, Python) can be used to get the simulated battery voltage.

The behavior of the simulated drivetrain can be improved to more closely match hardware by running the simulation logic at a faster update rate, such as by using a WPILib Notifier as demonstrated below.

Important

When using CommandSwerveDrivetrain from our examples or Tuner X, this is already handled by the subsystem.

private static final double kSimLoopPeriod = 0.004; // 4 ms
private Notifier m_simNotifier = null;
private double m_lastSimTime;

@Override
public void simulationInit() {
   m_lastSimTime = Utils.getCurrentTimeSeconds();

   /* Run simulation at a faster rate so PID gains behave more reasonably */
   m_simNotifier = new Notifier(() -> {
      final double currentTime = Utils.getCurrentTimeSeconds();
      double deltaTime = currentTime - m_lastSimTime;
      m_lastSimTime = currentTime;

      /* Use the measured time delta, get battery voltage from WPILib */
      drivetrain.updateSimState(deltaTime, RobotController.getBatteryVoltage());
   });
   m_simNotifier.startPeriodic(kSimLoopPeriod);
}
private:
   static constexpr units::second_t kSimLoopPeriod = 4_ms;
   std::unique_ptr<frc::Notifier> m_simNotifier;
   units::second_t m_lastSimTime;

public:
   void SimulationInit() override
   {
      m_lastSimTime = utils::GetCurrentTime();

      /* Run simulation at a faster rate so PID gains behave more reasonably */
      m_simNotifier = std::make_unique<frc::Notifier>([this] {
         units::second_t const currentTime = utils::GetCurrentTime();
         auto const deltaTime = currentTime - m_lastSimTime;
         m_lastSimTime = currentTime;

         /* Use the measured time delta, get battery voltage from WPILib */
         drivetrain.UpdateSimState(deltaTime, frc::RobotController::GetBatteryVoltage());
      });
      m_simNotifier->StartPeriodic(kSimLoopPeriod);
   }
_SIM_LOOP_PERIOD: units.second = 0.004  # 4 ms

def __init__(self):
   self._sim_notifier: Notifier | None = None
   self._last_sim_time: units.second = 0.0
   # ...

def simulationInit(self):
   def _sim_periodic():
      current_time = utils.get_current_time_seconds()
      delta_time = current_time - self._last_sim_time
      self._last_sim_time = current_time

      # Use the measured time delta, get battery voltage from WPILib
      self.drivetrain.update_sim_state(delta_time, RobotController.getBatteryVoltage())

   # Run simulation at a faster rate so PID gains behave more reasonably
   self._last_sim_time = utils.get_current_time_seconds()
   self._sim_notifier = Notifier(_sim_periodic)
   self._sim_notifier.startPeriodic(self._SIM_LOOP_PERIOD)

Simulation FAQ

Q: My robot does not move in simulation

A: Verify that all gains are non-zero and that the steer/drive inertia is non-zero.

Q: My robot drifts a bit when driving while rotating

A: Azimuth inertia and control latency is simulated. As a result, simulated swerve modules match the behavior of hardware in lagging behind the module targets, which can be improved by tuning the steer PID gains.