mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
Prove the density dose model host-side; close the idle-gap level loss
Four new sessions in the stream harness cover the model (grblHAL-glowforge 2bca017, pinned here). Density renders the commanded level exactly - levels 2, 3, 7, 15, 25 and 38 came back as 0.0158, 0.0237, 0.0551, 0.1182, 0.1969 and 0.2993 against level/127 of 0.01575, 0.02362, 0.05512, 0.11811, 0.19685 and 0.29921 - S1000 renders 1.0000 and still ends dark, every power byte carries full duty, and a level change inside a run costs no stream byte where analog ships one per level. Rule 13 is the one worth having: the same job run under both models produces an identical motion grid tick for tick, and all 20051 density FIRE ticks fall inside the 169776 the analog run fired. The model masks the core's fire state and never sources one, measured rather than argued. Rule 14 covers the idle-gap fix: a standalone S between moves, from a sender slow enough to drain the planner, now fires each move at its own level (28338 ticks each at duties 30, 52 and 84). Before the fix duty 30 held all 85014 and the other two levels never appeared. That closes "Next work" item 18, which this work opened earlier today. The harness now derives its expectations from the board's floor and chains two launches over one settings file, because the core precomputes the S to duty mapping once when the spindle is enabled: $35 written at runtime persists and reports immediately but only enters force at the next controller start. That is recorded in BRINGUP beside the existing defaults note, and laser.power-floor's failure message now says so. Acceptance: the density path shipping off by default is inert until laser_power_model is set, and the laser tests' covers already name grblhal-glowforge src/**; the model's own acceptance test waits on the bench drill that picks the base period.
This commit is contained in:
+51
-2
@@ -169,6 +169,14 @@ core mutex stands in for interrupt masking. `GFSINK` unset = null-sink mode
|
||||
`^X` mid-motion aborts via kernel `cnc/stop` (controlled decel) and raises an
|
||||
alarm; TCP disconnects never kill the process (the dead-man fd stays held).
|
||||
|
||||
**Spindle `$`-settings take effect at controller start.** The core
|
||||
precomputes the S -> duty mapping once, when the spindle is enabled, and a
|
||||
settings write does not re-run it: `$35=16` persists to the eeprom
|
||||
immediately and `$$` reports it immediately, but the mapping in force is
|
||||
still the one loaded at start until the controller restarts (a mode switch,
|
||||
a `POST /controller/stop` + `start`, or a boot). Verified host-side: after a
|
||||
runtime `$35=0` the shipped duties stay floored.
|
||||
|
||||
**Stored `$`-settings beat freshly baked defaults** — after changing
|
||||
`GLOWFORGE_DEFAULTS` values, run `$RST=$` once on the board (settings persist
|
||||
in the eeprom file in `/data`).
|
||||
@@ -194,6 +202,31 @@ of the first tick byte it covers, FIRE as bit 4 OR'd into tick bytes. The
|
||||
spindle PWM is precomputed to a period of exactly 127, so computed values ARE
|
||||
power bytes (`$30` default 1000 → S1000 = 127).
|
||||
|
||||
**Dose model.** `laser_power_model` in the shared machine config selects how
|
||||
the shipper renders the per-segment value the core computes: `analog` (the
|
||||
default) ships it as a power byte, `density` pins the duty at full and
|
||||
modulates the FIRE bit instead - a base period of `laser_pulse_ticks`
|
||||
(default 20 = 710 us at 28160 Hz, the factory's ~1.43 kHz) whose on-count is
|
||||
dithered between adjacent integers with the remainder carried, so densities
|
||||
finer than one tick per period average out. The model is selected per arm
|
||||
and reported (`laser armed (density)`). Density is what the tube's dead band
|
||||
below its lasing threshold requires: every pulse it emits is full-power, so
|
||||
no commanded level lands in the band, and a level change inside a run costs
|
||||
no stream byte at all. It wants `$35` = 0 - the floor exists only to keep an
|
||||
analog duty out of the band, and under density it just clamps the light end
|
||||
of the range; the arm warns when a floor is set. Structurally the model is a
|
||||
mask on the core's fire state and never a source of one, so emission stays
|
||||
exactly where the core commanded it.
|
||||
|
||||
An S word takes effect whether or not motion is in progress. Per-segment
|
||||
updates carry the level inside a laser block, but an S executed between
|
||||
blocks - with the planner drained, so nothing is streaming - arrives only
|
||||
through the synchronous spindle path, which publishes the duty without
|
||||
touching the fire state; and the next run re-asserts the laser state the
|
||||
core last asked for at its first byte, fire only inside an armed window.
|
||||
Without those two a standalone S from a sender slow enough to drain the
|
||||
planner left the following moves cutting at a stale duty, or dark.
|
||||
|
||||
Contract rules enforced structurally: a power byte leads every kernel run
|
||||
before any fire bit (a run start resets duty to ~100 %), transitions are
|
||||
coalesced per tick so power bytes are never consecutive, and power bytes cost
|
||||
@@ -1042,8 +1075,24 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
|
||||
the factory's, which still lets dose per unit length rise ~1.8× at a
|
||||
corner.
|
||||
|
||||
Owed: the density model itself. `$35` and the analog path stay as the
|
||||
fallback until it lands.
|
||||
The model itself is implemented and host-proven, off by default
|
||||
(`laser_power_model`, above). What the harness holds: density renders
|
||||
the commanded level exactly (level/127 to four decimals at every rung),
|
||||
no level ever reaches PWMSAR, a level change inside a run costs no
|
||||
stream byte where analog pays one each, and - run against the same job
|
||||
under both models - the motion grid is identical and every density FIRE
|
||||
tick is one the analog run also fired, so the model only ever masks.
|
||||
|
||||
Owed: one bench drill to choose the base period, which is the parameter
|
||||
the host cannot answer. The factory never emits a pulse shorter than
|
||||
100 us; a tick here is 35.5 us, and every pulse restarts the discharge,
|
||||
so each carries the strike transient the threshold ladder made visible
|
||||
- dose per pulse is therefore probably not proportional to pulse length
|
||||
and density -> dose may be superlinear at the low end. A density ladder
|
||||
on scrap at two or three `laser_pulse_ticks` values answers both that
|
||||
and the shortest pulse that marks reliably. grblHAL is userspace, so it
|
||||
deploys by replacing the binary; no image flash. Then the raster path
|
||||
below.
|
||||
|
||||
What that model means for image engraving, since it decides the design as
|
||||
much as cutting does. LightBurn has two image paths. Its 1-bit modes
|
||||
|
||||
Reference in New Issue
Block a user