A bare-metal quadcopter flight controller implemented end-to-end using Copper components.
This example demonstrates a complete flight controller running on the MicoAir H743 board (STM32H743). It showcases Copper's ability to run deterministic, real-time control loops on embedded hardware with zero dynamic allocation during runtime.
- 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.
The flight controller uses a cascaded control architecture:
RC Input (CRSF) -> RC Mapper -> Attitude Controller -> Rate Controller -> Mixer -> ESCs (BDShot)
^ ^
| |
IMU -> Calibrator -> AHRS ---------+
| 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 |
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 |
- Angle: Self-leveling mode with attitude hold
- Acro: Rate mode for aerobatic flight
- Position Hold: (placeholder for GPS integration)
- 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
# 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# 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.copperYou 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.copperThe 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.copperImplementation notes:
src/python_module.rsexposes an app-specific#[pymodule]- it uses
gen_cumsgs!("copperconfig.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.
# Test RC input via joystick
just rc# Run the normal full-window simulator
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-distThe 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 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 simNotes:
- 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.
- Simulation path: no receiver is used directly; input is read from the host joystick interface.
- Auto-detected TX joystick profiles:
- ExpressLRS-style USB joystick names (
expresslrs,radiomaster) - OpenTX / EdgeTX USB joystick names (
opentx,edgetx)
- ExpressLRS-style USB joystick names (
- 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).
The task graph is defined in copperconfig.ron. Key configurable parameters:
config: {
"kp": 0.04,
"ki": 0.0,
"kd": 0.0005,
"airmode": true,
"airmode_start_throttle_percent": 20.0,
}config: {
"angle_limit_deg": 60.0,
"acro_rate_dps": 180.0,
"acro_expo": 0.3,
"kp": 1.0,
}config: {
"arm_channel": 4,
"arm_min": 1700,
"arm_max": 1811,
"mode_channel": 5,
}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 |
just attachThe 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 textlogsKey Copper components used:
cu29- Core runtimecu-ahrs- Attitude estimationcu-bdshot- Bidirectional DShot ESC protocolcu-crsf- CRSF RC protocol (ExpressLRS)cu-msp-bridge- MSP protocol for VTX/OSDcu-pid- PID controllercu-micoairh743- MicoAir H743 HAL bundlecu-logmon- Log monitoring