Skip to content

fix(optical_flow): report actual integration timespan in PAW3902/PAA3905#27418

Open
dakejahl wants to merge 2 commits into
PX4:mainfrom
dakejahl:fix/optical-flow-integration-timespan
Open

fix(optical_flow): report actual integration timespan in PAW3902/PAA3905#27418
dakejahl wants to merge 2 commits into
PX4:mainfrom
dakejahl:fix/optical-flow-integration-timespan

Conversation

@dakejahl
Copy link
Copy Markdown
Contributor

Summary

PAW3902 and PAA3905 drivers hardcode sensor_optical_flow.integration_timespan_us to the nominal mode frame period (e.g. 1/126 s). The chips actually accumulate delta_x/delta_y until the Motion_Burst register is read, so when reads happen less often than every frame the reported timespan no longer matches reality. Derive it from the actual interval between consecutive burst reads.

Problem

The Motion pin only asserts when the chip detects motion since the last burst read. In a hover with no chip-detectable flow (textureless patch, sub-pixel translation, slow drift), the pin never falls and the driver's backup watchdog reads every kBackupScheduleIntervalUs (20 ms in mode 2) instead of every frame. The chip has been integrating that whole interval, but the driver still reports the hardcoded SAMPLE_INTERVAL_MODE_X.

Downstream consequences:

  • src/modules/sensors/vehicle_optical_flow/VehicleOpticalFlow.cpp:124,151 uses integration_timespan_us to size the gyro integration window for body-rate compensation. A wrong length means the gyro integral does not cover the same physical interval the chip integrated over, so the rotational-LOS compensation flow_compensated = flow_rate - gyro_rate.xy() does not cleanly cancel rotation from the observation.
  • src/modules/ekf2/EKF/aid_sources/optical_flow/optical_flow_control.cpp:293-298 updates _flow_gyro_bias via an LPF on flow_sample.gyro_rate - _ref_body_rate. With a persistently misaligned window the difference is no longer just a real bias - it contains the timing skew, which the LPF accumulates. That bias is then applied to every fusion including the motion-reported ones on the clean DRDY path.

The practical magnitude is small for steady hover (gyro ~= 0), but non-zero whenever the body rate is non-zero during a zero-flow publish (e.g. yaw scanning in hover, attitude wobble below the chip's pixel resolution, vibration). It is also a message-contract violation - msg/SensorOpticalFlow.msg defines the field as "accumulation timespan".

Solution

Track the timestamp of the previous successful Motion_Burst read in _timestamp_sample_last, and override the per-mode default with timestamp_sample - _timestamp_sample_last (clamped to [1 ms, 200 ms]). Update _timestamp_sample_last on every successful burst read since the chip clears its accumulator on each read, and reset it to 0 in Reset(). The first read after reset falls back to the per-mode default; subsequent reads use the measured interval.

Builds verified: make ark_can-flow_default, make ark_can-flow-mr_default.

Marking draft for review of the approach before flight testing.

The chip accumulates delta_x/delta_y in its burst registers until the
Motion_Burst register is read, which clears the accumulator. The drivers
previously reported a hardcoded mode-dependent SAMPLE_INTERVAL_MODE_X as
the integration timespan regardless of how long ago the last burst read
happened.

This is wrong any time reads span more than one frame period - most
commonly when the Motion DRDY pin doesn't fire (chip detected no motion)
and the backup watchdog fires the read kBackupScheduleIntervalUs (20 ms)
later instead of every frame.

Downstream, VehicleOpticalFlow uses integration_timespan_us to size the
gyro integration window used for body-rate compensation, and ekf2's
_flow_gyro_bias estimator compares that gyro against the IMU at the
matched delayed time. A wrong window length misaligns the gyro
compensation with what the chip actually saw, and slowly biases
_flow_gyro_bias - contaminating motion-reported fusions on the
correctly-timed DRDY path too.

Replace the hardcoded value with the actual interval between consecutive
successful burst reads (clamped to a sane range). _timestamp_sample_last
is reset on driver Reset() and updated on every successful burst read
regardless of publish, since the chip clears its accumulator on every
read.
@github-actions github-actions Bot added kind:bug Something is broken or behaving incorrectly. scope:drivers Device drivers and hardware interfaces. scope:sensors Sensor pipeline, calibration, voting, or sensor validation. labels May 20, 2026
…clamp

Replace raw 1000/200000 microsecond bounds with 1_ms/200_ms to match
the existing time_literals convention already used elsewhere in these
drivers. No behavior change.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
@dakejahl dakejahl marked this pull request as ready for review May 20, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind:bug Something is broken or behaving incorrectly. scope:drivers Device drivers and hardware interfaces. scope:sensors Sensor pipeline, calibration, voting, or sensor validation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant