Skip to content

Latest commit

 

History

History
 
 

README.md

cu-flight-controller

A bare-metal quadcopter flight controller implemented end-to-end using Copper components.

Overview

This example demonstrates a complete flight controller running on the MicoAir H743 board (STM32H743), plus a separate host Copper runtime intended for onboard compute. It showcases Copper's ability to run deterministic, real-time control loops on embedded hardware with zero dynamic allocation during runtime.

The deployment has one multi-Copper root, flight_controller.ron, with independent MCU and compute subsystems. Cargo features select coherent graph fragments inside each subsystem: firmware and BevyMon use the manual/preview fragments, while sim and end2end select the closed-loop autonomy fragments and typed Zenoh interconnects.

The simulator runs these same graphs in sim_mode; it replaces external hardware and transport at the graph boundaries without changing the application topology.

The real camera is enabled by the end2end feature and uses cu_zed::Zed. The simulator starts both subsystem runtimes in one Bevy process, preempts the hardware ZED source, and transfers the normal typed bridge messages through a simulator-owned link. On a deployed system the compute-side Copper Zenoh bridge listens on udp/0.0.0.0:7447 and uses bincode for the two fixed-size messages. Binding a no-std UDP network resource and Zenoh backend on the H743 is intentionally deferred; the firmware graph remains buildable without pretending that transport exists.

Hardware

  • MCU: STM32H743VIT @ 400MHz
  • IMU: BMI088 (accelerometer + gyroscope)
  • RC Input: CRSF protocol (ExpressLRS compatible)
  • ESC Output: BDShot (bidirectional DShot with telemetry)
  • VTX/OSD: MSP DisplayPort protocol
  • Storage: MicroSD card for logging
  • Battery: ADC voltage monitoring

See doc/PINOUT.md for the complete pinout reference.

Architecture

Manual and AUTO commands join at one MCU-side mode supervisor before the existing cascaded controller:

GNSS + AHRS + heading -> navigation -> round-trip mission -> MCU context bridge
                                                               |
                                                               v
                                                        ZED -> ViTFly (compute)
                                                               |
                                                               v
RC Input -> RC Mapper -------------------------------> mode supervisor -> attitude -> rate -> mixers
                    \-> AUTO mission/controller <- MCU command bridge ---/

Task Graph

Task Description
bmi088 BMI088 IMU driver (accelerometer + gyroscope)
imu_cal Gyroscope bias calibration on arm
ahrs Attitude and Heading Reference System
mapper RC channel mapping and arm/mode logic
navigation Latches GNSS, AHRS, and true heading into a freshness-checked navigation state
auto_mission Latches home and a point 500 m ahead / 8 m up, then alternates forever
autonomy_context Publishes the fixed-size ViTFly context at approximately 30 Hz
auto_controller Tracks fresh ViTFly world-frame [north, west, up] commands toward the current waypoint
mode_supervisor Selects manual or AUTO controls before the cascaded flight controller
attitude Outer loop PID (angle to rate setpoint)
rate Inner loop PID (rate to motor commands)
mixer0-3 QuadX motor mixing
battery_adc Battery voltage monitoring
vtx_osd MSP DisplayPort OSD rendering
led_blink Status LED heartbeat

Flight Modes

  • Angle: Self-leveling mode with attitude hold
  • Acro: Rate mode for aerobatic flight
  • Position Hold: (placeholder for GPS integration)
  • Auto: SC middle position. Each entry latches the current position/heading, creates a target 500 m ahead at current MSL altitude + 8 m, and alternates between that point and home forever. SC high remains reserved for forced recovery.

Features

  • Airmode: Maintains control authority at zero throttle for aerobatic maneuvers
  • Expo curves: Configurable stick expo for smoother control
  • Gyro calibration: Automatic bias calibration on arm
  • Zero-copy logging: Binary logs to SD card via unified logger

Building

Task graph

just dag

This renders the complete simulated deployment using the default mission. Both selectors remain overridable, for example with just dag mission=flow or just dag features=end2end.

Distributed subsystem compile check

just subsystems-check

This checks the real ZED/ViTFly/Zenoh compute graph and the current buildable STM32 firmware graph. The autonomy MCU graph becomes the firmware entrypoint once its no-std UDP resource and bridge backend are bound.

To compile or run the real ZED-enabled compute graph:

just end2end-check
just end2end

Firmware (for flashing to hardware)

# Build and flash with text logging (debug)
just fw

# Build and flash release (optimized, no text logs)
just fwr

# Build and flash debug profile
just fwd

Log Reader (host tool)

# Extract CopperLists from log file
just logreader log=logs/embedded.copper

# Check log file integrity
just fsck log=logs/embedded.copper

# Extract text logs (requires log index)
just textlogs log=logs/embedded.copper

Replay Debug Server

To replay a recorded simulation log through the remote debug API:

cd examples/cu_flight_controller
just resim
# or, for parity with other examples:
just resim-debug

To inspect the compute-side log in Time Traveler—including the depth raster that was actually passed to ViTFly—run:

# Generate a flight_compute_sim.copper log, then exit the simulator.
just sim

# Serve that compute-side log to Time Traveler.
just compute-resim

Then attach Time Traveler to copper/examples/cu_flight_controller/compute/debug/v1.

The replay target uses the standardized replay contract:

  • --debug-base selects the remote debug namespace
  • --log-base selects the recorded Copper log to inspect
  • --replay-log-base selects the per-session replay log template

If you are building another replay target, follow the same pattern with cu29::replay::{ReplayCli, ReplayDefaults, per_session_replay_log_base, serve_remote_debug}.

Python GNSS Extraction

You can use the PyO3 bindings to iterate CopperLists directly from Python and extract GNSS fields without going through JSON.

This is an offline log-analysis workflow, not a runtime Python task. Python only touches data that Copper has already recorded, so this does not affect the realtime behavior of the flight controller itself.

# Build the Python extension module
just py-build

# Print GNSS latitude/longitude from the flight-controller sim log
just py-gnss log=logs/flight_controller_sim.copper

The script is at python/print_gnss_from_log.py and can also be run directly:

python3 python/print_gnss_from_log.py logs/flight_controller_sim.copper

Implementation notes:

  • src/python_module.rs exposes an app-specific #[pymodule]
  • it uses gen_cumsgs!("mcu_config.ron") so the CopperList type matches this app
  • the Python script imports that module and iterates typed CopperLists plus runtime lifecycle records

This pattern is the recommended Python story in Copper: post-process logs in Python after the run, keep Python off the control path during the run.

RC Tester (simulation)

# Test RC input via joystick
just rc

Simulator (Bevy + Copper)

# Run the normal full-window simulator with MCU + compute Copper runtimes
just

# Run the split BevyMon simulator
just bevy

# Run the split BevyMon simulator in the browser
just web

# Build a deployable browser bundle into dist/flight-controller with hashed asset filenames
just web-dist

The split BevyMon path reuses the same cu_bevymon::spawn_split_layout(...) shell as cu_rp_balancebot and cu_bevymon_demo, but the left panel still runs the real flight-controller sim world, OSD, and help overlays.

The simulated compute subsystem publishes ZED2i-compatible 320×240 stereo RGBA images at a 100° vertical field of view, matching the 4:3 projection used to train and evaluate ViTFly. It also publishes depth and confidence maps, calibration and rig transforms, plus IMU, magnetometer, barometer, and frame metadata. Bevy performs the camera rendering and GPU depth readback; the simulator injects those buffers into the preempted cu_zed::Zed source. The compute graph runs ViTFly inference on the CPU by default and draws its predicted velocity over the top-right depth inset. Use just sim-cuda to run the same graph with ViTFly inference on NVIDIA CUDA.

The simulator has mutually exclusive forest-world and urban-world features. forest-world is the default for cargo run and just sim; use just sim-urban for the original city scene. The equivalent Cargo commands are:

cargo run
cargo run --no-default-features --features sim,urban-world

The forest is a deterministic 635 m hilly world built from the CC0 Kenney Nature Kit. Its source GLBs, license, editable Blender scene, final GLB, and spawn-point preview live under assets/. Regenerate the scene with:

just forest-assets

Both world GLBs are resolved through the same Copper CDN asset path as the quadcopter and lighting assets. Atmospheric fog is enabled only for the forest observer camera, so the simulated ZED depth camera continues to measure scene geometry without a cosmetic fog layer. Each world feature carries its own scale, bounds, spawn position, and spawn yaw.

CPU runs use RAYON_NUM_THREADS=8 from the checked-in .cargo/config.toml. Making the pool size explicit prevents Candle from repeatedly probing Linux CPU topology while retaining parallelism for Bevy and Avian. The setting is scoped to this example, and an existing shell value takes precedence.

RC Input In Simulation

The simulator reads RC input from a host joystick device (evdev). By default it only auto-connects to radio-style joystick profiles, to avoid false positives from keyboards/mice/gamepads that also expose joystick interfaces.

Environment variables:

# Prefer a specific device name substring (case-insensitive)
CU_SIM_JOYSTICK="radiomaster" just sim

# Allow generic/non-radio joystick devices as RC input
CU_SIM_ALLOW_GENERIC_JOYSTICK=1 just sim

Notes:

  • If no compatible RC joystick is found, the sim falls back to keyboard controls.
  • When connected, the bottom-right help panel shows the selected device name and technical axis bindings (ABS_X, ABS_RY, etc.).
  • USB and Bluetooth radios both work if they appear as a joystick device on the host.
  • R resets the rigid body, controller/estimator state, AUTO mission, camera cache, and ViTFly recurrent state. With a joystick, move ARM low and AUTO off after resetting before the interlock permits rearming.

Compatible TX/RX Notes

  • Simulation path: no receiver is used directly; input is read from the host joystick interface.
  • Auto-detected TX joystick profiles:
    • ExpressLRS-style joystick names (expresslrs, elrs, radiomaster)
    • OpenTX / EdgeTX USB joystick names (opentx, edgetx)
  • ExpressLRS BLE mapping: CH1-CH8 are joystick axes and CH9-CH16 are buttons. The sim uses CH5 / SA (ABS_Z) for arm, CH6 / SB (ABS_RZ) for Acro/Angle/Position Hold, and CH7 / SC (ABS_THROTTLE) middle position for Auto; the high position is reserved for forced recovery mode.
  • Keyboard mapping: 1/2/3 select Acro/Angle/Position Hold and 4 toggles Auto.
  • Firmware path (real hardware): RC input is CRSF on UART, so use a CRSF-compatible RX link (for example ExpressLRS/Crossfire-class receivers and compatible transmitters).

Configuration

mcu_config.ron and compute_config.ron contain each subsystem's shared graph exactly once. Their feature-gated manual/preview and autonomy fragments add only the nodes and connections that differ. The 500 m / 8 m waypoint geometry and infinite repetition are fixed demo constants, not RON parameters. Key configurable parameters:

Rate Controller

config: {
    "kp": 0.04,
    "ki": 0.0,
    "kd": 0.0005,
    "airmode": true,
    "airmode_start_throttle_percent": 20.0,
}

Attitude Controller

config: {
    "angle_limit_deg": 60.0,
    "acro_rate_dps": 180.0,
    "acro_expo": 0.3,
    "kp": 1.0,
}

RC Mapper

config: {
    "arm_channel": 4,
    "arm_min": 1700,
    "arm_max": 1811,
    "mode_channel": 5,
}

Motor Layout

QuadX configuration (props out):

    Front
  3       1
    \   /
      X
    /   \
  2       0
    Rear
Motor Position Rotation
0 Rear Right CCW
1 Front Right CW
2 Rear Left CW
3 Front Left CCW

Development

Attaching to Running Target

just attach

Viewing Logs

The firmware logs to the SD card in Copper's binary format. Use the log reader tools to extract and analyze:

# Extract structured data
just logreader

# Extract text logs (when compiled with textlogs feature)
just textlogs

Dependencies

Key Copper components used:

  • cu29 - Core runtime
  • cu-ahrs - Attitude estimation
  • cu-bdshot - Bidirectional DShot ESC protocol
  • cu-crsf - CRSF RC protocol (ExpressLRS)
  • cu-msp-bridge - MSP protocol for VTX/OSD
  • cu-pid - PID controller
  • cu-micoairh743 - MicoAir H743 HAL bundle
  • cu-logmon - Log monitoring