mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
bench: crash-probe drill run, coexist proven, detector is forgectrl-only
The de-risk drill ran on the bench: the IG registers program and poll over i2c-dev (I2C_SLAVE_FORCE) with st_accel bound, raw reads intact, so the crash detector is forgectrl-only (BRINGUP item 6, CAMPAIGN-LOG 2026-08-31). The drill found the IG needs a running ODR: st_accel leaves the part in power-down between one-shot reads. The script now saves CTRL1, runs the window at 800 Hz, and restores it on exit; its old ODR test checked the axis-enable bits instead of the ODR bits. No catalog consequence: the change is a bench drill script, its bench page text, and the docs record of the drill; no shipped behavior changes.
This commit is contained in:
+23
-15
@@ -1043,7 +1043,16 @@ is committed.
|
||||
and neither the factory DTS (head I²C `status = "disabled"`, the accel
|
||||
driven from userspace) nor ForgeFIRM's DTS gives the accel an `interrupts`
|
||||
property. ForgeFIRM neither arms the generator nor reads b1; it only polls
|
||||
raw samples.
|
||||
raw samples. `st_accel` leaves the part in **power-down between one-shot
|
||||
reads** (CTRL1 ODR bits 0) and the interrupt generator only samples at a
|
||||
running ODR, so an armed detector must set the ODR itself and re-assert it
|
||||
after any liveness read (each one-shot powers the part down again). The IG
|
||||
registers coexist with the bound driver over i2c-dev (I2C_SLAVE_FORCE):
|
||||
IG_SRC1 polls at ~166 Hz from Python with `st_accel` raw reads intact. At
|
||||
the factory +/-2 g full scale the threshold LSB is ~15.6 mg, so 1 g ~= 64;
|
||||
gravity rides Z on the head (raw ~-16916), so Z trips any sub-1 g
|
||||
threshold at rest while X and Y stay silent at rest and through a jog at
|
||||
threshold 40 (~0.62 g).
|
||||
- **Beam detect in the head MCU.** PTE16 into ADC0 gives reg 0x16, the raw
|
||||
analog level (`beam_detect_analog`, a head sysfs attr): near 1834 dark, 2600
|
||||
to 2890 during S300/S400 fire (the acceptance suite's mark witness),
|
||||
@@ -1225,20 +1234,19 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
|
||||
The rail-contact signature in the facts bank sets the first threshold in
|
||||
register units; a pause on contact is the first use. ForgeFIRM already
|
||||
reads the head accel raw for liveness, so this shares the bus, not new
|
||||
hardware. The open decision is where the IG programming lives, and it
|
||||
turns on a bench fact: the IG registers (0x30 to 0x35) are ones
|
||||
`st_accel` never touches, so if they can be reached over i2c-dev
|
||||
(I2C_SLAVE_FORCE) while the driver stays bound, the detector is
|
||||
forgectrl-only with the liveness path untouched; if the two collide, the
|
||||
accel moves under `glowforge.ko`. The de-risk drill
|
||||
(`scripts/bench/accel_crash_probe.py`, on the bench page) settles that
|
||||
first: it arms IG1 in coexist mode, provokes a strike, confirms IG_SRC1
|
||||
latches the expected axis at a factory-derived threshold, and checks that
|
||||
`st_accel`'s raw reads keep working through the run. Keeping the factory
|
||||
full scale (no CTRL4 write) is what keeps the coexistence clean. Owed
|
||||
after the drill: the chosen readout path, per-state (idle/run) thresholds
|
||||
seeded from a captured cut header's HA* values, and the two tiers wired
|
||||
into the existing feed-hold and stop-plus-latch paths.
|
||||
hardware. The readout path is settled by the de-risk drill
|
||||
(`scripts/bench/accel_crash_probe.py`, on the bench page; the record is
|
||||
in CAMPAIGN-LOG): the detector is **forgectrl-only**. The IG registers
|
||||
(0x30 to 0x35) program and poll over i2c-dev (I2C_SLAVE_FORCE) while
|
||||
`st_accel` stays bound, and the liveness raw reads keep working through
|
||||
an armed window. Keeping the factory full scale (no CTRL4 write) keeps
|
||||
the coexistence clean, and the armed detector must own the ODR: the
|
||||
facts bank ("The head accelerometer") has the power-down behavior. The
|
||||
gravity axis rules the thresholds: X and Y arm below 1 g, a Z threshold
|
||||
must sit above 1 g plus margin. Owed: the forgectrl detector itself,
|
||||
per-state (idle/run) thresholds seeded from a captured cut header's HA*
|
||||
values, and the two tiers wired into the existing feed-hold and
|
||||
stop-plus-latch paths.
|
||||
|
||||
Owed for the head IRQ, only if a coarse hardware interrupt is wanted
|
||||
instead of the poll: arm the accel bit in the head MCU (reg 0x03/0x04),
|
||||
|
||||
@@ -6795,6 +6795,39 @@ enables SEGGER RTT telemetry; regs 0x3c-0x3f read a debug capture ring
|
||||
(the previously-unexplained `i2cget 0x47 0x02`, `0x0f`, `0x3c`
|
||||
commands).
|
||||
|
||||
## 2026-08-31: crash-detector de-risk drill - coexist proven, detector is forgectrl-only
|
||||
|
||||
The de-risk drill for the head-accelerometer crash detector
|
||||
(`scripts/bench/accel_crash_probe.py`, bench page `accel-crash-probe`) ran
|
||||
three coexist windows on dev 20260831204710 with forgectrl and grblHAL up
|
||||
and idle: a rest window on xyz, a rest window on xy, and an xy window with
|
||||
one gentle jog (`$J=G91 X5 F1000`, +X first). No emission, no unbind.
|
||||
|
||||
- **Coexist PROVEN.** The IG registers (0x30-0x35) program and poll over
|
||||
i2c-dev with I2C_SLAVE_FORCE while `st_accel` stays bound; IG_SRC1
|
||||
polled at ~166 Hz from Python, and `st_accel` raw reads kept working
|
||||
through and after every window. The detector is **forgectrl-only**: no
|
||||
kernel change, the liveness path untouched. This was the item's one
|
||||
open design decision.
|
||||
- **Latch and per-axis source report work.** At threshold 40 (~0.62 g at
|
||||
the factory +/-2 g full scale) the gravity axis Z (raw -16916, about
|
||||
-1.03 g) latched IG_SRC1 on every poll; X and Y stayed silent at rest
|
||||
and through the jog. So the shipped detector arms X and Y below 1 g and
|
||||
a Z threshold must sit above 1 g plus margin.
|
||||
- **New fact: the IG needs a running ODR.** The first window returned no
|
||||
trips at all because `st_accel` leaves the part in power-down between
|
||||
one-shot reads (CTRL1 ODR bits 0) and the interrupt generator only
|
||||
samples at a running ODR. The armed detector must set the ODR and
|
||||
re-assert it after any liveness read (each one-shot powers the part
|
||||
down again). The drill script now saves CTRL1, runs the window at
|
||||
800 Hz, and restores the saved value on exit; its old ODR test checked
|
||||
the axis-enable bits (0x07) instead of the ODR bits (0x70), fixed in
|
||||
the same change.
|
||||
- **No strike was provoked, none owed.** The rail-contact signature from
|
||||
the retired homing spike (29-42 k counts within ~4 ms) already fixes
|
||||
the strike magnitude, 3x and more over a threshold that gravity
|
||||
already trips; a physical tap would add nothing the design needs.
|
||||
|
||||
## Reference notes
|
||||
|
||||
### Head-IRQ source validation — the beam-emission hypothesis
|
||||
|
||||
@@ -86,7 +86,8 @@ TOOLS = [
|
||||
_arg("jog", "str", None, "optional one jog at t=2 s, e.g. $J=G91X5F1000 (+X only)", flag="--jog")],
|
||||
"desc": "Arms the head LIS2HH12's on-chip interrupt generator (IG1) and polls IG_SRC1 for a latched strike. "
|
||||
"coexist mode reaches the IG registers over i2c-dev with st_accel still bound (the forgectrl-only "
|
||||
"path proof); provoke a trip by hand or with --jog. Touches only the IG registers, no emission, "
|
||||
"path proof); provoke a trip by hand or with --jog. Touches the IG registers and CTRL1 "
|
||||
"(saved, run at 800 Hz, restored; the IG only samples at a running ODR), no emission, "
|
||||
"no full-scale change. CSV to /tmp/accel_crash.csv."},
|
||||
# -- board-side, takeover / scope ------------------------------------------
|
||||
{"id": "pwm-sweep", "title": "LASER_PWM scope sweep", "script": "pwm_sweep.py",
|
||||
|
||||
@@ -20,11 +20,13 @@ rides on, before any kernel or forgectrl work:
|
||||
can be set in register units against the rail-contact signature
|
||||
(facts bank: 20-40x over creep within ~4 ms on a fast strike).
|
||||
|
||||
The drill touches ONLY the IG registers plus the IG-latch bit of CTRL7,
|
||||
which st_accel never writes; it does not change the full scale (CTRL4)
|
||||
or the ODR (CTRL1), so st_accel's raw scaling is undisturbed. Default is
|
||||
coexist mode with forgectrl left running: no emission, no commanded
|
||||
motion. Provoke a trip by hand (a firm tap or nudge on the head) or pass
|
||||
The drill touches the IG registers plus the IG-latch bit of CTRL7,
|
||||
which st_accel never writes, and CTRL1: st_accel powers the part down
|
||||
between one-shot reads (ODR=0), and the IG generator only samples at a
|
||||
running ODR, so the drill saves CTRL1, sets 800 Hz for the window and
|
||||
restores the saved value on exit. The full scale (CTRL4) is never
|
||||
changed, so st_accel's raw scaling is undisturbed. Default is coexist
|
||||
mode with forgectrl left running: no emission, no commanded motion. Provoke a trip by hand (a firm tap or nudge on the head) or pass
|
||||
--jog to send one gentle grblHAL jog; a jog needs the controller free,
|
||||
so run it from the bench page's takeover or stop the controller first.
|
||||
|
||||
@@ -163,11 +165,12 @@ def main():
|
||||
bind_ctl('bind')
|
||||
return 1
|
||||
|
||||
ctrl1 = rd(fd, CTRL1)
|
||||
orig_ctrl1 = rd(fd, CTRL1)
|
||||
ctrl1 = orig_ctrl1
|
||||
ctrl4 = rd(fd, CTRL4)
|
||||
fs = FS_G.get((ctrl4 >> 4) & 0b11, '?')
|
||||
if unbind and (ctrl1 & 0x07) == 0:
|
||||
wr(fd, CTRL1, 0x6F) # 800 Hz, BDU, XYZ on; st_accel does this itself
|
||||
if (ctrl1 & 0x70) == 0: # ODR bits 6:4 zero = power-down; IG needs
|
||||
wr(fd, CTRL1, 0x6F) # a running ODR: 800 Hz, BDU, XYZ on
|
||||
ctrl1 = rd(fd, CTRL1)
|
||||
lsb_mg = (fs * 1000.0 / 128.0) if isinstance(fs, int) else 0
|
||||
print('WHO_AM_I=0x%02x CTRL1=0x%02x CTRL4=0x%02x FS=+/-%sg '
|
||||
@@ -238,6 +241,8 @@ def main():
|
||||
# Disarm and restore.
|
||||
wr(fd, IG_CFG1, 0x00)
|
||||
wr(fd, CTRL7, ctrl7)
|
||||
if ctrl1 != orig_ctrl1:
|
||||
wr(fd, CTRL1, orig_ctrl1)
|
||||
if sock:
|
||||
try:
|
||||
sock.recv(4096)
|
||||
|
||||
Reference in New Issue
Block a user