ArduPilot: The Open-Source Autopilot That Learned to Fly, Drive, and Dive

A look inside the portable control stack that lets one C++ core power copters, planes, rovers, and submarines with hard real-time discipline and a surprisingly flexible mode system.

8-10 min read • View on GitHub • More from ArduPilot

A wide black-ink illustration shows one central control tower connected by thin technical lines to four very different vehicles: a quadcopter above, a fixed-wing plane in profile, a rover on the ground, and a submarine below a surface line. The image explains ArduPilot's central idea, that one shared control stack can span air, land, and water without becoming four separate projects.
ArduPilot's real trick is not just flying. It is keeping one control core portable across vehicles that move in completely different environments.
Key Takeaways

One codebase, four vehicle classes, many hardware targets

ArduPilot's first surprise is breadth. It is not one drone app with a few presets. It is a family of vehicle stacks for Copter, Plane, Rover, Sub, and Tracker, all orbiting a shared codebase. That makes the project feel less like firmware and more like a universal control layer for robots that move through different worlds.

That scope changes the question. The interesting problem is no longer, "How do you stabilize a quadcopter?" It is, "How do you keep a single architecture useful when lift, friction, drag, buoyancy, and mission logic all change underneath you?"

DimensionArduPilotSingle-vehicle autopilotProprietary stack
Vehicle breadthAir, land, and underwater vehiclesUsually one vehicle familyOften one product line or ecosystem
Hardware portabilityHigh through AP_HALModerate to lowTied to vendor hardware
Behavior modelMode-based controllersOften fixed mission logicVaries, usually closed
Timing disciplineScheduler-driven real-time workDepends on implementationOften hidden from users
Simulation and replaySITL and log replaySometimes partialUsually limited access
ExtensibilityLua scripting and modulesPatch-and-rebuildControlled by vendor policy
A close-up black-ink illustration of a mechanical scheduler ring with task cards placed at different intervals around a central heartbeat. Fast tasks cluster near the center, while slower tasks sit farther out, and a small fail-safe lever is visible at the edge. The image explains how ArduPilot treats time as a first-class design constraint, with critical control tasks running more often than telemetry and housekeeping.
The scheduler is where ArduPilot turns real-time control into a physical rhythm, not a vague software loop.

The portability layer that makes the whole project possible

The key abstraction is AP_HAL, ArduPilot's hardware layer. It keeps the flight logic from caring whether the target is a microcontroller, a Linux board, or another supported runtime. That separation is the reason the same project can move across boards without every hardware port becoming a private fork.

ArduPilot works because hardware, behavior, timing, and safety are separated, but still tightly coordinated.

class Mode {
public:
    virtual bool allows_arming() const = 0;
    virtual bool requires_position() const = 0;
    virtual void run() = 0;
};

Mode *mode_from_mode_num(uint8_t mode_num) {
    switch (mode_num) {
    case MODE_STABILIZE: return &mode_stabilize;
    case MODE_AUTO:      return &mode_auto;
    case MODE_LOITER:    return &mode_loiter;
    case MODE_RTL:       return &mode_rtl;
    default:             return &mode_stabilize;
    }
} 

That object model matters because a flight mode is not just a menu selection. It is a controller with its own rules about arming, position requirements, and acceptable behavior. The system can switch from manual stabilization to autonomous mission logic by swapping the active mode object, not by rewriting the whole vehicle.

PropertyWhat it means in ArduPilotWhy it matters
Mode is an objectEach flight behavior has methods and gatesBehavior stays modular
Mode is not a flagThe system can enforce rules per modeSafety is built into transitions
Mode switch is runtimeControllers can change without rebootingOne stack supports many missions

The scheduler is the heartbeat

ArduPilot's scheduler is where the project stops looking like ordinary application code. Fast control work, like inertial updates and rate control, has to happen on deadline. Slower work, like telemetry and battery checks, can wait. That timing hierarchy is the difference between a stable vehicle and a crash.

The important idea is not just that tasks run often. It is that frequency is part of the architecture. If the scheduler misses the wrong window, the vehicle does not merely lag. It loses the ability to stay upright, track attitude, or respond cleanly to sensor input.

static const AP_Scheduler::Task scheduler_tasks[] = {
    { "ins.update",            400,  2000 },
    { "rate_controller",       400,  1000 },
    { "attitude_control",      200,  1500 },
    { "telemetry",              50,  3000 },
    { "battery_check",          10,  5000 },
};

Seen this way, ArduPilot is not "real-time" as a slogan. It is real-time as an accounting system, where every task has a budget and some budgets are non-negotiable.

Startup order and failsafes are not afterthoughts

The boot path is carefully staged. Sensors, power, serial links, RC input, GPS, and compass do not just appear all at once. ArduPilot brings them up in a deliberate sequence so that the vehicle becomes trustworthy in steps, not in a single optimistic jump.

Failsafes extend that logic into bad conditions. A timer-based watchdog can intervene if the main loop hangs, which turns a software stall into a recoverable event instead of a silent failure. In a control system, that is not defensive coding. It is the difference between inconvenience and damage.

void init_ardupilot() {
    notify_init();
    battery_init();
    barometer_init();
    serial_init();
    rc_input_init();
    gps_init();
    compass_init();

    hal.scheduler->register_timer_failsafe(failsafe_check_static, 1000);
} 

Why ArduPilot looks more like infrastructure than firmware

ArduPilot's scale comes from the boring things done well. It has simulation in SITL, logging, replay, a dense test matrix, and enough compile-time gating to fit a huge feature set into constrained hardware. That combination makes it closer to robotics infrastructure than to a single-purpose embedded binary.

The comparison with narrower stacks is telling. A single-vehicle autopilot can optimize for one frame and one use case. A proprietary stack can optimize for product control. ArduPilot has to optimize for portability, transparency, and long-term maintainability across a scattered ecosystem. That is a harder problem, and it is why the project feels unusually durable.

The only place I could fly and test the early versions of Ardupilot was on Corona Heights in SF – June 2009. Luckily no crashes in populated areas... https://t.co/Lvn73tMwQr

Jason Short, jason4short · @jason4short on X

The open-source autopilot test that matters most: replay

Replay is the most revealing part of the project's operational culture. A real-world flight log can be fed back into a simulator, which turns a crash or anomaly into something reproducible. That means debugging is not limited to guesswork after the fact. It becomes an engineering loop.

This is where ArduPilot's black-box quality shows up. It does not just fly vehicles. It records enough of what happened to let the team ask, with precision, what the system believed when it made a bad decision.

Postmortem approachWhat you getWhat you lose
Read the log by handBroad contextReproducibility
Replay the log in SITLDeterministic rerunLess ambiguity
Infer from hardware onlyReal sensorsHarder debugging