Skip to content

Pr turret - #609

Open
veerwang wants to merge 9 commits into
Cephla-Lab:masterfrom
veerwang:pr-turret
Open

Pr turret#609
veerwang wants to merge 9 commits into
Cephla-Lab:masterfrom
veerwang:pr-turret

Conversation

@veerwang

Copy link
Copy Markdown
Contributor

Summary

Ports the turret-controller changes from pr-singlemotor onto this branch, as 5 commits:

  1. Per-slot calibration + gear backlash compensation (3c0400c)
    • Per-slot absolute pulse targets (OBJECTIVE_TURRET_CALIBRATED_PULSES), fall back to theoretical positions
    • Backlash compensation: approach every slot change from below to cancel gear backlash
  2. Re-port SingleMotor logic (14fc708): software homing (sweep → backoff → fine-search → set zero), DI1 origin switch, factory params auto-calibrated on connect
  3. Sync SingleMotor tuning (c03564e): fine-step 2, backoff 60, max speed 150, etc.
  4. Motor direction inversion (8e10165): OBJECTIVE_TURRET_DIRECTION_INVERTED
  5. Origin-switch DI polarity inversion (3dd9060): OBJECTIVE_TURRET_DI_INVERT

Files changed

  • software/control/_def.py / objective_turret_controller.py / microscope.py / modbus_rtu.py / tests/control/test_objective_turret_controller.py

Notes

  • Toggling direction/DI inversion requires re-homing and re-measuring calibrated slots
  • Turret controller tests: 74 passed (simulation, no hardware)

veerwang and others added 8 commits August 12, 2026 15:38
Port two positioning features from the SingleMotor source project:

- OBJECTIVE_TURRET_CALIBRATED_PULSES: per-slot calibrated absolute pulse
  targets (slot 1..4 -> pulses from homing zero). A calibrated slot is
  used verbatim; uncalibrated slots fall back to the theoretical
  (slot-1)*pulses_per_position + OBJECTIVE_TURRET_OFFSET_PULSES.
- OBJECTIVE_TURRET_BACKLASH_DEG: gear backlash compensation (0..1 turret
  degrees). When > 0 every slot change overshoots below the target and
  approaches it from below, so the final approach direction is always
  the same and gear backlash cancels out.

Both are validated at init (slot range, integer pulses, deviation bound
of one slot vs theoretical, degree range) so a bad machine .ini fails
fast. Defaults keep behavior identical to before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… switch, factory params

Re-merge from the SingleMotor source project (2026-07-23..27 changes):

- Homing rewritten as software homing (sweep in velocity mode polling the DI
  level -> backoff -> fine-search -> SET_ZERO at the trigger edge), replacing
  the driver's built-in homing modes. Repeatability +/-5 pulses; ends clamped
  at home with holding torque. Default home timeout raised to 120s.
- DI1 is now permanently "origin switch" (3). The old scheme (DI1 temporarily
  mapped to negative limit + homing method 17) faults FF0E on the current
  firmware whenever a move passes the sensor; all homing-register calibration
  is removed.
- Factory parameter set ported and auto-calibrated on connect: accel/decel
  1000, max speed 200, min speed 16 (written before max — fixes the
  min/max write-order bug), currents overload 1.3A / idle 0.6A /
  accel+run+decel 0.95A, microstep forced to 16 (write + save + fail fast
  asking for a power cycle when it differs). Decel current is marked volatile
  (firmware drops the write) so it cannot trigger an EEPROM save every
  connect. Direction register written to RAM after the EEPROM save.
- modbus_rtu: add read_input_registers() batch read so the homing sweep gets
  DI + position + alarm in one frame per poll (the ~50-pulse sensor window
  must not be crossable between two polls).
- _def.py: fix the calibrated-pulses doc example (6640 deviates more than one
  slot and fails validation) and note that slots must be re-measured after
  upgrading from driver homing (the zero reference moved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ne-search accel, max speed 150

Port SingleMotor f366df9..4119c32 (manager-specified values):
- Homing: fine step 5->2 (repeatability +/-2), backoff 150->60, sweep
  50->60 Step/s with 20ms poll (52ms window crossing >= 2.6 polls),
  fine travel limit 400->200
- Fine search now temporarily lowers acceleration (0x005F) to 50 to
  soften the microstep approach to the trigger edge; restored after
- Max speed 200->150: the 0.95A current cap loses steps under load at 200
- Idle current 60->21 (displayed 0.69A); drop the decel-current (0x15)
  calibration and its volatile mechanism — the SDM42 drive has no such
  parameter (writes silently dropped, reads back 0)

SingleMotor's reconnect-polling fix (8ab21dc) is Qt-panel-specific and
does not apply to this synchronous controller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…db6c

Some turret motor models are wired with the opposite phase order, so the
same commands spin the other way. Add OBJECTIVE_TURRET_DIRECTION_INVERTED
(per-machine .ini, default False = behavior unchanged):

- Inversion applied only at the register boundary: absolute-move targets,
  jog signs, homing-sweep direction bit, and position readbacks; slot
  mapping / calibration / backlash / homing logic stay in logical
  coordinates untouched
- Init direction-register (0x0052) expected value follows the flag
  (inverted expects 0), still RAM-only after the EEPROM save
- microscope.py passes the flag via turret_kwargs; the simulation twin
  accepts it for constructor parity
- 12 new tests incl. a default-off regression guard; theoretical-target
  assertions pass explicit offset/calibration to stay independent of
  machine .ini values loaded into _def

After toggling on an existing machine, re-home and re-measure the
calibrated slots (the physical zero moves with the sweep direction).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ngleMotor 8874934

New objective changers sense the origin switch on the opposite logic level
(homing direction flipped, jerky). Add a per-machine software option that
inverts the DI1 trigger verdict during software homing / distance search.

- _def.py: OBJECTIVE_TURRET_DI_INVERT (default False = old logic)
- objective_turret_controller.py: di_invert ctor kwarg (def fallback + bool
  validation, same pattern as direction_inverted); the verdict flips in
  _read_status_snapshot so the sweep/backoff/fine state machine, direction
  logic and calibration stay in the same logical frame
- microscope.py: pass di_invert through turret_kwargs
- tests: 6 di_invert tests (inside/outside window homing, backoff direction
  unchanged, def fallback, non-bool raises, sim accepts kwarg); 74 passed
  in a CI-equivalent env. Note: toggling requires re-homing — the fine-search
  edge (physical zero) sits on the other side of the sensor window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…step auto-write

Replace the per-slot OBJECTIVE_TURRET_CALIBRATED_PULSES map with uniform slot
targets: offset + (slot-1) x PULSES_PER_SLOT. The 2200 pulses/slot spacing is
now a named constant, cross-checked at init against the scale derived from the
mechanics constants and the microstep readback so the two cannot drift apart.
One measured offset per machine is enough; per-slot values are dropped.

On a microstep mismatch, init now raises without writing: the register reads
back the pending value (vendor-confirmed), so the previous write-then-raise
would let the next start pass the check while the drive still runs the old
scale until power-cycled. The error message directs to the SingleMotor setup
tool instead.

All factory register values and the motion/homing logic are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single source of truth: PULSES_PER_SLOT is now computed from the mechanics
constants (steps/rev x microsteps x gear ratio / slots) instead of being a
hand-copied 2200 held in sync by a runtime cross-check. The duplicate
_pulses_per_position instance field, the unreachable init check, and its
monkeypatch-only test are gone; a one-line test pins the value at 2200.
Also drops two tests fully subsumed by existing ones and inlines the
single-caller _target_pulses helper. No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ports updated objective turret controller behavior, expanding the real controller to use software homing and adding configuration knobs for backlash compensation, motor-direction inversion, and DI (origin switch) polarity inversion; updates the Modbus client and tests accordingly.

Changes:

  • Implement software homing (sweep → backoff → fine search) and initialize/calibrate a factory parameter set on connect.
  • Add backlash compensation to slot changes, plus optional direction inversion and DI polarity inversion (wired/configurable via _def.py and passed through microscope.py).
  • Extend Modbus RTU client with a batched FC0x04 input-register read API and add substantial test coverage for the new turret behaviors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
software/control/objective_turret_controller.py Adds software homing, backlash compensation, direction/DI inversion handling, and init-time parameter calibration.
software/control/modbus_rtu.py Adds read_input_registers() to read a consistent input-register snapshot in one FC0x04 transaction.
software/control/microscope.py Passes new turret configuration kwargs (backlash/direction/DI inversion) from global config into controller construction.
software/control/_def.py Introduces new turret configuration defaults and documents intended behavior/upgrade implications.
software/tests/control/test_objective_turret_controller.py Adds tests for homing flows, init calibration, backlash compensation, direction inversion, and DI inversion.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

pre = self.current_position_pulses
di1, _, _, alarm = self._read_status_snapshot()
self._check_alarm(alarm)
if not di1: # already inside the sensor window -> skip straight to backoff

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code] Fixed in commit 2050f6d - reworded to describe the branch that actually runs: off the sensor -> sweep; already in the window skips straight to backoff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants