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}