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:
ScottW514
2026-08-03 11:54:27 -04:00
parent f92147093e
commit f396dbde34
14 changed files with 76 additions and 79 deletions
+3
View File
@@ -8,3 +8,6 @@
# kas # kas
*.lock.yml.bak *.lock.yml.bak
# Python bytecode
__pycache__/
+2 -2
View File
@@ -1,8 +1,8 @@
# Build # Build
ForgeFIRM is built with [**kas**](https://kas.readthedocs.io/), which manages ForgeFIRM is built with [**kas**](https://kas.readthedocs.io/), which manages
the Yocto layers and pins their versions. This replaces the old Google `repo` the Yocto layers and pins their versions. The **forgefirm** repo is the base
+ `default.xml` workflow. The **forgefirm** repo is the base of the build. of the build.
Builds run on a Linux host, or on **WSL2** (officially supported by Yocto — Builds run on a Linux host, or on **WSL2** (officially supported by Yocto —
keep the tree on the native ext4 filesystem, not `/mnt/c`). keep the tree on the native ext4 filesystem, not `/mnt/c`).
+1 -1
View File
@@ -1,6 +1,6 @@
# LightBurn setup & operation (ForgeFIRM, motion-only stage) # LightBurn setup & operation (ForgeFIRM, motion-only stage)
Status: written 2026-08-02 for the grblHAL-glowforge controller. At this Status: motion-only. At this
stage **the laser cannot fire** — the driver forces the hardware laser stage **the laser cannot fire** — the driver forces the hardware laser
latch locked and never emits the laser bit in the pulse stream. A "job" latch locked and never emits the laser bit in the pulse stream. A "job"
runs every motion of the design (cuts at cut speed, travels at travel runs every motion of the design (cuts at cut speed, travels at travel
+16 -24
View File
@@ -3,8 +3,7 @@
The **forgefirm** repo is the base of the project: it controls the build, The **forgefirm** repo is the base of the project: it controls the build,
the resulting firmware images land here, and all build/install docs live here. the resulting firmware images land here, and all build/install docs live here.
It uses [**kas**](https://kas.readthedocs.io/) to manage Yocto layers and drive It uses [**kas**](https://kas.readthedocs.io/) to manage Yocto layers and drive
the build, replacing the old Google `repo` + `default.xml` manifest and the the build.
hand-maintained `base/conf/forgefirm-bblayers.conf`.
## Baseline ## Baseline
@@ -88,25 +87,21 @@ kas auto-loads the lockfile on subsequent runs. Commit it; refresh deliberately.
## Push & release order (source-of-truth sequencing) ## Push & release order (source-of-truth sequencing)
The build is only reproducible when recipe pins, layer branches, and the kas The build is only reproducible when recipe pins, layer branches, and the kas
config move in the right order (audit findings M13/N14 — the old `AUTOREV` + config move in the right order. The sequence, with current status:
untracked-overlay setup meant a plain `kas build` fetched the wrong camera
code). The sequence, with current status:
1. **Source repos pushed & pinned** — **DONE 2026-07-26.** 1. **Source repos pushed & pinned** — **DONE.**
`kernel-module-glowforge` (`029dfb6`) and `python3-gfhardware` (`9bf31fd`) `kernel-module-glowforge` (`029dfb6`) and `python3-gfhardware` (`9bf31fd`)
pushed to GitHub master; both recipes in meta-openglow pin those exact pushed to GitHub master; both recipes in meta-openglow pin those exact
SRCREVs (no `AUTOREV` anywhere). Whenever either repo changes: push it, SRCREVs (no `AUTOREV` anywhere). Whenever either repo changes: push it,
then bump the recipe `SRCREV` in meta-openglow deliberately. then bump the recipe `SRCREV` in meta-openglow deliberately.
2. **meta-openglow pushed** — **DONE 2026-07-26.** The Scarthgap port lives on 2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on
the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master` the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master`
is untouched). Development continues on the local sibling checkout; push / is untouched). Development continues on the local sibling checkout; push /
fast-forward `scarthgap` as work lands. fast-forward `scarthgap` as work lands.
3. **forgefirm pushed** with the kas config and a `kas lock` lockfile pinning 3. **forgefirm pushed** with the kas config and a `kas lock` lockfile pinning
the upstream layers (poky, meta-openembedded, meta-freescale, the upstream layers (poky, meta-openembedded, meta-freescale,
meta-freescale-distro). meta-freescale-distro).
4. **At release time** — gated on the audit **Phase 1 motion fixes** (do not 4. **At release time**:
ship an image while the cnc-probe and SDMA-firmware-collision defects make
motion dead on arrival, AUDIT-REPORT B1/M1):
- flip `meta-openglow` in `forgefirm-glowforge.yml` from the local-sibling - flip `meta-openglow` in `forgefirm-glowforge.yml` from the local-sibling
block to the pinned-remote block (commented FUTURE block in the file); block to the pinned-remote block (commented FUTURE block in the file);
- drop meta-openglow's `kernel-module-glowforge.bbappend` (externalsrc to - drop meta-openglow's `kernel-module-glowforge.bbappend` (externalsrc to
@@ -117,13 +112,10 @@ code). The sequence, with current status:
5. **GitHub release**: upload the image asset under the exact name the 5. **GitHub release**: upload the image asset under the exact name the
installer downloads — Scarthgap emits installer downloads — Scarthgap emits
`forgefirm-image-glowforge.rootfs.wic.gz`; align BUILD.md and `forgefirm-image-glowforge.rootfs.wic.gz`; align BUILD.md and
`install-forgefirm.sh` to one name before the first release (audit N13). `install-forgefirm.sh` to one name before the first release.
Dev-overlay note: `kas/externalsrc-dev.yml` (an untracked overlay building For gfhardware development, either bump the recipe pin per iteration or add a
python3-gfhardware from a local checkout via `/mnt/c`) was removed when the tracked externalsrc bbappend mirroring the kernel-module pattern.
SRCREV was pinned. For future gfhardware development, either bump the pin per
iteration or add a tracked externalsrc bbappend mirroring the kernel-module
pattern.
## Scarthgap migration backlog ## Scarthgap migration backlog
@@ -138,7 +130,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
2. **Kernel forward-port (4.14 → linux-fslc 6.12.20)** — the factory NXP vendor 2. **Kernel forward-port (4.14 → linux-fslc 6.12.20)** — the factory NXP vendor
kernel (linux-imx 4.14.98) carried 7 out-of-tree changes; these are re-derived kernel (linux-imx 4.14.98) carried 7 out-of-tree changes; these are re-derived
against mainline 6.12 in `meta-glowforge-bsp/recipes-kernel/linux/linux-fslc_%.bbappend` against mainline 6.12 in `meta-glowforge-bsp/recipes-kernel/linux/linux-fslc_%.bbappend`
(the forward-port landing zone), **not** re-applied as the old 4.14 patches. (the forward-port landing zone), **not** re-applied as the 4.14 patches.
- **Foundation — DONE.** `linux-fslc` 6.12.20 builds for `glowforge` with a - **Foundation — DONE.** `linux-fslc` 6.12.20 builds for `glowforge` with a
ported device tree (`glowforge.dts` + `openglow_common.dtsi` overlaid into ported device tree (`glowforge.dts` + `openglow_common.dtsi` overlaid into
`arch/arm/boot/dts/nxp/imx/`, registered via a Makefile patch) and deploys `arch/arm/boot/dts/nxp/imx/`, registered via a Makefile patch) and deploys
@@ -178,9 +170,9 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
`glowforge.dtb` compiles with the full pipeline. **HW bring-up:** confirm `glowforge.dtb` compiles with the full pipeline. **HW bring-up:** confirm
the real supply rails, CSI-2 lane count/order and CAM_SEL polarity, then the real supply rails, CSI-2 lane count/order and CAM_SEL polarity, then
validate with `media-ctl` + a v4l2 capture. validate with `media-ctl` + a v4l2 capture.
3. **u-boot** — ~~rebuild for Scarthgap~~ **DONE.** The `glowforge` u-boot is now 3. **u-boot** — **DONE.** The `glowforge` u-boot is
a standalone `u-boot_2020.01.bb` (Scarthgap dropped the Dunfell-era poky base a standalone `u-boot_2020.01.bb` (Scarthgap's poky has no u-boot 2020.01
recipe the old `.bbappend` extended). It reuses poky's base recipe to extend). It reuses poky's
`u-boot-common.inc`/`u-boot.inc`, pins `SRCREV` to the upstream **v2020.01** `u-boot-common.inc`/`u-boot.inc`, pins `SRCREV` to the upstream **v2020.01**
tag with the matching `Licenses/README` md5, and overlays the glowforge board tag with the matching `Licenses/README` md5, and overlays the glowforge board
support + arch-Kconfig patch. **Builds clean under Scarthgap (GCC 13, no support + arch-Kconfig patch. **Builds clean under Scarthgap (GCC 13, no
@@ -190,7 +182,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
them. them.
4. **Device tree** — revalidate the `glowforge` `.dts` against the linux-fslc 4. **Device tree** — revalidate the `glowforge` `.dts` against the linux-fslc
6.12 DT bindings (paired with the kernel forward-port in #2). 6.12 DT bindings (paired with the kernel forward-port in #2).
5. **Real-time strategy — decided (2026-07-26, audit M9/N15).** The kernel runs 5. **Real-time strategy — decided.** The kernel runs
`CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only `CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only
`PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no `PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no
`ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**: the SDMA `ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**: the SDMA
@@ -199,10 +191,10 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
a modest 1 MiB of queued data already rides out ~3–5 s of scheduling a modest 1 MiB of queued data already rides out ~3–5 s of scheduling
latency, orders of magnitude beyond anything PREEMPT exhibits. Deep latency, orders of magnitude beyond anything PREEMPT exhibits. Deep
buffering + `SCHED_FIFO` for the feeder is the design; revisit RT only if buffering + `SCHED_FIFO` for the feeder is the design; revisit RT only if
the underrun bench ever contradicts this arithmetic. (The audit Phase 2 the underrun bench ever contradicts this arithmetic. (Bench: 5 s of
bench: 5 s of continuous feed at a 1 s buffer depth, zero underruns.) continuous feed at a 1 s buffer depth, zero underruns.)
6. **gfui-client → forgectrl** — the Glowforge **cloud client is now removed** 6. **gfui-client → forgectrl** — the Glowforge **cloud client is excluded**
from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in
`meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`) — it connected to `meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`) — it connected to
Glowforge's servers, the dependency ForgeFIRM exists to cut. The grblHAL Glowforge's servers, the dependency ForgeFIRM exists to cut. The grblHAL
+3 -5
View File
@@ -9,8 +9,7 @@
# Machine: glowforge (i.MX6 Solo SOM inside Basic/Plus/Pro) # Machine: glowforge (i.MX6 Solo SOM inside Basic/Plus/Pro)
# Distro : forgefirm # Distro : forgefirm
# #
# Replaces the old Google `repo` + default.xml manifest and the hand-maintained # kas generates bblayers.conf and local.conf.
# base/conf/forgefirm-bblayers.conf. kas generates bblayers.conf and local.conf.
# #
# Run from the forgefirm repo root so outputs land inside it: # Run from the forgefirm repo root so outputs land inside it:
# cd forgefirm # cd forgefirm
@@ -86,8 +85,7 @@ repos:
meta-glowforge-bsp: meta-glowforge-bsp:
# meta-openglow-bsp (separate OpenGlow_std board) intentionally excluded. # meta-openglow-bsp (separate OpenGlow_std board) intentionally excluded.
# #
# FUTURE — flip to this pinned-remote block at release time (the scarthgap # FUTURE — flip to this pinned-remote block at release time (gated on
# branch is pushed as of 2026-07-26, so this now works; the flip is gated on
# active BSP development settling — see kas/README.md "Push & release order". # active BSP development settling — see kas/README.md "Push & release order".
# When flipping, also drop meta-openglow's kernel-module-glowforge.bbappend # When flipping, also drop meta-openglow's kernel-module-glowforge.bbappend
# (externalsrc to the local sibling) so a fresh clone is self-contained; its # (externalsrc to the local sibling) so a fresh clone is self-contained; its
@@ -110,7 +108,7 @@ local_conf_header:
eula: | eula: |
ACCEPT_FSL_EULA = "1" ACCEPT_FSL_EULA = "1"
# Kernel is now defaulted to linux-fslc 6.12 in conf/machine/glowforge.conf # The kernel defaults to linux-fslc 6.12 in conf/machine/glowforge.conf
# (with the factory drivers forward-ported — EPIT/SDMA/OV5648/glowforge.ko — # (with the factory drivers forward-ported — EPIT/SDMA/OV5648/glowforge.ko —
# see kas/README.md backlog #2). This explicit pin is redundant but harmless. # see kas/README.md backlog #2). This explicit pin is redundant but harmless.
kernel: | kernel: |
@@ -6,9 +6,9 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=40d2542b8c43a3ec2b7f5da31a697b88"
# Pinned to the v2.7.15 release tag, together with orcania v2.3.3 and # Pinned to the v2.7.15 release tag, together with orcania v2.3.3 and
# yder v1.4.20 (its release-mate dependency versions). AUTOREV against master # yder v1.4.20 (its release-mate dependency versions). AUTOREV against master
# was unbuildable: master's CMake requires orcania/yder versions that have no # is unbuildable: master's CMake requires orcania/yder versions that have no
# release tags, and the git:// fetch never brought in the bundled-subproject # release tags, and the git:// fetch never brings in the bundled-subproject
# fallback (audit M10). # fallback.
SRC_URI = "git://github.com/babelouest/ulfius;protocol=https;branch=master" SRC_URI = "git://github.com/babelouest/ulfius;protocol=https;branch=master"
SRCREV = "a0603447d3ed63c0880db396b9c395fb4bf6b559" SRCREV = "a0603447d3ed63c0880db396b9c395fb4bf6b559"
@@ -2,9 +2,8 @@ require forgefirm-image.bb
DESCRIPTION = "OpenGlow/ForgeFIRM development image for Glowforge" DESCRIPTION = "OpenGlow/ForgeFIRM development image for Glowforge"
# Strict superset of forgefirm-image (audit N12): everything the main image # Strict superset of forgefirm-image: everything the main image ships, plus
# ships (incl. the gfui-client removal and v4l-utils), plus the forgectrl # the forgectrl placeholder and debug tooling.
# placeholder and debug tooling.
IMAGE_INSTALL += " \ IMAGE_INSTALL += " \
forgectrl \ forgectrl \
" "
+12 -12
View File
@@ -5,23 +5,23 @@ target board (dev image, python3 present) unless noted.
| Tool | Purpose | | 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). | | `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 (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. | | `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 (audit M8): reads PWM2 PWMCR/PWMPR via /dev/mem, expects divider 13 × ~127 counts ≈ 40 kHz. | | `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). 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). | | `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. | | `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). | | `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. All-green 2026-08-02. | | `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. This is what showed the 10 % scheme was unusable (0.04 °C gap) and sized the 30 % check. | | `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_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_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). | | `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 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. | | `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-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. | | `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. | | `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` | 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. | | `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 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 2.7.15 work directory in the WSL build tree; if that path ages out after a
+5 -5
View File
@@ -1,15 +1,15 @@
#!/usr/bin/env python3 #!/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 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 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 Hi-Z and the hardware safety chain is open anyway), and only NOP (0x00) and
laser-bit (0x10) bytes are streamed. laser-bit (0x10) bytes are streamed.
Exercises: normal completion, underrun detection/ack (M5), parked no-replay Exercises: normal completion, underrun detection/ack, parked no-replay
guard (M4), alldone GPIO clear (M2, via GPIO2_DR readback - the data register guard, alldone GPIO clear (via GPIO2_DR readback - the data register
retains the last value the script wrote), resume(0) (M3), continuous-feed retains the last value the script wrote), resume(0), continuous-feed
stability, and 20x run/underrun cycling (M3 wedge check). stability, and 20x run/underrun cycling (wedge check).
""" """
import os, re, sys, time, mmap, struct import os, re, sys, time, mmap, struct
+2 -2
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/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, Expected with the fsl,extra-prescale=<13> fix: PWMCR prescaler divider = 13,
PWMPR ~125 (127 counts - 2), effective carrier = perclk / (13 * 127) ~= 40 kHz. 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 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. by the hardware chain. The definitive gate remains the scope on LASER_PWM.
""" """
+3 -3
View File
@@ -1,9 +1,9 @@
/* /*
* feeder.c - ForgeFIRM spike step 3: prove no-underrun continuous live * feeder.c - prove no-underrun continuous live feeding of the glowforge.ko
* feeding of the glowforge.ko SDMA pulse ring under load (audit 6.3). * SDMA pulse ring under load.
* *
* Streams NOP (0x00) pulse bytes to /dev/glowforge, pacing by wall clock to * 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 * with the deadman flock held. Motors must be locked and the laser latch
* locked by the caller (bench_phase6.sh does this). * locked by the caller (bench_phase6.sh does this).
* *
+21 -14
View File
@@ -1,11 +1,12 @@
#!/usr/bin/env python3 #!/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). board over ssh).
The raw->Celsius conversion in UAPI.md (C = raw * -0.09653 + 94) is an The raw->Celsius conversion in UAPI.md is the factory B-equation (10k
unverified best guess. This tool collects reference points - a measured B3380 NTC in a 10k divider behind a 1.3x gain stage, 10-bit ADC). This
real temperature paired with the machine's raw ADC readings - and fits tool collects reference points - a measured real temperature paired with
the actual line for THIS machine. the machine's raw ADC readings - and fits a per-machine line to
cross-check that curve against a thermometer.
Usage: Usage:
temp_calibrate.py watch live raw + current-formula C 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). heater on).
""" """
import json import json
import math
import os import os
import subprocess import subprocess
import sys import sys
@@ -26,8 +28,15 @@ import time
HOST = '172.16.1.97' HOST = '172.16.1.97'
STORE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_calibration.json') 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): def board(cmd):
@@ -79,12 +88,11 @@ def main():
mode = sys.argv[1] if len(sys.argv) > 1 else 'watch' mode = sys.argv[1] if len(sys.argv) > 1 else 'watch'
if mode == '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: while True:
r1, r2 = raws(1, 0) r1, r2 = raws(1, 0)
print(' %6.1f %6.1f %.2f / %.2f' print(' %6.1f %6.1f %.2f / %.2f'
% (r1, r2, r1 * GUESS_SLOPE + GUESS_OFFSET, % (r1, r2, uapi_c(r1), uapi_c(r2)))
r2 * GUESS_SLOPE + GUESS_OFFSET))
time.sleep(2) time.sleep(2)
elif mode == 'point': elif mode == 'point':
@@ -115,11 +123,10 @@ def main():
slope, offset = fit(pts, key) slope, offset = fit(pts, key)
print('\n%s:' % label) print('\n%s:' % label)
print(' fitted: C = raw * %.6f + %.4f' % (slope, offset)) 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): for raw in (600, 650, 700, 750, 800):
print(' raw %3d -> fitted %6.2f C guess %6.2f C diff %+.2f' print(' raw %3d -> fitted %6.2f C uapi %6.2f C diff %+.2f'
% (raw, raw * slope + offset, raw * GUESS_SLOPE + GUESS_OFFSET, % (raw, raw * slope + offset, uapi_c(raw),
(raw * slope + offset) - (raw * GUESS_SLOPE + GUESS_OFFSET))) (raw * slope + offset) - uapi_c(raw)))
else: else:
print(__doc__) print(__doc__)
return 1 return 1
+1 -2
View File
@@ -73,8 +73,7 @@ esac
# The env always lives on the eMMC (mmcblk2 0x80000/0x82000). On the factory # The env always lives on the eMMC (mmcblk2 0x80000/0x82000). On the factory
# firmware and on the ForgeFIRM image, /etc/fw_env.config already points # firmware and on the ForgeFIRM image, /etc/fw_env.config already points
# there; the mmcblk2-specific override is honored if someone provisioned one, # there; the mmcblk2-specific override is honored if someone provisioned one,
# but nothing ships it, so fall back rather than failing on a missing file # but nothing ships it, so fall back rather than failing on a missing file.
# (audit N16).
if [ -f "/etc/fw_env_mmcblk2.config" ] && [ ! -d "/factory" ]; then if [ -f "/etc/fw_env_mmcblk2.config" ] && [ ! -d "/factory" ]; then
FWCONFIG="/etc/fw_env_mmcblk2.config" FWCONFIG="/etc/fw_env_mmcblk2.config"
else else
+2 -3
View File
@@ -15,9 +15,8 @@ BRIGHT="\033[1;39m"
RESET="\033[0m" RESET="\033[0m"
ASTERISK="${LIGHTRED}✺${RESET}" ASTERISK="${LIGHTRED}✺${RESET}"
# Abort loudly instead of completing silently broken: several steps below # Abort loudly instead of completing silently broken: a failed step (image
# (image download, flash write, uEnv rewrite) used to fail without stopping # download, flash write, uEnv rewrite) must stop the install.
# the install (audit N13/M12/N16).
die () { die () {
echo echo
echo -e "${LIGHTRED}!! INSTALL FAILED:${RESET} $1" echo -e "${LIGHTRED}!! INSTALL FAILED:${RESET} $1"