Skip to main content

Optical Flow Setup Guide — ArduPilot / PX4 / INAV

One guide for the whole MTF family: the MTF-01, MTF-01P, and MTF-02 / MTF-02P all configure identically — wire the sensor to a spare UART, set the parameters below, and your drone hovers indoors without GPS.

Read this before touching any parameter

An optical flow sensor cannot stabilize an aircraft that doesn't already fly well. If you set every parameter perfectly but the airframe vibrates, you will get a drifting, twitching hover and blame the sensor. Verify vibration first (how, for each firmware), then add sensors. This single ordering mistake causes more "the sensor doesn't work" tickets than everything else combined.

How position hold actually works — and why vibration is the foundation

Cascade flight control block diagram — forward path from hover setpoint through position, attitude and motor blocks; IMU closes the fast inner loop; optical flow and rangefinder correct the EKF estimate in the outer loop; a red dashed path shows motor vibration entering the IMU

The flow sensor does not fly the aircraft. Read the diagram like any cascade control loop: the forward path runs along the top — position/velocity controller → attitude & rate controller → motors. Below it sit two nested feedback loops. The inner loop is the IMU: gyro and accelerometer sampled at kHz rates, feeding attitude and rate feedback straight back to the attitude controller, and simultaneously driving the EKF's high-rate motion prediction. The outer loop is where your new sensors live: the rangefinder supplies height — which is also what converts the flow camera's angular motion into real velocity in m/s (the same ground movement looks fast when low and slow when high) — and the optical flow velocity arrives at just 50–100 Hz as a correction to the EKF's IMU-based prediction. The corrected estimate closes the outer loop at the position controller.

Now trace the red dashed path from the motors block: the airframe's real motion is what the IMU is supposed to measure, but motor and prop vibration rides the exact same physical path into the IMU. A vibrating IMU means aliased gyro data and clipped accelerometers; the EKF's prediction wanders; and the slow 50–100 Hz flow corrections end up fighting a noisy estimate instead of trimming a clean one. The result is exactly what frustrated users describe: parameters all "correct", hover still breathing, drifting or oscillating. Both loops stand on the IMU — the sensor is the last 20%, the airframe is the first 80%.

Step 1 — prove the airframe flies clean

Hover the aircraft (GPS mode or manual, no flow yet) for 30–60 seconds and check the vibration data your firmware already records:

FirmwareWhere to lookHealthy
ArduPilotMission Planner status VibeX/Y/Z, or the VIBE message in dataflash logsAll axes < 30 m/s² sustained (occasional peaks OK); 30–60 is a gray zone; > 60 will cause problems. Clip0/1/2 counters must stay at (or very near) 0 — any steady climbing means the accelerometer is saturating
PX4Upload the log to Flight ReviewRaw Acceleration plotThe z-axis trace must stay clearly separated from x/y in hover. If z overlaps x/y, vibration is too high — PX4's own rule of thumb
Betaflight / INAVBlackbox log → spectral view in the log viewerGyro spectrogram shows distinct, narrow motor peaks over a low noise floor; a wall of broadband noise across the band means mechanical trouble

If you fail these checks, put the flow sensor back in the drawer and fix the airframe:

  1. Props first — the cause in most cases. Damaged, warped or unbalanced props; even new cheap props benefit from balancing. Try a fresh set before anything else.
  2. Motors — worn bearings (spin by hand, feel for grinding), bent shafts after crashes, loose bells.
  3. Mounting — flight controller not torqued down, mushy double-sided tape instead of proper standoffs/grommets, or the opposite: hard-mounted FC on a frame that resonates. Cables pulling on the FC count too.
  4. Frame — cracked arms, loose arm bolts, resonant thin-plate arms; anything rattling (GPS mast, camera, VTX antenna).

Then let the firmware's filters clean up the rest

Filters treat residual noise, not mechanical faults — apply them after the checklist above:

  • ArduPilot: enable the harmonic notch (INS_HNTCH_ENABLE = 1), driven by throttle, ESC RPM (with bi-directional DShot), or the in-flight FFT (INS_HNTCH_MODE). Log the spectrum with INS_LOG_BAT_MASK = 1 to see before/after.
  • Betaflight: turn on the RPM filter (requires bidirectional DShot) — the single biggest noise win — plus the default dynamic notch.
  • INAV: enable the dynamic gyro notch; keep the gyro LPF at sane defaults rather than chasing latency.
  • PX4: use the ESC-RPM dynamic notches (IMU_GYRO_DNF_EN) or configure the software notch/LPF (IMU_GYRO_NF0_FRQ, IMU_GYRO_CUTOFF) at the prop frequency seen in Flight Review's FFT.

Step 2 — protocol matching (now automatic)

Current-production MTF sensors auto-detect your flight controller's firmware on power-up and switch to the matching protocol themselves — ArduPilot, PX4, INAV or Micolink. Wire it, power it, done; there is no longer any need to pick a firmware variant when ordering.

Manual override still exists for older units or special cases:

FirmwareSensor protocol
ArduPilotmav_apm
PX4mav_px4
INAVmsp
FMT / custom codeMicolink (protocol reference)

Set it with MicoAir's MicoAssistant desktop tool, or on the MTF-02P via solder pads (pads take priority over software).

If you have an older unit

Pre-auto-detection sensors must match manually — "distance shows up but no optical flow" on an old unit is almost always a protocol mismatch (e.g. an mav_px4 sensor on an ArduPilot board).

Step 3 — firmware parameters

ArduPilot (≥ 4.2.0)

Serial and sensor parameters — n is the UART you wired (e.g. SERIAL5):

SERIALn_PROTOCOL = 1 # MAVLink1
SERIALn_BAUD = 115
SERIALn_OPTIONS = 1024 # Don't forward
FLOW_TYPE = 5 # MAVLink optical flow
RNGFND1_TYPE = 10 # MAVLink rangefinder

Reboot (or refresh parameters), then:

RNGFND1_MIN_CM = 1
RNGFND1_ORIENT = 25 # facing down
RNGFND1_MAX_CM = 800 # MTF-01 — use your sensor's real range:
# 1200 (MTF-01P) / 600 (MTF-02P) / 250 (MTF-02)

EKF source — flow-only indoor flight

This is the part most guides skip. Telling the EKF to trust flow velocity instead of GPS:

AHRS_EKF_TYPE = 3
EK3_SRC_OPTIONS = 0
EK3_SRC1_POSXY = 0 # no absolute XY position source
EK3_SRC1_POSZ = 2 # height from rangefinder
EK3_SRC1_VELXY = 5 # XY velocity from optical flow
EK3_SRC1_VELZ = 0
EK3_SRC1_YAW = 1 # compass

EKF source — GPS outdoors, flow indoors (advanced)

Keep GPS as source 1 and configure flow as source 2, switched from the radio. Not recommended as a first setup:

EK3_SRC2_POSXY = 0
EK3_SRC2_POSZ = 2
EK3_SRC2_VELXY = 5
EK3_SRC2_VELZ = 0
EK3_SRC2_YAW = 1
RC6_OPTION = 90 # RC6 3-position switch selects EKF source;
# middle position = source 2 (optical flow)
Can't find FLOW_TYPE?

First confirm firmware ≥ 4.2.0. If the version is fine, your board's firmware was built without optical flow support — common on F405-class boards where flash constraints force feature cuts. Use ArduPilot's custom firmware builder to build with flow enabled, or use a board with more flash such as the H743 V2.

PX4

Sensor protocol: mav_px4. Parameters differ by firmware line:

1.12.x – 1.13.x

MAV_1_CONFIG = TELEM n # reboot after setting
MAV_1_MODE = Normal
SER_TELn_BAUD = 115200 8N1
SENS_FLOW_ROT = No rotation
EKF2_AID_MASK = use optical flow
EKF2_RNG_AID = Range aid enable
EKF2_HGT_MODE = Range sensor

1.14.x – 1.16.x

MAV_1_CONFIG = TELEM n # reboot after setting
MAV_1_MODE = Normal
SER_TELn_BAUD = 115200 8N1
EKF2_OF_CTRL = Enabled
EKF2_RNG_CTRL = Enabled
EKF2_HGT_REF = Range sensor

Reboot again; SENS_FLOW_ROT only appears in QGC after the flow sensor has been detected once (its default "No rotation" is already correct).

1.17.x and later — additionally set MAV_PROTO_VER = 1.

INAV

Sensor protocol: msp. Enable the optical flow and rangefinder sensors in the INAV configurator. Avoid INAV 7.1.1 — a known bug breaks this sensor family; use a newer (or older) release.

Step 4 — tune altitude hold first, then position hold

Resist the urge to enable everything and launch. Split the bring-up so a failure points at one subsystem, not two coupled ones:

  1. Altitude hold first. Indoors or over a sensor-friendly surface (matte, textured, decent light), configure only the rangefinder as the height source and fly altitude-hold. The aircraft should lock its height crisply near the ground with no stair-stepping or slow porpoising. If height is unstable, fix that now — range data, surface, or vibration — before flow ever enters the picture.
  2. Then add optical flow as the velocity source and tune position hold in the same friendly environment. Any new misbehavior is now attributable to the flow side (light, texture, orientation, scale) instead of an ambiguous mix.

This is how we bring up every test aircraft in-house — it turns "it drifts and I don't know why" into two short, answerable questions.

Verify before you fly

Connect over USB (not telemetry — see below) and open QGC's MAVLink Inspector (or Mission Planner's equivalent). You should see both DISTANCE_SENSOR and OPTICAL_FLOW_RAD messages arriving:

QGC MAVLink Inspector showing DISTANCE_SENSOR and OPTICAL_FLOW_RAD messages

Message rates shown in the GCS are set by the flight controller's link bandwidth, not by the sensor — don't expect to see 100 Hz there, and don't worry about it.

Troubleshooting

Distance data but no optical flow

Two usual causes: a protocol mismatch (older, non-auto-detect units — see Step 2), or you're connected via a telemetry radio — on a slow link PX4 selectively drops messages, so OPTICAL_FLOW_RAD may simply not be forwarded to the GCS. Check over USB before suspecting the sensor.

PX4 keeps resetting my position estimate to zero

Normal. Optical flow provides velocity only, no absolute position, so while disarmed PX4 periodically re-zeros the estimated position to bound drift.

The rangefinder reads far less than the rated range

The rating is a laboratory number: measured against a 90%-reflectivity target (think bright white board) at a stated ambient light level — the spec table lists both a 600 lux (indoor) and a 70 klux (direct summer sun) figure, and they differ for a reason. Outdoors at noon, sunlight floods the receiver with infrared; over a low-reflectivity surface the return signal shrinks again — dry grass reflects only a fraction of what a white wall does, and dark asphalt can be under ~10%. Practical expectations: bright concrete at noon sits between the two rated figures; grass or dark ground in strong sun can halve the sunlit rating. This is physics, not a defective unit — and it is exactly why the laser-based MTF-01P holds 8 m even at 70 klux while LED units derate more steeply.

Position hold is mushy or drifts

Check the basics the physics demands: ground texture (bare uniform floors are hard to track), light (>60 lux), altitude within the rangefinder's real range, and the sensor lens clean and unobstructed. A printed case keeps fingers and crashes off the optics.


Written and maintained by the Robofusion engineering team. Parameters cross-checked against MicoAir's Chinese manuals (June 2026) and ArduPilot/PX4 documentation.