Skip to content

TCube safety fixes from the 642 rig's facts: fresh reads request twice, two-sided check_lock, calibration reference (0.2.6) - #74

Merged
kalidke merged 5 commits into
mainfrom
tcube-rig-facts
Oct 2, 2026
Merged

kalidke merged 5 commits into
mainfrom
tcube-rig-facts

Conversation

@kalidke

@kalidke kalidke commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

TCube laser safety fixes for 0.2.6, from the 642 nm rig's measurements of 2026-09-29. Laser files only; independent of #73.

Based on #72 (main + the 0.2.6-DEV bump, 7acee6c), because a PR into a released main must carry a raised version. Until #72 merges, this PR also shows its one commit.

What the rig found, and what changes

The rig found that the controller answers one request behind: the first status request after an enable returned the pre-enable bits. It also found that the readings are signed, and that the photodiode words did not follow a move of the range DIP switch.

  • Fresh reads request twice. read_limit_mA, read_status_fresh, read_digpot, the W/A read and initialize's limit read now go request, wait, request, wait, get, through one request_twice helper. Before this, light_on's stored-limit gate could pass a limit read before the potentiometer was raised.

  • check_lock makes its own reads and is two-sided. It requests the photocurrent twice, then the status word. It zeroes, disables and throws on any of three conditions:

    • a photocurrent above lock_ratio × the request (as before);
    • the controller reporting its current limit reached (status 0x400);
    • a photocurrent below the request divided by lock_ratio.

    The last two catch a loop driven to the clamp, whatever the cause, including a photodiode giving fewer counts per mW than at calibration. The high side is tested before the status read. At code == 0 it waits lock_check_s and runs only the 0x400 test.

  • lock_check_s has a floor. PhotodiodeLoop refuses lock_check_s below LOCK_CHECK_MIN_S = 0.1 s; a test pins that to 2 × POLL_INTERVAL_MS. 0 used to disable the check.

  • Optional calibration reference. ref_current_mA and ref_photocurrent_A (both or neither) and ref_ratio (default 1.5) are added to PhotodiodeLoop, TCubeLaser and SimDiodeLaser.

    • initialize never emits.
    • The first power-mode light_on after each initialize runs a check before it closes the loop:
      1. drive the diode in open loop at the reference current, which must be at most max_current and within max_power by the calibration's own scale;
      2. read the photodiode fresh and decode it with tia_range;
      3. refuse unless the reading is within ref_ratio of the reference, either way.
    • Before it enables, the check confirms by a fresh status read that the controller really is in open loop.
    • It holds the diode for a fixed REFERENCE_DWELL_S (0.1 s), not lock_check_s.
    • The output is off after the check either way.
    • Any failure during the check (a mismatch, a mode that does not take, a command error) latches a refusal until the next initialize, so the diode is not lit again on every retry.
    • Every initialize, failed ones too, first clears the clamp and the check's state.
    • The reference is stored in amps, so a tia_range relabelled while the words stay put (the rig's DIP-switch observation) fails the check.
    • Without a reference, that light_on warns once and carries on, with the two-sided check_lock as the guard. The CHANGELOG recommends a reference for every power-mode rig; CALIBRATION.md step 3b says how to record one.
  • Power-mode light_on and setoutputpower! also refuse on a TIA range mismatch. The fresh status word's range must match tia_range. setoutputpower! now decides on the fresh status word and, with the output on, a fresh photocurrent read, instead of the polled cache.

  • A safety check that refuses while the output may be on zeroes and disables it first. This covers the stored-limit check, and in power mode also a mode, TIA-range or clamp change, or an over-range photodiode. Before, the diode was left lit in the fault. "May be on" means the driver recorded it on, a fresh status read says so, or that read failed.

  • Open-loop initialize confirms open loop with a fresh status read after LD_SetOpenLoopMode, and refuses if the controller stays in closed loop.

  • Small fixes:

    • measured_current accepts the header-legal raw -32768 (-220 mA) instead of throwing.
    • The DIGPOT_MIN_mA floor gate (17.25 mA, from the header's wrong scale) goes, in open-loop initialize and in power-mode construction. The controller's limit readback decides.
    • The pot search's step estimate stays the header's 220/255 mA. That is at least the measured 0.83 mA/step, so moves approach max_current from below. The readback stays the authority.

Compatibility (decisions 0033 and 0035)

Nothing is breaking.

  • The new keywords all have defaults.
  • A construction with lock_check_s < 0.1 now throws, and check_lock refuses in cases it used to pass. Both are fixes on already-broken paths: 0 disabled the check, and a loop at its clamp or below its request is not emitting the commanded power.
  • No known downstream passes lock_check_s (MicroscopeAdapt, SeqSR and MINFLUX checked by the captain).
  • The version stays 0.2.6-DEV.

Tests

  • The fake Kinesis SDK now answers one request behind for status, readings, limit, dig-pot and W/A. Polling refreshes nothing unless a test says so. The setpoint read-back stays live, because the rig showed polling refreshes it.
  • It returns signed readings: dark is raw 65532 = -4.
  • It models a photodiode (threshold 67.6 mA) with a scale knob.
  • New cases include:
    • a potentiometer raised between two light_ons (the second refuses; a single request would pass);
    • a photodiode scale drop between two light_ons (trips 0x400 or the low side);
    • the fact-6 relabel (the reference check refuses);
    • the construction rules.
  • test/tcube_output_order.jl (0.2.4's own cases) passes unchanged.
  • New tests for the review fixes:
    • open-loop mode not taken (the diode is never enabled);
    • the mismatch latch;
    • reset on a failed initialize;
    • the range mismatch;
    • fresh reads in setoutputpower!;
    • 0x400 at a zero request;
    • the high side tripping before any status read;
    • a range change the words follow (passes);
    • a range-switch relabel while lit (refuses, output off);
    • an open-loop limit raised while lit (refuses, off);
    • a refusal with the output off (sends no disable);
    • a controller that stays in closed loop at open-loop initialize (refuses, never enables).
  • Local suite at 4e73adf (kitt, Julia 1.13, xvfb-run -a): 1916 pass, 3 broken, 0 fail.
  • lab/tests on 4e73adf: Core pass, 1919 tests; Core on Julia 1.11.9 pass (decision 0012).

Rig checks owed (none run; no hardware)

  • R1, polling on: move the pot one step, then compare one limit read with a second.
  • R2: at 90 mA open loop, the polled photocurrent is about 99 uA and follows a step to 100 mA.
  • R3: with the limit readback at about 90 mA and the setpoint at 100 mA, the current is about 90 mA and 0x400 is set, which shows the readback is the clamp.
  • R4: power-cycle with the DIP at 10 mA and re-read the photocurrent at 70, 80 and 110 mA.
  • Record the 642 rig's first calibration reference at its next W/A measurement.

v0.2.4 is not changed.

🤖 Generated with Claude Code

kalidke and others added 5 commits September 29, 2026 14:53
Decision 0033: after X.Y.Z, main carries X.Y.(Z+1)-DEV. TagOnMerge skips a
-DEV version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alibration-reference re-check at the first light_on

- (i) request_twice: every request-then-get site (limit, digpot, status, initialize's limit read, W/A read, tcube_get_current, new read_photocurrent_word) sends its request twice, since the TLD001 answers one request behind.
- (ii) check_lock reads its own photocurrent and status, and refuses on a high ratio, on 0x400, or on a photocurrent below 1/lock_ratio of the request; returns at code 0. PhotodiodeLoop refuses lock_check_s below LOCK_CHECK_MIN_S = 0.1.
- (iii) PhotodiodeLoop, TCubeLaser and SimDiodeLaser take ref_current_mA, ref_photocurrent_A, ref_ratio; the first power-mode light_on after each initialize re-measures the reference in open loop (check_scale!) and refuses on a mismatch, or warns once when there is none.
- Small fixes: measured_current accepts -32768; the header's 17.25 mA potentiometer floor gate is removed; the step estimate is the manual's 0.7 mA.
- The fake answers one request behind, models the photodiode and the 0x400 bit; tests N1-N21 added.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…enable, latch a reference mismatch, fresh reads in setoutputpower!

set_open_loop! confirms open loop before the re-check enables. initialize resets
the loop state first (scale_refused added); a mismatch latches until initialize
and the re-check dwells REFERENCE_DWELL_S. require_clamp checks the TIA range and
returns the fresh status word; check_lock tests the high side before the status
read and runs 0x400 at code 0. The pot step estimate returns to 220/255.
Deletes output_enabled, initialize's LD_RequestReadings and measured_current's
range check. CHANGELOG timings recomputed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…en-loop initialize confirms open loop, latch any re-check failure

M1 wraps require_clamp so a refusal found while the output may be on zeroes and
disables it; M2 makes open-loop initialize confirm open loop with a fresh status
read; M3 deletes setoutputpower!'s dead closed-loop check; M4 corrects the
step-estimate docstrings; M5 latches any failure inside the reference re-check.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@kalidke

kalidke commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

This ships in #76 (MicroscopeControl 0.2.6), the single 0.2.6 release PR, as a merge commit of this PR's accepted head. Please leave this PR open: GitHub marks it merged when #76 lands.

@kalidke
kalidke merged commit fb9c79e into main Oct 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant