diff --git a/core/config.py b/core/config.py
index 43c8385..6327e9e 100644
--- a/core/config.py
+++ b/core/config.py
@@ -23,6 +23,7 @@ class ScanDefaults:
oscope_ip: str = "192.168.0.1"
save_dir: str = str(DEFAULTS_PATH.parent / "scans")
helios_port: str = "/dev/ttyUSB2"
+ burst_mode: bool = False
@classmethod
def load(cls, path: Path = DEFAULTS_PATH) -> "ScanDefaults":
diff --git a/gui/scan_bridge.py b/gui/scan_bridge.py
index 2a3aff1..136be41 100644
--- a/gui/scan_bridge.py
+++ b/gui/scan_bridge.py
@@ -32,7 +32,7 @@ class QtScanController(QObject):
paused_changed = pyqtSignal(bool) # True while paused at a row boundary
def __init__(self, stage, scope, rotator, plan, out_path,
- resume=None, on_scan_active=None):
+ resume=None, on_scan_active=None, burst_mode=False):
super().__init__()
self._prompt_event = threading.Event()
self._on_scan_active = on_scan_active
@@ -47,7 +47,8 @@ class QtScanController(QObject):
prompt=self._blocking_prompt,
)
self._engine = ScanEngine(stage, scope, rotator, plan, out_path,
- resume=resume, callbacks=callbacks)
+ resume=resume, callbacks=callbacks,
+ burst_mode=burst_mode)
# ── Engine control (called from the GUI thread) ───────────────────────────
diff --git a/sc3-aui-main.ui b/sc3-aui-main.ui
index d9479dd..72b5095 100755
--- a/sc3-aui-main.ui
+++ b/sc3-aui-main.ui
@@ -997,6 +997,16 @@
+ -
+
+
+ Acquire as many whole rows per FastFrame acquisition as the scope can hold, and transfer each burst in one CURVe? transaction. The stage trigger output is gated off for the flyback between rows.
+
+
+ Burst acquisition (multi-row FastFrame)
+
+
+
-
diff --git a/sc3_aui_app.py b/sc3_aui_app.py
index 4aed6ef..7a53df5 100755
--- a/sc3_aui_app.py
+++ b/sc3_aui_app.py
@@ -917,6 +917,7 @@ class MainWindow(QMainWindow):
self.row_spacing_edit.setText("0.250")
self.scan_prefix_edit.setText("scan")
self.scan_save_dir_edit.setText(DEFAULTS.save_dir)
+ self.burst_mode_check.setChecked(DEFAULTS.burst_mode)
# Hide the old T3R manual controls; the connect toggle becomes the panel button.
for w in (
@@ -977,6 +978,7 @@ class MainWindow(QMainWindow):
self.t3r_comport_edit.editingFinished.connect(self._persist_defaults)
self.bbd202_comport_edit.editingFinished.connect(self._persist_defaults)
self.oscope_ip_edit.editingFinished.connect(self._persist_defaults)
+ self.burst_mode_check.toggled.connect(self._persist_defaults)
# Camera
self.show_camera_toggle.toggled.connect(self._on_camera_toggle)
@@ -1134,6 +1136,7 @@ class MainWindow(QMainWindow):
DEFAULTS.bbd_port = self.bbd202_comport_edit.text().strip()
DEFAULTS.oscope_ip = self.oscope_ip_edit.text().strip()
DEFAULTS.save_dir = self.scan_save_dir_edit.text().strip()
+ DEFAULTS.burst_mode = self.burst_mode_check.isChecked()
DEFAULTS.save()
# ── Camera toggle ─────────────────────────────────────────────────────────
@@ -1257,6 +1260,7 @@ class MainWindow(QMainWindow):
# a worker concern, not the engine's.
on_scan_active=lambda active: setattr(
self._bbd_worker, "scanning_active", active),
+ burst_mode=self.burst_mode_check.isChecked(),
)
self._scan_worker.moveToThread(self._scan_thread)
self._scan_thread.started.connect(self._scan_worker.run)
diff --git a/scan_format.md b/scan_format.md
index 23de40f..b7bdb0e 100755
--- a/scan_format.md
+++ b/scan_format.md
@@ -183,18 +183,48 @@ using that angle's `x_start` from the Per-Angle Geometry Table (not
---
-## Acquisition Settings (fixed by sc3_aui_app.py)
+## Acquisition Settings (fixed by core/scope_sras.py)
-| Parameter | Value |
-|-----------------------|------------------------------|
-| Oscilloscope trigger | CH2, rising edge, 1.24 V |
-| Trigger offset | 0 % (trigger at left edge) |
-| Sample rate | 6.25 GS/s (160 ps/sample) |
-| Channels recorded | CH1, CH3, CH4 |
-| Stage X velocity | 100 mm/s |
-| Stage X acceleration | 1500 mm/s² |
-| Stage X trigger out | Logic-high at max velocity |
-| Acquisition mode | FastFrame, Normal trigger |
+| Parameter | Value |
+|-----------------------|------------------------------------------|
+| Setup trigger | CH2, rising edge, 0.500 V (`TRIG_LEVEL_V`) |
+| Scan trigger | Logic AND, CH2 HIGH ∧ CH3 HIGH, 0.500 V |
+| Horizontal position | 30 (`HORizontal:POSition`) |
+| Sample rate | 6.25 GS/s (160 ps/sample) |
+| Transfer format | `DATa:ENCdg RIBinary`, `DATa:WIDth 1` |
+| Channels recorded | CH1, CH3, CH4 |
+| Stage X velocity | 100 mm/s |
+| Stage X acceleration | 1500 mm/s² |
+| Stage X trigger out | Logic-high at max velocity (`TRIGOUT_MAXV`) |
+| Acquisition mode | FastFrame, Normal trigger |
+
+None of these are stored in the file, so they do not affect byte layout — but
+they do set where the acoustic packet lands inside each frame. Read them from
+`core/scope_sras.py`; earlier revisions of this table drifted from the code.
+
+---
+
+## Acquisition Paths
+
+Two acquisition strategies write **byte-identical** files; the choice is a
+runtime flag (`ScanEngine(burst_mode=…)`, exposed as a checkbox in the app) and
+is not recorded in the file.
+
+| | Per-row (default) | Burst |
+|---|---|---|
+| FastFrame acquisitions | one per row | one per `floor(max_frames / n_frames)` rows |
+| Curve transfers | one per channel per row | one per channel per burst |
+| Stage X trigger out | armed for the whole scan | armed per acquiring pass, dropped for the flyback |
+
+Burst mode runs a single acquisition across several rows, so the return move
+must not trigger: the trigger output is dropped before each flyback and
+re-armed for each acquiring pass. Row boundaries inside the burst come from
+`ACQuire:NUMFRAMESACQuired?` sampled after each pass — the burst itself carries
+no row markers. See `core/scope_burst.py`.
+
+Either path squares each row up to the declared `n_frames` (zero-padding a
+short row, dropping the tail of a long one), because the format has no per-row
+length field and a mismatch would shift every later row.
---