Anti-lock Braking System (ABS)

This example demonstrates an anti-lock braking system (ABS) using nonlinear tire dynamics and event-driven slip control. The system prevents wheel lockup during braking by modulating brake torque to maintain optimal tire-road friction.

You can also find this example as a single file in the GitHub repository.

The ABS control system features:

  • Pacejka tire model: Realistic nonlinear friction coefficient vs. slip ratio characteristic

  • Multi-body dynamics: Separate vehicle and wheel rotational dynamics

  • Event-driven control: Zero-crossing events detect slip threshold crossings for precise brake modulation

  • Physical constraints: Prevents unphysical negative wheel speeds

System Dynamics

The ABS system consists of coupled vehicle and wheel dynamics. The vehicle longitudinal motion is governed by:

\[M \frac{dv}{dt} = -F_x\]

where \(M\) is the vehicle mass, \(v\) is the vehicle velocity, and \(F_x\) is the tire friction force.

The wheel rotational dynamics are described by:

\[J_w \frac{d\omega}{dt} = -T_{brake} + R \cdot F_x\]

where \(J_w\) is the wheel inertia, \(\omega\) is the wheel angular velocity, \(T_{brake}\) is the brake torque, and \(R\) is the wheel radius.

Tire Friction Model

The tire friction force depends on the slip ratio \(\lambda\), defined as:

\[\lambda = \frac{v - R\omega}{v}\]

where \(\lambda = 0\) represents free rolling (no slip) and \(\lambda = 1\) represents locked wheels.

We use the Pacejka “Magic Formula” to model the friction coefficient:

\[\mu(\lambda) = D \sin\left(C \arctan(B\lambda)\right)\]

The friction force is then:

\[F_x = \mu(\lambda) \cdot F_z\]

where \(F_z\) is the normal force on the tire. The Pacejka model exhibits a characteristic peak at an optimal slip ratio (typically around 15%), after which friction decreases for higher slip values.

ABS Control Strategy

The ABS controller uses a bang-bang control strategy with zero-crossing events:

  • Event 1: When \(\lambda > \lambda_{opt} + \delta\), release brake (\(T_{brake} = 0\))

  • Event 2: When \(\lambda < \lambda_{opt} - \delta\), apply brake (\(T_{brake} = T_{max}\))

where \(\lambda_{opt} = 0.15\) is the optimal slip ratio and \(\delta = 0.02\) is the control deadband.

Import and Setup

First let’s import the required classes and blocks:

[1]:
import numpy as np
import matplotlib.pyplot as plt

# Apply PathSim docs matplotlib style
plt.style.use('../pathsim_docs.mplstyle')

from pathsim import Simulation, Connection
from pathsim.blocks import Integrator, Amplifier, Adder, Function, Scope, Clip, Constant
from pathsim.solvers import RKCK54
from pathsim.events import ZeroCrossing

Vehicle and Tire Parameters

We define realistic parameters for a mid-size vehicle with Pacejka tire model coefficients:

[2]:
# Vehicle parameters
M = 1500         # Vehicle mass [kg]
R = 0.3          # Wheel radius [m]
J_w = 1.0        # Wheel rotational inertia [kg·m²]
F_z = M * 9.81 / 4  # Normal force per wheel [N]

# Tire friction model (Pacejka "Magic Formula")
B_coef = 10.0    # Stiffness factor
C_coef = 1.9     # Shape factor
D_coef = 1.0     # Peak friction coefficient

# ABS control parameters
lambda_opt = 0.15      # Optimal slip ratio
abs_threshold = 0.02   # Control band around optimal

# Brake torque
T_brake = 2000   # Maximum brake torque [N·m]

# Initial conditions
v0 = 30          # Initial vehicle speed [m/s]
omega0 = v0 / R  # Initial wheel angular velocity [rad/s]

Friction and Slip Models

Define the tire friction characteristic and slip calculation functions:

[3]:
def friction_coefficient(slip):
    """Pacejka tire friction model"""
    return D_coef * np.sin(C_coef * np.arctan(B_coef * slip))

def calculate_slip(v, omega):
    """Calculate slip ratio: lambda = (v - R*omega) / v"""
    omega_actual = max(0, omega)
    if v < 0.1:
        return 0.0
    slip = (v - R * omega_actual) / v
    return np.clip(slip, 0, 1)

def friction_force(slip_ratio):
    """Tire friction force"""
    return friction_coefficient(slip_ratio) * F_z

System Definition with ABS

Now we construct the vehicle braking system with ABS control:

[4]:
# ABS control state
abs_state = {'apply_brake': True}

# Wheel dynamics: J_w * domega/dt = -T_brake + R * F_x
omg_raw = Integrator(omega0)
omg_clp = Clip(min_val=0, max_val=1000)
omg_acc = Amplifier(1/J_w)

# Vehicle dynamics: M * dv/dt = -F_x
vel = Integrator(v0)
vel_acc = Amplifier(-1/M)

# Slip and friction
slp = Function(calculate_slip)
frc = Function(friction_force)
frc_cof = Function(friction_coefficient)

# ABS control
brk = Constant(T_brake)  # Will be modulated by events
brk_neg = Amplifier(-1)

# Torque summation
whl_trq = Amplifier(R)
trq_sum = Adder("++")

# Measurement
whl_vel = Amplifier(R)
sco1 = Scope(labels=["Vehicle Speed [m/s]", "Wheel Speed [m/s]"])
sco2 = Scope(labels=["Slip Ratio", "Friction Coeff"])

blocks = [
    omg_raw, omg_clp, omg_acc, vel, vel_acc,
    slp, frc, frc_cof, brk, brk_neg,
    whl_trq, trq_sum, whl_vel, sco1, sco2
]

The connections implement the coupled vehicle-wheel dynamics with Constant brake torque modulated by events:

[5]:
connections = [
    # Wheel dynamics
    Connection(omg_acc, omg_raw),
    Connection(omg_raw, omg_clp),
    Connection(omg_clp, whl_vel),
    Connection(trq_sum, omg_acc),

    # Vehicle dynamics
    Connection(vel_acc, vel),
    Connection(vel, sco1[0]),

    # Slip calculation
    Connection(vel, slp[0]),
    Connection(omg_clp, slp[1]),
    Connection(slp, sco2[0]),

    # Friction
    Connection(slp, frc),
    Connection(frc, whl_trq, vel_acc),
    Connection(slp, frc_cof),
    Connection(frc_cof, sco2[1]),

    # Brake control (ABS with events)
    Connection(brk, brk_neg),
    Connection(brk_neg, trq_sum[1]),

    # Torque balance
    Connection(whl_trq, trq_sum[0]),
    Connection(whl_vel, sco1[1]),
]

ABS Control Events

The ABS controller uses ZeroCrossing events to detect when the slip ratio exceeds the control thresholds and modulates brake torque accordingly:

[6]:
# Event: slip too high -> release brake
def evt_slip_high(t):
    """Detects when slip exceeds upper threshold"""
    slip_val = slp.outputs[0]
    return slip_val - (lambda_opt + abs_threshold)

def act_release_brake(t):
    """Release brake when slip is too high"""
    abs_state['apply_brake'] = False
    brk.value = 0

evt_high = ZeroCrossing(func_evt=evt_slip_high, func_act=act_release_brake, tolerance=1e-4)

# Event: slip too low -> apply brake
def evt_slip_low(t):
    """Detects when slip falls below lower threshold"""
    slip_val = slp.outputs[0]
    return (lambda_opt - abs_threshold) - slip_val

def act_apply_brake(t):
    """Apply brake when slip is too low"""
    abs_state['apply_brake'] = True
    brk.value = T_brake

evt_low = ZeroCrossing(func_evt=evt_slip_low, func_act=act_apply_brake, tolerance=1e-4)

events = [evt_high, evt_low]

Simulation with ABS

We initialize the simulation with the RKCK54 solver and ZeroCrossing events for accurate slip control:

[7]:
# Simulation initialization
Sim_ABS = Simulation(blocks, connections, events, Solver=RKCK54)

# Run simulation for 5 seconds
Sim_ABS.run(5)
09:31:00 - INFO - LOGGING (log: True)
09:31:00 - INFO - BLOCKS (total: 15, dynamic: 2, static: 13, eventful: 0)
09:31:00 - INFO - GRAPH (nodes: 15, edges: 18, alg. depth: 7, loop depth: 0, runtime: 0.091ms)
09:31:00 - INFO - STARTING -> TRANSIENT (Duration: 5.00s)
09:31:00 - INFO - --------------------   1% | 0.1s<2.5s | 2014.6 it/s
09:31:01 - INFO - ##------------------  11% | 1.1s<2.5s | 2005.6 it/s
09:31:02 - INFO - ####----------------  20% | 1.9s<2.1s | 1979.7 it/s
09:31:03 - INFO - #####---------------  29% | 2.9s<01:17 | 1946.4 it/s
09:31:04 - INFO - #######-------------  38% | 3.9s<8.8s | 1934.1 it/s
09:31:04 - INFO - ########------------  40% | 4.0s<1.7s | 1949.4 it/s
09:31:05 - INFO - #########-----------  49% | 5.0s<15.0s | 1958.1 it/s
09:31:06 - INFO - ###########---------  57% | 6.0s<1.4s | 1950.2 it/s
09:31:06 - INFO - ############--------  60% | 6.3s<1.4s | 1958.9 it/s
09:31:07 - INFO - #############-------  68% | 7.3s<1.5s | 2010.2 it/s
09:31:08 - INFO - ###############-----  75% | 8.3s<5.6s | 1921.0 it/s
09:31:09 - INFO - ################----  80% | 8.9s<0.7s | 1975.0 it/s
09:31:10 - INFO - #################---  87% | 9.9s<49.5s | 2025.2 it/s
09:31:11 - INFO - ##################--  94% | 10.9s<0.2s | 1985.8 it/s
09:31:11 - INFO - #################### 100% | 11.7s<--:-- | 1994.7 it/s
09:31:11 - INFO - FINISHED -> TRANSIENT (total steps: 22908, successful: 15004, runtime: 11738.20 ms)
[7]:
{'total_steps': 22908,
 'successful_steps': 15004,
 'runtime_ms': 11738.202169999568}

Comparison: System without ABS

To demonstrate the effectiveness of ABS, let’s simulate the same scenario without ABS control (constant maximum braking):

[8]:
# System without ABS - constant brake torque
omg_raw_noabs = Integrator(omega0)
omg_clp_noabs = Clip(min_val=0, max_val=1000)
omg_acc_noabs = Amplifier(1/J_w)

vel_noabs = Integrator(v0)
vel_acc_noabs = Amplifier(-1/M)

slp_noabs = Function(calculate_slip)
frc_noabs = Function(friction_force)
frc_cof_noabs = Function(friction_coefficient)

brk_noabs = Constant(T_brake)  # Constant brake torque (no ABS)
brk_neg_noabs = Amplifier(-1)

whl_trq_noabs = Amplifier(R)
trq_sum_noabs = Adder("++")
whl_vel_noabs = Amplifier(R)

sco1_noabs = Scope(labels=["Vehicle Speed [m/s]", "Wheel Speed [m/s]"])
sco2_noabs = Scope(labels=["Slip Ratio", "Friction Coeff"])

blocks_noabs = [
    omg_raw_noabs, omg_clp_noabs, omg_acc_noabs, vel_noabs, vel_acc_noabs,
    slp_noabs, frc_noabs, frc_cof_noabs, brk_noabs, brk_neg_noabs,
    whl_trq_noabs, trq_sum_noabs, whl_vel_noabs, sco1_noabs, sco2_noabs
]

connections_noabs = [
    Connection(omg_acc_noabs, omg_raw_noabs),
    Connection(omg_raw_noabs, omg_clp_noabs),
    Connection(omg_clp_noabs, whl_vel_noabs),
    Connection(trq_sum_noabs, omg_acc_noabs),
    Connection(vel_acc_noabs, vel_noabs),
    Connection(vel_noabs, sco1_noabs[0]),
    Connection(vel_noabs, slp_noabs[0]),
    Connection(omg_clp_noabs, slp_noabs[1]),
    Connection(slp_noabs, sco2_noabs[0]),
    Connection(slp_noabs, frc_noabs),
    Connection(frc_noabs, whl_trq_noabs, vel_acc_noabs),
    Connection(slp_noabs, frc_cof_noabs),
    Connection(frc_cof_noabs, sco2_noabs[1]),
    Connection(brk_noabs, brk_neg_noabs),
    Connection(brk_neg_noabs, trq_sum_noabs[1]),
    Connection(whl_trq_noabs, trq_sum_noabs[0]),
    Connection(whl_vel_noabs, sco1_noabs[1]),
]

# Simulation without ABS
Sim_NoABS = Simulation(blocks_noabs, connections_noabs, Solver=RKCK54, dt=0.001)
Sim_NoABS.run(5)
09:31:11 - INFO - LOGGING (log: True)
09:31:11 - INFO - BLOCKS (total: 15, dynamic: 2, static: 13, eventful: 0)
09:31:11 - INFO - GRAPH (nodes: 15, edges: 18, alg. depth: 7, loop depth: 0, runtime: 0.079ms)
09:31:11 - INFO - STARTING -> TRANSIENT (Duration: 5.00s)
09:31:11 - INFO - --------------------   1% | 0.0s<0.1s | 1745.3 it/s
09:31:11 - INFO - #################### 100% | 0.0s<--:-- | 1610.7 it/s
09:31:11 - INFO - FINISHED -> TRANSIENT (total steps: 9, successful: 9, runtime: 6.87 ms)
[8]:
{'total_steps': 9, 'successful_steps': 9, 'runtime_ms': 6.871988996863365}

Results and Comparison

Let’s plot and compare both scenarios:

[9]:
# Read data from both simulations
t_abs, [v_abs, w_abs] = sco1.read()
t_abs2, [slip_abs, mu_abs] = sco2.read()
t_noabs, [v_noabs, w_noabs] = sco1_noabs.read()
t_noabs2, [slip_noabs, mu_noabs] = sco2_noabs.read()

# Create comparison plots
fig, axs = plt.subplots(2, 2, figsize=(12, 8), tight_layout=True)

# Vehicle and wheel speeds
axs[0, 0].plot(t_abs, v_abs, label="Vehicle Speed (ABS)", lw=2)
axs[0, 0].plot(t_abs, w_abs, label="Wheel Speed (ABS)", lw=2, ls="--")
axs[0, 0].plot(t_noabs, v_noabs, label="Vehicle Speed (No ABS)", lw=2, alpha=0.7)
axs[0, 0].plot(t_noabs, w_noabs, label="Wheel Speed (No ABS)", lw=2, ls="--", alpha=0.7)
axs[0, 0].set_xlabel("Time [s]")
axs[0, 0].set_ylabel("Speed [m/s]")
axs[0, 0].set_title("Vehicle and Wheel Speeds")
axs[0, 0].legend()
axs[0, 0].grid(True)

# Slip ratio comparison
axs[0, 1].plot(t_abs2, slip_abs, label="ABS", lw=2)
axs[0, 1].plot(t_noabs2, slip_noabs, label="No ABS", lw=2, alpha=0.7)
axs[0, 1].axhline(lambda_opt, ls="--", c="gray", lw=1, label="Optimal Slip")
axs[0, 1].set_xlabel("Time [s]")
axs[0, 1].set_ylabel("Slip Ratio [-]")
axs[0, 1].set_title("Slip Ratio")
axs[0, 1].legend()
axs[0, 1].grid(True)

# Friction coefficient
axs[1, 0].plot(t_abs2, mu_abs, label="ABS", lw=2)
axs[1, 0].plot(t_noabs2, mu_noabs, label="No ABS", lw=2, alpha=0.7)
axs[1, 0].set_xlabel("Time [s]")
axs[1, 0].set_ylabel("Friction Coefficient [-]")
axs[1, 0].set_title("Friction Coefficient")
axs[1, 0].legend()
axs[1, 0].grid(True)

# Friction vs slip characteristic with operating points
slip_range = np.linspace(0, 1, 100)
mu_range = friction_coefficient(slip_range)
axs[1, 1].plot(slip_range, mu_range, 'k-', lw=2, label="Pacejka Model")
axs[1, 1].plot(slip_abs, mu_abs, 'b.', ms=1, alpha=0.3, label="ABS Operating Points")
axs[1, 1].plot(slip_noabs, mu_noabs, 'r.', ms=1, alpha=0.3, label="No ABS Operating Points")
axs[1, 1].axvline(lambda_opt, ls="--", c="gray", lw=1, label="Optimal Slip")
axs[1, 1].set_xlabel("Slip Ratio [-]")
axs[1, 1].set_ylabel("Friction Coefficient [-]")
axs[1, 1].set_title("Friction-Slip Characteristic")
axs[1, 1].legend()
axs[1, 1].grid(True)

plt.show()
../_images/examples_abs_braking_26_0.svg

Analysis

The results demonstrate the effectiveness of ABS:

With ABS:

  • Slip control: System maintains slip ratio near optimal value (0.15) through event-driven brake modulation

  • Wheel speed: Wheels continue rotating (no lockup), maintaining steering control

  • Friction utilization: ABS keeps friction coefficient near peak value (~1.0) throughout braking

  • Braking efficiency: Optimal slip control provides maximum deceleration

Without ABS:

  • Wheel lockup: Wheels lock immediately (wheel speed drops to zero)

  • Slip saturation: Slip ratio quickly reaches 1.0 (complete lockup)

  • Reduced friction: Friction coefficient drops significantly below peak value

  • Longer stopping distance: Reduced friction leads to increased braking distance

  • Loss of control: Locked wheels prevent steering corrections

The friction-slip characteristic plot clearly shows how ABS maintains operation near the peak friction region, while constant braking quickly pushes the system into the unstable high-slip region where friction degrades.

This example demonstrates PathSim’s capability to model complex multi-domain systems with event-driven control, combining:

  • Nonlinear multi-body dynamics (vehicle-wheel coupling)

  • Nonlinear tire friction characteristics (Pacejka model)

  • Zero-crossing event detection for precise control switching

  • Physical constraints (preventing negative wheel speeds)

  • Comparative analysis between controlled and uncontrolled systems