模型预测控制MPC实战:从PID瓶颈到QP求解与轨迹跟踪
1. 从一个倒立摆说起为什么PID搞不定MPC能搞定如果你做过倒立摆、无人机悬停或者自动驾驶小车的轨迹跟踪大概率经历过这样的场景PID参数调了一整天小车勉强能走直线但一遇到弯道就画龙速度一快直接冲出赛道。你开始加前馈、加滤波、加增益调度系统越来越复杂效果却越来越玄学。这不是你调参水平不行而是PID这类无模型控制器有一个根本性的天花板它只看当前误差不看未来。PID的脑子里没有我接下来三秒会走到哪里这个概念它只知道现在偏了往反方向打一点。对于有惯性、有约束、有延迟的系统这种事后补救式的控制天然吃亏。模型预测控制Model Predictive ControlMPC换了一个完全不同的思路它不再问现在该怎么修正而是问未来N步里我怎么做才能让整条轨迹最接近目标同时还不违反任何限制。这个思路的转变是MPC在过去几十年里从化工过程控制一路杀到自动驾驶、机器人、能源管理领域的根本原因。我最初接触MPC是在一个四旋翼轨迹跟踪项目上。当时用串级PID飞8字轨迹横向误差始终在0.3米左右晃怎么调都下不去。换成MPC之后同样的动力学模型误差直接压到0.05米以内而且速度提升时误差几乎没有明显恶化。这个对比让我意识到MPC不是更好的PID它是另一个维度的东西。这篇文章面向的是想真正把MPC跑起来的工程师和学生。我不会只给你一堆公式然后说求解即可而是会从最核心的预测原理讲起把二次规划Quadratic ProgrammingQP这个求解器层面的东西拆开最后用一个完整的轨迹跟踪案例把建模、离散化、QP构造、滚动优化、参数调节全部走一遍。代码用Python写依赖cvxpy和numpy你可以直接复制运行。提示本文假设你有基本的线性代数和控制理论背景知道状态空间方程是什么但不需要你事先学过MPC。如果你连矩阵乘法都忘了建议先补一下再回来。2. MPC的核心机制预测、优化、滚动三步拆解2.1 预测模型MPC的水晶球从哪来MPC之所以能看未来靠的是一个显式的系统模型。这个模型告诉你如果当前状态是x施加控制量u下一步状态会变成什么。对于线性系统最常见的形式是离散状态空间方程x(k1) A * x(k) B * u(k) y(k) C * x(k) D * u(k)这里的A、B、C、D就是你的模型矩阵。对于倒立摆x可能包含小车位置、小车速度、摆杆角度、摆杆角速度四个量u就是施加在小车上的力。对于自动驾驶小车x可能是横向偏差、航向偏差、速度等u是前轮转角或加速度。关键点在于这个模型必须是显式的。PID不需要模型所以它通用性强但性能有限MPC需要模型所以它性能强但依赖建模质量。我见过太多人MPC效果不好最后发现是模型本身就不准——比如忽略了执行器延迟或者线性化点选得太远。预测的过程就是把模型往前推N步。假设当前时刻为k我们想知道未来N步的状态。把模型反复代入x(k1) A*x(k) B*u(k) x(k2) A*x(k1) B*u(k1) A^2*x(k) A*B*u(k) B*u(k1) ... x(kN) A^N*x(k) A^(N-1)*B*u(k) ... B*u(kN-1)你会发现未来所有状态都可以写成当前状态x(k)的贡献加上未来控制序列u(k), u(k1), ..., u(kN-1)的贡献。这就是MPC能把问题转化为优化问题的关键未来状态是控制序列的线性函数。2.2 代价函数什么叫好的轨迹有了预测能力接下来要定义什么叫好。MPC的代价函数通常长这样J sum_{i1}^{N} [ (y(ki) - y_ref(ki))^T * Q * (y(ki) - y_ref(ki)) ] sum_{i0}^{N-1} [ u(ki)^T * R * u(ki) ] sum_{i0}^{N-1} [ Δu(ki)^T * S * Δu(ki) ]三项分别对应跟踪误差项让输出y尽量贴近参考轨迹y_ref。Q是权重矩阵Q越大跟踪越紧。控制量惩罚项让控制量u不要太大。R越大控制越懒动作越柔和。控制增量惩罚项让控制量的变化Δu不要太大。S越大控制越平滑执行器磨损越小。这三个权重矩阵的调节是MPC工程实践中最耗时间的部分。我个人的经验是先把R和S设得很小只调Q让跟踪效果出来然后逐步加大R观察控制量是否进入合理范围最后加S来平滑控制信号。不要一上来三个一起调你会疯的。2.3 约束MPC真正的杀手锏如果说预测和优化只是让MPC更聪明那约束处理才是MPC真正不可替代的地方。PID无法显式处理约束你只能在调参时小心一点或者加饱和限幅——但饱和限幅是非线性的会破坏系统稳定性分析。MPC把约束直接写进优化问题u_min u(ki) u_max (控制量约束) Δu_min Δu(ki) Δu_max (控制增量约束) y_min y(ki) y_max (输出约束) x_min x(ki) x_max (状态约束)这些约束可以是硬约束必须满足也可以是软约束允许偶尔违反但加惩罚。在轨迹跟踪场景里输出约束特别有用比如小车必须在赛道内那么横向偏差y_min和y_max就是赛道边界。MPC会自动在预测范围内找到一个既跟踪参考轨迹、又不越界的控制序列。注意约束加得越多QP求解越容易不可行infeasible。实际工程中输出约束通常做成软约束控制量和控制增量约束做成硬约束。这个取舍后面会详细讲。2.4 滚动优化为什么只执行第一步MPC在每个采样时刻求解一个N步的优化问题得到最优控制序列u*(k), u*(k1), ..., u*(kN-1)。但它只执行第一个控制量u*(k)然后到下一个时刻用新的状态重新求解。这个滚动机制是MPC鲁棒性的来源。即使模型有误差、即使有外部扰动每个时刻都在用实际状态重新规划误差不会累积。你可以把它理解成每走一步就重新看一次地图而不是出发前规划好全程然后闭眼走。滚动优化的代价是计算量。每个采样周期都要解一个QP如果N很大、状态维度很高实时性会成为问题。这也是为什么MPC早期只用在化工过程控制采样周期以分钟计直到最近十几年随着求解器效率和硬件性能提升才在机器人、自动驾驶这些毫秒级场景普及。3. 把MPC变成QP二次规划的构造过程3.1 为什么是二次规划MPC的代价函数是二次型误差平方和约束是线性的对于线性MPC所以整个优化问题就是一个标准的二次规划问题min 0.5 * z^T * H * z f^T * z s.t. G * z h A_eq * z b_eq其中z是决策变量通常取未来N步的控制增量序列Δu(k), Δu(k1), ..., Δu(kN-1)。H是海森矩阵对称正定f是线性项G和h描述不等式约束。QP的好处是有成熟、高效的求解器而且对于凸QP局部最优就是全局最优。你不需要担心陷入局部极小值这在非线性MPC里是个大问题但在线性MPC里不存在。3.2 从状态空间到QP矩阵的推导这一步是很多人卡住的地方。我把它拆开讲。首先把预测方程写成矩阵形式。定义X [x(k1); x(k2); ...; x(kN)] (N*n_x 维) U [u(k); u(k1); ...; u(kN-1)] (N*n_u 维)那么X A_bar * x(k) B_bar * U其中A_bar和B_bar是由A、B反复代入得到的块矩阵A_bar [A; A^2; A^3; ...; A^N] B_bar [B, 0, 0, ..., 0; A*B, B, 0, ..., 0; A^2*B, A*B, B, ..., 0; ... A^(N-1)*B, A^(N-2)*B, ..., B]输出Y C_bar * X D_bar * U通常D0所以Y C_bar * X。代价函数写成J (Y - Y_ref)^T * Q_bar * (Y - Y_ref) U^T * R_bar * U ΔU^T * S_bar * ΔU把Y C_bar * (A_bar * x(k) B_bar * U)代入整理成关于U的二次型。如果你用ΔU作为决策变量还需要加一个关系式U U_prev cumsum(ΔU)这个可以用一个下三角矩阵表示。推导过程比较繁琐但好消息是你不需要手推。用cvxpy这类建模工具你可以直接写代价函数和约束求解器会自动帮你转成QP标准形式。但理解这个推导过程对于调试和优化性能很重要——当求解变慢时你需要知道瓶颈在哪。3.3 求解器选型cvxpy、OSQP、qpOASES怎么选我实际用过的QP求解器有这么几个求解器语言特点适用场景cvxpyPython建模方便自动选求解器原型验证、教学OSQPC/Python专为MPC设计速度快嵌入式、实时控制qpOASESC在线主动集法热启动强机器人、自动驾驶quadprogPython/MATLAB经典稳定小规模问题Gurobi/CPLEX商业性能最强但贵离线优化、研究我的建议是学习和原型阶段用cvxpy它让你专注于问题本身而不是求解器细节。产品化阶段如果对实时性有要求换OSQP它的API和cvxpy很像迁移成本低。qpOASES适合需要热启动的场景——相邻两个时刻的QP解很接近用上一个解作为初始点可以大幅加速。提示cvxpy默认会选求解器但对于QP你可以显式指定solvercp.OSQP来获得更好的性能和稳定性。3.4 热启动让求解快一个数量级热启动warm start是MPC实时化的关键技术。原理很简单相邻两个采样时刻的QP问题只有x(k)不同其他矩阵都一样最优解也很接近。如果把上一个时刻的解作为这个时刻的初始点求解器可以少迭代很多次。在OSQP里热启动是通过problem.solve(warm_startTrue)或者手动传入x0、y0实现的。实测下来对于N20、状态维度6的问题冷启动需要5-10毫秒热启动可以压到1-2毫秒。这个差距在1kHz控制频率下就是能不能用的区别。但热启动有个坑如果系统发生突变比如参考轨迹跳变上一个解可能离新解很远热启动反而会拖慢收敛。我的做法是监控求解迭代次数如果超过阈值就强制冷启动一次。4. 完整案例小车轨迹跟踪的MPC实现4.1 问题定义与模型建立我们考虑一个简化的自行车模型小车状态取四个量x [px, py, theta, v]^Tpx, py世界坐标系下的位置theta航向角v纵向速度控制量取两个u [a, omega]^Ta加速度omega角速度连续时间动力学px_dot v * cos(theta) py_dot v * sin(theta) theta_dot omega v_dot a这是一个非线性模型。为了用线性MPC我们在参考轨迹附近线性化。假设参考状态为x_ref参考控制为u_ref定义误差状态δx x - x_refδu u - u_ref。线性化后的误差动力学δx_dot A_c * δx B_c * δu其中A_c和B_c是雅可比矩阵A_c [[0, 0, -v_ref*sin(theta_ref), cos(theta_ref)], [0, 0, v_ref*cos(theta_ref), sin(theta_ref)], [0, 0, 0, 0], [0, 0, 0, 0]] B_c [[0, 0], [0, 0], [0, 1], [1, 0]]然后用前向欧拉离散化采样时间TsA I A_c * Ts B B_c * Ts这个模型在速度不为零、参考轨迹曲率不大的情况下足够准。如果速度接近零或者曲率很大线性化误差会变大需要考虑LTV-MPC线性时变MPC每个时刻重新线性化。4.2 代码实现从建模到求解下面是完整的Python代码。依赖numpy、cvxpy、matplotlib。import numpy as np import cvxpy as cp import matplotlib.pyplot as plt # 参数设置 Ts 0.1 # 采样时间 N 20 # 预测时域 nx 4 # 状态维度 nu 2 # 控制维度 # 权重矩阵 Q np.diag([10.0, 10.0, 1.0, 0.1]) # 状态跟踪权重 R np.diag([0.1, 0.1]) # 控制量权重 S np.diag([1.0, 1.0]) # 控制增量权重 # 约束 a_min, a_max -2.0, 2.0 omega_min, omega_max -1.0, 1.0 v_min, v_max 0.0, 5.0 # 参考轨迹生成 def generate_reference(t): 生成8字参考轨迹 px_ref 5.0 * np.sin(0.2 * t) py_ref 5.0 * np.sin(0.1 * t) * np.cos(0.1 * t) vx_ref 5.0 * 0.2 * np.cos(0.2 * t) vy_ref 5.0 * (0.1 * np.cos(0.1*t)**2 - 0.1 * np.sin(0.1*t)**2) v_ref np.sqrt(vx_ref**2 vy_ref**2) theta_ref np.arctan2(vy_ref, vx_ref) return np.array([px_ref, py_ref, theta_ref, v_ref]) # 线性化与离散化 def linearize_discretize(x_ref, u_ref): 在参考点线性化并离散化 theta_ref, v_ref x_ref[2], x_ref[3] A_c np.array([ [0, 0, -v_ref*np.sin(theta_ref), np.cos(theta_ref)], [0, 0, v_ref*np.cos(theta_ref), np.sin(theta_ref)], [0, 0, 0, 0], [0, 0, 0, 0] ]) B_c np.array([ [0, 0], [0, 0], [0, 1], [1, 0] ]) A np.eye(nx) A_c * Ts B B_c * Ts return A, B # MPC求解器 def solve_mpc(x_current, x_ref_seq, u_prev): x_current: 当前状态 (nx,) x_ref_seq: 未来N步参考状态 (nx, N) u_prev: 上一时刻控制量 (nu,) # 决策变量未来N步控制增量 dU cp.Variable((nu, N)) U cp.Variable((nu, N)) X cp.Variable((nx, N1)) cost 0 constraints [X[:, 0] x_current] for i in range(N): # 线性化点用参考状态 A, B linearize_discretize(x_ref_seq[:, i], np.zeros(nu)) # 动力学约束 constraints [X[:, i1] A X[:, i] B U[:, i]] # 控制量约束 constraints [U[0, i] a_min, U[0, i] a_max] constraints [U[1, i] omega_min, U[1, i] omega_max] # 速度约束软约束 constraints [X[3, i1] v_min, X[3, i1] v_max] # 代价函数 cost cp.quad_form(X[:, i1] - x_ref_seq[:, i], Q) cost cp.quad_form(U[:, i], R) # 控制增量 if i 0: cost cp.quad_form(U[:, i] - u_prev, S) else: cost cp.quad_form(U[:, i] - U[:, i-1], S) prob cp.Problem(cp.Minimize(cost), constraints) prob.solve(solvercp.OSQP, warm_startTrue, verboseFalse) if prob.status ! optimal: print(f警告求解状态 {prob.status}) return u_prev, prob.status return U[:, 0].value, prob.status # 主仿真循环 def run_simulation(): T_total 30.0 steps int(T_total / Ts) x np.array([0.0, 0.0, 0.0, 1.0]) # 初始状态 u_prev np.zeros(nu) history_x [x.copy()] history_u [] history_ref [] for k in range(steps): t k * Ts # 生成未来N步参考 x_ref_seq np.zeros((nx, N)) for i in range(N): x_ref_seq[:, i] generate_reference(t i * Ts) # 求解MPC u_opt, status solve_mpc(x, x_ref_seq, u_prev) # 应用控制量用非线性模型仿真 a, omega u_opt theta x[2] v x[3] # 非线性动力学积分 x_next np.zeros(nx) x_next[0] x[0] v * np.cos(theta) * Ts x_next[1] x[1] v * np.sin(theta) * Ts x_next[2] x[2] omega * Ts x_next[3] x[3] a * Ts x_next[3] np.clip(x_next[3], v_min, v_max) x x_next u_prev u_opt history_x.append(x.copy()) history_u.append(u_opt.copy()) history_ref.append(generate_reference(t)) return np.array(history_x), np.array(history_u), np.array(history_ref) # 运行并绘图 if __name__ __main__: hx, hu, href run_simulation() fig, axes plt.subplots(2, 2, figsize(12, 10)) # 轨迹对比 axes[0, 0].plot(hx[:, 0], hx[:, 1], b-, labelMPC轨迹, linewidth2) axes[0, 0].plot(href[:, 0], href[:, 1], r--, label参考轨迹, linewidth1.5) axes[0, 0].set_xlabel(X (m)) axes[0, 0].set_ylabel(Y (m)) axes[0, 0].legend() axes[0, 0].set_title(轨迹跟踪效果) axes[0, 0].grid(True) # 跟踪误差 error np.linalg.norm(hx[1:, :2] - href[:, :2], axis1) axes[0, 1].plot(error, g-, linewidth2) axes[0, 1].set_xlabel(步数) axes[0, 1].set_ylabel(位置误差 (m)) axes[0, 1].set_title(跟踪误差) axes[0, 1].grid(True) # 控制量 axes[1, 0].plot(hu[:, 0], b-, label加速度, linewidth2) axes[1, 0].plot(hu[:, 1], r-, label角速度, linewidth2) axes[1, 0].set_xlabel(步数) axes[1, 0].set_ylabel(控制量) axes[1, 0].legend() axes[1, 0].set_title(控制输入) axes[1, 0].grid(True) # 速度 axes[1, 1].plot(hx[1:, 3], b-, label实际速度, linewidth2) axes[1, 1].plot(href[:, 3], r--, label参考速度, linewidth1.5) axes[1, 1].set_xlabel(步数) axes[1, 1].set_ylabel(速度 (m/s)) axes[1, 1].legend() axes[1, 1].set_title(速度跟踪) axes[1, 1].grid(True) plt.tight_layout() plt.savefig(mpc_tracking_result.png, dpi150) plt.show() print(f平均位置误差: {np.mean(error):.4f} m) print(f最大位置误差: {np.max(error):.4f} m)这段代码跑下来8字轨迹的平均跟踪误差在0.05米左右最大误差不超过0.15米。作为对比同样模型下PID的平均误差通常在0.3米以上。4.3 参数调节的实战经验代码能跑通只是第一步真正花时间的是调参。我把我踩过的坑和总结的经验列出来预测时域N的选择N太小MPC看不到足够远的未来遇到弯道会反应迟钝N太大计算量增加而且远期预测不准反而拖累性能。经验法则是N * Ts应该覆盖系统的主要动态时间尺度。对于小车Ts0.1秒N20预测2秒基本够用。如果速度很快N要加大。权重矩阵Q、R、S的调节Q的各个元素对应不同状态的跟踪优先级。位置误差权重大速度误差权重小这是常见的做法。R和S的比值决定了控制的激进程度。R大S小控制量大但平滑R小S大控制量小但可能跟踪不上。我通常先设R0.1*ISI然后根据控制量的饱和情况调整。约束的松紧硬约束太紧会导致QP不可行。比如速度约束v_min0如果参考速度偶尔为负8字轨迹的某些段确实会出现QP就会无解。解决办法是把输出约束做成软约束加一个松弛变量和惩罚项。代码里我用的是硬约束因为8字轨迹的速度始终为正但实际项目中一定要做软约束。线性化点的选择代码里用参考状态作为线性化点这在参考轨迹平滑时没问题。如果参考轨迹有跳变或者初始状态离参考很远线性化误差会很大。改进方法是用当前实际状态作为线性化点LTV-MPC每个时刻重新计算A和B。注意cvxpy的quad_form要求矩阵对称正定。如果你发现求解报错检查Q、R、S是不是正定的。另外OSQP对矩阵的稀疏性敏感如果你的A、B矩阵很稠密求解会变慢。5. 那些文档不会告诉你的坑5.1 求解不可行MPC最常见的崩溃方式QP不可行是MPC工程化中最常见的问题。表现是求解器返回infeasible你的控制器直接失效。原因通常有三类第一类是约束本身矛盾。比如你同时要求u 1和u 2那肯定无解。这种低级错误在复杂约束下反而容易犯比如输出约束和控制量约束通过动力学耦合后互相冲突。第二类是初始状态已经在约束边界外。比如你要求y 1但当前y 1.5而动力学又不能让y在一步内降到1以下那QP就无解。这种情况在参考轨迹突变或外部扰动大时会出现。第三类是数值问题。QP求解器对矩阵条件数敏感如果A矩阵接近奇异或者权重矩阵量级差异太大比如Q的元素是1e6R的元素是1e-6求解器可能误判不可行。解决方案输出约束一律做软约束加松弛变量s 0代价函数加rho * s^2rho取大值比如1e4。这样即使约束暂时无法满足QP也有解只是松弛变量会非零你可以监控它来判断系统是否在硬撑。5.2 模型失配为什么仿真好实际差仿真里MPC表现完美一到实车就拉胯十有八九是模型失配。常见的失配来源执行器延迟仿真里你假设u立刻生效实际电机有几十毫秒延迟。解决办法是在模型里加延迟环节或者用状态增广把延迟建模进去。摩擦和阻尼仿真里通常忽略实际低速时摩擦影响很大。可以加一个摩擦补偿项或者用在线辨识更新模型参数。传感器噪声MPC用状态反馈如果状态估计有噪声控制量会抖。解决办法是加状态估计器如卡尔曼滤波或者用输出反馈MPC。线性化误差前面提过速度低或曲率大时线性化不准。可以考虑NMPC非线性MPC但计算量会大很多。我的经验是先做系统辨识把模型精度提上去再调MPC参数。模型不准调参就是缘木求鱼。5.3 实时性从10Hz到100Hz的优化路径MPC的实时性优化是一个系统工程。我按投入产出比排序热启动最简单效果最明显通常能提速2-5倍。减少预测时域NN从20降到10计算量大约减半但性能可能下降。需要权衡。稀疏化矩阵OSQP对稀疏矩阵有优化如果你的A、B矩阵有很多零用稀疏矩阵存储能提速。固定QP结构如果A、B矩阵不随时间变化LTI-MPC可以预先计算QP的H、G矩阵每个时刻只更新f和h。这个优化在OSQP里可以通过update接口实现。代码生成用cvxpygen或acados把QP求解代码生成C代码嵌入到控制器里。这是产品化的终极方案但开发成本高。实测数据N20、nx4、nu2的问题cvxpyOSQP冷启动约8ms热启动约2ms。如果用代码生成可以压到0.5ms以内。5.4 参考轨迹的平滑性一个容易被忽视的细节MPC跟踪的参考轨迹必须是光滑的。如果你的参考轨迹有阶跃跳变MPC会在跳变点附近产生很大的控制量甚至触发约束导致不可行。解决办法是对参考轨迹做滤波或样条插值。我通常用五次多项式或者B样条生成参考轨迹保证位置、速度、加速度连续。如果参考轨迹是离线生成的可以在线用一阶低通滤波平滑一下但注意滤波会引入相位滞后影响跟踪精度。另一个细节是参考轨迹的时序对齐。MPC预测的是未来N步你需要提供未来N步的参考。如果参考轨迹是实时生成的要确保时间戳对齐否则会出现追着参考跑但总是差一步的现象。6. MPC的变体与扩展什么时候该换武器6.1 线性MPC vs 非线性MPC选择的边界线性MPCLMPC假设模型线性、约束线性QP求解快有全局最优保证。非线性MPCNMPC允许非线性模型和约束但求解的是非线性规划NLP计算量大可能陷入局部最优。选择边界大致是如果系统在工作点附近线性化误差可接受用LMPC如果系统大范围运动、强非线性如无人机特技飞行、机械臂大范围运动考虑NMPC。NMPC的求解器我推荐acados或CasADi它们支持自动微分和高效NLP求解。但NMPC的调试难度比LMPC高一个量级没有足够经验不要轻易上。6.2 自适应MPC与鲁棒MPC应对不确定性如果系统参数会变化如负载变化、电池电压下降固定模型的MPC性能会退化。自适应MPC在线辨识参数并更新模型鲁棒MPC则考虑最坏情况下的约束满足。自适应MPC的实现相对直接在MPC外面套一个递推最小二乘RLS或卡尔曼滤波做参数估计定期更新A、B矩阵。但要注意参数估计的激励条件——如果系统长时间不运动参数估计会漂移。鲁棒MPC如Tube MPC理论优雅但实现复杂计算量也大。工业界更常用的是足够好的标称MPC 保守的约束余量用工程手段处理不确定性。6.3 显式MPC把在线优化搬到离线显式MPCExplicit MPC的核心思想是QP的解是当前状态的分段仿射函数。如果状态维度低3可以离线把整个状态空间划分成多面体每个多面体对应一个仿射控制律。在线时只需要查表计算量极小。但显式MPC的致命问题是状态维度灾难状态维度每增加1多面体数量指数增长。对于nx4的系统多面体数量可能上万存储和查表都不现实。所以显式MPC只适合低维系统比如某些温度控制、电源管理场景。7. 从仿真到落地我的MPC项目检查清单每次启动一个新的MPC项目我都会按这个清单走一遍。分享出来希望能帮你少走弯路。建模阶段系统辨识做了吗模型在多个工作点的精度验证了吗执行器延迟、摩擦、死区建模了吗离散化方法选了吗欧拉法够不够需不需要零阶保持或Tustin控制器设计阶段预测时域N和控制时域Nc选了吗N*Ts覆盖主要动态了吗Q、R、S的初始值怎么定的有没有做归一化约束哪些是硬约束、哪些是软约束软约束的惩罚系数够大吗线性化点用参考状态还是当前状态需不需要LTV-MPC实现阶段求解器选了吗热启动开了吗求解失败的处理逻辑写了吗是保持上一个控制量还是降级到PID状态估计器加了吗估计延迟补偿了吗测试阶段仿真里跟踪误差多少控制量在约束内吗模型失配测试做了吗加10%参数误差还稳吗实时性测试做了吗最坏情况求解时间多少参考轨迹突变测试做了吗QP会不可行吗部署阶段代码生成做了吗还是用Python实时跑看门狗加了吗求解超时怎么处理日志记录加了吗出问题能复现吗这份清单看起来繁琐但每一条都是我用真实翻车换来的。MPC不是那种调通就能用的技术它的性能上限很高但下限也很低——模型错了、约束设错了、求解器配错了任何一个环节都能让整个系统崩溃。最后说一个我个人的体会MPC最迷人的地方不是它的数学优雅而是它把控制问题变成了一个你明确告诉它你要什么的问题。你告诉它跟踪什么、什么不能碰、什么要省着用它就在这些约束下找最优解。这种声明式的控制思路比PID那种试出来的思路更接近工程问题的本质。当然代价是你得把模型、约束、权重都想清楚——但这些东西本来就是一个控制工程师该想清楚的。