mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
Remove history narrative and audit references from docs and tooling
temp_calibrate now cross-checks the factory B-equation instead of the retired linear guess.
This commit is contained in:
+12
-12
@@ -5,23 +5,23 @@ target board (dev image, python3 present) unless noted.
|
||||
|
||||
| Tool | Purpose |
|
||||
|---|---|
|
||||
| `feeder.c` | Spike-step-3 underrun proof: streams NOP pulse bytes to `/dev/glowforge` with wall-clock pacing, bounded queue depth, deadman flock, SCHED_FIFO. Usage: `feeder <hz> <seconds> <depth_ms>`. Passed 100 kHz × 120 s under full load with 0.2 ms worst write latency. Cross-compile with `build-feeder.sh` (WSL). |
|
||||
| `bench_phase2.py` | End-of-data protocol bench (audit M2–M5): underrun detection/ack, parked no-replay guard, resume(0), continuous-feed stability, 20× run/underrun cycles. Motion-safe (motors locked, laser latched). 16/16 PASS on 2026-07-26. |
|
||||
| `check_pwm.py` | Laser PWM register check (audit M8): reads PWM2 PWMCR/PWMPR via /dev/mem, expects divider 13 × ~127 counts ≈ 40 kHz. |
|
||||
| `pwm_sweep.py` | LASER_PWM scope test (runs on the board): `check` = read-only safety readbacks + PWM2 dump; `sweep` = steps PWMSAR through 50/25/75/6/100 % duty with 4 s holds, then restores. Run only in the locked state (controller stopped, cnc disabled, latch locked). Waveform gate PASSED with it 2026-08-02: 40 kHz stable, duty tracks PWMSAR (6.4 % measured vs 6.3 % commanded at the low end). |
|
||||
| `feeder.c` | Underrun proof: streams NOP pulse bytes to `/dev/glowforge` with wall-clock pacing, bounded queue depth, deadman flock, SCHED_FIFO. Usage: `feeder <hz> <seconds> <depth_ms>`. Proven envelope: 100 kHz × 120 s under full load, 0.2 ms worst write latency. Cross-compile with `build-feeder.sh` (WSL). |
|
||||
| `bench_phase2.py` | End-of-data protocol bench: underrun detection/ack, parked no-replay guard, resume(0), continuous-feed stability, 20× run/underrun cycles. Motion-safe (motors locked, laser latched). |
|
||||
| `check_pwm.py` | Laser PWM register check: reads PWM2 PWMCR/PWMPR via /dev/mem, expects divider 13 × ~127 counts ≈ 40 kHz. |
|
||||
| `pwm_sweep.py` | LASER_PWM scope test (runs on the board): `check` = read-only safety readbacks + PWM2 dump; `sweep` = steps PWMSAR through 50/25/75/6/100 % duty with 4 s holds, then restores. Run only in the locked state (controller stopped, cnc disabled, latch locked). |
|
||||
| `pwm_hold.py` | Holds one PWMSAR value for a scope-measurement window (`pwm_hold.py <sar> <seconds>`), then restores. Same locked-state rule. |
|
||||
| `fire_test.py` | FIRE drop-timing scope test (runs on the board): A = latch locked (expects nothing on FIRE/LASER_ON), B = latch unlocked / normal end-of-data, U = true underrun. Duty 0 throughout; refuses to unlock if HV reports good. All gates PASSED with it 2026-08-02 (2.0000 s pulses exact, both paths). |
|
||||
| `fan_test.py` | Fan/coolant bench (Windows-side): snapshots fan PWMs/tachs/temps, drives M8 → cut fans, M9 → cooldown → idle, verifying via tach readbacks. All-green 2026-08-02. |
|
||||
| `flow_characterize.py` | Coolant flow characterization using the factory temperature curve: baseline → flow → no-flow → recovery, printing the ΔT bands and their separation. Takes the heater duty as an argument (`flow_characterize.py 30`); aborts if downstream passes 45 °C. This is what showed the 10 % scheme was unusable (0.04 °C gap) and sized the 30 % check. |
|
||||
| `fire_test.py` | FIRE drop-timing scope test (runs on the board): A = latch locked (expects nothing on FIRE/LASER_ON), B = latch unlocked / normal end-of-data, U = true underrun. Duty 0 throughout; refuses to unlock if HV reports good. |
|
||||
| `fan_test.py` | Fan/coolant bench (Windows-side): snapshots fan PWMs/tachs/temps, drives M8 → cut fans, M9 → cooldown → idle, verifying via tach readbacks. |
|
||||
| `flow_characterize.py` | Coolant flow characterization using the factory temperature curve: baseline → flow → no-flow → recovery, printing the ΔT bands and their separation. Takes the heater duty as an argument (`flow_characterize.py 30`); aborts if downstream passes 45 °C. |
|
||||
| `flow_matrix.py` | **The flow-detection design matrix** (with `flow_sampler.py`, which lives on the board at `/data/`): duty × duration × flow/no-flow, every run from a common cooled baseline, interleaved repeats. One heating trace yields the metric at every candidate duration, so cost and precision come from the same 60 runs. Prints a cost table, a precision table (mean±sd, worst-case margin, d′) and a ranked shortlist. Env: `FM_DUTIES`, `FM_REPEATS`, `FM_RESULTS`. |
|
||||
| `flow_sustained.py` | Long-run test of the real re-check cadence via M8: counts verdicts/false faults and tracks whether the loop accumulates heat. 40 min ⇒ 0 false faults, loop *cooled* 2 °C. |
|
||||
| `flow_sustained.py` | Long-run test of the real re-check cadence via M8: counts verdicts/false faults and tracks whether the loop accumulates heat. |
|
||||
| `flow_warm_validate.py` | Runs the real check from a heater-warmed baseline. Note the ceiling: 100 % duty pushes the downstream sensor past 50 °C in 30 s while the bulk barely moves, so warm-loop validation above ~23 °C needs the laser, not the heater. |
|
||||
| `flow_recheck_char.py` | Characterizes short in-run re-checks and the differential metric; documents two dead ends (over-temp cannot see a stopped pump; passive warming trends are ambiguous). |
|
||||
| `temp_calibrate.py` | Coolant temperature calibration helper (`watch` / `point <measured_C>` / `fit`) — pairs a measured temperature with averaged raw ADC readings and fits the line. Used to sanity-check the factory curve against a thermometer. |
|
||||
| `flow_recheck_char.py` | Characterizes short in-run re-checks and the differential metric; shows why over-temp cannot see a stopped pump and why passive warming trends are ambiguous. |
|
||||
| `temp_calibrate.py` | Coolant temperature spot-check helper (`watch` / `point <measured_C>` / `fit`) — pairs a measured temperature with averaged raw ADC readings and fits a per-machine line to sanity-check the factory curve against a thermometer. |
|
||||
| `build-glowforge.sh` | Cross-compiles **grblHAL-glowforge** (the canonical driver repo, `../../../grblHAL-glowforge`) in the forge-yocto WSL distro. Run: `wsl -d forge-yocto -- bash <path>/build-glowforge.sh` (from PowerShell; Git Bash mangles /mnt/c paths). This is the production controller build. |
|
||||
| `build-feeder.sh` | Cross-compiles `feeder.c` the same way. |
|
||||
| `puls_profile.py` | Decodes factory `.puls` streams (raw or GF1-headered) into velocity/accel profiles: peak speeds, ramp-slope fits, per-move segments, Z cadence. Runs anywhere (stdlib only). Source of the factory-true grblHAL defaults (milestone 2): 700/590 mm/s² accel, 200 mm/s max rate, 28160 Hz travel tick. |
|
||||
| `bench_m2.py` | Milestone-2 motion bench, runs against the board over TCP:23: bounded round-trip jogs (sanity, max-rate, diagonal) + feed-hold/resume mid-move, reporting peak feed, state transitions, and position drift. All-green 2026-08-02 at factory-true settings. |
|
||||
| `puls_profile.py` | Decodes factory `.puls` streams (raw or GF1-headered) into velocity/accel profiles: peak speeds, ramp-slope fits, per-move segments, Z cadence. Runs anywhere (stdlib only). Source of the factory-true grblHAL defaults: 700/590 mm/s² accel, 200 mm/s max rate, 28160 Hz travel tick. |
|
||||
| `bench_m2.py` | Motion-quality bench, runs against the board over TCP:23: bounded round-trip jogs (sanity, max-rate, diagonal) + feed-hold/resume mid-move, reporting peak feed, state transitions, and position drift. |
|
||||
|
||||
The build scripts borrow the Yocto cross toolchain + sysroot from the ulfius
|
||||
2.7.15 work directory in the WSL build tree; if that path ages out after a
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 2.8 bench test: the reworked end-of-data protocol (audit M2-M5).
|
||||
"""End-of-data protocol bench.
|
||||
|
||||
Motion-safe by construction: all four motors are locked via motor_lock (the
|
||||
SDMA script masks the STEP bits), the laser latch is locked (LASER_ON pin is
|
||||
Hi-Z and the hardware safety chain is open anyway), and only NOP (0x00) and
|
||||
laser-bit (0x10) bytes are streamed.
|
||||
|
||||
Exercises: normal completion, underrun detection/ack (M5), parked no-replay
|
||||
guard (M4), alldone GPIO clear (M2, via GPIO2_DR readback - the data register
|
||||
retains the last value the script wrote), resume(0) (M3), continuous-feed
|
||||
stability, and 20x run/underrun cycling (M3 wedge check).
|
||||
Exercises: normal completion, underrun detection/ack, parked no-replay
|
||||
guard, alldone GPIO clear (via GPIO2_DR readback - the data register
|
||||
retains the last value the script wrote), resume(0), continuous-feed
|
||||
stability, and 20x run/underrun cycling (wedge check).
|
||||
"""
|
||||
import os, re, sys, time, mmap, struct
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 3.2 software check: laser PWM carrier frequency from PWM2 registers.
|
||||
"""Software check: laser PWM carrier frequency from PWM2 registers.
|
||||
|
||||
Expected with the fsl,extra-prescale=<13> fix: PWMCR prescaler divider = 13,
|
||||
PWMPR ~125 (127 counts - 2), effective carrier = perclk / (13 * 127) ~= 40 kHz.
|
||||
Without the fix: divider 1 -> ~520 kHz (audit M8). Safe: read-only register
|
||||
Without the fix: divider 1 -> ~520 kHz. Safe: read-only register
|
||||
inspection; the laser PWM output feeds the PSU power input, firing stays gated
|
||||
by the hardware chain. The definitive gate remains the scope on LASER_PWM.
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* feeder.c - ForgeFIRM spike step 3: prove no-underrun continuous live
|
||||
* feeding of the glowforge.ko SDMA pulse ring under load (audit 6.3).
|
||||
* feeder.c - prove no-underrun continuous live feeding of the glowforge.ko
|
||||
* SDMA pulse ring under load.
|
||||
*
|
||||
* Streams NOP (0x00) pulse bytes to /dev/glowforge, pacing by wall clock to
|
||||
* hold a bounded queue depth (like a real grblHAL backend would - audit M6),
|
||||
* hold a bounded queue depth (like a real grblHAL backend would),
|
||||
* with the deadman flock held. Motors must be locked and the laser latch
|
||||
* locked by the caller (bench_phase6.sh does this).
|
||||
*
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coolant temperature calibration helper (runs on Windows, reads the
|
||||
"""Coolant temperature spot-check helper (runs on Windows, reads the
|
||||
board over ssh).
|
||||
|
||||
The raw->Celsius conversion in UAPI.md (C = raw * -0.09653 + 94) is an
|
||||
unverified best guess. This tool collects reference points - a measured
|
||||
real temperature paired with the machine's raw ADC readings - and fits
|
||||
the actual line for THIS machine.
|
||||
The raw->Celsius conversion in UAPI.md is the factory B-equation (10k
|
||||
B3380 NTC in a 10k divider behind a 1.3x gain stage, 10-bit ADC). This
|
||||
tool collects reference points - a measured real temperature paired with
|
||||
the machine's raw ADC readings - and fits a per-machine line to
|
||||
cross-check that curve against a thermometer.
|
||||
|
||||
Usage:
|
||||
temp_calibrate.py watch live raw + current-formula C
|
||||
@@ -18,6 +19,7 @@ machine in the morning, and warm after a fan-off soak with the flow
|
||||
heater on).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -26,8 +28,15 @@ import time
|
||||
HOST = '172.16.1.97'
|
||||
STORE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_calibration.json')
|
||||
|
||||
# Current unverified guess, for comparison only.
|
||||
GUESS_SLOPE, GUESS_OFFSET = -0.09653, 94.0
|
||||
|
||||
def uapi_c(raw):
|
||||
"""The UAPI.md factory conversion (B-equation NTC behind divider + gain)."""
|
||||
adc_f = 1024.0 * 1.3
|
||||
if raw <= 0 or raw >= adc_f:
|
||||
return float('nan')
|
||||
rinf = 10000.0 * math.exp(-3380.0 / 298.15)
|
||||
r = 10000.0 / (adc_f / raw - 1.0)
|
||||
return 3380.0 / math.log(r / rinf) - 273.15
|
||||
|
||||
|
||||
def board(cmd):
|
||||
@@ -79,12 +88,11 @@ def main():
|
||||
mode = sys.argv[1] if len(sys.argv) > 1 else 'watch'
|
||||
|
||||
if mode == 'watch':
|
||||
print('raw1(down) raw2(up) guess-C down/up (ctrl-C to stop)')
|
||||
print('raw1(down) raw2(up) uapi-C down/up (ctrl-C to stop)')
|
||||
while True:
|
||||
r1, r2 = raws(1, 0)
|
||||
print(' %6.1f %6.1f %.2f / %.2f'
|
||||
% (r1, r2, r1 * GUESS_SLOPE + GUESS_OFFSET,
|
||||
r2 * GUESS_SLOPE + GUESS_OFFSET))
|
||||
% (r1, r2, uapi_c(r1), uapi_c(r2)))
|
||||
time.sleep(2)
|
||||
|
||||
elif mode == 'point':
|
||||
@@ -115,11 +123,10 @@ def main():
|
||||
slope, offset = fit(pts, key)
|
||||
print('\n%s:' % label)
|
||||
print(' fitted: C = raw * %.6f + %.4f' % (slope, offset))
|
||||
print(' guess: C = raw * %.6f + %.4f' % (GUESS_SLOPE, GUESS_OFFSET))
|
||||
for raw in (600, 650, 700, 750, 800):
|
||||
print(' raw %3d -> fitted %6.2f C guess %6.2f C diff %+.2f'
|
||||
% (raw, raw * slope + offset, raw * GUESS_SLOPE + GUESS_OFFSET,
|
||||
(raw * slope + offset) - (raw * GUESS_SLOPE + GUESS_OFFSET)))
|
||||
print(' raw %3d -> fitted %6.2f C uapi %6.2f C diff %+.2f'
|
||||
% (raw, raw * slope + offset, uapi_c(raw),
|
||||
(raw * slope + offset) - uapi_c(raw)))
|
||||
else:
|
||||
print(__doc__)
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user