Skip to content

Mellinger Controller

The Mellinger controller is a geometric tracking controller originally developed for aggressive quadrotor maneuvers [1]. This implementation is based on the Crazyflie firmware version.

Controller Functions

state2attitude

drone_controllers.mellinger.control.state2attitude(pos, quat, vel, ang_vel, cmd, ctrl_errors=None, ctrl_info=None, ctrl_freq=100, *, mass, kp, kd, ki, gravity_vec, mass_thrust, int_err_max, thrust_max, pwm_max)

Compute the positional part of the mellinger controller.

All controllers are implemented as pure functions. Therefore, integral errors have to be passed as an argument and returned as well.

Parameters:

Name Type Description Default
pos Array

Drone position with shape (..., 3).

required
quat Array

Drone orientation as xyzw quaternion with shape (..., 4).

required
vel Array

Drone velocity with shape (..., 3).

required
ang_vel Array

Drone angular drone velocity in rad/s with shape (..., 3).

required
cmd Array

Full state command in SI units and rad with shape (..., 13). The entries are [x, y, z, vx, vy, vz, ax, ay, az, yaw, roll_rate, pitch_rate, yaw_rate].

required
ctrl_errors tuple[Array, ...] | None

Tuple of integral errors. For state2attitude, the tuple contains a single array (..., 3) for the position integral error or is None.

None
ctrl_info tuple[Array, ...] | None

Tuple of arrays with additional data. Not used in state2attitude.

None
ctrl_freq float

Control frequency in Hz

100
mass float

Drone mass used for calculations in the controller in kg.

required
kp Array

Proportional gain for the position controller with shape (3,).

required
kd Array

Derivative gain for the position controller with shape (3,).

required
ki Array

Integral gain for the position controller with shape (3,).

required
gravity_vec Array

Gravity vector with shape (3,). We assume gravity to be in the negative z direction. E.g., [0, 0, -9.81].

required
mass_thrust float

Conversion factor from thrust to PWM.

required
int_err_max Array

Range of the integral error with shape (3,). i_range in the firmware.

required
thrust_max float

Maximum thrust in N.

required
pwm_max float

Maximum PWM value.

required

Returns:

Type Description
Array

The RPY collective thrust command [rad, rad, rad, N], and the integral error of the position

Array

controller.

The position control component of the Mellinger controller. Converts desired position, velocity, and acceleration into attitude commands.

Example:

from drone_controllers import parametrize
from drone_controllers.mellinger import state2attitude

controller = parametrize(state2attitude, "cf2x_L250")

rpyt, pos_err_i = controller(pos, quat, vel, ang_vel, cmd)

attitude2force_torque

drone_controllers.mellinger.control.attitude2force_torque(pos, quat, vel, ang_vel, cmd, ctrl_errors=None, ctrl_info=None, ctrl_freq=500, *, kR, kw, ki_m, kd_omega, int_err_max, torque_pwm_max, thrust_max, pwm_min, pwm_max, L, KM, KF, mixing_matrix)

Compute the attitude to desired force-torque part of the Mellinger controller.

Note

We omit the axis flip in the firmware as it has only been introduced to make the controller compatible with the new frame of the Crazyflie 2.1.

Parameters:

Name Type Description Default
pos Array

Drone position with shape (..., 3).

required
quat Array

Drone orientation as xyzw quaternion with shape (..., 4).

required
vel Array

Drone velocity with shape (..., 3).

required
ang_vel Array

Drone angular drone velocity in rad/s with shape (..., 3).

required
cmd Array

Commanded attitude (roll, pitch, yaw) and total thrust [rad, rad, rad, N].

required
ctrl_errors tuple[Array, ...] | None

Tuple of integral errors. For attitude2force_torque, the tuple contains a single array (..., 3) for the angular velocity integral error or is None.

None
ctrl_info tuple[Array, ...] | None

Tuple of arrays with additional data. Not used in attitude2force_torque.

None
ctrl_freq int

Control frequency in Hz

500
kR Array

Proportional gain for the rotation error with shape (3,).

required
kw Array

Proportional gain for the angular velocity error with shape (3,).

required
ki_m Array

Integral gain for the rotation error with shape (3,).

required
kd_omega Array

Derivative gain for the angular velocity error with shape (3,).

required
int_err_max Array

Range of the integral error with shape (3,). i_range in the firmware.

required
torque_pwm_max Array

Maximum torque in PWM.

required
thrust_max float

Maximum thrust in N.

required
pwm_min float

Minimum PWM value.

required
pwm_max float

Maximum PWM value.

required
ang_vel_des

Desired angular velocity in rad/s.

required
prev_ang_vel

Previous angular velocity in rad/s.

required
prev_ang_vel_des

Previous angular velocity command in rad/s.

required
L float

Distance from the center of the quadrotor to the center of the rotor in m.

required
KM float

Torque constant (Nm/RPM).

required
KF float

Force constant (N/RPM).

required
mixing_matrix Array

Mixing matrix for the motor forces with shape (4, 3).

required

Returns:

Type Description
tuple[Array, Array, Array]

4 Motor forces [N], i_error_m

The attitude control component that converts attitude commands into desired forces and torques.

Example:

from drone_controllers import parametrize
from drone_controllers.mellinger import attitude2force_torque

controller = parametrize(attitude2force_torque, "cf2x_L250")

force, torque, att_err_i = controller(pos, quat, vel, ang_vel, rpyt_cmd)

force_torque2rotor_vel

drone_controllers.mellinger.control.force_torque2rotor_vel(force, torque, *, thrust_min, thrust_max, L, KM, KF, mixing_matrix)

Convert desired collective thrust and torques to rotor speeds.

The firmware calculates PWMs for each motor, compensates for the battery voltage, and then applies the modified PWMs to the motors. We assume perfect battery compensation here, skip the PWM interface except for clipping, and instead return desired motor forces.

Note

The equivalent function in the crazyflie firmware is power_distribution from power_distribution_quadrotor.c.

Warning

This function assumes an X rotor configuration.

Parameters:

Name Type Description Default
force Array

Desired thrust in SI units with shape (...,).

required
torque Array

Desired torque in SI units with shape (..., 3).

required
thrust_min float

Minimum thrust in N.

required
thrust_max float

Maximum thrust in N.

required
L float

Distance from the center of the quadrotor to the center of the rotor in m.

required
KM float

Torque constant (Nm/RPM).

required
KF float

Force constant (N/RPM).

required
mixing_matrix Array

Mixing matrix for the motor forces with shape (4, 3).

required

Returns:

Type Description
Array

The desired motor forces in SI units with shape (..., 4).

Converts desired forces and torques into individual rotor velocities using the quadrotor mixing matrix.

Example:

from drone_controllers import parametrize
from drone_controllers.mellinger import force_torque2rotor_vel

controller = parametrize(force_torque2rotor_vel, "cf2x_L250")

rotor_speeds = controller(force, torque)

Parameter Classes

StateParams

drone_controllers.mellinger.params.StateParams

Parameters for the Mellinger state controller.

load(drone_model) staticmethod

Load the parameters from the config file.

Parameters for the position control loop.

AttitudeParams

drone_controllers.mellinger.params.AttitudeParams

Parameters for the Mellinger attitude controller.

load(drone_model) staticmethod

Load the parameters from the config file.

Parameters for the attitude control loop.

ForceTorqueParams

drone_controllers.mellinger.params.ForceTorqueParams

Parameters for the Mellinger force torque controller.

load(drone_model) staticmethod

Load the parameters from the config file.

Parameters for the force/torque to rotor speed conversion.

Complete Controller Pipeline

Here's how to use all three components together:

import numpy as np
from drone_controllers import parametrize
from drone_controllers.mellinger import (
    state2attitude, 
    attitude2force_torque, 
    force_torque2rotor_vel
)

# Parametrize all three controller components
state_ctrl = parametrize(state2attitude, "cf2x_L250")
attitude_ctrl = parametrize(attitude2force_torque, "cf2x_L250")
rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250")

# Define state
pos = np.array([0.0, 0.0, 1.0])
quat = np.array([0.0, 0.0, 0.0, 1.0])  # [x, y, z, w]
vel = np.array([0.0, 0.0, 0.0])
ang_vel = np.array([0.0, 0.0, 0.0])

# Define command: [x, y, z, vx, vy, vz, ax, ay, az, yaw, r_rate, p_rate, y_rate]
cmd = np.array([1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])

# Run the complete pipeline
rpyt, pos_err_i = state_ctrl(pos, quat, vel, ang_vel, cmd)
force, torque, att_err_i = attitude_ctrl(pos, quat, vel, ang_vel, rpyt)
rotor_speeds = rotor_ctrl(force, torque)

print(f"Final rotor speeds: {rotor_speeds} rad/s")

Integral Error Handling

The Mellinger controller uses integral terms for robustness. You must pass integral errors between calls:

# Initialize
pos_err_i = None
att_err_i = None

for step in range(100):
    # Update state and command...

    # Pass previous integral errors
    ctrl_errors = (pos_err_i,) if pos_err_i is not None else None
    rpyt, pos_err_i = state_ctrl(pos, quat, vel, ang_vel, cmd, ctrl_errors=ctrl_errors)

    ctrl_errors = (att_err_i,) if att_err_i is not None else None  
    force, torque, att_err_i = attitude_ctrl(pos, quat, vel, ang_vel, rpyt, ctrl_errors=ctrl_errors)

    rotor_speeds = rotor_ctrl(force, torque)

Array API Compatibility

All Mellinger functions support the Python Array API and can be used with JAX, PyTorch, etc.:

import jax.numpy as jnp
from jax import jit

# Convert to JAX arrays
pos_jax = jnp.array([0.0, 0.0, 1.0])
quat_jax = jnp.array([0.0, 0.0, 0.0, 1.0])
# ... other arrays

# JIT compile the controller
jit_controller = jit(parametrize(state2attitude, "cf2x_L250"))

rpyt, pos_err_i = jit_controller(pos_jax, quat_jax, vel_jax, ang_vel_jax, cmd_jax)

References

[1] D. Mellinger and V. Kumar, "Minimum snap trajectory generation and control for quadrotors," 2011 IEEE International Conference on Robotics and Automation, Shanghai, China, 2011, pp. 2520-2525, doi: 10.1109/ICRA.2011.5980409.