OpenPilot Steering Control Strategies: Angle vs Torque vs PID Explained

OpenPilot supports three distinct lateral steering control strategies—Angle, Torque, and PID—which are selected automatically at startup based on the vehicle's EPS hardware capabilities defined in CarParams.lateralControlMethod.

The commaai/openpilot repository implements multiple lateral control algorithms to accommodate different vehicle electronic power steering (EPS) systems. Each strategy represents a different approach to commanding the steering actuator, ranging from direct angle requests to closed-loop torque control. Understanding how these steering control strategies work and how OpenPilot chooses between them is essential for debugging vehicle-specific tuning issues.

The Three Steering Control Strategies in OpenPilot

OpenPilot's lateral control architecture provides three concrete implementations of the base LatControl class, each targeting different steering command interfaces exposed by vehicle firmware.

Angle Control (Direct Steering Wheel Commands)

The Angle strategy commands a specific steering wheel angle in degrees, functioning as a direct position controller. Implemented in selfdrive/controls/lib/latcontrol_angle.py, this controller calculates the target angle from the desired curvature and applies a saturation check based on the measured angle difference.


# In a running OpenPilot loop

active = True                     # vehicle is enabled

angle_steer, angle_log = latcontrol.update(active, CS, VM, params,
                                          steer_limited_by_safety,
                                          desired_curvature,
                                          curvature_limited,
                                          lat_delay)

# `angle_steer` is the target steering wheel angle in degrees.

This method is selected for vehicles that expose direct angle control interfaces, such as some newer Tesla models, allowing the EPS to handle the low-level motor control internally.

Torque Control (PID on Torque)

The Torque strategy sends a steering torque command in Newton-meters (Nm) to the EPS. Implemented in selfdrive/controls/lib/latcontrol_torque.py, this approach uses a classic PIDController that computes a torque value to correct steering-angle error while respecting vehicle-specific torque limits.

torque, angle_des, torque_log = latcontrol.update(
    active, CS, VM, params,
    steer_limited_by_safety,
    desired_curvature,
    curvature_limited,
    lat_delay)

# `torque` (Nm) is sent to the vehicle's EPS.

According to the openpilot source code, this is the preferred method for many Toyota, Nissan, and Hyundai models that accept direct torque requests rather than angle setpoints.

PID Control (Angle-Based with Torque Output)

The PID strategy implements a PID loop on steering angle that outputs a torque command. Implemented in selfdrive/controls/lib/latcontrol_pid.py, this controller computes a torque feed-forward term from the desired curvature, then feeds the angle error back through a PID controller, capping the final output by the maximum allowed torque.

torque, angle_des, pid_log = latcontrol.update(
    active, CS, VM, params,
    steer_limited_by_safety,
    desired_curvature,
    curvature_limited,
    lat_delay)

# The PID controller (`PIDController`) computes `torque`.

This serves as the most widely compatible fallback method, converting angle errors into torque requests for EPS systems that do not support the other two native interfaces.

How OpenPilot Selects the Steering Controller

The steering control strategy selection occurs during controller initialization through a factory pattern that inspects vehicle-specific parameters populated during the fingerprinting process.

CarParams and the lateralControlMethod Field

The selection logic resides in selfdrive/controls/controlsd.py, where CP.lateralControlMethod (populated from selfdrive/car/interfaces.py during vehicle fingerprinting) determines which concrete class to instantiate:


# controlsd.py (excerpt)

if CP.lateralControlMethod == "torque":
    self.latcontrol = LatControlTorque(CP, CI, DT)
elif CP.lateralControlMethod == "angle":
    self.latcontrol = LatControlAngle(CP, CI, DT)
else:                     # default → pid

    self.latcontrol = LatControlPID(CP, CI, DT)

The CP.lateralTuning.which() method returns the string identifier "pid", "torque", or "angle" based on the vehicle's declared capabilities in its fingerprint.

Hardware Capability Mapping

  • Torque: Assigned to vehicles with torque-based EPS interfaces (many Toyota, Nissan, and Hyundai models)
  • Angle: Assigned to vehicles supporting direct angle commands (some newer Teslas)
  • PID: Default fallback for all other supported platforms that require angle-error-based torque derivation

Thus, the hardware capability of the specific vehicle model determines which steering control strategy OpenPilot uses, with the selection made automatically during startup based on the parsed CarParams.

Implementation Details and Code Examples

Each controller follows a consistent interface defined in selfdrive/controls/lib/latcontrol.py, implementing an update() method that receives the current vehicle state, desired curvature, and various safety limits.

The factory instantiation in controlsd.py (approximately lines 165-188) ensures that only one controller remains active throughout the drive, preventing strategy switching while the vehicle is enabled. This design allows the specific tuning parameters for each strategy—stored in CP.lateralTuning—to remain isolated and vehicle-specific.

Summary

  • Three strategies: OpenPilot implements Angle (direct position), Torque (PID on force), and PID (angle-error feedback) controllers in latcontrol_angle.py, latcontrol_torque.py, and latcontrol_pid.py respectively.
  • Automatic selection: The CarParams.lateralControlMethod field, populated during vehicle fingerprinting in selfdrive/car/interfaces.py, determines which strategy to instantiate.
  • Factory pattern: selfdrive/controls/controlsd.py uses a simple conditional factory to create the appropriate controller at runtime based on the vehicle's EPS capabilities.
  • Hardware-dependent: Torque control is preferred for compatible EPS systems, Angle for direct-actuation systems, and PID serves as the universal fallback.

Frequently Asked Questions

What determines which steering control strategy my car uses in OpenPilot?

Your vehicle's specific EPS hardware capabilities determine the strategy. During the fingerprinting process, OpenPilot reads the car's CarParams definition in selfdrive/car/interfaces.py to check lateralControlMethod. Cars with direct torque interfaces receive the Torque controller, those with angle actuation receive the Angle controller, and all others default to the PID controller.

Can I manually switch between Angle, Torque, and PID controllers?

No. The controller selection is hardcoded per vehicle model in the source code and determined at startup. While you could theoretically modify selfdrive/car/interfaces.py to change lateralControlMethod for your specific fingerprint, this would likely result in unsafe steering behavior unless your EPS actually supports the alternative command type.

Why does the Torque controller use PID while the PID controller also exists?

The Torque controller (latcontrol_torque.py) runs a PID loop calculating the optimal torque to apply based on steering angle error. The PID controller (latcontrol_pid.py) is an older compatibility layer that also runs a PID loop on angle error but was designed for vehicles where the Torque interface wasn't yet reverse-engineered. Torque control generally provides better performance when the EPS accepts direct torque commands.

Where are the steering control strategies defined in the codebase?

The concrete implementations reside in selfdrive/controls/lib/latcontrol_angle.py, selfdrive/controls/lib/latcontrol_torque.py, and selfdrive/controls/lib/latcontrol_pid.py. The selection logic and factory instantiation occur in selfdrive/controls/controlsd.py, while the vehicle-specific assignments are defined in selfdrive/car/interfaces.py within each car's CarParams configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →