Files
csaxs_bec/docs/developer/panda_box_free_running_setup.md
x12saandClaude Sonnet 5 acdca82137
CI for csaxs_bec / test (pull_request) Successful in 2m17s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 2m15s
docs(flomni): save the confirmed Mode A/Mode B PandA layouts
Captured 2026-09-15 directly from the omny-panda hardware via
GetState() (ophyd_devices/devices/panda_box/utility_scripts.py), one
per Design: omny.ini (Mode A) and omny_freerun.ini (Mode B). Mode B was
speed-tested successfully up to 20 kHz same day, though the saved
omny_freerun.ini itself reflects CLOCK1.PERIOD=0.0001 (10 kHz) -- flagged
in the setup doc as a discrepancy to double-check rather than assumed
away.

Setup doc updated with the exact save/load commands and a diff summary
of the two layouts' mode-specific differences (PCAP.GATE/TRIG source,
CLOCK1.ENABLE, capture types).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsfqLcPWspjJ6vedMyCxco
2026-09-15 15:30:10 +02:00

13 KiB
Raw Permalink Blame History

PandABox (omny_panda) burst acquisition: two operation modes

Status: Mode B confirmed working end-to-end against real PandA hardware (2026-09-15, omny-panda.psi.ch) — correct per-channel raw values, masked gate_detector_active, and frame_counter-based completion all verified, then speed-tested up to 20 kHz with no reported issues (though see the caveat under "Saving / restoring layouts" — the saved omny_freerun.ini layout itself was captured at 10 kHz, not 20 kHz). ptycho_flomni.yaml currently has omny_panda_continuous (Mode B) active — note the PandA's Design was switched back to OMNY on 2026-09-15 solely to capture the omny.ini layout file below, so double-check which Design is actually loaded on the physical PandA before running a scan against either config.

Context

flomni burst scans acquire frames_per_trigger sub-frames per scan point. Historically, the PandABox (omny_panda, device class PandaBoxOMNY in csaxs_bec/devices/panda_box/panda_box_omny.py) reports one PCAP-aggregated statistic (Mean/Min/Max/Sum/Diff) per gate, discarding the time structure within a gate. This document describes the two supported operation modes — the existing per-gate aggregate mode, and a new continuous free-running raw mode — and how the PandA hardware and ptycho_flomni.yaml device config must be set up for each.

Mode selection is a single software flag (raw_stream_mode in the device config); it does not by itself reconfigure the PandA hardware. The PandA layout must independently match the intended mode (see "Open question" at the end) before that mode can be used.

Mode comparison

Mode A: legacy (default) Mode B: free-running (new)
Selected by raw_stream_mode unset / false raw_stream_mode: true
PCAP capture fields Mean/Min/Max/Sum/Diff per gate Value (raw instantaneous sample) + gate bit + frame counter
Gate/arm timing Pulsed per point / sub-frame (rate = scan rate × frames_per_trigger) PCAP held open continuously for the burst window, free-running at CLOCK1's configured rate (started at 100 Hz for initial testing on 2026-09-15; will be ramped up as testing progresses)
_acquisition_group (BEC async metadata) fly / monitored / burst, chosen from scan_type/frames_per_trigger free_running
Message publishing One .data.put() per PandA network frame, unchanged Coalesced client-side: flushed every raw_stream_flush_row_count rows or raw_stream_flush_interval seconds, whichever comes first (mitigates Redis/HDF5 message-rate risk — see the acquisition plan's data-path notes)
Completion check (on_complete) Exact count: waits for *PCAP.CAPTURED? to reach num_points * frames_per_trigger Exact count: waits for COUNTER1.OUT? (frame_counter) to reach num_points * frames_per_trigger. *PCAP.CAPTURED? can't be used here — CLOCK1 drives PCAP.gate/PCAP.trig continuously (see "Continuous gate/arm" below), so the captured count never naturally stops increasing while armed; COUNTER1 instead counts completed detector exposures directly (it's clocked off the detector-trigger's falling edge), so it reaches the expected total exactly when the last exposure finishes
Data completeness verification Implicit in the exact-count completion check Same mechanism as Mode A: frame_counter (COUNTER1) is both the completion signal and the post-hoc completeness record — a gap in an otherwise-monotonic sequence in the HDF5 data means dropped rows even though the expected total was reached

Both modes are implemented in the same PandaBoxOMNY class; raw_stream_mode defaults to false so any device config that doesn't set it keeps today's exact behavior unchanged.

Physical wiring (Mode B only)

The detector-trigger signal is patched into TTL input 1 (TTLIN1) on the PandA front panel. That same TTLIN1.VAL signal feeds both downstream consumers (see "Block configuration" below): it is the source counted by COUNTER1 (the frame counter) and the source captured into the gate bit.

Block configuration

Mode A (legacy) — reference baseline

The blocks already configured and aliased today, for reference / so this layout can be restored unambiguously (see ptycho_flomni.yaml):

  • FMC_IN.VAL1/FMC_IN.VAL2 (capacitive sensor analog inputs) — Min, Max, Mean capture fields.
  • INENC1-INENC4.VAL (position-capture encoder inputs) — Min, Max, Mean capture fields.
  • PCAP.GATE_DURATIONValue.
  • PCAP gate is pulsed once per point / sub-frame by the existing external trigger electronics (unchanged by this work).

Mode B (new) — free-running raw capture

  • Gate-bit input: the detector-trigger signal, wired into TTLIN1 (see "Physical wiring" above). TTLIN1.VAL is captured into PCAP.BITS0, bit offset 0 — confirmed 2026-09-15 against the omny-panda hardware via:

    TTLIN1.VAL.CAPTURE_WORD?   -> OK =PCAP.BITS0
    TTLIN1.VAL.OFFSET?         -> OK =0
    

    PCAP.BITS0 is a shared 32-bit capture word — other captured bit_out signals (several TTLIN/LVDSIN/encoder bits are also flagged for capture on this layout) may occupy other bit positions of the same word, so the raw PCAP.BITS0.Value is not itself a clean 0/1. PandaBoxOMNY masks it down to bit 0 in raw_stream_mode (see _GATE_BIT_OFFSET in panda_box_omny.py) before publishing it as gate_detector_active — treat that masking as required, not optional, unless it's confirmed nothing else shares BITS0. Re-run the query above (read-only, safe against live hardware) if the PandA layout is ever rebuilt, since bit assignment is fixed by the FPGA build and not guaranteed stable across rebuilds.

  • Frame counter: COUNTER1, triggered off TTLIN1.VAL's falling edge (see layout below) — i.e. it increments once per completed detector exposure, not once per PCAP capture tick as originally guessed here. Captured with CAPTURE=Value; this is both the data-completeness signal and the completion-detection signal (frame_counter in signal_alias, see below, and "Completion check" in the mode-comparison table above) — once the last expected exposure's trigger pulse has fallen, COUNTER1.OUT reaches num_points * frames_per_trigger exactly.

  • Raw analog/position capture: FMC_IN.VAL1, FMC_IN.VAL2 and all four INENC1-4.VAL are switched from aggregate capture to CAPTURE=Value on this layout (confirmed 2026-09-15) — i.e. every analog/encoder channel goes raw together, not a subset. This means the current PandA layout cannot simultaneously produce Mode A's aggregate (Min/Max/Mean) data — see "Open question" below.

  • Continuous gate/arm: CLOCK1, enabled by the constant ONE, drives PCAP's gate and trig inputs continuously (instead of the external per-point trigger used in Mode A), so PCAP free-runs at CLOCK1's configured rate. Testing started at 100 Hz on 2026-09-15 and will be ramped up; there is no PandA-side layout change needed to change the rate, only CLOCK1's period.

    TTLIN1 (TTL input 1, detector trigger)
      ├──▶ COUNTER1.trig, falling edge  (Up/Down pulse counter) ──▶ COUNTER1.OUT  = frame_counter (CAPTURE=Value)
      │      = completion signal too: reaches num_points * frames_per_trigger when the last exposure finishes
      └──▶ captured into PCAP.BITS0 bit 0          ──▶ masked in software = gate_detector_active
    
    CLOCK1 (enabled by constant ONE) ──▶ PCAP.gate, PCAP.trig   (continuous arm/capture)
    

Saving / restoring layouts

Use ophyd_devices/devices/panda_box/utility_scripts.py to save the current PandA configuration to a .ini layout file and reload it later, so a configuration survives a PandA power cycle. Both layouts are saved in this repo under csaxs_bec/devices/panda_box/layouts/omny.ini (Mode A) and omny_freerun.ini (Mode B), captured 2026-09-15 directly from the PandA design named OMNY/omny_freerun respectively (switch the PandA's active Design first, then save):

python ophyd_devices/devices/panda_box/utility_scripts.py \
    --host omny-panda.psi.ch --save-layout csaxs_bec/devices/panda_box/layouts/omny.ini

python ophyd_devices/devices/panda_box/utility_scripts.py \
    --host omny-panda.psi.ch --save-layout csaxs_bec/devices/panda_box/layouts/omny_freerun.ini

To restore either, swap --save-layout for --load-layout with the same file. Diffing the two confirms the mode-specific differences documented above: PCAP.GATE/PCAP.TRIG are TTLIN1.VAL in Mode A vs CLOCK1.OUT in Mode B; CLOCK1.ENABLE is ZERO (off) in Mode A vs ONE in Mode B; INENC1-4.VAL.CAPTURE/FMC_IN.VAL1/VAL2.CAPTURE are Min Max Mean in Mode A vs Value in Mode B; PCAP.BITS0.CAPTURE/COUNTER1.OUT.CAPTURE are No in Mode A vs Value in Mode B.

omny_freerun.ini was captured with CLOCK1.PERIOD=0.0001 (100 µs → 10 kHz) — note this doesn't match the 20 kHz reported as tested successfully the same day, so it may not be the exact layout state the 20 kHz test ran at; worth double-checking before relying on this file as "the" validated high-rate layout.

Signal alias mapping

The physical PandA block feeding each BEC signal name, kept here and in csaxs_bec/device_configs/ptycho_flomni.yaml (omny_panda.deviceConfig.signal_alias for Mode A, omny_panda_continuous.deviceConfig.signal_alias for Mode B) in sync as a single source of truth:

PandA block Mode BEC signal name
FMC_IN.VAL1.Min/.Max/.Mean A cap_voltage_fzp_y_min/_max/_mean
FMC_IN.VAL2.Min/.Max/.Mean A cap_voltage_fzp_x_min/_max/_mean
INENC1.VAL.Min/.Max/.Mean A interf_st_fzp_y_min/_max/_mean
INENC2.VAL.Min/.Max/.Mean A interf_st_fzp_x_min/_max/_mean
INENC3.VAL.Min/.Max/.Mean A interf_st_rotz_min/_max/_mean
INENC4.VAL.Min/.Max/.Mean A interf_st_rotx_min/_max/_mean
PCAP.GATE_DURATION.Value A, B pcap_gate_duration_value
FMC_IN.VAL1/VAL2.Value, INENC1-4.VAL.Value B cap_voltage_fzp_y/_x, interf_st_fzp_y/_x/_rotz/_rotx
PCAP.BITS0.Value (bit 0 = TTLIN1.VAL, confirmed 2026-09-15; masked in software, see above) B gate_detector_active
COUNTER1.OUT.Value (confirmed 2026-09-15, triggered off TTLIN1.VAL's falling edge) B frame_counter

The BEC-side data schema produced by Mode B (what ends up in the HDF5 async dataset) is documented separately in docs/developer/panda_box_free_running_data_schema.md.

Switching between modes

omny_panda (Mode A) and omny_panda_continuous (Mode B) need different PandA hardware layouts and can't run simultaneously (see "Open question" below), so exactly one should be active in ptycho_flomni.yaml at a time, matching whichever layout is currently loaded on the PandA — comment out the other one's block entirely, don't rely on enabled: false alone.

That's a correction from earlier in this project: enabled: false on a device BEC has never connected to is an inert placeholder — connect_device in bec_server/device_server/devices/devicemanager.py is only called when enabled — so the theory was that both entries could stay uncommented and switching would just be flipping the flag. In practice (2026-09-15), disabling omny_panda_continuous this way was not sufficient once it had already been connected in the running session: both devices ended up active simultaneously against the same physical PandA. A device that's already connected doesn't appear to get disconnected just because a later config reload marks it disabled. Commenting the block out (so BEC never even constructs the device object) is the reliable way to guarantee it's gone; if in doubt, confirm the other device has actually dropped its connection (e.g. via a device server restart) rather than trusting enabled: false alone.

Confirmed end-to-end 2026-09-15: Mode B tested working (correct per-channel values, masked gate_detector_active, frame_counter-based completion), then switched back to Mode A (commented out) for A/B comparison testing.

Open question, now partially resolved

Can both modes coexist under a single static PandA layout — i.e. is mode switching purely the raw_stream_mode software flag, with the PandA always capturing both the aggregate and raw fields simultaneously — or do Mode A and Mode B require two distinct saved layouts that must be loaded before a scan depending on the desired mode?

As currently wired for testing (2026-09-15), the answer looks like two distinct layouts are required: FMC_IN.VAL1/VAL2 and INENC1-4.VAL are each set to CAPTURE=Value (single selection), not simultaneously CAPTURE=Value,Min,Max,Mean — so this layout cannot also produce Mode A's aggregate statistics at the same time. This has not yet been deliberately tested against the alternative (whether the PandA capture UI actually supports selecting Value together with Min/Max/Mean on the same field) — if that turns out to work, a single layout could serve both modes. Once Mode B testing is far enough along to save a named layout (see "Saving / restoring layouts" below), settle this explicitly and record the answer plus, if two layouts are needed, the load procedure to switch between them.