bbdoctor
Drone diagnostics from logs: Betaflight, INAV, ArduPilot, PX4
RUENESPTUA

PX4 ULog Analysis: Vibration, Filters and Rate Controller Tuning

Flight Review draws good plots but leaves the real questions open: which peak matters, is it mechanics or the controller, which filter to touch, and does the log even hold enough data. Every example below comes from real PX4 1.12–1.17 logs that bbdoctor was tested on, and every number was measured.

Getting a useful log

The .ulg is in log/ on the SD card, or use QGroundControl → Analyze Tools → Log Download. 11 of our 18 PX4 logs use the default SDLOG_PROFILE = 1: the gyro exists only in sensor_combined at 200 Hz, averaged over IMU_INTEG_RATE windows (200 Hz in 17 of 18 logs), and 1.12 logs had vehicle_rates_setpoint at just 5–50 Hz. Such a log shows neither vibration above 100 Hz nor the rate loop response.

SDLOG_PROFILE bitValueWhat it adds
0 Default+1The standard topic set
4 High rate+16Rate setpoint, angular velocity and motor commands at a high rate
8 Raw FIFO high-rate IMU (Gyro)+256Raw gyro in sensor_gyro_fifo at the sensor rate
9 Raw FIFO high-rate IMU (Accel)+512The same for the accelerometer; even bigger logs

Add bits to your current value rather than replacing it: 1 becomes 273, 25 (Default + System identification + High rate) becomes 281. A 1.15.4 log on an ARK FMU v6X with SDLOG_PROFILE = 1023 (bits 0–9) has sensor_gyro_fifo as 800 packets of 10 samples a second (8 kHz), filtered rates and actuator_motors at 800 Hz: 25 MB for 43 seconds, and the logger still dropped 0.13 s in 4 gaps.

sd_bench -r 100
param set SDLOG_PROFILE 273
reboot

SDLOG_PROFILE needs a reboot. Default topics stream about 50 KB/s (PX4 docs); High rate plus Raw FIFO is several times that. Logs die from write stalls of hundreds of milliseconds, not low average speed: run sd_bench in the MAVLink Console and use a card of 32 GB or less (the NuttX limit). A 1.16.2 log lost 5.4 s on the default profile: at that data rate, almost certainly the card.

Motor RPM. Pinning vibration on one motor needs esc_status with RPM: bidirectional DShot, DShot telemetry (DSHOT_TEL_CFG) or DroneCAN ESCs. On 1.16–1.17 bidirectional DShot is DSHOT_BIDIR_EN = 1 with the right MOT_POLE_COUNT; the current docs replace both with a BDShot output protocol and DSHOT_MOT_POL1…12. Our 1.17 log has esc_status at 167 Hz, a 1.12 log at 4 Hz.

Test flight: 20–30 seconds of hover, smooth throttle changes, then 10–20 short, sharp stick inputs per axis. Stabilized is fine: PX4 logs the rate setpoint in every mode. No flips needed. For Betaflight, see the blackbox setup guide.

Vibration: what to look at

Careful with accel_vibration_metric and gyro_vibration_metric: judging by our logs, their scale changes between versions. A 1.12.3 log with 68.5 deg/s of raw gyro noise has a median accel metric of 0.06; a 1.15.4 vehicle with a tenth of that noise shows 1.6, one on 1.17 shows 11.7. Compare them within one version and vehicle only.

bbdoctor measures raw gyro noise as RMS above 80 Hz on roll and pitch: under 4 deg/s clean, 4–10 noisy, above 10 bad. A resonance is a peak above 60 Hz standing over 6 dB above the local floor in at least 60% of throttle bands. Its frequency is unambiguous if the gyro is logged at the full sensor rate (sensor_gyro_fifo) or at 2 kHz or more with the peak below half Nyquist; otherwise you get candidates. Finding the specific motor: drone vibration: find the bad motor.

Real cases from logs

PX4 1.15.4: a 469 Hz resonance and a static notch

The 1.15.4 ARK FMU v6X log from above (6S, 8 kHz raw gyro): bbdoctor found a 469 Hz peak on pitch only, 6 dB above the floor at any throttle. Raw noise: roll 6.4, pitch 1.6, yaw 2.1 deg/s; 0.11 deg/s survives filtering. Above 250 Hz the usual suspects are the stack's soft mounts and fasteners, not the arms.

Left: PX4 1.15.4 pitch spectrum before and after filtering with a hump at 469 Hz; right: raw pitch spectrum at two throttle levels where the 469 Hz hump does not move
PX4 1.15.4, 6S, raw gyro from sensor_gyro_fifo at 8 kHz. Left: pitch before (grey) and after the filters (blue); the filtered rate is logged at 800 Hz, so blue stops at 400 Hz. Right: raw pitch at about 34% (A) and 37% throttle (B): motor humps at 280–400 Hz shift, the one near 469 Hz stays put.

As a stopgap, a static notch on that frequency:

param set IMU_GYRO_NF0_FRQ 469
param set IMU_GYRO_NF0_BW 100

Bandwidth is usually about 30% of the frequency, but IMU_GYRO_NF0_BW tops out at 100 Hz (as does IMU_GYRO_NF_BW on 1.12). Two subtleties. First, the onboard FFT is on here, but by default it searches only IMU_GYRO_FFT_MIN–IMU_GYRO_FFT_MAX (30–150 Hz); every peak it logged was in 31–146 Hz. An FFT-driven dynamic notch would never catch 469 Hz, and for a peak that doesn't move a static notch beats widening the window. Second, with IMU_GYRO_CUTOFF = 40 the second-order low-pass already cuts 469 Hz by about 43 dB, so the notch pays off once you raise the cutoff to win back some of the 5.6 ms delay. bbdoctor's verdict: there's headroom, but mechanics first.

PX4 1.17: a 42 Hz pitch self-oscillation

A 4S quad on an FMU v6C, no raw gyro but esc_status logged. Over 63 s of calm flight the pitch error (gyro − setpoint) has a narrow 41.7 Hz peak, 28 dB above the floor, 11.5 deg/s, below the motors (61–87 Hz). Roll, with identical P 0.18, I 0.15, D 0.003, is clean. Gyro-to-motor-command coherence at that frequency is 1.0 with +13° phase: P drives it.

Spectrum of gyro minus setpoint on PX4 1.17: a narrow 42 Hz peak on pitch, no peak on roll at that frequency, motor frequency band 61–87 Hz above the peak
PX4 1.17.0, 4S, error spectrum (gyro − setpoint) over 63 s of calm flight. Pitch (blue) peaks at 42 Hz; roll (grey), with identical gains, is 31 dB lower there. Orange: motor rotation frequency (5th–95th percentile, 61–87 Hz).
param set MC_PITCHRATE_P 0.162

That's P minus 10%. Fly again: peak shrank, it was the loop; grew, put P back; unchanged, put P back and look for mechanics. Note the filters: at 42 Hz IMU_GYRO_CUTOFF = 40 alone adds about 90° of lag, and IMU_DGYRO_CUTOFF = 20 cuts D several times over and adds its own lag, so D damps it poorly. A higher gyro cutoff would help, but without raw gyro you can't check the noise: fix P now, filters later with profile 281.

PX4 1.12.3: 68.5 deg/s of noise in a 222 Hz log

An old FMU v3 board, 4S, profile 1. Raw gyro noise above 80 Hz: roll 68.5, pitch 27.8, yaw 14.5 deg/s, from a log that only sees 80–111 Hz. A peak also stays put at any throttle, but at 222 Hz logging its true frequency could be 94, 129 or 316 Hz. So no blind notch: props, motors and fasteners first, then a short Raw FIFO log for the exact frequency.

PX4 1.12.3: motor 2 and the second harmonic

A 6S vehicle, over 15 minutes in the air, motors at 2500–3170 rpm (42–53 Hz) per esc_status. Order analysis (gyro demodulated by each motor's rotation phase) found a 5.3 deg/s 2× component on M2 and almost none on the others: typically a damaged blade or mismatched blade pitch. But RPM was logged at only 4 Hz and 2× (about 83 Hz) sits near the 100 Hz edge, so it's a hint, not a verdict. Motor 2 is rear left on a standard Quad X; this vehicle has a non-standard SYS_AUTOSTART, so check your mixer before pulling a prop.

PX4 filters

ParameterVersionsWhat it doesWhen to touch it
IMU_GYRO_CUTOFFallGyro low-pass (default 40 Hz, as in 10 of our 18 logs)Raise for less delay if the filtered gyro is clean and motors stay cool
IMU_DGYRO_CUTOFFallLow-pass for D, which works on the derivative of the angular rate. Default 20 Hz (30 on 1.12)Lower: less motor noise, weaker damping of fast oscillations
IMU_GYRO_NF0_FRQ, IMU_GYRO_NF0_BW, IMU_GYRO_NF1_*1.13+Static notchesOnly on an unambiguous resonance frequency
IMU_GYRO_NF_FREQ, IMU_GYRO_NF_BW1.12Static notchSame
IMU_GYRO_DNF_EN (bit 0 ESC RPM, bit 1 FFT), IMU_GYRO_DNF_HMC, IMU_GYRO_DNF_BW1.13+ (IMU_GYRO_DYN_NF on 1.12)Dynamic notch from RPM or the onboard FFTIf you have RPM telemetry, use bit 0
IMU_GYRO_FFT_EN, IMU_GYRO_FFT_MIN, IMU_GYRO_FFT_MAX1.12+Onboard FFT, searches 30–150 Hz by default (32–192 on 1.12)Filters nothing on its own

PX4 filters every FIFO sample at the full sensor rate and only then decimates to IMU_GYRO_RATEMAX (default 400 Hz, 800 in 16 of our 18 logs). That's why a 469 Hz notch works with an 800 Hz controller.

Delay. bbdoctor takes the gyro low-pass delay from the setting: √2/(2π·fc) for a second-order filter. The time offset between logged topics is no substitute: in PX4 it can be zero with a 30–60 Hz cutoff.

IMU_GYRO_CUTOFF, Hz2530406080
Delay, ms9.07.55.63.752.8

Raising the cutoff. Go in 10 Hz steps (40 → 50 → 60). After each, fly 20–30 seconds, land and touch the motors and ESCs: the PX4 docs warn they can overheat to the point of damage. Stop where Actuator Controls FFT noise visibly rises and back off a step. With raw noise above 4 deg/s, leave the filters alone and fix the mechanics. Why noise heats motors: noise and filter tuning.

param set IMU_GYRO_CUTOFF 50

Rate controller: P, I, D, FF and K

The PX4 rate controller computes K·(P·error + I·integral − D·angular acceleration) + FF·setpoint. D acts on measured acceleration, not the error, so sharp stick inputs don't kick it. Parameters: MC_ROLLRATE_P/I/D/FF/K plus the same for PITCHRATE and YAWRATE, with integrator limits MC_RR_INT_LIM, MC_PR_INT_LIM, MC_YR_INT_LIM.

The K multiplier. K = 1 gives the parallel form; P = 1 the standard form, where K is the overall gain. A 1.13.1 CubeOrange log has P 1, I 2, D 0.029 and K 0.08 on roll and pitch: effective gains 0.08, 0.16 and 0.0023, so comparing its "P = 1" with someone's 0.15 is meaningless. FF is not scaled by K.

param show MC_ROLLRATE*
param show MC_PITCHRATE*

Manual tuning order per the PX4 docs: tune P and D on roll and pitch iteratively, then I. Yaw usually runs with D at 0. The docs also say to disable MC_AIRMODE while tuning.

Autotune. Built in since 1.13: set MC_AT_EN = 1, then in QGroundControl (Vehicle Setup → PID Tuning) enable Autotune and press the button while flying in Altitude, Hold or Position mode. Any stick input aborts it, so keep a safe height. MC_AT_APPLY = 1 (default) applies the gains after disarm. If fast oscillation (over once a second) follows, the docs say to lower MC_ROLLRATE_K and MC_PITCHRATE_K in 0.02 steps.

No Betaflight-style TPA in PX4. None of our 1.12–1.17 logs has MC_TPA_*. Throttle-dependent gain is handled by the thrust model THR_MDL_FAC (0 = thrust linear in the signal, 1 = quadratic); most of our vehicles run 0, three 0.3, one 0.1. The docs call 0.3–0.5 typical: start at 0.3, move in 0.1 steps. Oscillation at low throttle means too high, at high throttle too low. Recheck the rate tuning afterwards.

param set MC_AT_EN 1
param set THR_MDL_FAC 0.3

bbdoctor reads the response from vehicle_rates_setpoint even in Stabilized if gyro and setpoint are logged at 200 Hz or more and at least 10 sharp stick stretches (and a quarter of all) are usable; 30 if the curve is unstable. In a 1.16.2 log roll reached only 84% of the setpoint, and bbdoctor suggested +10% P and I: MC_ROLLRATE_P 0.102 → 0.112, MC_ROLLRATE_I 0.1301 → 0.143. Reading a step response: PID tuning from blackbox.

Self-oscillation or frame resonance

A narrow peak has two opposite fixes, and mixing them up is costly: lower the filters against a loop self-oscillation and the extra lag makes it worse.

SignLoop self-oscillationMechanical resonance
FrequencyUsually 10–80 Hz, below the motor frequencyAnything, often above 100 Hz, independent of throttle
AxesOne axis, or roll+pitch without yawOften all axes including yaw
MotorsSwing in step with the gyro (high coherence)Weak link to the motor commands
P −10%Peak shrinksNo change
FixLower P (or D if D drives it)Fasteners, stack, arms; notch as a stopgap

bbdoctor looks for these peaks on calm stretches (setpoint under 60 deg/s, steady throttle) between 10 Hz and 80 Hz or 0.8 of the motor frequency. A peak must be narrow, 10 dB over the local floor and at least 1 deg/s; it's dropped if it matches motor harmonics, appears on every axis including yaw, or sits on a known frame resonance. The gyro-to-torque cross-spectrum shows whether the motors swing in step (coherence 0.5+), and the phase whether P, D or filter lag drives it.

Failsafe and battery

bbdoctor flags a failsafe from vehicle_status; the cause is in the log messages (Flight Review → Messages). A 1.13.1 CubeOrange log read "Failsafe enabled: no offboard", "Position-Ctrl activation failed", then "Failsafe: blind descend". Offboard was lost, the reaction was COM_OBL_RC_ACT = 0 (Position), and Position wouldn't engage. "Blind descend" means the position controller had no vertical velocity estimate, so it sank on slightly under hover thrust. Fix the companion link and the EKF2 height estimate, and pick a reaction your sensors support (Altitude or Land without position); the RC link isn't the issue. The timeout is COM_OF_LOSS_T; 1.13 also has COM_OBL_ACT for the no-RC case, 1.14+ only COM_OBL_RC_ACT.

In the 1.17 log, "Battery 1: over voltage. Land now!" appeared mid-flight at an average 3.6–3.7 V per cell. Current PX4 code raises it when one unfiltered voltage sample exceeds BAT1_N_CELLS × BAT1_V_CHARGED × 1.05, here 4 × 4.05 × 1.05 = 17.0 V. The 5 Hz logged samples peak at 16.27 V, but neighbours jump 2–3.5 V (16.13 → 12.6 → 14.96 V). A 6–19 A current swing explains 1.2 V at most even at the 22 mΩ per cell bbdoctor estimated, so the voltage reading itself is noisy and an unlogged spike tripped the check. Check the power module wiring and ground and the BAT1_V_DIV calibration; BAT1_V_CHARGED at 4.05 instead of 4.2 (the current default) only lowers the threshold.

ParameterValues
NAV_RCL_ACTRC loss: 1 Hold, 2 Return, 3 Land
COM_RC_LOSS_TSeconds before RC counts as lost: 0.5 in 11 of our logs, but we have also seen 10 and 35 s
BAT_LOW_THR, BAT_CRIT_THR, BAT_EMERGEN_THRRemaining-charge thresholds: 0.15, 0.07, 0.05 by default
COM_LOW_BAT_ACT0 warning only, 2 Land, 3 Return at critical and Land at emergency

12 of our 18 logs have COM_LOW_BAT_ACT = 0, warning only. For a large vehicle with GPS:

param set COM_LOW_BAT_ACT 3
param set NAV_RCL_ACT 2

Return needs a valid position estimate. Without GPS, choose landing: COM_LOW_BAT_ACT 2 and NAV_RCL_ACT 3.

A worn pack shows in internal resistance. bbdoctor estimates it from the voltage-vs-current slope over sharp current changes: under 12 mΩ per cell is fine, 12–25 a warning, more is bad. This 1.17 log gave about 22 mΩ against 2–10 on the other vehicles, but with voltage this noisy the figure is shaky: fix the measurement and check the pack on a charger. The flight started part-used (3.94 V per cell), and the sustained minimum (0.5 s median) was 3.41 V per cell at only 18 A peak.

Common traps in PX4 logs

Checking it automatically

bbdoctor analyses PX4 .ulg logs online for free: noise before and after the filters, resonances, loop self-oscillation, the rate response, motor load and RPM, battery, failsafes and logging gaps. If the log is too slow, it names the SDLOG_PROFILE to use. Its thresholds were tuned on Betaflight FPV quads, so compare large vehicles with their own earlier logs. See the example report, or the ArduPilot logs and INAV blackbox guides.

FAQ

How do I log vibration data in PX4?

Add bit 4 High rate (+16) and bit 8 Raw FIFO high-rate IMU Gyro (+256) to your current SDLOG_PROFILE: 1 becomes 273; reboot to apply it. The raw gyro is then logged in sensor_gyro_fifo at the sensor rate. Logs get several times bigger, so use it for test flights and check the SD card with sd_bench.

Which PX4 filters should I tune?

IMU_GYRO_CUTOFF (gyro low-pass), IMU_DGYRO_CUTOFF (D-term low-pass), the static notches IMU_GYRO_NF0_FRQ/BW (IMU_GYRO_NF_FREQ/BW on 1.12) and the dynamic notch IMU_GYRO_DNF_EN (IMU_GYRO_DYN_NF on 1.12). Fix the mechanics first: filters are for what is left after props, motors and mounts. After every cutoff increase, fly 20–30 seconds and feel the motors.

How is bbdoctor different from Flight Review?

Flight Review draws plots and leaves the conclusions to you. bbdoctor measures noise before and after the filters, looks for resonances and loop self-oscillation, compares motors and the battery, and gives param set commands. They work well together: plots in Flight Review, diagnosis and commands in bbdoctor.

Why can't my 200 Hz log show motor vibration?

A 200 Hz log only shows frequencies up to 100 Hz, and prop and motor vibration is usually higher. By default PX4 logs sensor_combined, where the gyro is averaged over IMU_INTEG_RATE windows (200 Hz). Anything faster is either smoothed away or folds onto a false frequency. You need the Raw FIFO profile.

Updated:

More guides