code wiki / _hdl_build / nx_jam_check.nx
nx_jam_check.nx source
↩ module page · 196 lines · 9341 B
1// nx_jam_check.nx -- JAM-PHYSICS PRE-FLIGHT GATE (operator: the QIDI X-Max 3 "constantly jams";
2// identify WHERE the hardware is failing to prevent it, "aka too cold or what"). The #1 jam physics:
3// a move DEMANDS volumetric flow (mm^3/s) = extruded filament volume / move time; the hotend can only
4// MELT a temperature-dependent maximum. Demand > capacity -> under-extrusion -> heatbreak back-pressure
5// -> JAM. QIDIStudio/slicers don't gate on this; we do, per G-code line, with the exact cause and fix.
6//
7// Scans G-code text: tracks M104/M109 S<temp> + G1 F/X/Y/E; for each extruding move computes
8// flow in MILLI-mm^3/s (integer fixed-point, 1.75mm filament cross-section = 2.405 mm^2) and
9// compares against a DATA-DRIVEN capacity table (Rule 11: profile data, not magic numbers).
10// Verdicts name the line, the demanded flow, the capacity, and the FIX (temp to set OR that
11// it's beyond the hotend = slow down). Also counts retraction churn (heat-creep advisory).
12//
13// HONEST BOUNDARY: the PLA capacity table is PROVISIONAL (community-class values); the team's
14// A7 calibration print measures OUR X-Max 3's real curve and replaces the table -- the GATE
15// LOGIC + parser + math are exact and KAT-tested here regardless of the table values.
16// LAWS: struct-free, integer-only, no floats; composes with nx_klipper_gcode_validator (syntax)
17// as the physics gate beside it. license_tier: ORIGINAL
18import "nx_syscalls.nx"
19import "nx_vecmath.nx"
20const JC_MAGIC_6000: i64 = 6000
21const JC_MAGIC_8000: i64 = 8000
22const JC_MAGIC_10000: i64 = 10000
23const JC_MAGIC_11500: i64 = 11500
24const JC_MAGIC_13000: i64 = 13000
25const JC_MAGIC_60000: i64 = 60000
26
27// verdict codes (structured, machine-readable)
28const JC_OK: i64 = 0
29const JC_TOO_COLD: i64 = 1 // demand > capacity at set temp, but a hotter table entry covers it
30const JC_TOO_FAST: i64 = 2 // demand > capacity even at the table's max temp -> must slow down
31const JC_NO_TEMP: i64 = 3 // extruding move before any M104/M109 -> cold extrusion = certain jam
32
33// filament cross-section for 1.75mm: pi * 0.875^2 = 2.40528 mm^2 -> 2405 micro-mm^2-per-um scale:
34// volume_milli_mm3 = e_um * 2405 / 1000
35const JC_XSEC_MILLI: i64 = 2405
36
37// ---- PROVISIONAL PLA capacity table (temp C -> max flow in milli-mm^3/s) ----
38// Community-class values for a stock high-flow-ish 0.4mm hotend; REPLACE with A7-measured curve.
39const JC_NTAB: i64 = 5
40func jc_tab_temp(i: i64) -> i64 {
41 if i == 0 { return 190 }
42 if i == 1 { return 200 }
43 if i == 2 { return 210 }
44 if i == 3 { return 220 }
45 return 230
46}
47func jc_tab_cap(i: i64) -> i64 {
48 if i == 0 { return JC_MAGIC_6000 }
49 if i == 1 { return JC_MAGIC_8000 }
50 if i == 2 { return JC_MAGIC_10000 }
51 if i == 3 { return JC_MAGIC_11500 }
52 return JC_MAGIC_13000
53}
54
55// capacity at temp (linear interpolation; clamp below/above table ends)
56func jc_capacity_milli(temp_c: i64) -> i64 {
57 if temp_c <= jc_tab_temp(0) { return jc_tab_cap(0) }
58 if temp_c >= jc_tab_temp(JC_NTAB - 1) { return jc_tab_cap(JC_NTAB - 1) }
59 var i: i64 = 1
60 while i < JC_NTAB {
61 if temp_c <= jc_tab_temp(i) {
62 let t0: i64 = jc_tab_temp(i - 1); let t1: i64 = jc_tab_temp(i)
63 let c0: i64 = jc_tab_cap(i - 1); let c1: i64 = jc_tab_cap(i)
64 return c0 + ((c1 - c0) * (temp_c - t0)) / (t1 - t0)
65 }
66 i = i + 1
67 }
68 return jc_tab_cap(JC_NTAB - 1)
69}
70
71// lowest TABLE temp whose capacity covers the demanded flow; -1 if none (must slow down)
72func jc_temp_for_flow(flow_milli: i64) -> i64 {
73 var i: i64 = 0
74 while i < JC_NTAB {
75 if jc_tab_cap(i) >= flow_milli { return jc_tab_temp(i) }
76 i = i + 1
77 }
78 return 0 - 1
79}
80
81// integer sqrt (Newton) for XY distance
82func jc_isqrt(x: i64) -> i64 { return vm_isqrt(x) }
83
84// ---- G-code text scanning helpers (integer micro-units: 1 unit = 0.001 of the printed token) ----
85
86// parse a decimal number "123.456" / "-0.33" at pos -> value in thousandths; advances pos
87func jc_parse_milli(src: *u8, pos: *i64, n: i64) -> i64 {
88 var p: i64 = pos[0]
89 var neg: i64 = 0
90 if p < n { if src[p] == (45 as u8) { neg = 1; p = p + 1 } }
91 var ip: i64 = 0
92 while p < n {
93 let c: i64 = src[p] as i64
94 if c >= 48 && c <= 57 { ip = ip * 10 + (c - 48); p = p + 1 } else { break }
95 }
96 var fr: i64 = 0
97 var fd: i64 = 0
98 if p < n { if src[p] == (46 as u8) { // '.'
99 p = p + 1
100 while p < n {
101 let d: i64 = src[p] as i64
102 if d >= 48 && d <= 57 {
103 if fd < 3 { fr = fr * 10 + (d - 48); fd = fd + 1 }
104 p = p + 1 // consume extra digits beyond 3
105 } else { break }
106 }
107 } }
108 while fd < 3 { fr = fr * 10; fd = fd + 1 } // right-pad to thousandths
109 pos[0] = p
110 var v: i64 = ip * 1000 + fr
111 if neg == 1 { v = 0 - v }
112 return v
113}
114
115// ---- the gate: scan gcode[0..n), write worst verdict + detail into out (i64[8]) ----
116// out[0]=verdict out[1]=line out[2]=flow_milli out[3]=cap_milli out[4]=fix_temp out[5]=n_extr_moves
117// out[6]=n_retracts out[7]=worst_flow_milli ; returns out[0]
118func jc_scan(g: *u8, n: i64, out: *i64) -> i64 {
119 var temp: i64 = 0 - 1 // no temp set yet
120 var feed_mm_min: i64 = 0 // F (mm/min, milli-scaled /1000)
121 var x: i64 = 0; var y: i64 = 0 // current pos, um
122 var line: i64 = 1
123 var worst: i64 = JC_OK
124 var wline: i64 = 0; var wflow: i64 = 0; var wcap: i64 = 0; var wfix: i64 = 0
125 var nextr: i64 = 0; var nretr: i64 = 0; var maxflow: i64 = 0
126 var i: i64 = 0
127 while i < n {
128 // line start: classify
129 var isG1: i64 = 0
130 var isM10x: i64 = 0
131 if g[i] == (77 as u8) { // 'M'
132 if i + 4 < n {
133 if g[i+1] == (49 as u8) { if g[i+2] == (48 as u8) {
134 if g[i+3] == (52 as u8) { isM10x = 1 } // M104
135 if g[i+3] == (57 as u8) { isM10x = 1 } // M109
136 } }
137 }
138 }
139 if g[i] == (71 as u8) { if i + 1 < n { if g[i+1] == (49 as u8) { isG1 = 1 } } } // 'G1'
140 // walk the line, harvesting words
141 var nx2: i64 = x; var ny: i64 = y; var e_milli: i64 = 0; var has_e: i64 = 0
142 var j: i64 = i
143 while j < n {
144 if g[j] == (10 as u8) { break } // end of line
145 let c: i64 = g[j] as i64
146 if isM10x == 1 { if c == 83 { // 'S'
147 let pb: *i64 = sys_mmap(16) as *i64; pb[0] = j + 1
148 temp = jc_parse_milli(g, pb, n) / 1000
149 j = pb[0] - 1
150 } }
151 if isG1 == 1 {
152 if c == 70 { let pb1: *i64 = sys_mmap(16) as *i64; pb1[0] = j + 1; feed_mm_min = jc_parse_milli(g, pb1, n); j = pb1[0] - 1 } // F
153 if c == 88 { let pb2: *i64 = sys_mmap(16) as *i64; pb2[0] = j + 1; nx2 = jc_parse_milli(g, pb2, n); j = pb2[0] - 1 } // X -> um
154 if c == 89 { let pb3: *i64 = sys_mmap(16) as *i64; pb3[0] = j + 1; ny = jc_parse_milli(g, pb3, n); j = pb3[0] - 1 } // Y -> um
155 if c == 69 { let pb4: *i64 = sys_mmap(16) as *i64; pb4[0] = j + 1; e_milli = jc_parse_milli(g, pb4, n); has_e = 1; j = pb4[0] - 1 } // E -> um
156 }
157 j = j + 1
158 }
159 // evaluate the move
160 if isG1 == 1 { if has_e == 1 {
161 if e_milli < 0 { nretr = nretr + 1 }
162 if e_milli > 0 {
163 nextr = nextr + 1
164 let dx: i64 = nx2 - x; let dy: i64 = ny - y
165 let dist_um: i64 = jc_isqrt(dx*dx + dy*dy)
166 if dist_um > 0 { if feed_mm_min > 0 {
167 // time_ms = dist_um * 60 / (feed_mm_min/1000) = dist_um * 60000 / feed_milli
168 let time_ms: i64 = (dist_um * JC_MAGIC_60000) / feed_mm_min
169 if time_ms > 0 {
170 let vol_milli: i64 = (e_milli * JC_XSEC_MILLI) / 1000
171 let flow_milli: i64 = (vol_milli * 1000) / time_ms
172 if flow_milli > maxflow { maxflow = flow_milli }
173 if temp < 0 {
174 if worst < JC_NO_TEMP { worst = JC_NO_TEMP; wline = line; wflow = flow_milli; wcap = 0; wfix = jc_tab_temp(0) }
175 }
176 if temp >= 0 {
177 let cap: i64 = jc_capacity_milli(temp)
178 if flow_milli > cap {
179 let fix: i64 = jc_temp_for_flow(flow_milli)
180 if fix >= 0 { if worst < JC_TOO_COLD { worst = JC_TOO_COLD; wline = line; wflow = flow_milli; wcap = cap; wfix = fix } }
181 if fix < 0 { if worst < JC_TOO_FAST { worst = JC_TOO_FAST; wline = line; wflow = flow_milli; wcap = cap; wfix = 0 - 1 } }
182 }
183 }
184 }
185 } }
186 }
187 } }
188 x = nx2; y = ny
189 // advance to next line
190 while i < n { if g[i] == (10 as u8) { i = i + 1; break } i = i + 1 }
191 line = line + 1
192 }
193 out[0] = worst; out[1] = wline; out[2] = wflow; out[3] = wcap
194 out[4] = wfix; out[5] = nextr; out[6] = nretr; out[7] = maxflow
195 return worst
196}