Skip to content

Control Modes

Crazyflow provides four levels of control abstraction, from high-level position setpoints down to direct motor commands. Each level is a separate control mode selected at construction time.

Control hierarchy

Commands flow down a hierarchy. A state command is converted to an attitude command by the Mellinger controller; an attitude command is converted to force/torque by the geometric controller; force/torque is converted to rotor velocities by the mixer.

State (13D)
  └─ Mellinger controller
       └─ Attitude (4D: roll, pitch, yaw, thrust)
            └─ Geometric controller
                 └─ Force/torque (4D: Fc, Tx, Ty, Tz)
                      └─ Mixer
                           └─ Rotor velocities (4D: ω₁…ω₄)

When you select Control.state, the full chain runs on every control tick. When you select Control.attitude, only the lower two stages run.

State control

from crazyflow.sim import Sim
from crazyflow.control import Control

sim = Sim(control=Control.state, state_freq=100, attitude_freq=500)
sim.reset()

Command shape: (n_worlds, n_drones, 13)

Index Variable Units
0–2 Target position \(x, y, z\) m
3–5 Target velocity \(\dot{x}, \dot{y}, \dot{z}\) m/s
6–8 Target acceleration \(\ddot{x}, \ddot{y}, \ddot{z}\) m/s²
9 Yaw rad
10 Roll rate rad/s
11 Pitch rate rad/s
12 Yaw rate rad/s

Set unused elements to zero. A common hover command sets only the z position:

import numpy as np
from crazyflow.sim import Sim
from crazyflow.control import Control

sim = Sim(control=Control.state)
sim.reset()

cmd = np.zeros((1, 1, 13), dtype=np.float32)
cmd[0, 0, 2] = 1.0  # hover at 1 m

sim.state_control(cmd)
sim.step(sim.freq // sim.control_freq)

Attitude control

from crazyflow.sim import Sim, Physics
from crazyflow.control import Control

sim = Sim(control=Control.attitude, physics=Physics.so_rpy, attitude_freq=500)
sim.reset()

Command shape: (n_worlds, n_drones, 4)

Index Variable Units
0 Roll setpoint rad
1 Pitch setpoint rad
2 Yaw setpoint rad
3 Collective thrust N

For a hover command, set thrust to mass × g:

import numpy as np
from crazyflow.sim import Sim, Physics
from crazyflow.control import Control

sim = Sim(control=Control.attitude, physics=Physics.so_rpy)
sim.reset()

mass = float(sim.data.params.mass[0, 0, 0])
cmd = np.zeros((1, 1, 4), dtype=np.float32)
cmd[0, 0, 3] = mass * 9.81

sim.attitude_control(cmd)
sim.step(sim.freq // sim.control_freq)

Force-torque control

Direct force and torque input. Requires Physics.first_principles.

Command shape: (n_worlds, n_drones, 4)

Index Variable Units
0 Collective force \(F_c\) N
1 Body-frame torque \(\tau_x\) Nm
2 Body-frame torque \(\tau_y\) Nm
3 Body-frame torque \(\tau_z\) Nm
import numpy as np
from crazyflow.sim import Sim, Physics
from crazyflow.control import Control

sim = Sim(control=Control.force_torque, physics=Physics.first_principles)
sim.reset()

mass = float(sim.data.params.mass[0, 0, 0])
cmd = np.zeros((1, 1, 4), dtype=np.float32)
cmd[0, 0, 0] = mass * 9.81

sim.force_torque_control(cmd)
sim.step(1)

Rotor velocity control

Direct motor commands. Requires Physics.first_principles.

Command shape: (n_worlds, n_drones, 4)

Index Motor Units
0–3 Motors 0–3 angular velocity RPM

The hover RPM for cf2x_L250 is approximately 15 000 RPM, but the exact value depends on drone mass.

import numpy as np
from crazyflow.sim import Sim, Physics
from crazyflow.control import Control

sim = Sim(control=Control.rotor_vel, physics=Physics.first_principles)
sim.reset()

cmd = np.full((1, 1, 4), 15_000.0, dtype=np.float32)

sim.rotor_vel_control(cmd)
sim.step(1)

Control frequency

Each control mode has its own update rate. The physics tick (freq) is always the fastest.

Mode Rate argument Default
state state_freq 100 Hz
attitude attitude_freq 500 Hz
force_torque force_torque_freq 500 Hz
rotor_vel every physics step

The simulator applies a new command only when the control tick fires. Between ticks, the previous command is held. The number of physics steps per control tick is freq // control_freq.

Next steps