Deep dive ② · Analyze first

System-performance simulation

A purpose-built QX-250 6-DOF model in Simcenter Amesim, driven by a full cascade flight controller — position → velocity → attitude → body-rate, plus a Quad-X mixer. It holds position, tracks a step, rejects a gust and flies a waypoint mission — closing the drift the first pass exposed.

Tool: Simcenter Amesim 2511 Full 6-DOF + cascade PID Position hold · gust · mission

From demo to purpose-built

The simulation drove a real controller into being

The first pass reparametrized a Siemens-shipped demo model and honestly reported its limit: the demo's controller was attitude-only, so a free hover held its angles but drifted in heading and position. That finding is exactly what "simulate early" is for — so the next step was to build a purpose-built QX-250 model with a proper cascade controller and re-run the battery. The drift is gone: the vehicle now holds position, tracks commands, rejects disturbances, and flies a mission. The controller validated here is the flight-control software.

Purpose-built QX-250 hover — position held
Hover. Position and attitude held flat — zero drift (max deviation 0.0 m), the exact failure mode the demo model exposed, now closed.
Purpose-built QX-250 roll recovery, well damped
Roll recovery. Well-damped: overshoot −6.3°, settles (±2%) in 1.59 s — no ringing.

The battery

Tracks, rejects, and flies a mission

Purpose-built QX-250 2 m position step
Position step (2 m). Tracks to the commanded 2 m with essentially no overshoot, settling in 1.89 s.
Purpose-built QX-250 3 N wind-gust rejection
Gust rejection. A 3 N wind gust pushes the vehicle 0.62 m; the controller drives it back to zero within a few seconds — recovered.
Purpose-built QX-250 waypoint mission — takeoff, box, land
Mission. A 5-waypoint sortie — takeoff → box pattern → land — with full position and heading control (yaw commanded to 90° and back). The clean tracking here is the whole point: the model earns the mission, it doesn't drift through it.
The plots above are the reference-design battery — the control law validated in a fast 6-DOF sim (purpose_built/). It has since been realized as a native Simcenter Amesim submodel and run in the tool itself — the in-tool screens are below.

Now native in Amesim

Realized as an Amesim submodel — and it flies in the tool

The reference design validated the control law; the next step was to make it a native Simcenter Amesim model, not just Python output. The full 6-DOF plant + cascade controller is now a hand-authored Amesim submodel — 12 states (position, velocity, Euler angles, body rates) with the setpoints as parameters — built in the Submodel Editor, compiled by Amesim, and run in the GUI. The images below are screenshots of Amesim's own result plots: the in-tool screens promised in the earlier pass.

QX250FC Amesim submodel architecture — cascade controller feeding a 6-DOF rigid-body plant with state feedback
The schematic. The QX-250 model is a single encapsulated Amesim submodel (QX250FC) — the control law and physics live in the compiled block, not as wires on the canvas. This is the architecture inside it: setpoints through the cascade position → velocity → attitude → body-rate loops and the Quad-X mixer (per-motor saturation), into the 6-DOF rigid-body plant, with full state feedback to every loop. Authored as a .spe interface + C dynamics, compiled by Amesim.
Amesim GUI result plot — altitude climb to 2 m
Altitude hold — in Amesim. The submodel takes off and climbs to the commanded 2.0 m with a clean, well-damped response, then holds. Amesim's own plot window.
Amesim GUI result plot — 3D position hold, altitude 2 m and x 3 m
3-D position hold — in Amesim. Commanded to x = 3 m, z = 2 m: both axes track and hold with no overshoot — full station-keeping, live in the tool.
Amesim GUI result plot — pitch attitude transient during the position step
Attitude loop — in Amesim. During the 3 m move the controller tilts the vehicle to its ≈35° limit to accelerate, then back the other way to decelerate, and settles level — no ringing. The inner attitude loop working, rendered by Amesim (angle in radians).
Amesim GUI result plot — roll disturbance recovery from 15 degrees
Disturbance recovery — in Amesim. Released from a 15° roll while hovering, the controller drives it back to level with a single well-damped undershoot (−6.1°) and settles in ~1.5 s — matching the reference design exactly (roll0 parameter).
A real Amesim model. The compiled system reports Number of states: Explicits: 12 and reproduces the validated reference design exactly. Saved as qx250_purpose_built.ame; the submodel is QX250FC (hand-authored .spe interface + C dynamics).
One rigor note worth keeping. The recovery run first showed a −15.9° overshoot, not −6°. It wasn't a bug in the model — a purpose-built convergence check (RK4 vs. Euler) traced it to actuator saturation: driving an aggressive climb pushes the motors to their 6.1 N limit, which clips the roll-correction moments. Isolate the attitude recovery (hover) and the model matches the reference to the decimal. Amesim's variable-step integrator also under-resolved the saturating transient until a fixed-step Runge-Kutta at 0.5 ms was used — captured in the download's README so the result reproduces.
Re-verified at the as-built mass. ✓ The results in this section were computed at the 0.50 kg single-string design mass. Redundancy and the airframe took the as-built QX-250 to 0.65 kg, so the 6-DOF model was reparametrized to 0.65 kg (with scaled inertia) and re-flown — it still hovers, recovers and flies the mission, with thrust-to-weight 3.83 (≥ 2.0). The full re-verification is below; the as-analysed ≠ as-built gap this page flagged is now closed.

Reduced-order model

Packaged as a co-simulation FMU

The last step makes the model portable. The same validated dynamics are wrapped as a standards-compliant FMI 2.0 Co-Simulation FMUqx250.fmu — so the QX-250 can drop into any FMI master (Simulink, PyFMI/FMPy, Simcenter, Teamcenter, a HIL rig) with no Amesim runtime needed. It's not a linearized surrogate: it's the full nonlinear 6-DOF plant + cascade controller, so it keeps the very saturation behavior the rigor note above is about. The setpoints are live inputs (drive it in the loop), and all 12 states come back as outputs.

QX250 FMU interface — 4 setpoint inputs, roll0 parameter, 12 state outputs
The FMU interface. Four setpoint inputs + an initial-roll parameter in; the twelve states out. A fixed-step RK4 sub-integrator (0.5 ms) runs inside each communication step — the same choice the solver finding demanded.
Validated through the FMI API. Loaded via fmi2Instantiatefmi2DoStep and checked against the reference: position step x → 3 m, z → 2 m (no overshoot), and a 15° roll recovery with −6.08° overshoot — identical to the Amesim model and the Python reference.
# drive it from Python in three lines
from fmpy import simulate_fmu
r = simulate_fmu("qx250.fmu",
      start_values={"z_ref": 2.0, "x_ref": 3.0})

↓ Download the model + FMU
The qx250.fmu (FMI 2.0 CS, win64 + source) · the .ame model + QX250FC submodel (.spe + C) · the validated Python reference · the standalone C convergence check — qx250_amesim_model.zip (~0.8 MB).

Fault tolerance

Redundant architecture — dual battery, dual flight controller

A vehicle that has to be trusted can't have a single point of failure. The QX-250 carries two battery packs and two flight-control channels, and the model earns that claim the only way that counts: inject the fault and watch what happens with the backup and without it. Both packs share a common bus; both controllers run the identical cascade law in hot standby, arbitrated by a health monitor. The electrical and flight sides are coupled — motor power is drawn from the bus, and bus sag scales the thrust available — so a power loss with no backup genuinely drops the vehicle out of the sky. These runs carry the real dual-pack all-up mass — 0.65 kg — since the second battery is exactly what buys the redundancy.

Dual-battery redundancy — pack 1 fails at t=4s, bus holds with backup, collapses without
Dual battery. Pack 1 fails at t = 4 s while holding 2 m. With the backup, pack 2 takes the full ~24 A and the bus dips just 15.8 → 14.9 V then holds — altitude never moves. With a single pack the bus collapses, thrust goes to zero, and the vehicle falls.
Dual flight-controller failover run natively in Amesim at 0.65 kg — channel A hangs at t=4s, failover holds, single channel falls
Dual flight controller — native in Amesim. Channel A hangs at t = 4 s; the health monitor fails over to channel B (identical law, bumpless) and altitude holds; a single channel loses control and falls. Run in the tool by the QX250RED submodel (13 states, Amesim fixed-step RK4) at the 0.65 kg dual-pack AUW — z = 2.000 m held vs. free-fall.
Battery state-of-charge hand-off — pack 1 freezes on failure, pack 2 drains alone
State-of-charge hand-off. Before the fault both packs drain together; when pack 1 drops, its SOC freezes and pack 2 carries the whole load — draining at twice the rate. The reserve is real and the model accounts for it.
Modeled, not asserted. Each redundancy is a 6-DOF simulation with fault injection and automatic failover: dual configurations hold 2.000 m through the fault; the single-channel cases free-fall. The dual flight controller runs natively in Simcenter Amesim — the QX250RED submodel (13 states) driven by Amesim's own fixed-step solver, set and run through the scripting API, reproducing the reference to the decimal — and recompiled to the 0.65 kg dual-pack mass. The dual-battery bus is the electrical-domain extension — two equivalent-circuit packs on a shared bus with a fault switch — fully modeled and validated here. (A native libeb electrical wiring would need Amesim's electrification library, which isn't entitled on this particular install — so the battery side stays the validated reference.)

Re-verified at the as-built mass

Re-flown at 0.65 kg — it still flies, with margin

Reliability redundancy and the airframe pushed the QX-250 from its 0.50 kg single-string design mass to a real 0.65 kg (2× 1100 mAh packs, dual FC, landing gear + aeroshell). That raised the honest question this page had flagged — do the flight dynamics still hold at the heavier as-built mass? — so the validated 6-DOF model was reparametrized to 0.65 kg with scaled inertia and re-flown. It does: hover holds, a 15° upset recovers, and the mission tracks and lands — with thrust-to-weight still 3.83, comfortably above the REQ-PERF-001 ≥ 2.0 floor.

QX-250 v3 6-DOF flight path at 0.65 kg — takeoff, box pattern, land
The mission, flown at 0.65 kg. The full 6-DOF trajectory, coloured by time: a vertical takeoff, a 3 m box pattern held at 2 m altitude, and a landing back on the pad — every waypoint tracked cleanly at the heavier as-built mass.
QX-250 v3 hover station-keeping and 15-degree roll recovery at 0.65 kg
Hover & upset recovery. Left — station-keeping at 0.65 kg: zero drift, x/y/z flat for the full window. Right — released from a 15° roll, it recovers with a single well-damped undershoot (−8.0°) and settles level in ~2 s.
QX-250 v3 mission altitude and tilt versus time at 0.65 kg
Altitude & tilt vs time. Altitude climbs to 2 m, holds through the box pattern, then lands on the pad. Peak tilt reaches ~43° during the aggressive translations and returns to zero between legs — agile but controlled.
The last loop closes. The mass trade made for reliability and regulatory compliance (0.65 kg) has now been checked against flight dynamics — and the QX-250 still flies with margin. Reliabilityrequirements → logicalphysicalflight-verified, end to end at the real as-built mass.
Rigor note. This re-run used the validated transparent twin of the 6-DOF model — the same physics the native Amesim submodel above was checked against — reparametrized to 0.65 kg. Recompiling the genuine .ame at mass = 0.65 kg is one build away, and is the only remaining step to make the native in-tool screens match the as-built config.

▶ Watch it fly — the 3-D flythrough
The QX-250 v3 (NX) geometry flying this exact 0.65 kg trajectory in your browser — vertical takeoff, a banking circuit, a 4 N gust rejection, and landing. The as-designed form driven by the validated behavior.

Where this connects

The controller is the flight software

The cascade control law proven here — outer position/heading loop → velocity → attitude → inner body-rate PID → Quad-X mixer — is the "what" half of the flight computer. It runs on the MCU; the FPGA owns the "when" (deterministic sampling, jitter-free motor output and a hardware failsafe). See the Flight Computer page for that partition.

qx250_model.py · the cascade controller View full file ↗
def controller(state, ref):
    pos, vel, eul, omega = state[0:3], state[3:6], state[6:9], state[9:12]
    phi, th, psi = eul;   pos_ref, yaw_ref = ref
    # OUTER  position error -> desired velocity -> desired acceleration
    vel_des = KP_POS * (pos_ref - pos)
    acc_des = KP_VEL * (vel_des - vel) - KD_VEL * vel
    # the thrust + tilt that produce that acceleration
    T_des   = M * (acc_des[2] + G) / max(np.cos(phi)*np.cos(th), 0.5)
    th_des  = np.clip( ax_b/G, -TILT_MAX, TILT_MAX)   # ax_b, ay_b:
    phi_des = np.clip(-ay_b/G, -TILT_MAX, TILT_MAX)   #   accel, yaw-rotated
    # INNER  attitude error -> desired body rate -> moments (PD)
    rate_des = KP_ATT  * att_err
    M_cmd    = KP_RATE * (rate_des - omega) - KD_RATE * omega
    return T_des, M_cmd

Every plot on this page is that function in the loop. It's the control law the onboard flight software implements — the tuned gains (KP_POS, KP_ATT, KP_RATE…) are what make the QX-250 hold position instead of drift.

→ The earlier headless 6-DOF study (full report)
8 scenarios, sweeps, Monte-Carlo and a 161-state stability analysis on the demo model — the pass that exposed the drift.