mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
laser power-good: the line characterized, the kernel-drill guard, the probe, the dev image's mmap and ctypes
The supply's power-good line is active high, static across HV enable and emission, and driven; the facts bank and CAMPAIGN-LOG carry the measurement and the item closes. The kernel-drill latch-unlock guard read the old inverted value as HV not good, a check that was vacuous and would refuse every run once the module reads the line correctly; it now uses the chain's own witnesses, the charge-pump watchdog and the engine state. pgood_probe.py watches the line beside the chain through the kernel readbacks and is registered on the bench page. The dev image lists python3-mmap and python3-ctypes again for the pad-level bench tools the python trim had left without them.
This commit is contained in:
@@ -59,6 +59,7 @@ page's takeover does that; from a host, stop them first.
|
||||
| `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. |
|
||||
| `cp_watchdog_timing.py` | HV charge-pump watchdog one-shot timing (runs on the board): latches every CHG_PUMP feed pulse in GPIO3's edge detector (pin 24 only, IMR untouched, ICR2 restored on exit) and polls the `!Q` (`charge_pump_alive`) and `!HV_ENABLE` (`hv_enable`) pads through /dev/mem in a tight loop while it commands short local jogs; prints per-run t_w (last pulse → Q fall), Q → HV_ENABLE delay, priming latency and the feed period, with the loop's worst gap as the resolution. Motion only, laser locked, no other Grbl client attached. |
|
||||
| `resume_dark_lead.py` | Pause/resume safety-chain timing (runs on the board, as root): samples LASER_ON, FIRE, HV_ENABLE, the charge-pump watchdog, the button and the doors straight off the SoC pads through /dev/mem at ~2 kHz, with motion dated from the kernel step counters, across a pause and a resume driven by the operator's button presses. Reports how long HV_ENABLE survives the stream stopping, how fast the chain re-arms on the resume, and - on `--run live` - the dark lead between FIRE going back on and LASER_ON following it, in milliseconds and in millimeters at the job's feed. `--run dry` (default) commands no laser at all and `--auto P,R` drives the pause and resume with `!`/`~` for an unattended rehearsal; `--run live` needs the arm press, eye protection, fire watch, extinguisher and exhaust. GRBL mode, no other Grbl client attached. |
|
||||
| `pgood_probe.py` | The supply's power-good line (J1_14, `cnc/laser_pgood`) against the laser chain, without a scope (runs on the board, as root): polls the kernel readbacks (`laser_pgood` reported as the raw pin level, `laser_on`, `laser_enable`, `charge_pump_alive`) and the switch device's HV_ENABLE and doors bits at a few hundred hertz, `hv_current` at 20 Hz, and prints every transition with a timestamp plus a per-line summary. Drive the machine meanwhile (a dry jog, an armed cut, a pause, a lid open); the probe only watches. Feed it over ssh stdin: `ssh root@<board> 'python3 - --secs 90' < pgood_probe.py`. |
|
||||
| `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. |
|
||||
|
||||
Data files kept beside the tools: `flow_matrix_results.json` /
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""LASER_PGOOD (J1_14) against the rest of the laser chain, through the
|
||||
kernel's readbacks.
|
||||
|
||||
Runs ON the board as root. Polls the kernel's GPIO readbacks (sysfs, one
|
||||
open descriptor each, re-read with pread) and the switch device's EV_SW
|
||||
word (EVIOCGSW) as fast as the loop allows, a few hundred hertz, and
|
||||
reports every transition of the watched lines with a timestamp, plus a
|
||||
per-line summary, so the meaning of the supply's line can be read off
|
||||
against what the chain and the supply were doing: idle, a dry run
|
||||
(HV_ENABLE follows the charge pump), an armed cut (LASER_ON, FIRE,
|
||||
hv_current), a pause and a resume, a lid open. hv_current comes from the
|
||||
PIC at a lower rate and rides along as a range.
|
||||
|
||||
PGOOD is reported as the RAW PIN LEVEL (the kernel's laser_pgood attribute
|
||||
is the logical, inverted value: 1 = pin low). Everything else is the
|
||||
kernel's logical value.
|
||||
|
||||
The loop must not hog the CPU: single core, the protocol thread is
|
||||
SCHED_OTHER, so the sampler sleeps between passes and reports its worst gap.
|
||||
|
||||
Usage: pgood_probe.py [--secs N] [--json FILE]
|
||||
Drive the machine from a sender or the button meanwhile; the probe only
|
||||
watches. GRBL mode, any state.
|
||||
"""
|
||||
import argparse
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
CNC = '/sys/glowforge/cnc/'
|
||||
ATTRS = [ # name, attribute, invert-to-raw
|
||||
('PGOOD', CNC + 'laser_pgood', True),
|
||||
('LASER_ON', CNC + 'laser_on', False),
|
||||
('FIRE', CNC + 'laser_enable', False),
|
||||
('CP_ALIVE', CNC + 'charge_pump_alive', False),
|
||||
]
|
||||
SWITCH_DEV = '/dev/input/event0'
|
||||
SW_BITS = [('HV_ENABLE', 4), ('DOORS', 3)] # EV_SW codes on the switch device
|
||||
EVIOCGSW = (2 << 30) | (8 << 16) | (0x45 << 8) | 0x1b
|
||||
HV_CURRENT = '/sys/glowforge/pic/hv_current'
|
||||
STATE = CNC + 'state'
|
||||
HV_HZ = 20.0
|
||||
SLEEP_S = 0.001
|
||||
|
||||
|
||||
def rd(fd):
|
||||
return os.pread(fd, 32, 0).strip()
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--secs', type=float, default=60.0)
|
||||
ap.add_argument('--json', default='')
|
||||
args = ap.parse_args()
|
||||
try:
|
||||
os.nice(5)
|
||||
except OSError:
|
||||
pass
|
||||
fds = [(n, os.open(p, os.O_RDONLY), inv) for n, p, inv in ATTRS]
|
||||
sw = os.open(SWITCH_DEV, os.O_RDONLY)
|
||||
hv_fd = os.open(HV_CURRENT, os.O_RDONLY)
|
||||
st_fd = os.open(STATE, os.O_RDONLY)
|
||||
|
||||
def sample():
|
||||
s = {}
|
||||
for n, fd, inv in fds:
|
||||
v = int(rd(fd) or b'0')
|
||||
s[n] = (1 - v) if inv else v
|
||||
buf = fcntl.ioctl(sw, EVIOCGSW, b'\0' * 8)
|
||||
for n, bit in SW_BITS:
|
||||
s[n] = (buf[bit >> 3] >> (bit & 7)) & 1
|
||||
return s
|
||||
|
||||
t0 = time.monotonic()
|
||||
state = sample()
|
||||
hv = int(rd(hv_fd) or b'0')
|
||||
print('t=%8.3f start %s hv=%d kstate=%s'
|
||||
% (0.0, ' '.join('%s=%d' % kv for kv in state.items()), hv, rd(st_fd).decode()))
|
||||
sys.stdout.flush()
|
||||
trans = []
|
||||
hv_log = []
|
||||
counts = {k: [0, 0] for k in state}
|
||||
worst_gap = 0.0
|
||||
n = 0
|
||||
next_hv = t0
|
||||
t_prev = t0
|
||||
while True:
|
||||
now = time.monotonic()
|
||||
if now - t0 > args.secs:
|
||||
break
|
||||
gap = now - t_prev
|
||||
if gap > worst_gap:
|
||||
worst_gap = gap
|
||||
t_prev = now
|
||||
new = sample()
|
||||
n += 1
|
||||
for k in new:
|
||||
if new[k] != state[k]:
|
||||
trans.append((now - t0, k, new[k]))
|
||||
print('t=%8.3f %-9s -> %d hv=%s kstate=%s'
|
||||
% (now - t0, k, new[k], rd(hv_fd).decode(), rd(st_fd).decode()))
|
||||
sys.stdout.flush()
|
||||
counts[k][new[k]] += 1
|
||||
state = new
|
||||
if now >= next_hv:
|
||||
hv_log.append((now - t0, int(rd(hv_fd) or b'0')))
|
||||
next_hv = now + 1.0 / HV_HZ
|
||||
time.sleep(SLEEP_S)
|
||||
secs = time.monotonic() - t0
|
||||
print('--- %.1f s, %d passes (%.0f Hz), worst gap %.1f ms' % (secs, n, n / secs, worst_gap * 1e3))
|
||||
for k, (c0, c1) in counts.items():
|
||||
print(' %-9s 0: %6.2f %% 1: %6.2f %%' % (k, 100.0 * c0 / n, 100.0 * c1 / n))
|
||||
if hv_log:
|
||||
vals = [v for _t, v in hv_log]
|
||||
lit = [t for t, v in hv_log if v > 20]
|
||||
print(' hv_current min %d max %d; > 20 raw for %.1f s%s'
|
||||
% (min(vals), max(vals), len(lit) / HV_HZ,
|
||||
(' (%.1f .. %.1f s)' % (lit[0], lit[-1])) if lit else ''))
|
||||
if args.json:
|
||||
with open(args.json, 'w') as f:
|
||||
json.dump({'secs': secs, 'passes': n, 'worst_gap_ms': worst_gap * 1e3,
|
||||
'counts': counts, 'transitions': trans, 'hv': hv_log}, f)
|
||||
print('record: %s' % args.json)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user