资讯动态

用CasADi在MATLAB中实现非线性MPC轨迹跟踪的完整实践

发布时间:2026/10/21 19:32:08 来源:尧图企业网站定制
我第一次在MATLAB里做模型预测控制MPC时第一反应是找现成的MPC工具箱。倒腾了一圈之后发现如果预测模型是线性的、约束是规则的那填参数确实省事可一旦预测模型是非线性的或者想在目标函数里加自定义惩罚项工具箱就变得相当别扭。后来我把目光转向CasADi用它对一个质点车辆模型做轨迹跟踪把完整实现跑通之后整个过程反而清爽得多。这篇文章就以这个例子为主线讲清楚在MATLAB环境里用CasADi实现MPC的完整思路、关键代码和调参心得。适合两类人一是刚接触MPC、想把教科书理论跑成仿真结果的学生二是在自带工具箱里折腾腻了、想换一条更自由路线的工程师。1. 为什么用CasADi做MPC先想清楚工具选型1.1 三条实现路线实际体验差距很大先别急着写代码工具选型这一步值得花点时间想清楚。我见过很多同学一上手就抱着MATLAB自带的MPC工具箱不放结果为了加一个非线性约束硬生生把问题绕成“近似线性”最后控制效果差还找不到原因。就我个人的实践体感来说主流路线其实有三条各自适配的场景很不一样。第一条是MATLAB自带的MPC工具箱。它的优势在于开箱即用建立状态空间模型、设计调节器、加观测器、跑仿真都是图形化和标准化流程适合线性模型、标准约束的问题。但它的短板也很明显——预测模型基本被限制在状态空间模型里自定义非线性预测模型很不方便目标函数的结构也是预设好的你很难随意加一个“控制增量惩罚”或者“终端代价项”。质点车辆模型虽然本身不复杂可一旦出现了航向角、速度、加速度的非线性耦合自带工具箱用起来就有点束手束脚。第二条是用YALMIP这类建模工具配第三方求解器。YALMIP的语法同样很简洁很多学术论文里的算例都是拿它搭的。我自己的体验是如果只是求解一个独立的优化问题YALMIP非常顺手但MPC需要“每个周期更新参数、重复求解同一个优化问题”在这类场景下YALMIP每次都要重新把代价和约束解析一遍对实时性要求高的闭环仿真来说效率上不如CasADi来得直接。第三条就是我最终选的路CasADi。它内部有符号引擎和自动微分能把非线性模型直接交给IPOPT这类求解器对外又提供了一个叫Opti的高级接口写优化问题时几乎就是照着数学表达式翻译。更重要的是它的parameter机制天生适合滚动优化——同一个优化问题结构不变每次只需要更新外部参数然后重新求解。这简直就是为MPC量身定做的。把三条路线放在同一个场景里对比感受会更直观实现路线最适合的场景主要短板MATLAB自带MPC工具箱线性模型、标准约束的开发与研究非线性预测模型和自定义目标函数受限YALMIP第三方求解器学术建模、变量多但求解频率低反复滚动求解时效率偏弱CasADi非线性MPC、任意约束、需要反复快速求解需要自己搭滚动循环文档偏工程化在这篇文章的场景里质点车辆模型是一个相当简化的运动学模型但它并不“小得没价值”。正因为模型简单我们可以把MPC的结构本身看清楚将来把模型换成自行车模型甚至动力学模型改动只集中在离散模型那几行MPC的框架代码可以原封不动地复用。这种自由度正是CasADi这类符号优化工具的核心价值。1.2 CasADi到底解决了什么问题符号、自动微分与OptiCasADi这个名字第一次看到时容易让人头疼因为它不是“一个工具箱”更像“一个符号计算加优化的底层框架”。但只要抓住三个核心概念它就没那么神秘。第一个是符号变量。在MATLAB里先import casadi.*然后可以用MX.sym创建符号变量用Opti接口的时候更省事直接opti.variable(4, N1)就能声明一个4行N1列的优化变量矩阵。这个变量和double数组不一样它记录的是一个表达式图所有基于它的运算都会被CasADi记录下来形成一颗计算图。第二个是自动微分。这是CasADi对非线性MPC最大的贡献。目标函数、约束里只要涉及模型你不需要手推梯度和HessianCasADi会自动对表达式求导。我以前手推雅可比矩阵的时候公式稍微复杂一点就很容易出错而且模型一变就要重新推一遍。有了自动微分改模型就像改一个函数表达式代价函数、梯度、海森矩阵全部自动更新。第三个是Opti接口。CasADi底层其实还有一个更接近机器的nlpsol接口但日常写MPC我更推荐用Opti。Opti把优化问题分成了几个直观的部分variable声明决策变量parameter声明外部参数minimize写目标函数subject_to加约束solver指定求解器solve开始求解。你可以把它想成一份菜谱决策变量是还没定的食材参数是留白的位置每次做菜时只需要把当天的食材填进留白处菜谱本身不用重写。parameter这一点对滚动优化尤其关键——每个控制周期里当前状态和参考轨迹都在变但优化问题的结构完全不变所以只需set_value更新参数求解器就能很快进入状态。2. 质点车辆模型与目标函数数学上先对齐2.1 状态量和控制量怎么定做MPC之前第一个棘手问题是模型怎么选。标题里说的是“质点车辆模型”但这个词在不同资料里的含义并不完全一样。我通常会在两种形式里取舍一种是状态取[x; y; theta; v]控制取[a; omega]代表一个带有航向和速度的点在平面里运动另一种是状态取[x; y; vx; vy]控制取[ax; ay]位置和速度矢量完全解耦更像双积分模型。本文采用前一种也就是带航向角的速度型质点模型。它的连续时间动态可以写成dx/dt v * cos(theta) dy/dt v * sin(theta) dtheta/dt omega dv/dt a其中(x, y)是车辆在平面中的位置theta是航向角v是速度大小控制量a是加速度omega是航向角速度。有人会问既然叫“质点”为什么还需要航向角因为轨迹跟踪通常不只是让位置落在参考线上还要让车辆的朝向跟上参考轨迹的切向方向。保留theta和v之后跟踪问题才完整——位置、航向、速度三个维度都能被控制。这个模型和自行车模型的关系也值得一说。它本质上是自行车运动学模型“揭开轮胎约束之后”的最简退化形式不考虑前轮转角到曲率之间的几何映射直接用omega控制航向变化率。因此它非常适合中低速路径跟踪和MPC算法验证。如果后续你想提高保真度把模型扩展成前后轮运动学模型整个MPC框架里的代码不需要动只需要把离散更新表达式换成自行车模型的公式。2.2 离散化欧拉积分为什么够用MPC的预测模型是在计算机里一步步迭代的所以连续微分方程必须离散化。最常见的做法是前向欧拉x_{k1} x_k dt * v_k * cos(theta_k) y_{k1} y_k dt * v_k * sin(theta_k) theta_{k1} theta_k dt * omega_k v_{k1} v_k dt * a_k有朋友会问这么简单的欧拉积分精度会不会不够我的经验是在MPC里它通常够用。原因有两个一是MPC的预测时域一般只有2到5秒在这个长度内欧拉法的截断误差不至于让状态跑偏太多二是MPC本身是滚动优化的每一拍都在用最新状态做“反馈修正”预测误差会被闭环过程抑制掉。真正需要注意的是dt的选取。预测步长不一定非要等于控制周期但通常保持一致这样参考轨迹的采样和预测模型迭代都在同一个时间轴上写代码最不容易错。如果你把dt加大到0.2秒以上或者希望提高精度可以考虑两点中点欧拉或者龙格库塔做离散但代价是求解变量略微变多、计算量上升。对入门级别的仿真实验前向欧拉配合dt 0.1秒已经能跑出很漂亮的结果。2.3 参考轨迹和跟踪误差的定义轨迹跟踪的“参考轨迹”得有明确的数学定义。本文的例子用一个半径10米的圆形轨迹期望速度8米每秒绕圆心逆时针运动。参考状态序列可以由解析公式一次性生成w v_des / R x_ref(t) R * cos(w * t) y_ref(t) R * sin(w * t) theta_ref(t) w * t pi/2 v_ref(t) v_des这里有一个非常隐蔽但很常见的坑theta_ref如果直接写成atan2(y_ref, x_ref) pi/2由于atan2的返回值被限制在[-pi, pi]角度会在某些时刻突然从pi跳变到-pi。这个跳变反映到目标函数里就是误差项瞬间增大2*piMPC会以为航向误差突然变得极大进而给出一个猛打方向的控制量把好好的跟踪过程搞乱。所以参考航向角最好用解析连续表达式生成或者生成后在目标函数里采用周期性的角度误差表达。有了参考状态跟踪误差就定义为每一步的状态差X - Xref。在目标函数里这个误差会被一个权重矩阵Q加权。除了状态误差还可以让控制量跟踪参考输入Uref在本文的圆周跟踪问题里参考加速度是0参考角速度是w v_des / R。这样目标函数里同时包含了状态跟踪和控制平滑两项控制解会更稳定也不会出现为了强行贴住参考位置而把控制量打得非常激进的现象。3. 核心实现CasADi Opti接口搭MPC的完整写法3.1 预测模型与决策变量的构造把优化问题写出来准备工作的代码大致是这样import casadi.* dt 0.1; % 预测步长 / 控制周期 N 20; % 预测时域步数 R 10; % 参考圆半径 v_des 8; % 期望速度 w_ref v_des / R; % 参考角速度 opti casadi.Opti(); % 决策变量状态序列 X控制序列 U X opti.variable(4, N1); % 每列是一步预测状态 [x; y; theta; v] U opti.variable(2, N); % 每列是一个预测控制量 [a; omega] % 外部参数当前状态、参考状态序列、参考控制序列 X0 opti.parameter(4, 1); Xref opti.parameter(4, N1); Uref opti.parameter(2, N); % 预测模型欧拉离散 opti.subject_to(X(:,1) X0); for k 1:N dx X(4,k) * cos(X(3,k)); dy X(4,k) * sin(X(3,k)); dtheta U(2,k); dv U(1,k); opti.subject_to(X(:,k1) X(:,k) dt * [dx; dy; dtheta; dv]); end这段代码的要点在于X和U是“全部决策变量”而不是某个单一变量。X(:,k)表示第k步的预测状态U(:,k)表示第k步到第k1步之间的控制量。预测模型被写成N条等式约束这些约束共同决定了状态序列必须沿着离散动态前进。为什么把全部变量一次性声明而不是只声明第一个控制量因为MPC的优化问题本质上是在一段预测时域内寻找完整的状态轨迹和控制轨迹而不是只找一个数。一次性声明所有变量便于把模型等式、约束和目标函数都描述清楚求解器也能在一个大的非线性优化问题里统一求解。3.2 目标函数与约束的代码表示目标函数和约束的代码接着写% 权重矩阵 Q diag([5; 5; 0.5; 0.1]); % 位置误差权重给大一些 Rw diag([0.1; 0.1]); % 控制量权重 J 0; for k 1:N1 e X(:,k) - Xref(:,k); J J e * Q * e; end for k 1:N du U(:,k) - Uref(:,k); J J du * Rw * du; end opti.minimize(J); % 约束速度、加速度、角速度的执行器限制 opti.subject_to(X(4,:) 5); % 最低速度 opti.subject_to(X(4,:) 12); % 最高速度 opti.subject_to(U(1,:) -3); % 最小加速度 opti.subject_to(U(1,:) 3); % 最大加速度 opti.subject_to(U(2,:) -1.2); % 最小角速度 opti.subject_to(U(2,:) 1.2); % 最大角速度这里值得展开说一下。第一Q的前两个元素是位置误差权重第三个是航向误差权重第四个是速度误差权重。位置是轨迹跟踪的主目标所以给得大一些如果一开始位置权重太小MPC会优先保证速度不超限结果车辆虽然一直处于“安全状态”却迟迟贴不回参考轨迹。第二约束条件我刻意用矩阵形式一次加一整行CasADi会自动按列拆成多条约束。注意不要写5 X(4,:) 12这种链式比较旧版本很可能不兼容分成两条写最稳妥。关于控制偏差项U - Uref我想多说两句。有的实现只写U * Rw * U也就是单纯惩罚控制量的大小我这里用的是对参考控制输入的偏差惩罚。区别在于用Uref时稳态解会自然贴近“维持参考轨迹所需的前馈控制量”在匀速圆周跟踪问题里就是omega v_des / R而单纯惩罚控制量大小时稳态附近可能会因为权重而偏出一个不必要的控制值。两种写法都能跑通但前者在跟踪类问题上更自然。3.3 滚动优化的主循环从离线到在线MPC和一次离线优化的最大区别就是“滚动”。每个控制周期要做四件事读取当前状态生成未来N步的参考轨迹求解优化问题把最优控制序列的第一个控制量真正作用到对象上然后推进一个周期。主循环的长这个样子T_sim 60; % 仿真时长 n_steps T_sim / dt; x_cur [15; 5; 0; 8]; % 初始状态故意偏离参考轨迹 X_log zeros(4, n_steps1); U_log zeros(2, n_steps); X_log(:,1) x_cur; % 上一次的最优解用于热启动 X_prev repmat(x_cur, 1, N1); U_prev zeros(2, N); for i 1:n_steps t (i-1) * dt; % 生成未来 N 步参考轨迹 [Xr, Ur] genCircleRef(t, dt, N, R, v_des); % 更新参数 opti.set_value(X0, x_cur); opti.set_value(Xref, Xr); opti.set_value(Uref, Ur); % 用上一轮的最优解作初值热启动 opti.set_initial(X, [x_cur, X_prev(:,2:end)]); opti.set_initial(U, [U_prev(:,2:end), U_prev(:,end)]); % 求解 tic; try sol opti.solve(); U_prev sol.value(U); X_prev sol.value(X); u_opt U_prev(:,1); catch warning(Step %d solve failed, reuse previous input., i); u_opt U_prev(:,1); end solve_time toc; % 将第一个控制量作用到被控对象上 x_cur x_cur dt * [x_cur(4)*cos(x_cur(3)); x_cur(4)*sin(x_cur(3)); u_opt(2); u_opt(1)]; X_log(:,i1) x_cur; U_log(:,i) u_opt; end配套的参考轨迹生成函数是这样的function [Xr, Ur] genCircleRef(t0, dt, N, R, v) w v / R; ts t0 (0:N) * dt; Xr [R * cos(w * ts); R * sin(w * ts); w * ts pi/2; v * ones(1, N1)]; Ur [zeros(1, N); w * ones(1, N)]; end主循环里有几个细节容易踩坑。第一opti.set_initial用上一轮最优解平移一位作为初值这不是可选项而是MPC实时性的大杀器——求解器从靠近最优解的位置出发迭代次数会大幅下降。第二try-catch是一定要写的。实时系统最怕“这一拍求不出来就崩溃”用上一拍的控制先顶着然后记录一个warning远比让仿真中断要好。第三我这里仿真用的状态更新表达式和预测模型完全一致相当于“模型完全匹配”的理想实验真实系统里模型失配会带来稳态误差这个后面专门讲。4. 求解器配置与实时性把IPOPT调顺4.1 我常用的IPOPT参数与原因很多第一次用CasADi的人写完优化问题就直接opti.solver(ipopt)然后发现求解器在命令行里刷出一大堆迭代信息每拍求解都慢吞吞的。其实IPOPT的默认配置是在线求解和离线求解之间的一个折中对实时MPC来说太“啰嗦”了。我常用的配置是这样opti.solver(ipopt, struct(print_time, false), ... struct(print_level, 0, ... max_iter, 100, ... acceptable_tol, 1e-4, ... acceptable_iter, 10));几个参数分别说。print_time和print_level用于关掉输出版本否则每个控制周期刷屏命令行根本没法看。max_iter设成100是为了给求解失败设置一个“止损线”对实时系统来说如果100次迭代还不收敛说明问题很可能无解或者初值太差与其死等不如马上降级处理。acceptable_tol和acceptable_iter也很关键IPOPT默认追求高精度解但对控制来说位置误差到厘米甚至分米量级已经够用放宽到这个量级后迭代数往往能减少一半以上。这里要提醒一句不同版本的CasADi对Opti.solver的参数解析略有差异。如果这段代码在你的版本上报错优先打开help casadi.Opti确认签名把false换成0或者把两个结构体合并成一个结构体再试。框架层面的大方向不变这类细节属于版本适配。4.2 热启动MPC实时性的关键热启动这个概念在MPC里再怎么强调都不过分。想象一下如果没有热启动每一个控制周期IPOPT都是从一个冷冰冰的零初值开始找解非线性优化通常要迭代四五十次才能收敛而有了上一轮最优解做初值很多情况下十几次迭代就达到可接受精度。代码里的热启动就体现在这两行opti.set_initial(X, [x_cur, X_prev(:,2:end)]); opti.set_initial(U, [U_prev(:,2:end), U_prev(:,end)]);这里做的事情是把上一轮解出的状态序列整体“向前平移一拍”把当前实测状态放在第一列控制序列也平移一拍。为什么这样因为上一轮预测的未来状态在时间上比当前时刻落后了一个采样周期。把序列平移后新的预测视野里每个位置都有一个接近最优的解作为起点求解器自然跑得又快又稳。我实测下来N20、四个状态两个控制的这个例子在普通桌面MATLAB环境里热启动后单步求解时间经常稳定在几十毫秒量级完整小于0.1秒的控制周期。相比之下不加热启动的话初始阶段有些周期能跑到一两百毫秒。所以“MPC跑不动”很多时候不是方法的问题而是你没有给求解器一个合理的起点。4.3 求解超时时的降级策略如果调了半天单步求解时间还是超过控制周期问题就出在配置太重。这时候我有几条降级策略按从易到难排序手段代价收益减小预测时域N预测视野变短大误差时控制变“短视”求解维度明显降低放宽acceptable_tol控制精度轻微下降迭代次数大幅减少限制max_iter极端情况下返回次优解求解时间有上界简化预测模型模型精度下降非线性度降低求解明显变快编译成C/C后部署工程复杂度和部署门槛高实时性彻底解决其中“减小N”是最容易走极端的。我的建议是不要盲目从80降到10而是先做一组N15、20、30的对比实验画一条求解时间和跟踪误差的曲线再选折中点。对本文这个质点模型N20已经是一个比较稳的默认值“限制max_iter”则适合对控制品质要求不是极端苛刻的场景IPOPT在达到迭代上限时会返回一个当前迭代点最坏情况下控制品质略差但不至于中断系统。5. 闭环仿真轨迹跟踪到底跟住了吗5.1 仿真主流程与结果观察仿真设置里我故意把初始状态取在参考圆外面而且初始航向为0让起点既不是参考轨迹上的点朝向也完全不对。这样的好处是能直接观察MPC“把状态拉回参考轨迹”的过程比从参考点上起步更有说服力。跑完仿真之后通常会画三张图XY平面上的实际轨迹和参考轨迹状态误差随时间的变化控制量随时间的变化。实际轨迹从起点开始会先朝参考圆冲过去在开始的十几步里误差快速缩小进入稳态后车辆沿着圆形轨迹行驶位置误差会维持在一个很小的量级但不会完全等于零。这个现象很正常因为前向欧拉离散本身有截断误差而且有限预测时域下的MPC并不保证稳态误差严格为零。速度会被牢牢限制在约束区间内初始阶段加速度会触达下界因为MPC在积极减速以调整位置驶入稳定状态后控制量会收敛到接近参考输入的结构也就是加速度接近0、角速度接近v_des / R。几个典型的观察现象整理成一张表观察项典型现象主要原因起始阶段误差快速收敛位置误差在数秒内被拉回MPC在预测中看到了大误差会积极用满执行器稳态小偏差位置误差保持在分米量级离散误差、缺少终端约束控制量边界初始阶段加速度触达约束下界大误差下最优解倾向于用满执行器求解耗时初始阶段较慢之后明显变快热启动生效且初值越来越接近最优解这些都是相对典型的趋势具体数值会随权重、预测时域、参考速度而变不用把它当成固定指标。如果你想让稳态误差更小可以考虑减小dt或改用更高阶的离散格式如果你想让MPC在大误差下不要一开始就猛加速可以把控制权重Rw稍微调大一点。5.2 常见异常现象与排查仿真跑不出来或者结果不对通常集中在几个问题上。这些坑我几乎都踩过列出来帮你节省排查时间。第一种是求解报错提示不可行或求解器返回失败。常见原因是约束过紧、参考轨迹不可达或者初始状态不满足约束。排查方法很有条理先把所有约束注释掉看问题能不能解然后一条一条把约束加回来定位是哪一条约束导致不可行。对质点模型来说最容易卡人的其实是“速度下限配合反向跟踪”——如果参考轨迹要求车辆后退而你设置了速度不能为负MPC会非常难受。第二种是求解成功但状态发散。这通常不是MPC框架的问题而是模型写错了。最典型的错误是角度更新漏了dt把theta_{k1} theta_k dt * omega_k写成了theta_{k1} theta_k omega_k。还有参考轨迹生成错误角度跳变会让控制器往错误方向使劲。遇到发散先单独用开环模型跑一遍看模型本身的动态是否正确再谈控制器。第三种是控制量剧烈抖动。原因往往是位置权重给得太大、控制权重太小或者dt太小导致模型对控制过于敏感。先增大Rw在目标函数里加一个控制增量的惩罚项再把acceptable_tol放宽一点通常就能压下来。这种情况在真实系统里更要重视因为执行器受不了频繁切换方向控制器输出平滑度有时比微小误差更关键。6. 调参经验和进阶方向6.1 权重矩阵先大后小的调试顺序MPC调参最容易犯的错就是一上来同时调四个权重然后调一晚上都不知道是谁在起作用。我的调试顺序是第一先只保留位置误差看系统能否把车辆拉到参考路径上第二确认位置收敛后再加入航向权重和速度权重第三最后才加入控制权重和控制平滑项。这个顺序的道理在于位置是轨迹跟踪的主目标位置控制不稳其他权重再合理都是空中楼阁。航向和速度权重在质点模型里与位置耦合但属于“辅助目标”只在位置基本贴住之后才有意义。控制权重是润滑剂加太大会让系统变得懒洋洋位置误差迟迟不收敛加太小又会让控制量抖动。我记得第一次调参时把Rw设成了和Q同一个数量级结果车一直慢慢悠悠根本追不上参考轨迹后来把Rw降了一个数量级才反应过来。6.2 预测时域N怎么选预测时域的选择有一条经验原则N * dt应该覆盖被控系统从当前误差回到参考轨迹的“主要动态过程”。说得直白一点就是让MPC看得到未来足够远的画面否则它在约束面前会表现得短视。以本文这个例子来说初始位置误差大约是10米量级速度上限12米每秒加速度上限3米每秒平方。粗略估算要把车拉回参考圆大约需要2到5秒所以N * dt至少应该大于2秒。取dt 0.1时N至少要20如果N只有10预测视野只有1秒MPC可能刚预测到需要减速就结束了下一拍又重复发现同样的问题控制动作难免犹豫。N太大也有代价变量更多、求解更慢。我的经验是先用N20跑通再用N15和N30做对比实验选择“误差下降明显但求解时间尚可”的临界点。6.3 软约束、终端代价和参考轨迹生成到这一步MPC已经能跑通但仿真和工程之间还有一段距离。我这里再提三个进阶方向每个都是我在实际项目中反复用过的。第一是软约束。硬约束意味着约束边界被突破就无解。机器人的速度限制、执行器限幅在工程中常常是“尽量不要超过而不是绝对不许超过”这时候可以引入非负松弛变量s把速度约束改写成X(4,:) - s 12和X(4,:) s 5同时在目标函数里加一个rho * sum(s)惩罚项rho取一个较大值比如1000。这样即使约束被少量突破求解器也不会直接崩溃而是在“突破约束”和“偏离参考”之间做一个合理权衡。第二是终端代价。有限预测时域下最后一步状态通常不可能正好回到参考点。如果不做任何处理MPC会把所有压力都压在后几步控制行为容易变得短视。一个简单有效的办法是把最后一步状态误差乘以一个更大的权重比如Q_terminal 2 * Q相当于用终端代价去引导整个预测轨迹的方向。这个改动成本极低但对稳定性的改善非常明显。第三是参考轨迹生成。本文用的是解析圆形轨迹所以不用插值。如果是任意道路线形建议先把路径参数化比如基于弧长或时间构建样条再从路径上按预测时域的采样点插值出参考状态。生成角度序列时要格外小心必须保证角度是连续的如果无法避免角度表示范围跳变可以在误差项里用sin(theta_ref - theta)这类周期惩罚替代线性差值从根上绕开角度跳变问题。做完这个例子之后我有一个很深的体会MPC真正难的点从来不是写那几十行代码而是模型与约束是否如实反映了物理世界、目标函数是否把“你想要的控制行为”翻译成了数学语言。CasADi只是把这层翻译工作变得直白了一点。最后再分享一个小技巧如果你也总被求解失败折磨不妨先给所有硬约束加上软约束松弛再把IPOPT的acceptable_tol放宽一个数量级很多所谓求不出来解的问题其实是参数卡得太死。先用宽松设置跑通闭环再逐步收紧这也是我每次搭MPC仿真时的固定流程。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑