Mixer Reference

From sticks and gyro to µs on the wire: the complete geometry of every mixer PteronautOS can select — wing axes, rudder blends, elevons, trims and clamps.

This page documents the mixer layer of src/lib/Ornithopter/Ornithopter.cpp and the profile system of OrnithopterConfig.h. For the surrounding architecture, start with the Architecture article ; for pulse timing and trim ranges, see the Servo Kernel article.

Servo function tags

Every output of every mixer is a µs value written into _f[], indexed by a ServoFunc tag. Tags are transport — they say what a signal means, never where it is wired:

Tag Name Meaning
0 SF_NONE Reserved — no function (first PWM slots stay free for BOOT and I2C)
1 / 2 SF_LEFT_WING / SF_RIGHT_WING Front flapping wings (waveform kernel)
3 SF_RUDDER Head/crest rudder (waveform) or fin rudder (gearbox) — yaw + roll blend
4 SF_MOTOR Brushless ESC throttle (gearbox kernel only)
5 / 6 SF_VTAIL_LEFT / SF_VTAIL_RIGHT V-tail surfaces, elevon-mixed (gearbox kernel)
7 SF_ELEVATOR Traditional separate elevator (gearbox profiles 6/7)
8 / 9 SF_BACK_LEFT_WING / SF_BACK_RIGHT_WING Rear wing pair of a 4-wing airframe (mirror of the front pair, own trim)

A runtime profile provides the funcMap: an array that maps PWM output index → ServoFunc. The first three slots (PWM 1…3) are always SF_NONE — they belong to the BOOT strap and the I2C bus — so real servo functions begin at PWM index 3 (the firmware comment spells this out: index 3 = CH4, 4 = CH5, 5 = CH6).

The eight mixer profiles

Eight mixer profiles exist. Profiles 0–2 drive the waveform kernel (flapping wings), profiles 3–7 the gearbox kernel (motor + tails):

# Profile Kernel Servos funcMap (PWM 3 · 4 · 5)
0 SERVO_2WING waveform 2 Left wing · Right wing · —
1 SERVO_2WING_1RUD waveform 3 Left wing · Right wing · Rudder
2 SERVO_4WING waveform 4 (back-right shares the back-left mirror) Left wing · Right wing · Back-left wing
3 GEARBOX_2VTAIL_1RUD gearbox 3 Rudder · V-tail left · V-tail right
4 GEARBOX_1MOT_2VTAIL gearbox 3 Motor · V-tail left · V-tail right
5 GEARBOX_1MOT_2VTAIL_1RUD gearbox 4 (V-tail right shares the left mirror) Rudder · Motor · V-tail left
6 GEARBOX_1ELE_1RUD gearbox 2 Rudder · Elevator · —
7 GEARBOX_1MOT_1ELE_1RUD gearbox 3 Rudder · Motor · Elevator

Selection: build-time flag MIXER_PROFILE (legacy ORNITHOPTER_GEARBOX=1 → profile 5), then runtime-overridable through the WebUI (setOrnithopterProfile). The kernel test is simply activeProfile ≥ GEARBOX_2VTAIL_1RUD.

Wing axis mixing (waveform kernel)

All channel values enter the mixers normalized: _crsfToNorm maps the raw 172…1811 window linearly onto −1…+1. Steering commands are then scaled by the per-profile authority sliders (aileronScale, elevatorScale, 0–100%) times ±60° (ORNI_STEER_MAX_DEG).

Mirror geometry. Wing servos are mirror-mounted, which swaps the meaning of common-mode and differential terms. The code therefore treats aileron (roll) as common — it adds to both wings, which becomes a differential physical deflection — and elevator (pitch) as differential in code, which becomes a common physical deflection. The angle equations, with ORNI_NEUTRAL_ANGLE_DEG = 100° and the 2× multiplier applied to deviation terms only:

angleLeft  = NEUTRAL + (aileron + elevator + flapCentre − degL) · 2
angleRight = NEUTRAL + (aileron − elevator − flapCentre + degR) · 2

flapping:  degL/R = amplitudeL/R · shapedWave(rawPhase, ferocityL/R, …)
glide:     degL = degR = 0 → static glide offset takes over

degL/degR are the instantaneous stroke deflections of the left and right wing, each the product of its own amplitude and a ferocity-shaped wave. Elevator-up strengthens the downstroke; elevator-down the upstroke (asymmetric ferocity). Yaw splits the wings in two complementary ways:

Angles are clamped 0…180° and converted to pulses with the configurable envelope servoMinUs…servoMaxUs (defaults 988…2012): µs = min + angle·(max−min)/180, plus the per-function trim. The glide/flap centre offsets (glideAngleDeg, flappingAngleDeg, ±15°) enter as static terms on their respective branch — so glide and flap centres can be tuned independently per flight profile.

Rudder mixing (both kernels)

The rudder mixer is shared verbatim by both kernels — one equation, two airframes:

mix  = rudderNorm · rudderYawWeight  +  aileronNorm · rudderRollWeight
mix  = clamp(mix, −1 … +1)
µs   = 1500 + mix · 500            (rudder centre ± 500 µs)
       + gyroRudderCorrection       (Zephyrus, when enabled)
       + servoTrimUs[SF_RUDDER]

Defaults: rudderYawWeight = 65%, rudderRollWeight = 35% (runtime-tunable in the WebUI, stored per backup). Roll coupling on the rudder is what lets a wing-heavy pterosaur hold coordinated turns without yaw-string gymnastics; reduce the roll weight if the head rudder feels too aggressive in roll.

Gearbox tail & elevon mixing

The gearbox kernel runs statelessly every tick. Its three sub-mixers:

VtailLeft  = 1500 + (aileron + elevator)·scale + rollPID + pitchPID + trimL
VtailRight = 1500 + (aileron − elevator)·scale − rollPID + pitchPID + trimR
scale = 500 µs;  both aileron/elevator sums clamped to −1…+1 first

On the gearbox kernel, Zephyrus injects both roll and pitch PID corrections into the tail servos (roll anti-phase, pitch common-mode), each hard-clamped to ±250 µs (ZEPHYR_GEARBOX_CLAMP_US). A gyro glitch can therefore waggle, but never stall, a tail servo.

Zephyrus injection, trims & failsafe

Three different injection points exist depending on kernel and axis:

Axis Waveform kernel Gearbox kernel
Rudder / yaw gyroRudderCorrection added to the crest rudder µs gyroRudderCorrection added to the fin rudder µs
Roll (aileron) — (waveform wings carry no roll PID) gyroAileronCorrection, anti-phase across V-tail, clamped ±250 µs
Pitch (elevator) — (injected as wave modulation in the Zephyrus layer) gyroElevatorCorrection, common-mode, clamped ±250 µs

Trims & limits. Every function has a signed per-servo trim (servoTrimUs[tag], default 0, ±300 µs) added last, and every value passes _clampServo — an absolute 500…2500 µs safety clamp that outranks everything. On failsafe or link-down, all functions return to 1500 µs and a gearbox motor to minimum, so the model always glides to neutral.

Ready for the practical layer? The Waveform Tuning tutorial shows how these numbers feel from the sticks, and the Architecture article shows where each mixer sits in the pipeline.