code wiki / _hdl_build / nx_jam_check.nx
nx_jam_check.nx source
↩ module page · 208 lines · 9552 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"
19const JC_MAGIC_6000: i64 = 6000
20const JC_MAGIC_8000: i64 = 8000
21const JC_MAGIC_10000: i64 = 10000
22const JC_MAGIC_11500: i64 = 11500
23const JC_MAGIC_13000: i64 = 13000
24const JC_MAGIC_60000: i64 = 60000
25
26// verdict codes (structured, machine-readable)
27const JC_OK: i64 = 0
28const JC_TOO_COLD: i64 = 1 // demand > capacity at set temp, but a hotter table entry covers it
29const JC_TOO_FAST: i64 = 2 // demand > capacity even at the table's max temp -> must slow down
30const JC_NO_TEMP: i64 = 3 // extruding move before any M104/M109 -> cold extrusion = certain jam
31
32// filament cross-section for 1.75mm: pi * 0.875^2 = 2.40528 mm^2 -> 2405 micro-mm^2-per-um scale:
33// volume_milli_mm3 = e_um * 2405 / 1000
34const JC_XSEC_MILLI: i64 = 2405
35
36// ---- PROVISIONAL PLA capacity table (temp C -> max flow in milli-mm^3/s) ----
37// Community-class values for a stock high-flow-ish 0.4mm hotend; REPLACE with A7-measured curve.
38const JC_NTAB: i64 = 5
39func jc_tab_temp(i: i64) -> i64 {
40 if i == 0 { return 190 }
41 if i == 1 { return 200 }
42 if i == 2 { return 210 }
43 if i == 3 { return 220 }
44 return 230
45}
46func jc_tab_cap(i: i64) -> i64 {
47 if i == 0 { return JC_MAGIC_6000 }
48 if i == 1 { return JC_MAGIC_8000 }
49 if i == 2 { return JC_MAGIC_10000 }
50 if i == 3 { return JC_MAGIC_11500 }
51 return JC_MAGIC_13000
52}
53
54// capacity at temp (linear interpolation; clamp below/above table ends)
55func jc_capacity_milli(temp_c: i64) -> i64 {
56 if temp_c <= jc_tab_temp(0) { return jc_tab_cap(0) }
57 if temp_c >= jc_tab_temp(JC_NTAB - 1) { return jc_tab_cap(JC_NTAB - 1) }
58 var i: i64 = 1
59 while i < JC_NTAB {
60 if temp_c <= jc_tab_temp(i) {
61 let t0: i64 = jc_tab_temp(i - 1); let t1: i64 = jc_tab_temp(i)
62 let c0: i64 = jc_tab_cap(i - 1); let c1: i64 = jc_tab_cap(i)
63 return c0 + ((c1 - c0) * (temp_c - t0)) / (t1 - t0)
64 }
65 i = i + 1
66 }
67 return jc_tab_cap(JC_NTAB - 1)
68}
69
70// lowest TABLE temp whose capacity covers the demanded flow; -1 if none (must slow down)
71func jc_temp_for_flow(flow_milli: i64) -> i64 {
72 var i: i64 = 0
73 while i < JC_NTAB {
74 if jc_tab_cap(i) >= flow_milli { return jc_tab_temp(i) }
75 i = i + 1
76 }
77 return 0 - 1
78}
79
80// integer sqrt (Newton) for XY distance
81func jc_isqrt(x: i64) -> i64 {
82 if x <= 0 { return 0 }
83 var r: i64 = x
84 var p: i64 = 0
85 if r > 1 { r = x / 2 }
86 var n: i64 = 0
87 while n < 64 {
88 p = r
89 r = (r + x / r) / 2
90 if r >= p { n = 64 }
91 if n != 64 { n = n + 1 }
92 }
93 return p
94}
95
96// ---- G-code text scanning helpers (integer micro-units: 1 unit = 0.001 of the printed token) ----
97
98// parse a decimal number "123.456" / "-0.33" at pos -> value in thousandths; advances pos
99func jc_parse_milli(src: *u8, pos: *i64, n: i64) -> i64 {
100 var p: i64 = pos[0]
101 var neg: i64 = 0
102 if p < n { if src[p] == (45 as u8) { neg = 1; p = p + 1 } }
103 var ip: i64 = 0
104 while p < n {
105 let c: i64 = src[p] as i64
106 if c >= 48 && c <= 57 { ip = ip * 10 + (c - 48); p = p + 1 } else { break }
107 }
108 var fr: i64 = 0
109 var fd: i64 = 0
110 if p < n { if src[p] == (46 as u8) { // '.'
111 p = p + 1
112 while p < n {
113 let d: i64 = src[p] as i64
114 if d >= 48 && d <= 57 {
115 if fd < 3 { fr = fr * 10 + (d - 48); fd = fd + 1 }
116 p = p + 1 // consume extra digits beyond 3
117 } else { break }
118 }
119 } }
120 while fd < 3 { fr = fr * 10; fd = fd + 1 } // right-pad to thousandths
121 pos[0] = p
122 var v: i64 = ip * 1000 + fr
123 if neg == 1 { v = 0 - v }
124 return v
125}
126
127// ---- the gate: scan gcode[0..n), write worst verdict + detail into out (i64[8]) ----
128// out[0]=verdict out[1]=line out[2]=flow_milli out[3]=cap_milli out[4]=fix_temp out[5]=n_extr_moves
129// out[6]=n_retracts out[7]=worst_flow_milli ; returns out[0]
130func jc_scan(g: *u8, n: i64, out: *i64) -> i64 {
131 var temp: i64 = 0 - 1 // no temp set yet
132 var feed_mm_min: i64 = 0 // F (mm/min, milli-scaled /1000)
133 var x: i64 = 0; var y: i64 = 0 // current pos, um
134 var line: i64 = 1
135 var worst: i64 = JC_OK
136 var wline: i64 = 0; var wflow: i64 = 0; var wcap: i64 = 0; var wfix: i64 = 0
137 var nextr: i64 = 0; var nretr: i64 = 0; var maxflow: i64 = 0
138 var i: i64 = 0
139 while i < n {
140 // line start: classify
141 var isG1: i64 = 0
142 var isM10x: i64 = 0
143 if g[i] == (77 as u8) { // 'M'
144 if i + 4 < n {
145 if g[i+1] == (49 as u8) { if g[i+2] == (48 as u8) {
146 if g[i+3] == (52 as u8) { isM10x = 1 } // M104
147 if g[i+3] == (57 as u8) { isM10x = 1 } // M109
148 } }
149 }
150 }
151 if g[i] == (71 as u8) { if i + 1 < n { if g[i+1] == (49 as u8) { isG1 = 1 } } } // 'G1'
152 // walk the line, harvesting words
153 var nx2: i64 = x; var ny: i64 = y; var e_milli: i64 = 0; var has_e: i64 = 0
154 var j: i64 = i
155 while j < n {
156 if g[j] == (10 as u8) { break } // end of line
157 let c: i64 = g[j] as i64
158 if isM10x == 1 { if c == 83 { // 'S'
159 let pb: *i64 = sys_mmap(16) as *i64; pb[0] = j + 1
160 temp = jc_parse_milli(g, pb, n) / 1000
161 j = pb[0] - 1
162 } }
163 if isG1 == 1 {
164 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
165 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
166 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
167 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
168 }
169 j = j + 1
170 }
171 // evaluate the move
172 if isG1 == 1 { if has_e == 1 {
173 if e_milli < 0 { nretr = nretr + 1 }
174 if e_milli > 0 {
175 nextr = nextr + 1
176 let dx: i64 = nx2 - x; let dy: i64 = ny - y
177 let dist_um: i64 = jc_isqrt(dx*dx + dy*dy)
178 if dist_um > 0 { if feed_mm_min > 0 {
179 // time_ms = dist_um * 60 / (feed_mm_min/1000) = dist_um * 60000 / feed_milli
180 let time_ms: i64 = (dist_um * JC_MAGIC_60000) / feed_mm_min
181 if time_ms > 0 {
182 let vol_milli: i64 = (e_milli * JC_XSEC_MILLI) / 1000
183 let flow_milli: i64 = (vol_milli * 1000) / time_ms
184 if flow_milli > maxflow { maxflow = flow_milli }
185 if temp < 0 {
186 if worst < JC_NO_TEMP { worst = JC_NO_TEMP; wline = line; wflow = flow_milli; wcap = 0; wfix = jc_tab_temp(0) }
187 }
188 if temp >= 0 {
189 let cap: i64 = jc_capacity_milli(temp)
190 if flow_milli > cap {
191 let fix: i64 = jc_temp_for_flow(flow_milli)
192 if fix >= 0 { if worst < JC_TOO_COLD { worst = JC_TOO_COLD; wline = line; wflow = flow_milli; wcap = cap; wfix = fix } }
193 if fix < 0 { if worst < JC_TOO_FAST { worst = JC_TOO_FAST; wline = line; wflow = flow_milli; wcap = cap; wfix = 0 - 1 } }
194 }
195 }
196 }
197 } }
198 }
199 } }
200 x = nx2; y = ny
201 // advance to next line
202 while i < n { if g[i] == (10 as u8) { i = i + 1; break } i = i + 1 }
203 line = line + 1
204 }
205 out[0] = worst; out[1] = wline; out[2] = wflow; out[3] = wcap
206 out[4] = wfix; out[5] = nextr; out[6] = nretr; out[7] = maxflow
207 return worst
208}