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 bit | Value | What it adds |
|---|---|---|
| 0 Default | +1 | The standard topic set |
| 4 High rate | +16 | Rate setpoint, angular velocity and motor commands at a high rate |
| 8 Raw FIFO high-rate IMU (Gyro) | +256 | Raw gyro in sensor_gyro_fifo at the sensor rate |
| 9 Raw FIFO high-rate IMU (Accel) | +512 | The 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
- Actuator Controls FFT in Flight Review (needs the High rate profile). Per the PX4 docs a healthy plot has one peak at the very bottom (below about 20 Hz) and is low and flat elsewhere. Scales differ between vehicles, so compare a vehicle only with itself.
- Raw gyro spectrum (Raw FIFO only). Peaks that move with throttle come from motors and props. A peak that stays put at any throttle is a resonance of the frame, stack, GPS mast or gimbal.
- Accelerometer. On the Raw Acceleration plot, z touching x and y in hover is bad news. Clipping counters live in
vehicle_imu_status(accel_clipping); clipping means mechanics first, and it corrupts the attitude estimate.
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.

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.

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
| Parameter | Versions | What it does | When to touch it |
|---|---|---|---|
IMU_GYRO_CUTOFF | all | Gyro 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_CUTOFF | all | Low-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 notches | Only on an unambiguous resonance frequency |
IMU_GYRO_NF_FREQ, IMU_GYRO_NF_BW | 1.12 | Static notch | Same |
IMU_GYRO_DNF_EN (bit 0 ESC RPM, bit 1 FFT), IMU_GYRO_DNF_HMC, IMU_GYRO_DNF_BW | 1.13+ (IMU_GYRO_DYN_NF on 1.12) | Dynamic notch from RPM or the onboard FFT | If you have RPM telemetry, use bit 0 |
IMU_GYRO_FFT_EN, IMU_GYRO_FFT_MIN, IMU_GYRO_FFT_MAX | 1.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, Hz | 25 | 30 | 40 | 60 | 80 |
|---|---|---|---|---|---|
| Delay, ms | 9.0 | 7.5 | 5.6 | 3.75 | 2.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.
| Sign | Loop self-oscillation | Mechanical resonance |
|---|---|---|
| Frequency | Usually 10–80 Hz, below the motor frequency | Anything, often above 100 Hz, independent of throttle |
| Axes | One axis, or roll+pitch without yaw | Often all axes including yaw |
| Motors | Swing in step with the gyro (high coherence) | Weak link to the motor commands |
| P −10% | Peak shrinks | No change |
| Fix | Lower 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.
| Parameter | Values |
|---|---|
NAV_RCL_ACT | RC loss: 1 Hold, 2 Return, 3 Land |
COM_RC_LOSS_T | Seconds 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_THR | Remaining-charge thresholds: 0.15, 0.07, 0.05 by default |
COM_LOW_BAT_ACT | 0 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
- Motor numbering. PX4 Quad X: 1 front right, 2 rear left, 3 front left, 4 rear right. Not the Betaflight order. In the 1.17 log the M3+M4 diagonal worked 29% harder, confirmed by
esc_status: about 93 Hz in flight against 76–77 Hz on M1 and M2. The mixer is fighting yaw: look for a tilted motor, twisted arm or mismatched props. In another log the front motors carried 23% more than the rear: CG too far forward. - FFT on, notch off. Six of our 1.13+ logs have
IMU_GYRO_FFT_EN = 1withIMU_GYRO_DNF_EN = 0, and three 1.12 logs do the same withIMU_GYRO_DYN_NF = 0. The FFT finds peaks (thesensor_gyro_ffttopic) but filters nothing. - A notch you can't verify. A 1.12.3 log has a 100 Hz notch with 200 Hz logging, right at Nyquist: that log can't show whether it works.
- Slow topics and dropouts. A rate setpoint logged at 5 Hz gives no usable response, however fast the gyro. Gaps break spectra too: if the logger drops data even on profile 1, replace the card.
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: