Driver support for multi-row FastFrame bursts; fix read_raw block parsing
Groundwork for burst acquisition: the scope needs to report and transfer a whole multi-row FastFrame acquisition, and the BBD needs to gate its trigger output per row rather than staying armed for the scan. tektronix_base - get_fastframe_max_frames() exposes HORizontal:FASTframe:MAXFRames?, which is what sizes a burst once the horizontal settings are fixed. - transfer_fastframe_bulk() pulls a burst as one contiguous buffer. Unlike transfer_fastframe it does not assume how the scope frames the response: it accumulates until the expected byte count is reached, so one large IEEE block and one block per frame both work. - set_data_encoding() / set_data_width() make the transfer format settable instead of inherited from whatever the front panel was left on. - read_raw() had two real defects. The length-digit read used a bare recv() and only checked the length afterwards, so a short read raised "Failed to read data length" on a perfectly good transfer; it now goes through a _recv_exact() helper, as does the trailing separator. And a #0 indeterminate-length block was parsed as int("") -> ValueError. #0 is normally delimited by EOI, which a raw socket never sees, so read_raw now takes expected_bytes to size it. The bulk transfer relies on this. pybbd202 - arm_scan_gate(axis, armed) raises and drops the max-velocity trigger output the scope's AND-gate uses. A burst spans several rows with the scope running throughout, so the gate must be low for the flyback or the return move reaches max velocity and injects frames between rows. - set_trigger_verified() reads the mode back after setting it. set_trigger is fire-and-forget over the shared TX queue; burst mode toggles the gate between every row, where a dropped change silently corrupts the file rather than failing loudly. - set_trigger_gate_off() so the scan can leave the output idle on exit. - TRIGOUT_GATE_OFF is deliberately marked unverified. §7.6 of the BBD203 protocol doc describes `mode` as an enumeration capping at 0x11, which contradicts the bitmask this driver actually sends (TRIGOUT_MAXV = 0x90, known working), so the doc cannot settle which value idles the pin low. The engine's preflight check resolves it on the rig instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
import time
|
||||
from threading import Thread, Event
|
||||
from queue import Queue, Empty
|
||||
from .apt_constants import StatusBits, TriggerBitsServo
|
||||
from .apt_constants import StatusBits, TriggerBitsServo, TRIGOUT_GATE_OFF
|
||||
from .apt_messages import APTProtocol
|
||||
from .serial_comms import SerialSnooper
|
||||
|
||||
@@ -465,3 +465,42 @@ class ThorlabsServoDriver():
|
||||
def set_trigger_trigout_maxv(self, axis):
|
||||
'''Set trigger output high + pulse at max velocity (TRIGOUT_MAXV).'''
|
||||
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_MAXV)
|
||||
|
||||
def set_trigger_gate_off(self, axis):
|
||||
'''Drive the trigger output inactive, so no pulses reach the gate.'''
|
||||
self.set_trigger(axis, TRIGOUT_GATE_OFF)
|
||||
|
||||
def arm_scan_gate(self, axis, armed, verify=True):
|
||||
'''
|
||||
arm_scan_gate(axis, armed): Arms or drops the max-velocity trigger
|
||||
output the oscilloscope AND-gate uses.
|
||||
|
||||
Burst acquisition runs one scope acquisition across many rows, so
|
||||
the gate must be armed only for the acquiring pass and dropped for
|
||||
the flyback — otherwise the return move hits max velocity and
|
||||
injects frames between rows.
|
||||
'''
|
||||
mode = TriggerBitsServo.TRIGOUT_MAXV if armed else TRIGOUT_GATE_OFF
|
||||
if verify:
|
||||
self.set_trigger_verified(axis, mode)
|
||||
else:
|
||||
self.set_trigger(axis, mode)
|
||||
|
||||
def set_trigger_verified(self, axis, mode, timeout=5.0, retries=2):
|
||||
'''
|
||||
set_trigger_verified(axis, mode): Sets the trigger mode and reads
|
||||
it back to confirm it landed.
|
||||
|
||||
set_trigger is fire-and-forget over the shared TX queue. Burst
|
||||
acquisition toggles the gate between every row, and a dropped
|
||||
change there silently fills the acquisition with flyback frames —
|
||||
so confirm rather than assume.
|
||||
'''
|
||||
for _ in range(retries + 1):
|
||||
self.set_trigger(axis, mode)
|
||||
if int(self.get_trigger(axis, timeout=timeout)) == int(mode):
|
||||
return
|
||||
raise RuntimeError(
|
||||
f"Axis 0x{axis:02X} did not accept trigger mode 0x{int(mode):02X} "
|
||||
f"after {retries + 1} attempts"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user