Aerostack2

DocsPlugins

Motion controller

The PID speed, differential flatness and geometric controllers, and every gain they take.

Authored by
  • @alvgaona

The motion controller turns the references a behavior sends into commands the platform accepts, and its plugin holds the control law. How the controller picks a mode and when it steps aside is on the motion controller concept page.

PluginTakesSendsNeeds the mass
pid_speed_controllerPosition, speed, speed in a plane or trajectory, with either yaw modeSpeed with a yaw rateNo
differential_flatness_controllerTrajectory with a yaw angleBody rates and thrustYes
geometric_controllerPosition or speed with either yaw mode, or trajectory with a yaw angleBody rates and thrust, or speed with a yaw rateYes, for body rates

Choosing a plugin

  • PID speed works with any platform that takes velocities, which is most real drones, Gazebo and the multirotor simulator. It's the only one that serves speed in a plane.
  • Differential flatness tracks trajectories on a platform that takes body rates and thrust, and uses the trajectory's acceleration as a feed-forward. It only takes trajectories, so the behaviors need their trajectory plugins.
  • Geometric also drives body rates with the trajectory's acceleration as a feed-forward, but takes position and speed references too. When the platform doesn't take body rates, or the mass isn't set, it sends velocities instead.

Running it

ros2 launch as2_motion_controller controller_launch.py \
  namespace:=drone0 \
  plugin_name:=pid_speed_controller \
  plugin_config_file:=config/pid_speed_controller.yaml

Add use_sim_time:=true in simulation. Your plugin file is merged over the plugin's defaults, so it only needs the gains you change. The gains sit under the plugin's name:

config/pid_speed_controller.yaml
/**:
  ros__parameters:
    pid_speed_controller:
      position_control:
        kp: {x: 1.0, y: 1.0, z: 1.0}
        ki: {x: 0.01, y: 0.01, z: 0.01}
      yaw_control:
        kp: 1.0

Set the gains for every mode you fly

Every default gain is 0 for PID speed and geometric, and they refuse a mode whose gains are all 0. The controller then won't start that mode. Write every gain as a decimal, 1.0 rather than 1. A whole number stops the controller when it starts.

Gains can be changed while flying, for example to tune them:

ros2 param set /drone0/controller_manager pid_speed_controller.position_control.kp.x 1.5

PID speed

PID loops that turn each kind of reference into a velocity and a yaw rate. With a position reference, a PID on the position error gives the velocity. With a trajectory, one PID acts on the position error with its derivative on the velocity error. The setpoint's acceleration isn't used. With speed in a plane, the height comes from its own PID on the reference height.

LoopTurnsInto
position_controlA position errorA velocity
speed_controlA velocity errorA velocity, when use_bypass is false
trajectory_controlA position and velocity errorA velocity
speed_in_a_plane_control.heightA height errorA vertical velocity
speed_in_a_plane_control.speedA horizontal velocity errorA horizontal velocity
yaw_controlA yaw errorA yaw rate

Each loop takes these settings, shown here for position_control:

ParameterDefaultWhat it does
position_control.kp.x, .y, .z0.0The proportional gains
position_control.ki.x, .y, .z0.0The integral gains
position_control.kd.x, .y, .z0.0The derivative gains
position_control.antiwindup_cte0.0Caps the accumulated error on each axis. 0 means no cap
position_control.alpha0.0Filters the derivative, from 0 to 1. 1 means no filter, and 0 turns the derivative off
position_control.reset_integralfalseClears the accumulated error when the error changes sign and the cap is exceeded

The default alpha of 0 turns the derivative off, so raise it before tuning kd. A few loops differ from the pattern:

  • yaw_control and speed_in_a_plane_control.height take single gains, with no axes.
  • speed_in_a_plane_control.speed takes x and y only.
  • speed_in_a_plane_control takes antiwindup_cte, alpha and reset_integral once, for both its loops. Its antiwindup_cte only reaches the height loop for now.
  • trajectory_control ignores alpha.

A mode needs non-zero gains in its loop, and every mode needs a yaw gain. Speed references are the exception while use_bypass is on. Speed in a plane still needs a non-zero speed gain, even with bypass on.

ParameterDefaultWhat it does
use_bypasstruePasses velocity references through instead of running speed_control
proportional_limitationtrueScales the whole velocity down when it's over the speed limit of a position reference, so its direction stays the same
debug.desired_velocity_topicdesired_velocityPublishes the velocity under debug/controller/. Empty turns it off

With a position reference, the speed the behavior sends becomes a speed limit on each axis. The plugin's use_bypass is separate from the controller's own. When the controller's is on and the platform takes velocities, speed references skip the plugin entirely.

Differential flatness

Computes the force the drone needs from the position and velocity errors, gravity and the trajectory's acceleration. It turns that force into a desired attitude and sends body rates toward it, with the thrust in newtons.

ParameterDefaultWhat it does
mass0.82The drone's mass, in kg
trajectory_control.kp.x, .y, .z6.0, 6.0, 6.0The position gains, in N/m
trajectory_control.kd.x, .y, .z1.5, 1.5, 3.0The velocity gains, in N·s/m
trajectory_control.ki.x, .y, .z0.005, 0.005, 0.065The integral gains. They have almost no effect for now
trajectory_control.antiwindup_cte1.0The largest integral force, in newtons
trajectory_control.roll_control.kp5.5The roll attitude gain
trajectory_control.pitch_control.kp5.5The pitch attitude gain
trajectory_control.yaw_control.kp2.0The yaw attitude gain

Set mass to your drone's real mass. The platform has to take thrust in newtons.

Geometric

Cascaded PID loops from position to velocity to acceleration, followed by a geometric attitude controller that sends body rates and thrust in newtons. Trajectories skip the cascade and use one PID plus the trajectory's acceleration.

When it sends velocities instead, it stops after the position loop and sends the velocity with a yaw rate. Trajectories then use the position gains plus the trajectory's velocity.

The kp, ki and kd gains are lists of three values, for x, y and z. The other settings are single values.

ParameterDefaultWhat it does
position.kp, .ki, .kd[0.0, 0.0, 0.0]Turn the position error into a velocity
position.antiwindup_cte0.0Caps the accumulated error. 0 means no cap
position.alpha1.0Filters the derivative, from 0 to 1. 1 means no filter
velocity.kp, .ki, .kd[0.0, 0.0, 0.0]Turn the velocity error into an acceleration
velocity.antiwindup_cte, velocity.alpha0.0, 1.0As for position
velocity.saturation_upper, velocity.saturation_lower[0.0, 0.0, 0.0]The acceleration limits, in m/s². Equal values turn them off
trajectory.kp, .ki, .kd[0.0, 0.0, 0.0]Turn the trajectory's position and velocity errors into an acceleration
trajectory.antiwindup_cte0.0Caps the accumulated error
yaw.kp, .ki, .kd0.0Turn the yaw error into a yaw rate, when sending velocities
yaw.antiwindup_cte, yaw.alpha0.0, 1.0As for position
geometric.mass0.0The drone's mass, in kg
geometric.rotation_kp[0.0, 0.0, 0.0]The attitude gains for roll, pitch and yaw
geometric.yaw_speed_limit0.0The largest yaw rate a yaw rate reference can ask for, in rad/s. 0 means no limit

To use the acceleration limits, set both, with every value of saturation_lower below saturation_upper. Setting only the upper one leaves the lower at 0, which blocks every negative acceleration.

Only the kp gains decide which modes are available:

SendingNeeds
Body ratesgeometric.mass and every rotation_kp above 0, velocity.kp, plus position.kp for position references and trajectory.kp for trajectories
Velocitiesyaw.kp above 0, plus position.kp for position references and trajectories

With a position reference, the speed the behavior sends limits the size of the velocity. Debug topics are set with debug.desired_velocity_topic, debug.acceleration_topic and debug.velocity_norm_topic.

Last updated on November 3, 2026

On this page

Last updated on November 3, 2026