fix(aerotech): position capture latches the gate start (DATAACQ trigger 3)
CI for csaxs_bec / test (push) Successful in 3m31s

Measured with TTL gates on J12 at 2 mm/s: trigger 0 ("UINT1 rising" in the A3200
help) latched at the gate end (+17.2 um for a 10 ms gate, expected +20), trigger 3
at the gate start (-3.0 um). The Npaq UINT input is inverted compared with the help.
Default trigger_input is now 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
This commit is contained in:
Mirko HollerandClaude Opus 5.5 committed 2026-10-29 12:08:34 +01:00
1 parent 28ba071d68
commit 3aca5030a2
3 files changed
+18 -9

No files matched your search

+4 -2
View File
@@ -94,7 +94,9 @@ scans.aerotech_cont_grid(dev.aerox, 0, 1, 0.01, dev.aeroy, 0, 1, 0.1, exp_time=0
the scan); X reaches 1 um within ~60 ms but 10 nm only after 0.5-2 s
- `capture_positions=True` (default False until commissioned): device `aerocapture` latches
the encoder counters on each detector trigger (DATAACQ on UINT1, up to 16 kHz, 4 kHz with
MXR sources); after each line they are read back (~2600 values/s over the ASCII interface)
MXR sources). The Npaq input is inverted compared with the A3200 help (measured
2026-10-29): DATAACQ trigger 3 latches at the rising TTL edge at J12 (gate start, the
default), trigger 0 at the falling one (gate end); after each line they are read back (~2600 values/s over the ASCII interface)
and published as async data `aerocapture_positions_enc<channel>`. The counter offset is
calibrated in pre_scan with one software PSO fire, before any trigger receiver is armed.
@@ -150,7 +152,7 @@ lines run back to back without re-arming.
|-------|-----|--------|
| TTLIN3 (50 Ω) | line trigger | Npaq J7 opto output: J7-25 (+5 V) -> J7-6, J7-7 -> TTLIN3, J7-24 -> TTLIN3 ground |
| TTLOUT2 | "shutter": trigger edge to end of the last gate | scope |
| TTLOUT3 | "detector" gates (high during exposure): n pulses, width exp_time - readout_time, period exp_time, first one shutter_delay after the edge | scope, later Npaq J12 pin 1 (+) / pin 2 (-) for position sampling (rising edge = exposure start) |
| TTLOUT3 | "detector" gates (high during exposure): n pulses, width exp_time - readout_time, period exp_time, first one shutter_delay after the edge | scope and Npaq J12 pin 1 (+) / pin 2 (-) for position sampling (rising edge = exposure start, DATAACQ trigger 3) |
Blocks: LUT8 = TTLIN3 | BITS.A (software trigger); SRGATE1 arm gate; LUT7 = rising edge of
LUT8 & SRGATE1 & no line running (PULSE3/PULSE4 low) -> PULSE3 (shutter), PULSE4 (gates).
+13 -6
View File
@@ -394,7 +394,8 @@ class A3200Controller(Controller):
def dataacq_arm(self, axis: str, trigger: int, channels: list[int], n_triggers: int) -> None:
"""
Capture the source signals `channels` on each trigger into the drive array,
starting at index 0. Npaq trigger 0 = Input 12 (UINT1) rising edge, 6 = PSO firing.
starting at index 0. Npaq trigger 0 / 3 = Input 12 (UINT1) "rising" / "falling" edge
(measured: 3 latches when the TTL level at J12 rises), 6 = PSO firing.
Npaq sources: 0-5 local encoder counter, 6-11 encoder multiplier (MXR) channel 0-5.
"""
self.command(f"DATAACQ {axis} 1 OFF")
@@ -774,7 +775,12 @@ class A3200Motor(Device, PositionerBase):
# Npaq DATAACQ position sources: 0-5 local encoder counter, 6-11 encoder multiplier (MXR)
# channel 0-5 (max. 4 kHz trigger rate); 12-15 are analog inputs, not used here.
DATAACQ_POSITION_SOURCES = 12
DATAACQ_TRIGGER_UINT1_RISING = 0
# Npaq DATAACQ triggers on UINT1 (J12 pin 1+/2-). Measured 2026-10-29 with a TTL gate on
# J12 (PandA TTLOUT3, X at 2 mm/s): trigger 3 ("falling edge" in the A3200 help) latches at
# the rising TTL edge (start of the exposure gate), trigger 0 ("rising edge") at the falling
# TTL edge (end of the gate). The opto input is inverted compared with the help.
DATAACQ_TRIGGER_UINT1_TTL_RISING = 3
DATAACQ_TRIGGER_UINT1_TTL_FALLING = 0
DATAACQ_TRIGGER_PSO_FIRING_EVENT = 6
@@ -782,8 +788,8 @@ class A3200PositionCapture(PSIDeviceBase):
"""
Latch axis positions on an external trigger (Npaq DATAACQ, drive array).
Each rising edge on the trigger input (default: Input 12 / UINT1, the detector
exposure trigger) stores the encoder counters of the configured channels in the
Each trigger edge (default: rising TTL edge on Input 12 / UINT1, i.e. the start of the
detector exposure gate) stores the encoder counters of the configured channels in the
drive array. After a scan line, read_line() reads them back, converts them to user
units (CountsPerUnit of the axis) and publishes them as async data (signals enc<source>).
@@ -794,7 +800,8 @@ class A3200PositionCapture(PSIDeviceBase):
its PositionFeedbackChannel, by default its axis index in the Npaq. Values are
in controller coordinates (the BEC motor sign is not applied).
drive_axis: any axis of the Npaq, used to address the drive (shared array).
trigger_input: DATAACQ trigger, 0 = Input 12 (UINT1) rising edge.
trigger_input: DATAACQ trigger, 3 = rising TTL edge at UINT1 (gate start, default),
0 = falling TTL edge (gate end); see DATAACQ_TRIGGER_UINT1_TTL_RISING.
"""
USER_ACCESS = ["controller", "arm", "read_line", "calibrate", "disarm"]
@@ -819,7 +826,7 @@ class A3200PositionCapture(PSIDeviceBase):
port=8000,
channels=None,
drive_axis="X",
trigger_input=DATAACQ_TRIGGER_UINT1_RISING,
trigger_input=DATAACQ_TRIGGER_UINT1_TTL_RISING,
socket_cls=SocketIO,
device_manager=None,
scan_info=None,
+1 -1
View File
@@ -166,7 +166,7 @@ class SimA3200State:
if not (min(start, target) <= low and high <= max(start, target)):
return
self.pso_window_entries += 1
if acq["armed"] and acq["trigger"] == 0 and acq["inputs"]:
if acq["armed"] and acq["trigger"] in (0, 3) and acq["inputs"]:
n_triggers = acq["n_elements"] // len(acq["inputs"])
step = (high - low) / max(n_triggers, 1)
for k in range(n_triggers):