# How Peng's PID Controller Prevents Integral Windup: A Code-Level Analysis

> Discover how Peng's PID controller prevents integral windup with code-level analysis. Learn to clamp accumulated error terms for stable control in your projects.

- Repository: [Yang Zhou/peng](https://github.com/makeecat/peng)
- Tags: internals
- Published: 2026-03-06

---

**Peng's quadrotor simulation prevents integral windup by clamping accumulated error terms to configurable bounds immediately after each integration step in the `PIDController` struct.**

Integral windup occurs when a PID controller accumulates error over time, causing excessive corrective action and system instability. The Peng simulation framework (makeecat/peng) implements a strict anti-windup strategy that caps integral terms before they can saturate the control outputs. This article examines the specific implementation details in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) and the configuration options available in [`src/config.rs`](https://github.com/makeecat/peng/blob/main/src/config.rs).

## Clamping Mechanism in src/lib.rs

The `PIDController` maintains separate integral accumulators for position and attitude control. During each control update, the framework adds the current error multiplied by the timestep (`dt`) to the running totals, then immediately applies clamping limits.

### Position Integral Protection

For position control, the `integral_pos_error` vector is constrained using the `max_integral_pos` bounds. According to the source code in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) (lines 69-73), the controller first integrates the position error and then clamps the result to prevent unbounded growth. This ensures that even if the quadrotor remains far from its target position for an extended period, the integral term cannot exceed the specified maximum.

### Attitude Integral Protection

Similarly, attitude control utilizes `integral_att_error` with corresponding `max_integral_att` limits. The implementation in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) (lines 17-21) follows the same pattern: accumulation followed by immediate clamping. This parallel structure ensures consistent anti-windup behavior across both translational and rotational control axes.

## Configuring Integral Limits in src/config.rs

The maximum allowable integral values are defined in the `PIDControllerConfig` struct within [`src/config.rs`](https://github.com/makeecat/peng/blob/main/src/config.rs) (lines 80-88). Users can tune these parameters through two key fields:

- **`pos_max_int`**: Configures the `max_integral_pos` bounds for translational control
- **`att_max_int`**: Configures the `max_integral_att` bounds for rotational control

These configuration values map directly to the clamping thresholds used in the control loop, allowing pilots and autonomy engineers to balance responsiveness against overshoot for specific flight scenarios.

## Practical Implementation Example

The following Rust example demonstrates how to instantiate the `PIDController` with protective integral limits and execute a control step:

```rust
use nalgebra::{Vector3, UnitQuaternion};
use peng_quad::PIDController;

// Define PID gains for position [P, D, I] and attitude [P, D, I]
let kpid_pos = [
    [1.2, 0.0, 0.0], // P gains
    [0.0, 1.0, 0.0], // D gains  
    [0.0, 0.0, 0.5], // I gains
];
let kpid_att = [
    [0.8, 0.0, 0.0],
    [0.0, 0.6, 0.0],
    [0.0, 0.0, 0.4],
];

// Configure anti-windup limits
let max_integral_pos = [0.5, 0.5, 0.5];
let max_integral_att = [0.3, 0.3, 0.3];

let mass = 1.5;
let gravity = 9.81;

// Initialize controller with integral clamping enabled
let mut pid = PIDController::new(
    kpid_pos,
    kpid_att,
    max_integral_pos,
    max_integral_att,
    mass,
    gravity,
);

// Execute control step with dt = 0.02s
let desired_pos = Vector3::new(0.0, 0.0, 2.0);
let current_pos = Vector3::new(0.0, 0.0, 0.0);
let desired_vel = Vector3::zeros();
let current_vel = Vector3::zeros();
let dt = 0.02;

let (thrust, orientation) = pid.compute_position_control(
    &desired_pos,
    &desired_vel,
    0.0,
    &current_pos,
    &current_vel,
    dt,
);

println!("Thrust: {thrust:.2} N");

```

In this example, the `integral_pos_error` will never exceed the vector `[0.5, 0.5, 0.5]`, regardless of how long the position error persists. This hard limit prevents the controller from commanding excessive thrust when recovering from large position deviations.

## Summary

- **Immediate clamping**: Peng's `PIDController` clamps integral errors immediately after accumulation in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs), preventing windup before it affects the output.
- **Dual protection**: Separate clamping mechanisms exist for both `integral_pos_error` (lines 69-73) and `integral_att_error` (lines 17-21).
- **Configurable limits**: The `PIDControllerConfig` struct in [`src/config.rs`](https://github.com/makeecat/peng/blob/main/src/config.rs) (lines 80-88) exposes `pos_max_int` and `att_max_int` for user tuning.
- **Stability assurance**: By capping integral terms, the controller maintains authority to stabilize the quadrotor even after sustained tracking errors.

## Frequently Asked Questions

### What is integral windup in PID control?

Integral windup occurs when the integral term accumulates a large error during periods of saturation or sustained deviation, causing the controller to overshoot significantly when the system finally corrects. In aerial robotics, this can lead to dangerous oscillations or loss of control authority during aggressive maneuvers.

### Why does Peng clamp the integral after adding the error rather than before?

Clamping after integration (`error * dt` addition) ensures the controller captures the current system state while preventing historical accumulation from growing indefinitely. This approach, implemented in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs), allows the controller to respond to recent persistent errors without retaining excessive integral values from past large deviations.

### How should I tune the `max_integral_pos` and `max_integral_att` values?

Tune these values based on your quadrotor's physical limits and desired agility. Start with conservative values (e.g., 0.3-0.5 for position in meters, 0.1-0.3 for attitude in radians) and increase gradually if the system exhibits steady-state error during hover, or decrease if you observe oscillation after large setpoint changes. The `PIDControllerConfig` fields `pos_max_int` and `att_max_int` in [`src/config.rs`](https://github.com/makeecat/peng/blob/main/src/config.rs) control these bounds.

### Where exactly in the codebase are the integral clamping operations performed?

The clamping operations occur in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) within the `PIDController` implementation. Position integral clamping appears at lines 69-73, while attitude integral clamping is located at lines 17-21. The maximum values themselves are configured in [`src/config.rs`](https://github.com/makeecat/peng/blob/main/src/config.rs) at lines 80-88 within the `PIDControllerConfig` struct definition.