PX4 Autopilot Guide 2026
Complete guide to PX4 v1.15: build from source, SITL simulation with Gazebo, QGroundControl configuration, pymavlink offboard control, and native ROS 2 integration via XRCE-DDS.
1. Installation & SITL
Get PX4 running in simulation before touching any hardware.
Clone PX4 and install dependencies
Clone the PX4-Autopilot repo and run the ubuntu_sim.sh dependency script.
# Clone PX4-Autopilot (stable release v1.15) git clone --recurse-submodules https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot # Install all dependencies (compilers, SITL tools, Gazebo) bash Tools/setup/ubuntu.sh # Reload environment source ~/.bashrc
💡 ubuntu.sh installs gcc-arm-none-eabi, gstreamer, Gazebo Harmonic, and all Python deps. Run on Ubuntu 22.04 or 24.04.
Build and run SITL simulation
Build PX4 for SITL with Gazebo and start a simulated quadcopter. No real hardware needed.
# Build PX4 for SITL with Gazebo Harmonic (recommended 2026) make px4_sitl gz_x500 # This builds PX4 and launches: # - PX4 SITL process # - Gazebo Harmonic with x500 quadcopter model # - MAVLink ground station link on UDP 14550 # In the PX4 shell that opens, type: commander arm commander takeoff # You should see the drone take off in Gazebo.
Connect QGroundControl to SITL
QGroundControl (QGC) auto-connects to SITL via UDP on launch. Use it to upload parameters, plan missions, and monitor telemetry.
# Download QGroundControl AppImage (Linux) wget https://d176tv9ibo4jno.cloudfront.net/latest/QGroundControl.AppImage chmod +x QGroundControl.AppImage ./QGroundControl.AppImage # QGC auto-discovers PX4 SITL on UDP 14550. # For real hardware via USB: # - Plug in Pixhawk USB → QGC detects /dev/ttyACM0 auto # For telemetry radio: # - QGC → Application Settings → Comm Links → Add → UDP port 14550
💡 PX4 listens on UDP 14550 (GCS), 14540 (offboard), and 14557 (forward). All three ports are available in SITL.
2. MAVLink — pymavlink & MAVROS
Command PX4 from Python or ROS using the MAVLink protocol.
Offboard control with pymavlink
Use pymavlink to send MAVLink commands from Python — arm, takeoff, set waypoints.
from pymavlink import mavutil
import time
# Connect to SITL (UDP) or real FC (serial)
master = mavutil.mavlink_connection('udpin:0.0.0.0:14540')
# Wait for heartbeat
master.wait_heartbeat()
print(f"Connected: system {master.target_system}")
# Arm the vehicle
master.arducopter_arm()
master.motors_armed_wait()
print("Armed!")
# Command takeoff to 5m
master.mav.command_long_send(
master.target_system,
master.target_component,
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
0, 0, 0, 0, 0, 0, 0, 5.0 # altitude = 5m
)
time.sleep(5)
# Fly to waypoint (lat, lon, alt)
master.mav.set_position_target_global_int_send(
0, master.target_system, master.target_component,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
0b0000111111111000, # position only
int(47.397742 * 1e7), int(8.545594 * 1e7), 10,
0, 0, 0, 0, 0, 0, 0, 0
)MAVROS for ROS 1 (or MAVSDK for Python)
MAVROS is the ROS 1 bridge. For ROS 2, use XRCE-DDS (built-in to PX4 v1.14+) instead — see the ROS 2 section.
# Install MAVROS for ROS Humble (for ROS 1 projects only)
sudo apt install ros-humble-mavros ros-humble-mavros-extras -y
sudo /opt/ros/humble/lib/mavros/install_geographiclib_datasets.sh
# Launch MAVROS connected to SITL
ros2 launch mavros px4.launch fcu_url:="udp://:14540@127.0.0.1:14557"
# Check connection
ros2 topic echo /mavros/state
# Arm and takeoff via MAVROS service
ros2 service call /mavros/cmd/arming mavros_msgs/srv/CommandBool "{value: true}"
ros2 service call /mavros/cmd/takeoff mavros_msgs/srv/CommandTOL "{altitude: 5.0}"💡 For new projects targeting ROS 2, prefer PX4's native XRCE-DDS bridge (next section). MAVROS adds 10-30ms latency vs native uORB topics.
3. Native ROS 2 — XRCE-DDS (PX4 v1.14+)
PX4's native uORB-to-ROS2 bridge. No MAVROS, sub-millisecond latency, direct topic access.
PX4 native ROS 2 via XRCE-DDS (PX4 v1.14+)
PX4 v1.14+ ships XRCE-DDS client built-in. Run the Micro XRCE-DDS Agent on the companion computer to bridge uORB topics to ROS 2 natively — no MAVROS.
# Install Micro XRCE-DDS Agent pip install -U micro-xrce-dds-agent # Or build from source (for ROS 2 Jazzy) git clone https://github.com/eProsima/Micro-XRCE-DDS-Agent cd Micro-XRCE-DDS-Agent mkdir build && cd build cmake .. -DUXRCE_BUILD_SHARED_LIBS=ON make -j$(nproc) sudo make install # Start the agent (UDP for SITL, serial /dev/ttyS0 for hardware) MicroXRCEAgent udp4 -p 8888 # In another terminal, start PX4 SITL make px4_sitl gz_x500 # PX4 XRCE client auto-connects to agent. # In ROS 2: ros2 topic list | grep /fmu
💡 XRCE-DDS gives sub-millisecond latency from PX4 uORB topics to ROS 2. All PX4 internal messages (VehicleOdometry, SensorGps, VehicleCommand) publish directly.
ROS 2 offboard control node
Write a ROS 2 node that arms PX4 and commands it in offboard mode using native uORB topics.
import rclpy
from rclpy.node import Node
from px4_msgs.msg import (
OffboardControlMode, TrajectorySetpoint,
VehicleCommand, VehicleStatus
)
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy, DurabilityPolicy
class OffboardControl(Node):
def __init__(self):
super().__init__('offboard_control')
qos = QoSProfile(
reliability=ReliabilityPolicy.BEST_EFFORT,
durability=DurabilityPolicy.TRANSIENT_LOCAL,
history=HistoryPolicy.KEEP_LAST,
depth=1
)
self.offboard_pub = self.create_publisher(OffboardControlMode, '/fmu/in/offboard_control_mode', qos)
self.traj_pub = self.create_publisher(TrajectorySetpoint, '/fmu/in/trajectory_setpoint', qos)
self.cmd_pub = self.create_publisher(VehicleCommand, '/fmu/in/vehicle_command', qos)
self.timer = self.create_timer(0.1, self.timer_callback) # 10Hz
self.counter = 0
def arm(self):
cmd = VehicleCommand()
cmd.command = VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM
cmd.param1 = 1.0 # arm
cmd.from_external = True
self.cmd_pub.publish(cmd)
def engage_offboard(self):
cmd = VehicleCommand()
cmd.command = VehicleCommand.VEHICLE_CMD_DO_SET_MODE
cmd.param1 = 1.0 # custom mode
cmd.param2 = 6.0 # offboard
self.cmd_pub.publish(cmd)
def timer_callback(self):
# Must publish offboard mode setpoints at >2Hz before switching to offboard
mode = OffboardControlMode()
mode.position = True
self.offboard_pub.publish(mode)
setpoint = TrajectorySetpoint()
setpoint.position = [0.0, 0.0, -5.0] # NED frame: -Z = up 5m
setpoint.yaw = 0.0
self.traj_pub.publish(setpoint)
if self.counter == 10: # After 1s of setpoints, arm and switch
self.arm()
self.engage_offboard()
self.counter += 1
def main():
rclpy.init()
node = OffboardControl()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()💡 PX4 requires the offboard setpoint stream to run for >0.5s before accepting VEHICLE_CMD_DO_SET_MODE offboard. The 10-counter delay above ensures this.
4. PX4-Compatible Flight Controllers 2026
| FC | Chip | IMU | Price | Best for |
|---|---|---|---|---|
| Pixhawk 6C | STM32H753 | ICM-42688-P × 2 | $279 | Best for research + ROS 2 |
| Pixhawk 6X | STM32H753 | ICM-42688-P × 3 | $399 | Triple IMU redundancy — critical systems |
| Holybro Kakute H7 | STM32H743 | ICM42688-P | $49 | Best budget FC for PX4 |
| mRo Control Zero H7 | STM32H743 | ICM-42688-P | $189 | Compact form factor for tight builds |