code wiki / _hdl_build / nx_printer_ctl_test.nx

nx_printer_ctl_test.nx source

↩ module page · 90 lines · 5469 B

1// nx_printer_ctl_test.nx -- ACCEPTANCE GATE for the sovereign-printer control substrate 2// (nx_motion_plan + nx_heater_pid). Motion KATs are exact-integer by construction: 3// TRAPEZOID: dist=100mm vmax=50mm/s acc=500mm/s^2 -> d_acc=2500um t_acc=100ms cruise 95mm 4// t_cruise=1900ms total=2100ms 5// TRIANGLE: dist=4mm acc=400mm/s^2 (acc*dist = 1.6e9 = 40000^2 exact) -> v_peak=40000um/s 6// d_acc=2000um t_acc=100ms total=200ms 7// Thermal gates are PROPERTIES of the deterministic sim (reach the band, stay settled, bounded 8// overshoot, anti-windup engaged) -- machine-checked, no hand-waving. license_tier: ORIGINAL 9import "nx_motion_plan.nx" 10import "nx_heater_pid.nx" 11import "nx_syscalls.nx" 12 13func pt_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 14func pt_putn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1} sys_write(1,bb,k); return 0 } 15 16func main() -> i64 { 17 var pass: i64 = 0 18 var total: i64 = 0 19 let out: *i64 = sys_mmap(64) as *i64 20 21 // T1: trapezoid plan, every field exact 22 total = total + 1 23 var t1: i64 = 1 24 if mp_plan(100000, 50000, 500000, out) != MP_TRAPEZOID { t1 = 0 } 25 if out[1] != 50000 { t1 = 0 } // v_peak = vmax 26 if out[2] != 2500 { t1 = 0 } // d_acc um 27 if out[3] != 95000 { t1 = 0 } // d_cruise um 28 if out[4] != 100 { t1 = 0 } // t_acc ms 29 if out[5] != 1900 { t1 = 0 } // t_cruise ms 30 if out[6] != 2100 { t1 = 0 } // total ms 31 if t1 == 1 { pass = pass + 1 } else { pt_puts("T1 FAIL trap " as *u8); pt_putn(out[1]); pt_puts("," as *u8); pt_putn(out[2]); pt_puts("," as *u8); pt_putn(out[3]); pt_puts("," as *u8); pt_putn(out[4]); pt_puts("," as *u8); pt_putn(out[5]); pt_puts("," as *u8); pt_putn(out[6]); pt_puts("\n" as *u8) } 32 33 // T2: triangle plan (move too short to reach vmax), exact via 40000^2 34 total = total + 1 35 var t2: i64 = 1 36 if mp_plan(4000, 50000, 400000, out) != MP_TRIANGLE { t2 = 0 } 37 if out[1] != 40000 { t2 = 0 } // v_peak = sqrt(400000*4000) exact 38 if out[2] != 2000 { t2 = 0 } // half distance 39 if out[4] != 100 { t2 = 0 } // t_acc ms 40 if out[6] != 200 { t2 = 0 } // total ms 41 if t2 == 1 { pass = pass + 1 } else { pt_puts("T2 FAIL tri " as *u8); pt_putn(out[1]); pt_puts("," as *u8); pt_putn(out[2]); pt_puts("," as *u8); pt_putn(out[4]); pt_puts("," as *u8); pt_putn(out[6]); pt_puts("\n" as *u8) } 42 43 // T3: defensive boundary -- zero/negative inputs refuse 44 total = total + 1 45 var t3: i64 = 1 46 if mp_plan(0, 50000, 400000, out) != MP_BAD_INPUT { t3 = 0 } 47 if mp_plan(1000, 0, 400000, out) != MP_BAD_INPUT { t3 = 0 } 48 if mp_plan(1000, 50000, 0 - 5, out) != MP_BAD_INPUT { t3 = 0 } 49 if t3 == 1 { pass = pass + 1 } else { pt_puts("T3 FAIL guard\n" as *u8) } 50 51 // T4: steps + cruise interval -- 100mm @ 80 steps/mm = 8000 steps; 50mm/s @ 80 -> 4000 steps/s -> 250us 52 total = total + 1 53 var t4: i64 = 1 54 if mp_steps(100000, 80) != 8000 { t4 = 0 } 55 if mp_cruise_interval_us(50000, 80) != 250 { t4 = 0 } 56 if t4 == 1 { pass = pass + 1 } else { pt_puts("T4 FAIL steps\n" as *u8) } 57 58 // ---- thermal: PLA hotend scenario, ambient 25C, target 200C (milli-degC) ---- 59 // plant: k_heat=40 (full PWM 255 -> +10.2C/tick), k_loss=50 (5% of delta lost per tick); 60 // equilibrium PWM at 200C = (175000*50/1000)/40 = 219. With +/-10C integral separation the 61 // P term ALONE must reach within 10C (kp=25000 -> droop = 8.33C < 10C, derived: 20e = T-25000), 62 // then the integral (ki=200, needs acc ~1.1e6 < clamp 2e6) carries the last 8C to the band. 63 64 // T5: closed loop reaches the +/-2C band and SETTLES (stays to the end of 1500 ticks) 65 total = total + 1 66 let stayed: i64 = hp_sim(200000, 25000, 25000, 25000, 200, 20000, 2000000, 40, 50, 2000, 1500, out) 67 var t5: i64 = 1 68 if stayed != 1 { t5 = 0 } 69 if out[2] < 0 { t5 = 0 } 70 if t5 == 1 { pass = pass + 1 } else { pt_puts("T5 FAIL settle final=" as *u8); pt_putn(out[0]); pt_puts(" max=" as *u8); pt_putn(out[1]); pt_puts(" firstin=" as *u8); pt_putn(out[2]); pt_puts(" stayed=" as *u8); pt_putn(out[3]); pt_puts("\n" as *u8) } 71 72 // T6: overshoot bounded -- never beyond target + 8C on the heat-up 73 total = total + 1 74 if out[1] <= 208000 { pass = pass + 1 } else { pt_puts("T6 FAIL overshoot max=" as *u8); pt_putn(out[1]); pt_puts("\n" as *u8) } 75 76 // T7: anti-windup engaged -- with a TINY clamp (integral nearly disabled) the loop must 77 // still never lock high: max T below target+8C even though it can no longer settle tight 78 total = total + 1 79 hp_sim(200000, 25000, 25000, 25000, 200, 20000, 50000, 40, 50, 2000, 1500, out) 80 if out[1] <= 208000 { pass = pass + 1 } else { pt_puts("T7 FAIL windup max=" as *u8); pt_putn(out[1]); pt_puts("\n" as *u8) } 81 82 // T8: cold plant with PWM 0 stays at ambient (plant sanity) 83 total = total + 1 84 if hp_plant_tick(25000, 25000, 0, 40, 50) == 25000 { pass = pass + 1 } else { pt_puts("T8 FAIL plant\n" as *u8) } 85 86 pt_puts("PRINTER-CTL " as *u8); pt_putn(pass); pt_puts("/" as *u8); pt_putn(total); pt_puts("\n" as *u8) 87 if pass == total { pt_puts("PRINTER-CTL ALL-PASS\n" as *u8); sys_exit(0) } 88 sys_exit(1) 89 return 1 90}