code wiki / (root) / nx_pid.nx

nx_pid.nx source

↩ module page · 83 lines · 2776 B

1// nx_pid.nx -- PID controller (proportional-integral-derivative). 2// 3// u(t) = Kp*e + Ki*integral(e) + Kd*derivative(e) 4// 5// Closes the loop from Kalman state estimation to control actuation. 6// Pure i64 with Q14 gains. 7// 8// What this unlocks: 9// + temperature / motor / position control 10// + auto-stabilization (drone attitude, robot balance) 11// + feedback loops with arbitrary measurement source 12// + paired with Kalman: estimate state -> drive actuator 13// 14// Anti-windup: integral term is clamped to [-i_limit, +i_limit] so 15// long error sustains don't destroy the controller. 16// 17// genealogy_id: ziegler_nichols_1942 + minorsky_1922_first_pid 18// lineage_id: feedback_control_loop 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "syscalls.nx" 27 28const NX_PID_Q: i64 = 16384 // Q14 29 30struct PID { 31 kp: i64, // Q14 proportional gain 32 ki: i64, // Q14 integral gain 33 kd: i64, // Q14 derivative gain 34 integral: i64, // accumulated error 35 prev_error: i64, 36 has_prev: i64, 37 i_limit: i64, // anti-windup clamp 38} 39 40func nx_pid_init(p: *PID, kp_q14: i64, ki_q14: i64, kd_q14: i64, 41 i_limit: i64) -> i64 { 42 p.kp = kp_q14 43 p.ki = ki_q14 44 p.kd = kd_q14 45 p.integral = 0 46 p.prev_error = 0 47 p.has_prev = 0 48 p.i_limit = i_limit 49 return 0 50} 51 52func nx_pid_reset(p: *PID) -> i64 { 53 p.integral = 0 54 p.prev_error = 0 55 p.has_prev = 0 56 return 0 57} 58 59// Compute control signal. setpoint = desired value, measured = current. 60// Returns control output (raw, not clamped; caller may clamp to actuator range). 61 62func nx_pid_update(p: *PID, setpoint: i64, measured: i64) -> i64 { 63 let error: i64 = setpoint - measured 64 65 // Integral with anti-windup clamp. 66 p.integral = p.integral + error 67 if p.integral > p.i_limit { p.integral = p.i_limit } 68 if p.integral < -p.i_limit { p.integral = -p.i_limit } 69 70 // Derivative (zero on first call). 71 var derivative: i64 = 0 72 if p.has_prev == 1 { 73 derivative = error - p.prev_error 74 } 75 p.prev_error = error 76 p.has_prev = 1 77 78 // u = Kp*e + Ki*integral + Kd*derivative, gains in Q14. 79 let p_term: i64 = (p.kp * error) / NX_PID_Q 80 let i_term: i64 = (p.ki * p.integral) / NX_PID_Q 81 let d_term: i64 = (p.kd * derivative) / NX_PID_Q 82 return p_term + i_term + d_term 83}