It only limits acceleration directly if you slew rate limit velocity. If the user is passing in voltage, the acceleration approaches a maximum while the voltage is changing at the slew rate limit.

Notice how the acceleration in the figure below is converging to a steady-state nonzero value when the voltage is changing, and it’s converging to zero when the voltage is constant.

Here’s the script I was using to test this stuff a few days ago for the WPILib 2023 differential drive acceleration limiter and implicit model follower PRs.

```
#!/usr/bin/env python3
import control as ct
import frccontrol as fct
import matplotlib.pyplot as plt
import numpy as np
DT = 0.005
class Drivetrain(fct.System):
def __init__(self, dt):
"""Drivetrain subsystem.
Keyword arguments:
dt -- time between model/controller updates
"""
fct.System.__init__(
self,
np.array([[-12.0]]),
np.array([[12.0]]),
dt,
np.zeros((1, 1)),
np.zeros((1, 1)),
)
def create_model(self, states, inputs):
Kv = 3.02
Ka = 0.642
A = np.array([[-Kv / Ka]])
B = np.array([[1.0 / Ka]])
C = np.array([[1]])
D = np.array([[0]])
return ct.ss(A, B, C, D)
def plot_data(t_rec, x_rec, u_rec, a_rec, title):
plt.figure()
plt.title(title)
plt.xlabel("Time (s)")
plt.ylabel("Velocity (m/s)")
plt.plot(t_rec, x_rec)
plt.figure()
plt.title(title)
plt.xlabel("Time (s)")
plt.ylabel("Voltage (V)")
plt.plot(t_rec, u_rec)
plt.figure()
plt.title(title)
plt.xlabel("Time (s)")
plt.ylabel("Acceleration (m/s²)")
plt.plot(t_rec, a_rec)
def run_voltage_slew_rate_limit_sim(drivetrain, t1, t2):
"""Runs simulation with voltage slew rate limit and returns t_rec, x_rec,
u_rec, and a_rec.
"""
t_rec = []
x_rec = []
u_rec = []
a_rec = []
x = np.array([[0]])
# dx/dt = Ax + Bu where u = αt and [α] = V/T
# d/dt(dx/dt = Ax + Bαt)
# da/dt = A dx/dt + Bα
# 0 = A dx/dt + Bα for steady-state acceleration
# Bα = -A dx/dt
# α = -B⁻¹A dx/dt
# α = -(1/Ka)⁻¹(-Kv/Ka) dx/dt
# α = Ka(Kv/Ka) dx/dt
# α = Kv dx/dt
#
# Plug in max acceleration.
#
# α = Kv (u_max / Ka)
# α = Kv/Ka u_max
# α = -A u_max
Kv = 3.02
Ka = 0.642 * 2
alpha = -drivetrain.sysc.A @ np.array([[12]])
ts = np.arange(0, t1, DT)
for i, t in enumerate(ts):
u = np.clip(alpha * t, np.array([[-12]]), np.array([[12]]))
a = drivetrain.sysc.A @ x + drivetrain.sysc.B @ u
x = drivetrain.sysd.A @ x + drivetrain.sysd.B @ u
t_rec.append(t)
x_rec.append(x[0, 0])
u_rec.append(u[0, 0])
a_rec.append(a[0, 0])
ts = np.arange(t1, t2, DT)
for i, t in enumerate(ts):
u = np.array([[12]]) - alpha * (t - t1)
u = np.clip(
np.array([[12]]) - alpha * (t - t1), np.array([[-12]]), np.array([[12]])
)
a = drivetrain.sysc.A @ x + drivetrain.sysc.B @ u
x = drivetrain.sysd.A @ x + drivetrain.sysd.B @ u
t_rec.append(t)
x_rec.append(x[0, 0])
u_rec.append(u[0, 0])
a_rec.append(a[0, 0])
plot_data(t_rec, x_rec, u_rec, a_rec, "Slew Rate Limit")
def run_imf_sim(drivetrain, t1, t2):
"""Runs simulation with voltage slew rate limit and returns t_rec, x_rec,
u_rec, and a_rec.
"""
t_rec = []
x_rec = []
u_rec = []
a_rec = []
x = np.array([[0]])
# IMF ref model
Kv = 3.02
Ka = 0.642 * 2
A = np.array([[-Kv / Ka]])
B = np.array([[1.0 / Ka]])
C = np.array([[1]])
D = np.array([[0]])
sysc_ref = ct.ss(A, B, C, D)
A_imf = -np.linalg.solve(drivetrain.sysc.B, drivetrain.sysc.A - sysc_ref.A)
B_imf = np.linalg.solve(drivetrain.sysc.B, sysc_ref.B)
ts = np.arange(0, t1, DT)
for i, t in enumerate(ts):
u = A_imf @ x + B_imf @ np.array([[12]])
a = drivetrain.sysc.A @ x + drivetrain.sysc.B @ u
x = drivetrain.sysd.A @ x + drivetrain.sysd.B @ u
t_rec.append(t)
x_rec.append(x[0, 0])
u_rec.append(u[0, 0])
a_rec.append(a[0, 0])
ts = np.arange(t1, t2, DT)
for i, t in enumerate(ts):
u = A_imf @ x + B_imf @ np.array([[0]])
a = drivetrain.sysc.A @ x + drivetrain.sysc.B @ u
x = drivetrain.sysd.A @ x + drivetrain.sysd.B @ u
t_rec.append(t)
x_rec.append(x[0, 0])
u_rec.append(u[0, 0])
a_rec.append(a[0, 0])
plot_data(t_rec, x_rec, u_rec, a_rec, "IMF")
def main():
drivetrain = Drivetrain(DT)
run_voltage_slew_rate_limit_sim(drivetrain, 1, 2)
# run_imf_sim(drivetrain, 1, 2)
plt.show()
if __name__ == "__main__":
main()
```

If you want open-loop, slew rate limiting voltage is your only option. If you have encoders and a drivetrain model, you can use DifferentialDriveAccelerationLimiter to limit linear and angular acceleration directly, or ImplicitModelFollower to make the drivetrain behave like a more benign one. The latter is also nice for making the robot easier to control by novice drivers.