Comparison of PD and PID controllers with anti-windup in the task of controlling the distance in the column.
Column movement control is one of the key tasks in the field of intelligent transport systems. The main goal is to ensure the synchronous movement of a group of vehicles with a set distance between neighboring cars, minimizing jerks, eliminating overtaking inside the convoy and guaranteeing safety. To achieve this goal, each driven vehicle is equipped with a controller that calculates the required acceleration based on information about the position and speed of the vehicle in front. However, the real conditions – acceleration and speed restrictions, delays in communication channels, measurement noise – significantly complicate the task. This paper presents two models of the column control system implemented in the Engee simulation environment: a basic model based on a classical PD controller and an improved model using a PID controller. In this example, a comparative analysis of their effectiveness is carried out according to criteria such as the accuracy of maintaining distance, the presence of overtaking and resistance to disturbances.
Simple model (PD controller)
The basic version uses a classic PD controller.:
,
where ( ) – distance error,
( D ) – set distance,
( ) – speed error.
This model demonstrates a variant of parallel construction, in which the slaves strive to occupy specified positions relative to the leader. During the simulation, it is observed that the pairs "Slave‑1 and Slave‑3", as well as "Slave‑2 and Slave‑4" line up synchronously — each pair is at the same distance from the leader. Thanks to the different starting positions, you can clearly see how the slaves accelerate in different ways to reach the required distance.
However, the current implementation has the following limitations:
-
If there are constant external disturbances (for example, road slope or crosswind), a fixed distance error occurs in the system – the slaves cannot accurately maintain the set distance.
-
The dead zone cuts off small control signals. Combined with the lack of an integral component in the regulator, this leads to a long–term position drift, and eventually to overtaking by the leader's wingmen. Thus, a purely PD controller does not provide stable tracking of the distance over long time intervals.
The structure of the slave includes error calculation, signal amplification, adder, acceleration limitation unit and dual integrator (speed → position).
All slaves have an identical architecture and are connected in parallel: information from the leader goes directly to each slave, which leads to the formation of pairs "Slave‑1 and Slave‑3", "Slave‑2 and Slave‑4" moving synchronously.
function run_model(name)
p, is_loaded = joinpath(@__DIR__, name * ".engee"), name ∉ [m.name for m in engee.get_all_models()]
is_loaded && engee.load(p, force=true)
out = engee.run(engee.open(name), verbose=true)
is_loaded && engee.close(name, force=true)
sleep(0.1); return out
end
initial_positions = [-10.0, -20.0, -30.0, -40.0] # The initial positions of the wingmen relative to the leader
accel_limit_lower = -3.0 # min. acceleration, m/s2
accel_limit_upper = 3.0 # max. acceleration, m/s2
speed_limit_lower = 0.0 # min. speed, m/s
speed_limit_upper = 15.0 # max. speed, m/s
# PD Controller Parameters
D = 10.0 # set distance, m
Kp = 0.5 # proportional coefficient
Kd = 2.0 # The differential coefficient
run_model("Platooning_Basic")
# Data extraction with thinning
function get_signal(simout, key, step=1)
t = collect(simout[key]).time
val = collect(simout[key]).value
return t[1:step:end], val[1:step:end]
end
t_lx, x_l = get_signal(simout, "Platooning_Basic/Leader.X_0", 5)
t_lv, v_l = get_signal(simout, "Platooning_Basic/Leader.V_0", 5)
t1x, x1 = get_signal(simout, "Platooning_Basic/Slave-1.x", 5)
t1v, v1 = get_signal(simout, "Platooning_Basic/Slave-1.v", 5)
t2x, x2 = get_signal(simout, "Platooning_Basic/Slave-2.x", 5)
t2v, v2 = get_signal(simout, "Platooning_Basic/Slave-2.v", 5)
t3x, x3 = get_signal(simout, "Platooning_Basic/Slave-3.x", 5)
t3v, v3 = get_signal(simout, "Platooning_Basic/Slave-3.v", 5)
t4x, x4 = get_signal(simout, "Platooning_Basic/Slave-4.x", 5)
t4v, v4 = get_signal(simout, "Platooning_Basic/Slave-4.v", 5)
# Speed graph
plot(title="Speeds", xlabel="Time (s)", ylabel="Speed (m/s)")
plot!(t_lv, v_l, label="Leader")
plot!(t1v, v1, label="Slave-1")
plot!(t2v, v2, label="Slave-2")
plot!(t3v, v3, label="Slave-3")
display(plot!(t4v, v4, label="Slave-4"))
# Chart of positions
plot(title="Positions", xlabel="Time (s)", ylabel="Position (m)")
plot!(t_lx, x_l, label="Leader")
plot!(t1x, x1, label="Slave-1")
plot!(t2x, x2, label="Slave-2")
plot!(t3x, x3, label="Slave-3")
plot!(t4x, x4, label="Slave-4")
As we can see, the wingmen are ahead of each other, which should not be the case.
Improved model (PID controller with anti‑windup)
To eliminate these problems, a more sophisticated version was developed, which implements a PID controller with additional mechanisms.:
Improvements made:
- An integral channel has been added – it provides zero steady-state error with constant disturbances.
- Integrator limitation (anti‑windup) – the error integrator output is saturated within ([-0.3; 0.3]), this prevents its unlimited accumulation during prolonged presence of the control signal in the dead zone or in acceleration/speed saturation. Anti-windup — this is a set of methods and techniques aimed at preventing or mitigating the "windup" effect in control systems, especially in PID controllers, when the control signal exceeds the capabilities of the actuator (for example, it reaches the power or speed limit). This effect can lead to the accumulation of errors, instability of the system and deterioration of its performance.
.png)
- Acceleration limitation is used.
- Readjustment of coefficients – to improve transients, the differential coefficient was increased (K_d = 2.0), and to slow down the accumulation of the integral, a relatively small integral coefficient was selected (K_i = 0.05). The proportional coefficient remained the same (K_p = 0.5).
The article demonstrates the evolution of the model: from a simple PD controller to a full-fledged PID controller with windup protection. It is shown that it is the addition of an integral component and an anti‑windup mechanism that makes it possible to completely eliminate the effect of overtaking the leader, with proper adjustment, it is possible to eliminate overtaking ahead, ensure stable distance keeping and realistic column behavior over long simulation intervals.
The results of the work can be useful in the design of adaptive cruise control (ACC) systems and control of unmanned columns.
leader_speed_max = 10.0 # max. leader's speed, m/s
leader_accel_max = 3.0 # acceleration of the leader, m/s2
D = 10.0 # set distance, m
Kp, Kd, Ki = 0.5, 2.0, 0.05 # coefficients of the PID controller
accel_limit_lower, accel_limit_upper = -2.0, 2.0 # acceleration limit, m/s2
speed_limit_lower, speed_limit_upper = 0.0, 15.0 # speed limit, m/s
int_err_lower, int_err_upper = -0.3, 0.3 # anti-windows integrator
# Measurement noise
noise_mean = 0.0
noise_variance_position = 0.01
noise_variance_speed = 5
noise_sample_time = 0.01
delay_time = 0.3 # transport delay, with
initial_positions = [-20.0, -40.0, -60.0] # The initial positions of the slaves (from the leader)
run_model("Platooning_AntiWindup")
t_lx, x_l = get_signal(simout, "Platooning_AntiWindup/Leader.X_0")
t_lv, v_l = get_signal(simout, "Platooning_AntiWindup/Leader.V_0")
t1x, x1 = get_signal(simout, "Platooning_AntiWindup/Slave-1.x")
t1v, v1 = get_signal(simout, "Platooning_AntiWindup/Slave-1.v")
t2x, x2 = get_signal(simout, "Platooning_AntiWindup/Slave-2.x")
t2v, v2 = get_signal(simout, "Platooning_AntiWindup/Slave-2.v")
t3x, x3 = get_signal(simout, "Platooning_AntiWindup/Slave-3.x")
t3v, v3 = get_signal(simout, "Platooning_AntiWindup/Slave-3.v")
t = t_lx # General timeline (using the leader's time)
n = length(t);
This graph shows the dynamics of the relative position of the wingmen relative to the leader over time, where negative values correspond to lag, the zero line is the leader's position, and the absence of an intersection of this line confirms the absence of overtaking.
t = t_lx
plot(t, [x1 .- x_l, x2 .- x_l, x3 .- x_l],
label=["Ved-1" "Ved-2" "Vedas-3"],
xlabel="Time (s)", ylabel="Relative position (m)")
hline!([0], linestyle=:dash, label="The leader's position")
The animation of the phase portrait illustrates the evolution of the trajectories of all cars in the coordinates "position – speed", where the absence of intersection of the trajectories of the driven with the trajectory of the leader confirms the safe driving mode without overtaking.
@gif for i in 1:n fps = 5
plot(title="Phase portrait of the column",
xlabel="Position, m", ylabel="Speed, m/s",
legend=:bottomright, xlims=(minimum(x_l)-10, maximum(x_l)+5), ylims=(-1, 16))
# Current position points
scatter!([x_l[i]], [v_l[i]], label="Leader", markersize=8, color=:black)
scatter!([x1[i]], [v1[i]], label="Slave‑1", markersize=6, color=:red)
scatter!([x2[i]], [v2[i]], label="Slave‑2", markersize=6, color=:green)
scatter!([x3[i]], [v3[i]], label="Slave‑3", markersize=6, color=:blue)
# Trajectory lines for the last 5 seconds
dt = 5.0
idx = findall(t .>= t[i] - dt)
plot!(x_l[idx], v_l[idx], color=:black, alpha=0.5, linewidth=1, label="")
plot!(x1[idx], v1[idx], color=:red, alpha=0.5, linewidth=1, label="")
plot!(x2[idx], v2[idx], color=:green, alpha=0.5, linewidth=1, label="")
plot!(x3[idx], v3[idx], color=:blue, alpha=0.5, linewidth=1, label="")
annotate!(x_l[i], v_l[i]+0.6, text("t = $(round(t[i], digits=1)) with", 8, :black))
end
This animation shows the movement of the column in a frame of reference associated with the leader, where each driven car is shown as a point on the road with a signed current speed, and negative values along the abscissa axis correspond to the lag, which clearly demonstrates the preservation of a safe distance and the absence of overtaking.
y_coord = 1.0
colors = [:black, :red, :green, :blue]
labels = ["Leader", "Slave-1", "Slave-2", "Slave-3"]
x_min = -80
x_max = 10
@gif for i in 1:n fps = 3
# Positions relative to the leader
pos_leader = x_l[i] - x_l[i] # 0
pos1 = x1[i] - x_l[i]
pos2 = x2[i] - x_l[i]
pos3 = x3[i] - x_l[i]
positions = [pos_leader, pos1, pos2, pos3]
speeds = [v_l[i], v1[i], v2[i], v3[i]]
current_time = round(t[i], digits=1)
# Basic schedule
scatter(positions, fill(y_coord, 4),
markersize = 12,
markercolor = colors,
xlim = (x_min, x_max),
ylim = (0.5, 1.5),
xlabel = "Distance from the leader (m)",
ylabel = "",
yticks = ([y_coord], ["Road"]),
legend = false,
title = "Time: $(current_time) s, Leader's speed: $(round(v_l[i], digits=1)) m/s"
)
for (j, (pos, sp)) in enumerate(zip(positions, speeds))
if j == 1
continue
else
annotate!(pos + 2, y_coord + 0.05, text("$(round(sp, digits=1)) m/s", 8, colors[j]))
end
end
for x in range(-100, 50, step=5)
if x_min < x < x_max
plot!([x, x], [0.6, 1.4], linewidth=0.5, color=:gray, alpha=0.4, label="")
end
end
end
From data analysis, it can be seen that all the slaves are moving in the phase domain, without crossing the leader's trajectory.
The integral component successfully compensates for the effects of noise and inaccuracies, and the anti‑windup mechanism prevents overshoot during prolonged saturations. As a result, the column remains stable throughout the simulation.
Conclusion
The paper compares two approaches to column distance control: a classic PD controller and a PID controller with windup protection. Modeling in the Engee environment showed that:
- The PD controller is easy to implement, but suffers from an established error, is sensitive to the dead zone and noise, which leads to an unacceptable effect of overtaking the leader by the slaves.
- The anti-windup PID controller completely eliminates these disadvantages. The addition of an integral channel ensures zero error in steady–state mode, and the limitation of the integrator is stability at saturation. The improved model demonstrates the correct behavior of the column over long time intervals, resistance to noise and delays, which makes it suitable for practical use in adaptive cruise control (ACC) and unmanned column control systems.
Thus, the evolution from a PD to a PID controller with an anti-windup is necessary to ensure reliable and safe movement in the convoy.

