Skip to content

Scan drops steps under load → open-loop position drift / homing off (photo save races the stepper) #125

@bgrupczy

Description

@bgrupczy

Environment

  • Device: OpenScan Mini (greenshield), camera IMX519
  • Firmware: 0.11.4
  • Raspberry Pi, software GPIO stepping (gpiozero / RPi.GPIO), open-loop, no endstop

Symptom

During a scan the steppers grind/growl and drop steps. Because the rig is open-loop with no endstop, the lost steps make the firmware's position estimate drift by several degrees, so homing ends up off and the soft limits no longer reliably protect the mechanism (risk of over-travelling the range). The motion is smooth when the device is idle — the roughness only appears under the CPU load of a running scan. That's the tell that it's load-related, not an acceleration/max-speed setting (changing those makes no audible difference at idle).

Root cause

In ScanTask._capture_photos_at_position (openscan_firmware/controllers/services/tasks/core/scan_task.py) each photo is saved fire-and-forget — both the single-photo path and the focus-stacking path:

asyncio.create_task(self._ctx.project_manager.add_photo_async(photo_data))
await asyncio.sleep(0)

The loop never awaits that task, so it advances to the next await motors.move_to_point(...) while the previous photo's save is still running. add_photo_async does the JPEG write + metadata + _recalculate_and_save_scan_size (an os.walk over the scan directory that grows with photo count), hopping into the default ThreadPoolExecutor. The motor move's stepping, meanwhile, is a software time.sleep-timed GPIO bit-bang running in MotorController._executor. The two thread pools contend for the CPU, the step-pulse intervals overrun, and steps are dropped. Open-loop + no endstop ⇒ the position estimate drifts.

Impact

  • Audible rough/grinding scan motion
  • Dropped steps → open-loop position drift (several degrees)
  • Homing ends up off by several degrees
  • On endstop-less rigs, risk of driving past the intended range

Suggested fix

Await the save instead of detaching it, so each point is strictly move → capture → save → next move and the motor only ever steps while the CPU is otherwise idle. (pause/cancel are still checked at the top of the loop, so dropping the await asyncio.sleep(0) costs nothing.)

Verification

Patched both capture branches on a Mini (greenshield, IMX519, firmware 0.11.4): the audible growl during scans is gone, scans complete cleanly, and homing holds across repeated runs.

🤖 Filed with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions