Skip to content

STARBackend: direct X-drive head96_move_x via EEPROM X-offset (per-axis motion control)#1081

Merged
BioCam merged 4 commits into
PyLabRobot:mainfrom
BioCam:head96-x-offset-direct-x-move
Jun 10, 2026
Merged

STARBackend: direct X-drive head96_move_x via EEPROM X-offset (per-axis motion control)#1081
BioCam merged 4 commits into
PyLabRobot:mainfrom
BioCam:head96-x-offset-direct-x-move

Conversation

@BioCam

@BioCam BioCam commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

What

Rework head96_move_x from the EM "move all axes together" command into a real single-axis X-arm drive, so the CoRe 96 head's X move gets the same per-axis acceleration and current control that head96_move_y and head96_move_z already have. The implementation mirrors the existing iSWAP rotation-drive X handling exactly: a master-EEPROM offset getter (_head96_request_x_offset alongside _iswap_rotation_drive_request_x_offset), the offset cached on the setup info record (Head96Information.x_offset alongside iSWAPInformation.rotation_drive_x_offset), and a move that converts a target deck position to a carriage target via that offset (the inverse of iswap_rotation_drive_request_x).

Why

head96_move_x was the only head96 axis without dedicated motion control: it did a position round-trip then issued EM, which moves all axes together and exposes no speed/acceleration. A direct X-arm drive needs the X distance between the X-arm carriage center and channel A1, which the firmware stores in master EEPROM.

Pattern

This is the second adopter of a low-to-high command-layering standard for STAR moves, after move_iswap_x: a low-level primitive that exposes motion control (X0:XP via experimental_x_arm_move), a cached EEPROM calibration offset on the *Information record, and a deck-frame public method that transforms via the offset and threads the motion params through. It replaces the anti-pattern of going straight to a monolithic EM macro that bundles all axes and exposes nothing. Candidates to migrate next: iSWAP Y/Z, head96 Y/Z, CoRe 384 head, nano pipettor.

What this enables

Per-move acceleration, impossible with EM's single fixed ramp:

await star.head96_move_x(500.0, acceleration_level=5)   # empty head: fast repositioning
await star.head96_move_x(500.0, acceleration_level=1)   # 96 tips full of liquid: gentle, no sloshing
  • Gentle ramp (level 1) for liquid-laden transits avoids sloshing/droplet loss across all 96 tips; level 5 for empty repositioning.
  • Lower acceleration tames the cantilever wobble when the head is extended forward (the X-drive sits at the back).
  • Max acceleration on empty traverses recovers cycle time.

Changes

  • Add _head96_request_x_offset(): reads the carriage-center-to-A1 X offset from master EEPROM (parameter kf, set via C0:AF, read via C0:RA), in mm.
  • Cache it as Head96Information.x_offset, populated in set_up_core96_head (mirrors iSWAPInformation.rotation_drive_x_offset).
  • Rework head96_move_x to drive the X arm to x + x_offset via X0:XP (channel A1 sits left of the carriage center, so the carriage target is A1 + offset), guarded by the 96-head A1 X range, exposing acceleration_level / current_protection_limiter.
  • Chatterbox returns the factory-default offset (365.0 mm).

Behavior change

head96_move_x now issues a single-axis X move instead of the EM all-axes coordinate move. Like head96_move_y / head96_move_z it moves only its own axis and assumes a safe Z (no EM lower-then-move). The signature stays backward compatible: x remains positional, the new parameters default.

Compatibility

All firmware commands used (C0:RA, C0:AF/kf, X0:XP) are long-standing STAR commands, stable across firmware versions; the change was validated on a head96 running 2021 firmware. current_protection_limiter is capped at 7, which is valid on every firmware track.

Validation

Hardware A/B on a STAR against the previous EM move: for target A1 X = 500, the direct drive lands A1 at 499.90 mm vs 500.00 (EM), a 0.1 mm delta (one firmware increment), and is Y-invariant (identical front and back of the deck) where EM drifted ~0.2 mm with Y. The EEPROM read returned 368.2 mm on that machine. lint, mypy, and the STAR test suite pass.

BioCam and others added 4 commits June 9, 2026 16:06
…AP), keep EM as _OLD

Mirror the iSWAP rotation-drive X handling on the 96-head: read the X-arm-carriage <-> channel-A1
X-offset from master EEPROM (_head96_request_x_offset, RA ra=kf - key pending hardware
verification), cache it on Head96Information.x_offset (populated in set_up_core96_head like the
iSWAP block), and rework head96_move_x to drive the X arm directly to (x - x_offset) via
experimental_x_arm_move, with an A1 X-range guard and acceleration/current control.

The previous EM-based move is preserved as head96_move_x_OLD for A/B verification; move_iswap_x_OLD
recovers the pre-PyLabRobot#1053 relative-step iSWAP X move for the same comparison. Both _OLD methods are
scaffolding to delete after validation. Chatterbox supplies a canned x_offset. format/lint/mypy
clean; STAR tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hardware A/B (target A1 500 -> OLD EM lands 500.0, NEW direct-drive landed 94.8) exposed two
bugs in head96_move_x:

- Sign: A1 sits left of (below) the X-arm carriage center, so deck-A1 = carriage - offset; the
  carriage target is therefore A1 + offset (inverse of iswap_rotation_drive_request_x). The move
  subtracted instead of added.
- Field width: the head96 offset is ~10x the iSWAP's, so its 0.1 mm EEPROM value is 4 digits
  (3684); fmt "kf###" truncated it to 368 -> 36.8 mm. Widen to "kf####" -> 368.4 mm, which
  matches the value the hardware geometry implies.

Chatterbox canned x_offset updated 0.0 -> 368.4 to match. lint/mypy clean; STAR tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… x_offset

The C0 command set lists the CoRe 96 head X-offset (AF/kf) default as 3650 (0.1 mm) = 365.0 mm.
Use that for the chatterbox canned value, mirroring how the iSWAP chatterbox uses its documented
34.0 mm default; real hardware reads its per-machine calibrated value from EEPROM at setup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The direct-drive head96_move_x is hardware-validated (lands A1 within 0.1 mm of the EM command,
Y-invariant), so drop the head96_move_x_OLD A/B scaffold and its dead docstring reference. Also
drop move_iswap_x_OLD, which was only an iSWAP A/B helper and does not belong in a head96 change;
it is recoverable from history for the separate iSWAP work. Remove the "kf hypothesis" hedge in
the getter docstring now that the EEPROM field is confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BioCam BioCam requested a review from rickwierenga June 9, 2026 16:20
@BioCam BioCam merged commit e8923b2 into PyLabRobot:main Jun 10, 2026
21 checks passed
@BioCam BioCam deleted the head96-x-offset-direct-x-move branch June 10, 2026 12:34
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.

1 participant