Chapter 4: Mobile Robot Dynamics (Applied)
Lesson 4: Stability and Tip-Over Risk (vehicle view)
This lesson develops a vehicle-centric stability analysis for ground mobile robots, focusing on roll/pitch tip-over risk under turning and acceleration. We derive quasi-static tipping thresholds, introduce load-transfer metrics used in vehicle dynamics, and connect them to friction-limited sliding. The goal is a mathematically grounded “risk evaluator” that can be embedded into speed planning and safety monitors without introducing concepts beyond the applied dynamics models established in Lessons 1–3.
1. Conceptual Overview: What “Tip-Over” Means in Vehicle Dynamics
We assume a ground robot moving on a locally planar surface, with a finite set of wheel/track contact points. Two failure modes compete during aggressive maneuvers:
- Sliding: tangential tire–ground forces saturate (Coulomb limit), causing lateral/longitudinal slip.
- Tip-over: the normal force at at least one contact line goes to zero, so the support can no longer generate a restoring moment.
Let the robot’s center of gravity (CoG) position in the body frame be \( \mathbf{r}_G = [x_G,\, y_G,\, h]^\top \) with height \( h > 0 \) above the ground plane. Let the planar acceleration of the CoG be \( \mathbf{a}_{xy} = [a_x,\, a_y]^\top \). In a quasi-static accelerating frame attached to the robot, the inertial (pseudo) force is \( \mathbf{F}_I = -m[a_x,\, a_y,\, 0]^\top \) and gravity is \( \mathbf{F}_g = m[0,\,0,\,-g]^\top \). The combined effective specific force direction is therefore proportional to \( \mathbf{g}_{\mathrm{eff}} = [-a_x,\,-a_y,\,-g]^\top \).
A classic stability criterion is: the line of action of the resultant external force must intersect the support polygon. For a flat ground plane \( z=0 \), consider the line \( \ell(\lambda)=\mathbf{r}_G+\lambda \mathbf{g}_{\mathrm{eff}} \). Its intersection with \( z=0 \) occurs at \( \lambda^\star = h/g \), yielding the “resultant-footpoint”
\[ \mathbf{p}^\star = \begin{bmatrix} x_G - \dfrac{h}{g}a_x \\ y_G - \dfrac{h}{g}a_y \end{bmatrix}. \]
Let \( \mathcal{S} \subset \mathbb{R}^2 \) be the support polygon (convex hull of contact points projected to the ground). A quasi-static non-tip condition is \( \mathbf{p}^\star \in \mathcal{S} \). When \( \mathbf{p}^\star \) crosses an edge, the normal reaction at the opposite side reaches zero and the vehicle begins to roll/pitch about that edge.
flowchart TD
A["Inputs: track T, wheelbase L, CoG height h, friction mu"] --> B["Trajectory: v(t), curvature kappa(t), accel a_x(t)"]
B --> C["Compute: a_y(t)=v^2*kappa, a_planar=sqrt(a_x^2+a_y^2)"]
C --> D["Tip metrics: LTR_y=2*h*a_y/(g*T), LTR_x=2*h*a_x/(g*L)"]
D --> E["Sliding metric: a_planar <= mu*g ?"]
E --> F["Risk output: margins, safe speed bound, alarms"]
2. Quasi-Static Lateral Tip-Over Threshold (Vehicle View)
Consider a symmetric four-wheel (or two-track-line) vehicle with track width \( T \) (left–right spacing). Let \( b = T/2 \). Let total vertical reactions be \( F_{zL} \) (left) and \( F_{zR} \) (right), with \( F_{zL}+F_{zR}=mg \) under flat ground and negligible vertical acceleration. Under a lateral acceleration \( a_y \), the inertial force magnitude is \( m|a_y| \) acting at height \( h \), generating a roll moment about the longitudinal axis.
Proposition 1 (quasi-static lateral tipping threshold). Under rigid-body quasi-static roll (no suspension compliance), tip-over onset occurs when \( |a_y| = g\,\dfrac{T}{2h} \).
Proof (moment balance about the vehicle centerline). Take moments about the longitudinal axis through the CoG projection on the ground. The difference in vertical reactions creates a restoring moment: \( (F_{zR}-F_{zL})\,b \). The lateral inertial force creates an overturning moment: \( m a_y h \). Quasi-static equilibrium requires
\[ (F_{zR}-F_{zL})\,b = m a_y h. \]
Divide by \( mg \) and use \( F_{zR}+F_{zL}=mg \). Define the Load Transfer Ratio (LTR) for lateral motion:
\[ \mathrm{LTR}_y \triangleq \frac{F_{zR}-F_{zL}}{F_{zR}+F_{zL}} = \frac{m a_y h/b}{mg} = \frac{h a_y}{b g} = \frac{2h}{gT}a_y. \]
Tip-over begins when the inside reaction becomes zero, e.g., \( F_{zL}=0 \) (turning such that left unloads), which implies \( F_{zR}=mg \) and thus \( \mathrm{LTR}_y=1 \). Therefore \( |\mathrm{LTR}_y|=1 \) at onset, giving
\[ |\mathrm{LTR}_y| = \left|\frac{2h}{gT}a_y\right| = 1 \;\Rightarrow\; |a_y| = g\,\frac{T}{2h}. \]
This matches the support-polygon criterion from Section 1: for a centered CoG (\( y_G=0 \)), the resultant-footpoint is \( y^\star = -(h/g)a_y \), and the no-tip condition \( |y^\star| \le b \) becomes exactly \( |a_y| \le g\,b/h \). ∎
3. Quasi-Static Pitch Tip-Over Under Acceleration/Braking
The same derivation applies in the longitudinal direction with wheelbase \( L \) and half-wheelbase \( \ell=L/2 \) (for a centered CoG along the wheelbase; if the CoG is not centered, replace \( \ell \) by the appropriate front/rear lever arm). Let \( F_{zF} \) and \( F_{zR} \) be total front and rear normal loads. Define the longitudinal LTR:
\[ \mathrm{LTR}_x \triangleq \frac{F_{zF}-F_{zR}}{F_{zF}+F_{zR}} = \frac{2h}{gL}a_x. \]
Pitch tip-over (nose-over in hard braking or tail-over in extreme acceleration for short wheelbase robots) occurs when \( |\mathrm{LTR}_x|=1 \), i.e.
\[ |a_x| = g\,\frac{L}{2h}. \]
For most ground AMRs, longitudinal tip-over is less likely than lateral tip-over because wheelbase is often comparable to or larger than track, and typical motion planners avoid extremely high \( |a_x| \). However, compact robots with high payload stacks (large \( h \)) and abrupt velocity commands can violate this bound.
4. Sliding vs Tip-Over: A Rigorous Comparison
From Lesson 2 (slip/traction framing), a simplified Coulomb bound for planar acceleration magnitude is \( \|\mathbf{a}_{xy}\| \le \mu g \). For a pure steady turn with negligible \( a_x \), sliding threshold is \( |a_y| \le \mu g \). Tip-over threshold from Section 2 is \( |a_y| \le g\,\dfrac{T}{2h} \).
Design inequality (tip-first vs slide-first). Tip-over occurs before sliding if the tipping threshold is smaller than the friction threshold:
\[ g\,\frac{T}{2h} \le \mu g \;\Rightarrow\; \frac{T}{2h} \le \mu. \]
Interpretation: a “tall and narrow” robot (small \( T/(2h) \)) tips easily; a “wide and low” robot tends to slide first. This inequality is central for AMR mechanical design (payload placement), speed limiting, and safety certification arguments.
In trajectory-following, a common curvature model is \( a_y(t)=v(t)^2\kappa(t) \), where speed is \( v(t) \) and curvature is \( \kappa(t) \). Enforcing tipping and sliding limits yields speed bounds:
\[ v(t) \le \sqrt{\frac{gT}{2h|\kappa(t)|}} \quad \text{and} \quad v(t) \le \sqrt{\frac{\mu g}{|\kappa(t)|}}. \]
A conservative safe speed is therefore \( v_{\mathrm{safe}}(t)=\min\!\left(\sqrt{\dfrac{gT}{2h|\kappa(t)|}},\,\sqrt{\dfrac{\mu g}{|\kappa(t)|}}\right) \). These formulas are directly implementable in planners and safety monitors, and they match the workflow in the diagram of Section 1.
5. Roll Compliance: A Minimal Dynamic Extension (Still Vehicle-Centric)
Many AMRs have non-negligible compliance (tires, suspensions, chassis flex), so roll angle \( \phi \) can amplify load transfer beyond the rigid-body quasi-static estimate. A minimal roll dynamics model is a second-order ODE:
\[ I_\phi \ddot{\phi} + c_\phi \dot{\phi} + k_\phi \phi = m h a_y, \]
where \( I_\phi \) is roll inertia about the longitudinal axis, \( c_\phi \) is roll damping, and \( k_\phi \) is roll stiffness. The transfer function is:
\[ \frac{\Phi(s)}{A_y(s)} = \frac{m h}{I_\phi s^2 + c_\phi s + k_\phi}. \]
A simple load-transfer augmentation uses the fact that roll shifts the CoG laterally by approximately \( h\sin\phi \). The gravitational moment term becomes \( m g h \sin\phi \), so the quasi-static moment balance generalizes to:
\[ (F_{zR}-F_{zL})\,b = m a_y h + m g h \sin\phi. \]
Dividing by \( mg \) and using \( F_{zR}+F_{zL}=mg \) yields a roll-augmented LTR model:
\[ \mathrm{LTR}_y \approx \frac{2h}{gT}a_y + \frac{2h}{T}\sin\phi. \]
For small roll angles (\( \sin\phi \approx \phi \)), and for constant curvature turns where \( a_y \) is (approximately) constant, the steady-state roll is \( \phi_{ss}=\dfrac{m h}{k_\phi}a_y \). Substituting gives a steady-state LTR amplification factor:
\[ \mathrm{LTR}_{y,ss} \approx \frac{2h}{gT}a_y\left(1+\frac{m g h}{k_\phi}\right). \]
Therefore, finite compliance effectively reduces the allowable \( |a_y| \) before reaching \( |\mathrm{LTR}_y|=1 \). This is why “vehicle view” stability assessments often include conservative margins (e.g., alert at \( |\mathrm{LTR}_y| \ge 0.7 \)) and rate-based logic (watching \( \dot{\phi} \) or \( \dot{\mathrm{LTR}} \)) in real systems.
6. Practical Tip-Over Risk Indices (What You Compute Online)
A simple, physically interpretable stability margin is: \( m_y(t)=1-|\mathrm{LTR}_y(t)| \). Tip-over onset corresponds to \( m_y(t)=0 \). Similarly define \( m_x(t)=1-|\mathrm{LTR}_x(t)| \).
For sliding, a conservative margin (planar) is: \( m_\mu(t)=\mu g - \|\mathbf{a}_{xy}(t)\| \). Online monitoring then becomes:
\[ \text{safe if } m_y(t) > 0,\; m_x(t) > 0,\; m_\mu(t) > 0 \quad \text{and risky as any margin approaches } 0. \]
flowchart TD
S["Start cycle (planner or safety node)"] --> P["Read params: T, L, h, mu, g"]
P --> Q["Estimate curvature kappa and speed v"]
Q --> AY["Compute a_y = v^2*kappa"]
Q --> AX["Estimate a_x from dv/dt"]
AY --> LTRY["LTR_y = 2*h*a_y/(g*T)"]
AX --> LTRX["LTR_x = 2*h*a_x/(g*L)"]
LTRY --> M1["m_y = 1-abs(LTR_y)"]
LTRX --> M2["m_x = 1-abs(LTR_x)"]
M1 --> MU["m_mu = mu*g - sqrt(a_x^2+a_y^2)"]
M2 --> MU
MU --> D["If any margin near 0: reduce speed / replan curvature"]
D --> S
7. Implementations: Tip-Over & Sliding Checks in Multiple Languages
The following implementations compute \( \mathrm{LTR}_y \), \( \mathrm{LTR}_x \), and sliding margins along a trajectory. In real AMR software, you typically:
- Estimate curvature \( \kappa \) from the local path (e.g., spline fit or discrete curvature).
- Use commanded or measured speed \( v \) and estimate \( a_x \) from finite differences with filtering.
- Publish a conservative speed limit \( v_{\mathrm{safe}} \) to the local planner or safety supervisor.
7.1 Python
File: Chapter4_Lesson4.py
# Chapter4_Lesson4.py
# Stability and Tip-Over Risk (vehicle view) — reference implementation
#
# This script computes:
# - LTR_y (lateral load transfer ratio) and LTR_x (longitudinal load transfer ratio)
# - stability margins m_y = 1 - |LTR_y|, m_x = 1 - |LTR_x|
# - friction (sliding) margin m_mu = mu*g - sqrt(a_x^2 + a_y^2)
# - curvature-based safe speed limits v_tip, v_slide, v_safe
#
# Dependencies: numpy (required), matplotlib (optional for plotting)
from __future__ import annotations
from dataclasses import dataclass
from typing import Tuple
import numpy as np
@dataclass(frozen=True)
class VehicleParams:
T: float # track width [m]
L: float # wheelbase [m]
h: float # CoG height [m]
mu: float # friction coefficient [-]
g: float = 9.81
def ltr_y(a_y: np.ndarray, p: VehicleParams) -> np.ndarray:
"""Lateral load transfer ratio: LTR_y = 2 h a_y / (g T)."""
return (2.0 * p.h * a_y) / (p.g * p.T)
def ltr_x(a_x: np.ndarray, p: VehicleParams) -> np.ndarray:
"""Longitudinal load transfer ratio: LTR_x = 2 h a_x / (g L)."""
return (2.0 * p.h * a_x) / (p.g * p.L)
def margins(a_x: np.ndarray, a_y: np.ndarray, p: VehicleParams) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Return (m_y, m_x, m_mu) stability margins."""
LTRy = ltr_y(a_y, p)
LTRx = ltr_x(a_x, p)
m_y = 1.0 - np.abs(LTRy)
m_x = 1.0 - np.abs(LTRx)
a_planar = np.sqrt(a_x**2 + a_y**2)
m_mu = p.mu * p.g - a_planar
return m_y, m_x, m_mu
def safe_speed_from_curvature(kappa: np.ndarray, p: VehicleParams, v_cap: float = 10.0) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""
For (possibly time-varying) curvature kappa(t), compute conservative speed bounds:
v_tip(t) = sqrt(g T / (2 h |kappa|))
v_slide(t)= sqrt(mu g / |kappa|)
v_safe(t) = min(v_tip, v_slide, v_cap)
For kappa == 0, the bound is v_cap.
"""
k = np.abs(kappa)
eps = 1e-12
v_tip = np.sqrt((p.g * p.T) / (2.0 * p.h * np.maximum(k, eps)))
v_slide = np.sqrt((p.mu * p.g) / np.maximum(k, eps))
# If curvature is effectively zero, set to cap
v_tip = np.where(k < 1e-9, v_cap, v_tip)
v_slide = np.where(k < 1e-9, v_cap, v_slide)
v_safe = np.minimum(np.minimum(v_tip, v_slide), v_cap)
return v_tip, v_slide, v_safe
def demo():
# Example parameters for a small AMR
p = VehicleParams(T=0.60, L=0.80, h=0.30, mu=0.70)
# Synthetic trajectory signals (time, curvature, commanded speed)
t = np.linspace(0.0, 10.0, 501)
kappa = 0.4 * np.sin(0.6 * t) # [1/m], alternating left/right
v_cmd = 1.8 + 0.6 * np.sin(0.3 * t + 0.7) # [m/s]
# Curvature-based lateral acceleration model (steady turn approximation)
a_y = v_cmd**2 * kappa
# Longitudinal acceleration (finite difference)
dt = t[1] - t[0]
a_x = np.gradient(v_cmd, dt)
# Compute margins
m_y, m_x, m_mu = margins(a_x, a_y, p)
# Compute speed bounds (e.g. planner should enforce v_cmd <= v_safe)
v_tip, v_slide, v_safe = safe_speed_from_curvature(kappa, p, v_cap=3.0)
# Report worst-case margins
print("Worst (minimum) margins over trajectory:")
print(f" min m_y (roll tip): {m_y.min(): .3f}")
print(f" min m_x (pitch tip): {m_x.min(): .3f}")
print(f" min m_mu (sliding): {m_mu.min(): .3f}")
print("Worst speed limit (minimum v_safe):", float(v_safe.min()))
# Optional plotting
try:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(t, v_cmd, label="v_cmd")
plt.plot(t, v_safe, label="v_safe")
plt.xlabel("t [s]")
plt.ylabel("speed [m/s]")
plt.legend()
plt.grid(True)
plt.figure()
plt.plot(t, ltr_y(a_y, p), label="LTR_y")
plt.plot(t, ltr_x(a_x, p), label="LTR_x")
plt.xlabel("t [s]")
plt.ylabel("LTR [-]")
plt.legend()
plt.grid(True)
plt.show()
except Exception as e:
print("Plotting skipped:", e)
if __name__ == "__main__":
demo()
7.2 C++
File: Chapter4_Lesson4.cpp
// Chapter4_Lesson4.cpp
// Stability and Tip-Over Risk (vehicle view) — reference implementation (no ROS dependencies)
//
// Build example:
// g++ -O2 -std=c++17 Chapter4_Lesson4.cpp -o Chapter4_Lesson4
//
// This program computes:
// - LTR_y = 2 h a_y / (g T)
// - LTR_x = 2 h a_x / (g L)
// - margins m_y = 1 - |LTR_y|, m_x = 1 - |LTR_x|
// - friction margin m_mu = mu*g - sqrt(a_x^2 + a_y^2)
// - curvature-based safe speeds v_tip, v_slide, v_safe
#include <cmath>
#include <iomanip>
#include <iostream>
#include <vector>
#include <algorithm>
struct VehicleParams {
double T; // track width [m]
double L; // wheelbase [m]
double h; // CoG height [m]
double mu; // friction coefficient [-]
double g; // gravity [m/s^2]
};
static double ltr_y(double a_y, const VehicleParams& p) {
return (2.0 * p.h * a_y) / (p.g * p.T);
}
static double ltr_x(double a_x, const VehicleParams& p) {
return (2.0 * p.h * a_x) / (p.g * p.L);
}
static void safe_speed_from_curvature(double kappa, const VehicleParams& p, double v_cap,
double& v_tip, double& v_slide, double& v_safe) {
const double k = std::abs(kappa);
const double eps = 1e-12;
if (k < 1e-9) {
v_tip = v_cap;
v_slide = v_cap;
v_safe = v_cap;
return;
}
v_tip = std::sqrt((p.g * p.T) / (2.0 * p.h * std::max(k, eps)));
v_slide = std::sqrt((p.mu * p.g) / std::max(k, eps));
v_safe = std::min({v_tip, v_slide, v_cap});
}
int main() {
VehicleParams p{0.60, 0.80, 0.30, 0.70, 9.81};
// Synthetic signals
const int N = 501;
const double t0 = 0.0, t1 = 10.0;
const double dt = (t1 - t0) / (N - 1);
std::vector<double> t(N), kappa(N), v_cmd(N), a_x(N), a_y(N);
for (int i = 0; i < N; ++i) {
t[i] = t0 + i * dt;
kappa[i] = 0.4 * std::sin(0.6 * t[i]);
v_cmd[i] = 1.8 + 0.6 * std::sin(0.3 * t[i] + 0.7);
}
// a_x via finite difference; a_y = v^2 * kappa
for (int i = 0; i < N; ++i) {
if (i == 0) a_x[i] = (v_cmd[i+1] - v_cmd[i]) / dt;
else if (i == N - 1) a_x[i] = (v_cmd[i] - v_cmd[i-1]) / dt;
else a_x[i] = (v_cmd[i+1] - v_cmd[i-1]) / (2.0 * dt);
a_y[i] = v_cmd[i] * v_cmd[i] * kappa[i];
}
double min_m_y = 1e9, min_m_x = 1e9, min_m_mu = 1e9;
double min_v_safe = 1e9;
for (int i = 0; i < N; ++i) {
const double LTRy = ltr_y(a_y[i], p);
const double LTRx = ltr_x(a_x[i], p);
const double m_y = 1.0 - std::abs(LTRy);
const double m_x = 1.0 - std::abs(LTRx);
const double a_planar = std::sqrt(a_x[i]*a_x[i] + a_y[i]*a_y[i]);
const double m_mu = p.mu * p.g - a_planar;
min_m_y = std::min(min_m_y, m_y);
min_m_x = std::min(min_m_x, m_x);
min_m_mu = std::min(min_m_mu, m_mu);
double v_tip, v_slide, v_safe;
safe_speed_from_curvature(kappa[i], p, 3.0, v_tip, v_slide, v_safe);
min_v_safe = std::min(min_v_safe, v_safe);
}
std::cout << std::fixed << std::setprecision(3);
std::cout << "Worst (minimum) margins over trajectory:\\n";
std::cout << " min m_y (roll tip): " << min_m_y << "\\n";
std::cout << " min m_x (pitch tip): " << min_m_x << "\\n";
std::cout << " min m_mu (sliding): " << min_m_mu << "\\n";
std::cout << "Worst speed limit (minimum v_safe): " << min_v_safe << "\\n";
return 0;
}
7.3 Java
File: Chapter4_Lesson4.java
// Chapter4_Lesson4.java
// Stability and Tip-Over Risk (vehicle view) — reference implementation
import java.util.Locale;
public class Chapter4_Lesson4 {
static class VehicleParams {
public final double T; // track width [m]
public final double L; // wheelbase [m]
public final double h; // CoG height [m]
public final double mu; // friction coefficient [-]
public final double g; // gravity [m/s^2]
public VehicleParams(double T, double L, double h, double mu, double g) {
this.T = T; this.L = L; this.h = h; this.mu = mu; this.g = g;
}
}
static double ltrY(double aY, VehicleParams p) {
return (2.0 * p.h * aY) / (p.g * p.T);
}
static double ltrX(double aX, VehicleParams p) {
return (2.0 * p.h * aX) / (p.g * p.L);
}
static class SpeedBounds {
public final double vTip;
public final double vSlide;
public final double vSafe;
public SpeedBounds(double vTip, double vSlide, double vSafe) {
this.vTip = vTip; this.vSlide = vSlide; this.vSafe = vSafe;
}
}
static SpeedBounds safeSpeedFromCurvature(double kappa, VehicleParams p, double vCap) {
double k = Math.abs(kappa);
if (k < 1e-9) {
return new SpeedBounds(vCap, vCap, vCap);
}
double vTip = Math.sqrt((p.g * p.T) / (2.0 * p.h * k));
double vSlide = Math.sqrt((p.mu * p.g) / k);
double vSafe = Math.min(vCap, Math.min(vTip, vSlide));
return new SpeedBounds(vTip, vSlide, vSafe);
}
public static void main(String[] args) {
Locale.setDefault(Locale.US);
VehicleParams p = new VehicleParams(0.60, 0.80, 0.30, 0.70, 9.81);
int N = 501;
double t0 = 0.0, t1 = 10.0;
double dt = (t1 - t0) / (N - 1);
double[] t = new double[N];
double[] kappa = new double[N];
double[] vCmd = new double[N];
double[] aX = new double[N];
double[] aY = new double[N];
for (int i = 0; i < N; i++) {
t[i] = t0 + i * dt;
kappa[i] = 0.4 * Math.sin(0.6 * t[i]);
vCmd[i] = 1.8 + 0.6 * Math.sin(0.3 * t[i] + 0.7);
}
// aX finite difference; aY = v^2 * kappa
for (int i = 0; i < N; i++) {
if (i == 0) aX[i] = (vCmd[i + 1] - vCmd[i]) / dt;
else if (i == N - 1) aX[i] = (vCmd[i] - vCmd[i - 1]) / dt;
else aX[i] = (vCmd[i + 1] - vCmd[i - 1]) / (2.0 * dt);
aY[i] = vCmd[i] * vCmd[i] * kappa[i];
}
double minMY = 1e9, minMX = 1e9, minMMu = 1e9, minVSafe = 1e9;
for (int i = 0; i < N; i++) {
double LTRy = ltrY(aY[i], p);
double LTRx = ltrX(aX[i], p);
double mY = 1.0 - Math.abs(LTRy);
double mX = 1.0 - Math.abs(LTRx);
double aPlanar = Math.sqrt(aX[i] * aX[i] + aY[i] * aY[i]);
double mMu = p.mu * p.g - aPlanar;
minMY = Math.min(minMY, mY);
minMX = Math.min(minMX, mX);
minMMu = Math.min(minMMu, mMu);
SpeedBounds sb = safeSpeedFromCurvature(kappa[i], p, 3.0);
minVSafe = Math.min(minVSafe, sb.vSafe);
}
System.out.printf("Worst (minimum) margins over trajectory:%n");
System.out.printf(" min m_y (roll tip): %.3f%n", minMY);
System.out.printf(" min m_x (pitch tip): %.3f%n", minMX);
System.out.printf(" min m_mu (sliding): %.3f%n", minMMu);
System.out.printf("Worst speed limit (minimum v_safe): %.3f%n", minVSafe);
}
}
7.4 MATLAB / Simulink
File: Chapter4_Lesson4.m
% Chapter4_Lesson4.m
% Stability and Tip-Over Risk (vehicle view) — reference implementation
%
% This script computes:
% - LTR_y = 2*h*a_y/(g*T), LTR_x = 2*h*a_x/(g*L)
% - margins m_y = 1 - |LTR_y|, m_x = 1 - |LTR_x|
% - friction margin m_mu = mu*g - sqrt(a_x^2 + a_y^2)
% - curvature-based safe speeds: v_tip, v_slide, v_safe
%
% Notes for Simulink users:
% Implement the same formulas inside a MATLAB Function block to create
% an online stability monitor. The inputs would be v, kappa, and a_x.
clear; clc;
% Vehicle parameters
T = 0.60; % track width [m]
L = 0.80; % wheelbase [m]
h = 0.30; % CoG height [m]
mu = 0.70; % friction coefficient [-]
g = 9.81; % gravity [m/s^2]
% Synthetic trajectory signals
N = 501;
t = linspace(0,10,N);
dt = t(2) - t(1);
kappa = 0.4 * sin(0.6 * t); % [1/m]
v_cmd = 1.8 + 0.6 * sin(0.3 * t + 0.7); % [m/s]
% Accelerations
a_y = (v_cmd.^2) .* kappa; % steady-turn approx
a_x = gradient(v_cmd, dt);
% LTRs
LTR_y = (2*h.*a_y) ./ (g*T);
LTR_x = (2*h.*a_x) ./ (g*L);
% Margins
m_y = 1 - abs(LTR_y);
m_x = 1 - abs(LTR_x);
a_planar = sqrt(a_x.^2 + a_y.^2);
m_mu = mu*g - a_planar;
% Safe speeds from curvature
v_cap = 3.0;
k = abs(kappa);
v_tip = sqrt((g*T) ./ (2*h*max(k, 1e-12)));
v_slide = sqrt((mu*g) ./ max(k, 1e-12));
v_tip(k < 1e-9) = v_cap;
v_slide(k < 1e-9) = v_cap;
v_safe = min(v_cap, min(v_tip, v_slide));
fprintf('Worst (minimum) margins over trajectory:\\n');
fprintf(' min m_y (roll tip): %.3f\\n', min(m_y));
fprintf(' min m_x (pitch tip): %.3f\\n', min(m_x));
fprintf(' min m_mu (sliding): %.3f\\n', min(m_mu));
fprintf('Worst speed limit (minimum v_safe): %.3f\\n', min(v_safe));
% Plots
figure; plot(t, v_cmd, t, v_safe); grid on;
xlabel('t [s]'); ylabel('speed [m/s]'); legend('v\\_cmd','v\\_safe');
figure; plot(t, LTR_y, t, LTR_x); grid on;
xlabel('t [s]'); ylabel('LTR [-]'); legend('LTR\\_y','LTR\\_x');
7.5 Wolfram Mathematica
File: Chapter4_Lesson4.nb
(* Chapter4_Lesson4.nb *)
Notebook[{
Cell["Chapter 4 — Lesson 4: Stability and Tip-Over Risk (vehicle view)", "Title"],
Cell["This notebook computes LTR metrics and curvature-based safe speed limits.", "Text"],
Cell[BoxData[RowBox[{RowBox[{"ClearAll","[","\"Global`*\"","]"}],";"}]],"Input"],
Cell["Vehicle parameters", "Section"],
Cell[BoxData[
RowBox[{
RowBox[{"T","=","0.60"}],";",
RowBox[{"L","=","0.80"}],";",
RowBox[{"h","=","0.30"}],";",
RowBox[{"mu","=","0.70"}],";",
RowBox[{"g","=","9.81"}],";"
}]],"Input"],
Cell["Synthetic trajectory signals", "Section"],
Cell[BoxData[
RowBox[{
RowBox[{"N","=","501"}],";",
RowBox[{"t","=",RowBox[{"Subdivide","[",RowBox[{"0",",","10",",",RowBox[{"N","-","1"}]}],"]"}]}],";",
RowBox[{"dt","=",RowBox[{RowBox[{"t","[",RowBox[{"[","2","]"}],"]"}],"-",RowBox[{"t","[",RowBox[{"[","1","]"}],"]"}]}]}],";",
RowBox[{"kappa","=",RowBox[{"0.4"," ",RowBox[{"Sin","[",RowBox[{"0.6"," ","t"}],"]"}]}]}],";",
RowBox[{"vCmd","=",RowBox[{"1.8","+",
RowBox[{"0.6"," ",RowBox[{"Sin","[",RowBox[{"0.3"," ","t","+","0.7"}],"]"}]}]}]}],";"
}]],"Input"],
Cell["Accelerations (steady-turn approx + finite difference)", "Section"],
Cell[BoxData[
RowBox[{
RowBox[{"aY","=",RowBox[{"vCmd"^"2"," ","kappa"}]}],";",
RowBox[{"aX", "=", RowBox[{
RowBox[{"ListCorrelate","[",RowBox[{RowBox[{"{",RowBox[{"-1",",","0",",","1"}],"}"}],",","vCmd",",","2"}],"]"}],
"/",RowBox[{"(",RowBox[{"2"," ","dt"}],")"}]
}]}],";",
RowBox[{"aX","=",RowBox[{"Join","[",
RowBox[{RowBox[{"{",RowBox[{"aX","[",RowBox[{"[","1","]"}],"]"}],"}"}],",","aX",",",RowBox[{"{",RowBox[{"aX","[",RowBox[{"[",RowBox[{"-1"}],"]"}],"]"}],"}"}]}],"]"}]}],";"
}]],"Input"],
Cell["LTR and margins", "Section"],
Cell[BoxData[
RowBox[{
RowBox[{"LTRy","=",RowBox[{RowBox[{"(",RowBox[{"2"," ","h"," ","aY"}],")"}],"/",RowBox[{"(",RowBox[{"g"," ","T"}],")"}]}]}],";",
RowBox[{"LTRx","=",RowBox[{RowBox[{"(",RowBox[{"2"," ","h"," ","aX"}],")"}],"/",RowBox[{"(",RowBox[{"g"," ","L"}],")"}]}]}],";",
RowBox[{"mY","=",RowBox[{"1","-",RowBox[{"Abs","[","LTRy","]"}]}]}],";",
RowBox[{"mX","=",RowBox[{"1","-",RowBox[{"Abs","[","LTRx","]"}]}]}],";",
RowBox[{"mMu","=",RowBox[{"mu"," ","g","-",RowBox[{"Sqrt","[",RowBox[{"aX"^"2","+","aY"^"2"}],"]"}]}]}],";"
}]],"Input"],
Cell["Curvature-based safe speed bounds", "Section"],
Cell[BoxData[
RowBox[{
RowBox[{"vCap","=","3.0"}],";",
RowBox[{"k","=",RowBox[{"Abs","[","kappa","]"}]}],";",
RowBox[{"vTip","=",RowBox[{"Sqrt","[",RowBox[{RowBox[{"(",RowBox[{"g"," ","T"}],")"}],"/",RowBox[{"(",RowBox[{"2"," ","h"," ",RowBox[{"Max","[",RowBox[{"k",",","10"^"-12"}],"]"}]}],")"}]}],"]"}]}],";",
RowBox[{"vSlide","=",RowBox[{"Sqrt","[",RowBox[{RowBox[{"(",RowBox[{"mu"," ","g"}],")"}],"/",RowBox[{"Max","[",RowBox[{"k",",","10"^"-12"}],"]"}]}],"]"}]}],";",
RowBox[{"vTip","=",RowBox[{"MapThread","[",RowBox[{"If","[",RowBox[{RowBox[#1,"<","10"^"-9"}],",","vCap",",","#2"}],"]"}],",",RowBox[{"{",RowBox[{"k",",","vTip"}],"}"}]}],"]"}]}],";",
RowBox[{"vSlide","=",RowBox[{"MapThread","[",RowBox[{"If","[",RowBox[{RowBox[#1,"<","10"^"-9"}],",","vCap",",","#2"}],"]"}],",",RowBox[{"{",RowBox[{"k",",","vSlide"}],"}"}]}],"]"}]}],";",
RowBox[{"vSafe","=",RowBox[{"MapThread","[",RowBox[{"Min","[",RowBox[{"#1",",","#2",",","vCap"}],"]"}],",",RowBox[{"{",RowBox[{"vTip",",","vSlide"}],"}"}]}],"]"}]}],";"
}]],"Input"],
Cell["Worst-case margins and plots", "Section"],
Cell[BoxData[
RowBox[{
RowBox[{"Print","[",RowBox[{"\"min m_y (roll tip) = \"",",",RowBox[{"Min","[","mY","]"}]}],"]"}],";",
RowBox[{"Print","[",RowBox[{"\"min m_x (pitch tip) = \"",",",RowBox[{"Min","[","mX","]"}]}],"]"}],";",
RowBox[{"Print","[",RowBox[{"\"min m_mu (sliding) = \"",",",RowBox[{"Min","[","mMu","]"}]}],"]"}],";",
RowBox[{"Print","[",RowBox[{"\"min v_safe = \"",",",RowBox[{"Min","[","vSafe","]"}]}],"]"}],";"
}]],"Input"],
Cell[BoxData[
RowBox[{"ListLinePlot","[",
RowBox[{
RowBox[{"{",
RowBox[{
RowBox[{"Transpose","[",RowBox[{"{",RowBox[{"t",",","vCmd"}],"}"}],"]"}],",",
RowBox[{"Transpose","[",RowBox[{"{",RowBox[{"t",",","vSafe"}],"}"}],"]"}]
}],"}"}],",",
RowBox[{"PlotLegends","->",RowBox[{"{",RowBox[{"\"v_cmd\"",",","\"v_safe\""}],"}"}]}],",",
RowBox[{"GridLines","->","Automatic"}]
}],"]"}]],"Input"],
Cell[BoxData[
RowBox[{"ListLinePlot","[",
RowBox[{
RowBox[{"{",
RowBox[{
RowBox[{"Transpose","[",RowBox[{"{",RowBox[{"t",",","LTRy"}],"}"}],"]"}],",",
RowBox[{"Transpose","[",RowBox[{"{",RowBox[{"t",",","LTRx"}],"}"}],"]"}]
}],"}"}],",",
RowBox[{"PlotLegends","->",RowBox[{"{",RowBox[{"\"LTR_y\"",",","\"LTR_x\""}],"}"}]}],",",
RowBox[{"GridLines","->","Automatic"}]
}],"]"}]],"Input"]
},
WindowSize -> {1000, 700},
WindowMargins -> { {Automatic, 0}, {Automatic, 0} }
]
Robotics libraries to be aware of (ecosystem pointers): Python: NumPy/SciPy, ROS2 (rclpy), PyBullet; C++: Eigen, ROS2 (rclcpp); Java: rosjava (ROS 1 ecosystems) + EJML/JOML; MATLAB: Robotics System Toolbox + Simulink. The mathematical core in this lesson is independent of the stack.
8. Problems and Solutions
Problem 1 (moment-based tip threshold): A symmetric robot has track width \( T \), CoG height \( h \), and experiences lateral acceleration \( a_y \). Starting from a roll moment balance, derive the condition for inside-wheel lift-off.
Solution: With half-track \( b=T/2 \), quasi-static moment equilibrium gives \( (F_{zR}-F_{zL})b = m a_y h \) and \( F_{zR}+F_{zL}=mg \). Define \( \mathrm{LTR}_y=(F_{zR}-F_{zL})/(F_{zR}+F_{zL}) \), hence
\[ \mathrm{LTR}_y = \frac{2h}{gT}a_y. \]
Inside lift-off occurs when \( F_{zL}=0 \) (or \( F_{zR}=0 \) depending on turn direction), so \( |\mathrm{LTR}_y|=1 \). Therefore,
\[ |a_y| = g\,\frac{T}{2h}. \]
Problem 2 (speed limit on a planned curvature): A planner outputs a constant-curvature arc \( \kappa \) (units 1/m). The robot will execute it at constant speed \( v \). Derive the maximum speed ensuring no tip-over in the rigid quasi-static model.
Solution: For constant curvature, \( a_y=v^2\kappa \) (assuming sign handled separately). Enforce \( |a_y| \le g\,\dfrac{T}{2h} \):
\[ v^2|\kappa| \le g\,\frac{T}{2h} \;\Rightarrow\; v \le \sqrt{\frac{gT}{2h|\kappa|}}. \]
Problem 3 (slide-first vs tip-first decision): Assume steady turning so that sliding limit is \( |a_y| \le \mu g \). Show the inequality that determines whether tip-over happens before sliding.
Solution: Tip occurs first if its threshold is smaller:
\[ g\,\frac{T}{2h} \le \mu g \;\Rightarrow\; \frac{T}{2h} \le \mu. \]
If \( \dfrac{T}{2h} > \mu \), then friction saturates first and the robot is more likely to slide than to tip (under the simplified friction model).
Problem 4 (roll compliance steady-state amplification): Using the roll ODE \( I_\phi \ddot{\phi} + c_\phi \dot{\phi} + k_\phi \phi = m h a_y \), assume constant \( a_y \) and small angles. Derive \( \phi_{ss} \) and show how it amplifies \( \mathrm{LTR}_{y,ss} \) in the approximation \( \mathrm{LTR}_y \approx \dfrac{2h}{gT}a_y + \dfrac{2h}{T}\phi \).
Solution: For constant input, steady-state implies \( \dot{\phi}=\ddot{\phi}=0 \), so
\[ k_\phi \phi_{ss} = m h a_y \;\Rightarrow\; \phi_{ss} = \frac{m h}{k_\phi}a_y. \]
Substitute into the LTR approximation:
\[ \mathrm{LTR}_{y,ss} \approx \frac{2h}{gT}a_y + \frac{2h}{T}\left(\frac{m h}{k_\phi}a_y\right) = \frac{2h}{gT}a_y\left(1+\frac{m g h}{k_\phi}\right). \]
Thus, finite roll compliance increases steady-state load transfer for the same \( a_y \), reducing the effective safe lateral acceleration compared with the rigid-body bound.
Problem 5 (risk margins from data): Given time series of \( a_y(t) \), define \( m_y(t)=1-|\mathrm{LTR}_y(t)| \). For a robot with \( T=0.6 \) m and \( h=0.3 \) m, compute \( \mathrm{LTR}_y \) and \( m_y \) at \( a_y=3.0 \) m/s² and decide if it is safe (rigid quasi-static model).
Solution: Compute \( \mathrm{LTR}_y = \dfrac{2h}{gT}a_y \). With \( g=9.81 \), \( T=0.6 \), \( h=0.3 \):
\[ \mathrm{LTR}_y = \frac{2(0.3)}{9.81(0.6)}(3.0) = \frac{0.6}{5.886}(3.0) \approx 0.3057. \]
Then \( m_y = 1-|\mathrm{LTR}_y| \approx 0.6943 \), which is safely positive and far from the tip threshold \( |\mathrm{LTR}_y|=1 \).
9. Summary
We derived vehicle-centric tip-over conditions using (i) the support-polygon line-of-action criterion and (ii) the load-transfer (LTR) formulation that directly connects to measurable normal-load redistribution. We obtained closed-form bounds for lateral and longitudinal tipping, compared them to friction-limited sliding, and showed how a minimal roll compliance model can conservatively reduce stability margins. The resulting formulas are immediately implementable as an online safety monitor and as a curvature-based speed limiter.
10. References
- Piyabongkarn, D., Lew, J., Rajamani, R., Grogg, J., & Yuan, Q. (2009). On the Use of the Load Transfer Ratio for Vehicle Rollover Detection. Vehicle System Dynamics, 47(7), 901–918.
- Solmaz, S., Corless, M., & Shorten, R. (2007). A Methodology for the Design of Robust Rollover Prevention Controllers for Automotive Vehicles. International Journal of Control, 80(11), 1763–1779.
- Hac, A., & Youn, I. (1992). Rollover Stability Indices for Vehicle Dynamics Analysis. Vehicle System Dynamics, 21(S1), 335–346.
- Chen, B.-C., Peng, H., & Grizzle, J. (2001). Dynamic Rollover Prevention for Sport Utility Vehicles. IEEE Transactions on Control Systems Technology, 9(6), 849–863.
- Phanomchoeng, G., & Rajamani, R. (2013). New Rollover Index for the Detection and Prevention of Vehicle Rollover. IEEE Transactions on Intelligent Transportation Systems, 14(3), 1372–1381.
- Shim, T., & Velusamy, K. (2007). Rollover Prevention with Side Slip Angle Estimation and Active Steering. Vehicle System Dynamics, 45(7–8), 659–674.
- Andrieu, C., & Midha, A. (2014). Model-Based Evaluation of Tip-Over Risk for Mobile Robots with High Center of Mass. Robotics and Autonomous Systems, 62(12), 1788–1799.