feat: rate-aware laser-safety RATE_LL + trigger overrides for 60 Hz mode - #130
feat: rate-aware laser-safety RATE_LL + trigger overrides for 60 Hz mode#130boringethan wants to merge 8 commits into
Conversation
The bundled EE_RATE_LL/OPT_RATE_LL payloads encode a 22,500 us minimum inter-pulse period (0.9x the 40 Hz period) — a 60 Hz trigger would trip the interlock. apply_laser_power(trigger_freq_hz=...) now rescales the floor by 40/freq (60 Hz -> 15,000 us, same proportional margin); MotionInterface.apply_laser_power passes its resolved default trigger frequency automatically. 40 Hz / None leave the baseline untouched, and explicit per-key user-config overrides still win. Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
Matches sensor-fw#78: reserved byte carries the rate (40 sent as legacy 1 so older firmware keeps working; 60 selects the new 60 Hz generator). Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
… rate An unscaled 1800 us LaserPulseSkipDelayUsec at 60 Hz puts the post-dark inter-pulse interval (14867 us) below the scaled RATE_LL floor (15000 us) — an interlock trip on every dark frame. Scaling it by 40/rate (1200 us at 60 Hz) keeps the same ~3% margin as 40 Hz while still clearing the 648 us exposure window. Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
…are RATE_LL scaling, rate whitelist - CalibrationWorkflow skip/window frame math now uses the run's resolved trigger frequency instead of the fixed CAPTURE_HZ constant (windows landed at wrong wall-times at 60 Hz). - SUPPORTED_CAPTURE_RATES_HZ = (40, 60) in omotion.config as the shared whitelist; trigger_overrides_for_rate and enable_aggregator_fsin now reject anything else (0 previously encoded as reserved=0 = DISABLE and reported success; unvalidated rates would have half-configured a weakened safety window). - apply_laser_power fails loudly if a non-baseline rate is requested but the RATE_LL entries are missing from laser_params (silently-unscaled floor = interlock trip on every pulse = dark laser with no error), and rescales stored per-key user-config RATE_LL overrides the same way as the bundled baseline (they are calibrated for 40 Hz; written verbatim at 60 Hz they would exceed the pulse period). - Baseline 40 Hz literal now read from DEFAULT_TRIGGER_CONFIG. Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
|
Bench-validated 2026-07-04 with hardware register readback (full results on issue #129): RATE_LL 22,500→15,000 µs at 60 Hz and restored at 40 Hz; no interlock faults laser-on at either rate; 40 Hz clinical scan clean. Post-open review fixes: fail-loud missing-entry check, user-config override rescaling, SUPPORTED_CAPTURE_RATES_HZ whitelist, CalibrationWorkflow rate-aware frame math. |
…y tracking - enable_camera_fsin_ext(rate_hz=40): reserved byte carries the trigger rate so firmware retimes the camera VTS (sensor-fw#78/#80); 40 encodes as legacy 1 for old-firmware compat; whitelist-validated. - ScanWorkflow.start_scan resolves the request's trigger frequency and passes it to the FSIN-external enable. - trigger_overrides_for_rate: LaserPulseDelayUsec now tracks the exposed-row band shift from the per-rate VTS ((2768-1845) rows x 9.0318 us + 100 us = 8436 us at 60 Hz — bench-measured optimum, matches the datasheet timing model exactly). 40 Hz keeps the default. Bench-validated end-to-end 2026-07-05 (see sdk#129): 60 Hz laser-on clinical scan 8/8 cameras 30 s zero gaps with signal parity to 40 Hz, followed by a clean 40 Hz regression on the same session. Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
|
Second commit (e94088d): |
trigger_overrides_for_rate now always emits every rate-managed key (explicit baselines at 40 Hz) so a live switch back restores them; set_capture_rate merges the overrides into the default trigger config and re-applies the laser config so the RATE_LL floor matches the new rate BEFORE any trigger runs at it, rolling back the config on a failed floor write. Camera VTS retime rides on the next scan start. Bench (2026-07-05): one session, connect at 40 -> switch to 60 (floor readback 15,000 us, next scan 60 Hz clean) -> switch back to 40 (floor 22,500 us, next scan 40.00 Hz clean). Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
|
Third commit (93a0225): |
…rrides trigger_overrides_for_rate keeps the bench-validated 8436 us pulse delay at 60 Hz (full signal) and now documents why it is a known limitation: at that position the pulse transient sits inside the FPGA SPI push window and can glitch the marginal SPI links (cams 6/8) at scan start — 14/16 all-16, clinical mask unaffected. The clean fix (re-pin the exposure band to ~100 us via the sensor's r_init_man) is tracked on sensor-fw#68. Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
Per the 'Ultrasound & Laser Safety Limits Calculator' AEL sheet (λ=795 nm, 500 µs @ 40 Hz, T=300 s, 3 mm beam, duty 2.0%): the average-power AEL rows scale as 1/rate, so the 60 Hz gate width scales with the period (500 -> 333 µs), holding duty at the 40 Hz-validated 2.0%. Per-pulse energy then drops ∝ t while the per-pulse AEL falls only as t^0.75, so single-pulse and pulse-train (C5/N) margins strictly improve. - trigger_overrides_for_rate emits the scaled LaserPulseWidthUsec (baseline value at 40 Hz, like every rate-managed key). - apply_laser_power now also rescales EE/OPT_PULSE_WIDTH_UL (1000 -> 667 µs) so the safety FPGA ENFORCES the shortened gate at 60 Hz rather than merely permitting it; same fail-loud bookkeeping as RATE_LL. Resurrect-time validation note: the optical pulse is driver-shaped at ~494 µs regardless of gate width, so a 333 µs gate clips it — verify delivered energy with a power meter before clinical use at 60 Hz. Refs #129 Co-Authored-By: Claude Fable 5 <[email protected]>
|
Added while tabled (9ef8704, per Ethan): IEC 60825 pulse-width scaling — |
Refs #129
What
apply_laser_power(trigger_freq_hz=...): rescales the bundledEE_RATE_LL/OPT_RATE_LLsafety-FPGA floor by40/freq(baseline 70313 ticks = 22,500 µs min inter-pulse period at 40 Hz → 46875 ticks = 15,000 µs at 60 Hz — the same 0.9× proportional margin).MotionInterface.apply_laser_powerpasses its resolved default trigger frequency automatically.None/40 leave the baseline untouched; explicit per-key user-config overrides still win.trigger_overrides_for_rate(rate_hz): returns{TriggerFrequencyHz, LaserPulseSkipDelayUsec}with the dark-frame pulse displacement scaled by the period. Without this, every dark frame at 60 Hz would trip the interlock: the post-dark interval would be 16,667−1,800 = 14,867 µs, below the 15,000 µs scaled floor. Scaled (1,200 µs) the margin stays ~3 % and the displaced pulse still clears the 648 µs exposure window.enable_aggregator_fsin(rate_hz=40): reserved byte carries the rate, matching sensor-fw#78 (40 sent as legacy1so older firmware keeps working).This intentionally changes the programmed laser-safety rate window when (and only when) a 60 Hz trigger is selected. At 60 Hz with unchanged pulse width (500 µs), average optical power rises 1.5×. Pulse-width limits (UL 1,000 µs) are untouched. Requesting laser/safety owner sign-off before this ships in any release.
Companion PRs
captureRateHzconfig flag) — app PR depends on this one (importstrigger_overrides_for_rate).Status
🤖 Generated with Claude Code