1. 问题理解与整体设计思路
1.1 线性时不变系统的容错控制到底在解决什么问题
做自动控制研究的人都知道,真实系统在长期运行中很难保证所有部件一直健康。执行器可能因为磨损、过热或电气故障而卡死或失效,传感器可能出现漂移、偏置甚至完全断路,这些故障如果不处理,轻则降低控制性能,重则导致系统失稳甚至安全事故。而传统的反馈控制设计(如PID、LQR)都是基于系统健康模型设计的,一旦发生故障,控制律的性能会明显恶化,因此如何在故障发生后依然维持系统的稳定性和可接受的性能,就成了容错控制(Fault-Tolerant Control, FTC)研究的核心。
容错控制主要分两大类:被动容错控制和主动容错控制。被动容错的思想比较保守,它把可能发生的故障当作一种不确定性来处理,在设计控制器时就让系统对这类故障具有鲁棒性,好处是不需要故障诊断模块,响应快,但代价是控制性能偏保守,而且对设计者事先没考虑到的故障无能为力。主动容错则不同,它依靠一个实时运行的故障诊断(Fault Detection and Diagnosis, FDD)模块,在检测到故障后主动调整控制器的结构或参数,比如重构控制律、切换控制通道、修改优化目标函数中的约束条件,从而在故障发生后仍能维持系统稳定和尽可能好的性能。我的这套研究方案就走的是主动容错路线,把故障诊断和模型预测控制(Model Predictive Control, MPC)结合起来,形成FT-MPC框架。
1.2 为什么选择模型预测控制作为容错控制的底层算法
MPC能在容错控制领域得到广泛应用,根本原因是它的“预测-滚动优化”机制天然适合处理故障后的约束变化和性能折中。普通控制器在故障发生后,往往需要重新设计控制律,这个过程既耗时又可能引入切换冲击;而MPC每一拍都在线求解一个有限时域优化问题,只需要把故障影响体现到预测模型或约束条件里,下一拍的优化问题就会自动重新求解,相当于每一拍都在做“在线重新设计”。这种特性让MPC在面对系统参数变化、执行器部分失效、约束收窄等情况时,具有天然的适应性。
举个例子,如果某个执行器发生了部分失效(比如输出增益降到原来的50%),传统控制器必须等故障诊断结果出来之后切换控制律;但用MPC的话,只要故障诊断模块能估计出执行器的实际增益,把它带入预测模型的输入矩阵B中,优化问题就会自动重新分配其他健康执行器的控制量来补偿损失。这种“模型修正+滚动优化”的组合,就是FT-MPC的核心逻辑。
1.3 整体方案架构和技术路线
我这套方案面向的对象是离散线性时不变系统,用状态空间模型描述:
x(k+1) = Ax(k) + Bu(k) + Ff(k) y(k) = Cx(k) + Du(k) + Gf(k)
其中x(k)是n维状态向量,u(k)是m维控制输入,y(k)是p维量测输出,f(k)代表执行器或传感器故障信号,F和G是故障分布矩阵。整个方案分三条主线并行推进:
首先是故障诊断模块,基于状态观测器生成残差信号,通过对残差的统计检验实现故障检测,再用故障重构或参数估计方法得到故障的大小,为容错控制提供量化依据。其次是FT-MPC控制器设计,在标准MPC框架中引入故障信息:把估计出的故障信号融入预测模型,同时根据故障严重程度对控制输入幅值、变化率约束做动态调整。最后是Matlab仿真验证平台,搭建完整的闭环仿真环境,对比无故障、发生故障但无容错、发生故障且有容错三种工况下的控制表现。
我一直觉得做研究最忌讳“为了复杂而复杂”,所以这套方案在设计时有三个明确原则:一是模块解耦,故障诊断模块、MPC控制器模块、对象模型模块各自独立,既能单独测试也能整体联调;二是计算量可控,所有算法都考虑实时性,不在线求解非线性优化问题;三是结果可复现,所有参数、阈值、权重都在代码中显式给出,便于后续扩展和改进。也正因为如此,这套框架特别适合刚开始接触容错控制和MPC的同学作为入门和进阶的研究基础。
2. 故障诊断模块设计
2.1 基于观测器的残差生成原理
故障诊断的第一步是生成残差。最经典的做法是设计一个Luenberger状态观测器或Kalman滤波器,用系统的健康模型来预测输出,再把实际输出和预测输出之间的差值定义为残差。当系统正常运行时,残差主要由噪声和模型失配引起,幅度很小;当故障发生时,残差中会出现明显的偏移或周期性波动。
观测器的状态方程和输出方程如下:
z(k+1) = Az(k) + Bu(k) + L[y(k) - Cz(k) - Du(k)] r(k) = y(k) - Cz(k) - Du(k)
其中z(k)是观测器估计的状态,L是观测器增益矩阵,r(k)是残差。观测器增益L的设计目标是让状态估计误差e(k) = x(k) - z(k)满足e(k+1) = (A - LC)e(k),也就是说通过配置(A - LC)的特征值,决定估计误差的收敛速度。在Matlab里直接用place命令,指定期望极点位置即可求出L矩阵。
这里有个细节值得注意:观测器极点的位置和故障检测的灵敏度是矛盾的。极点配置得离原点越近,观测器收敛越快,残差对故障响应也越灵敏,但同时对噪声的放大也越明显,容易导致误报;反之,极点太靠近单位圆,收敛慢,检测到故障的时间延迟会变大。我在调试中一般把极点模值取在0.05到0.3之间,再根据噪声水平微调。
2.2 阈值设计与故障检测策略
有了残差信号,接下来就是怎么判断“有没有故障”。最简单的方案是设置固定阈值,当残差的范数连续超过阈值若干个采样周期后,判定系统发生了故障。固定阈值实现简单,在噪声统计特性已知且模型匹配较好的情况下够用,但它的缺点是阈值取太大则小故障漏报,取太小则噪声波动会引起误报。
我在实际仿真中更推荐自适应阈值方案。具体做法是:在系统正常运行阶段,在线采集残差信号,统计其标准差σ和均值,然后设置阈值边界为μ ± Kσ,K一般取3到5。这样阈值能跟随噪声水平自动调整,降低误报率。检测逻辑上,建议采用“连续N次超限才确认故障”的策略,N一般为3到5拍。这样做虽然会带来几拍检测延迟,但能有效避免瞬时尖峰噪声引起的误报。
2.3 故障大小的估计与重构
检测出故障只是第一步,主动容错还需要知道故障的严重程度。这就要用到故障重构技术。如果故障表现为执行器增益变化,比如第j个执行器的实际输入为u_j_actual = β_j u_j,其中β_j在0到1之间,那么我们可以通过设计故障估计器来在线估计β_j。
对于离散LTI系统,可以使用增广状态方法,把故障量增广到状态向量中一起估计。定义增广状态x_a = [x; f],则增广状态空间模型为:
x_a(k+1) = A_a x_a(k) + B_a u(k) + 扰动 y(k) = C_a x_a(k) + Du(k)
然后用标准的Kalman滤波或Luenberger观测器对增广状态进行估计,直接读取出故障估计值。这种方法的好处是实现简单,不需要专门的参数辨识算法,而且在故障是缓变或常值的情况下估计精度很高。如果需要估计时变的故障信号,还可以考虑用高阶滑模观测器或未知输入观测器,但在基础研究阶段,增广状态方法已经完全够用,而且更容易向工程人员解释清楚。
2.4 诊断模块的Matlab实现
我在实现诊断模块时,把代码分成了两个函数:一个是离线设计函数,负责计算观测器增益L和阈值参数;另一个是在线运行函数,负责在仿真循环中实时生成残差、判定故障并输出故障估计值。离线部分的核心代码如下:
function [L, threshold] = design_fdd(A, C, Qnoise, Rnoise, alpha) % A, C: 系统矩阵和输出矩阵 % Qnoise, Rnoise: 过程噪声和量测噪声协方差 % alpha: 阈值系数,通常取3~5 % 返回观测器增益 L 和自适应阈值阈值因子 % 方法1:极点配置 p = [0.1, 0.2, 0.15]; % 期望极点,需要根据系统阶数修改 L = place(A', C', p)'; % 方法2:Kalman滤波增益(更推荐) [~, Lkf, ~] = dare(A', C', Qnoise, Rnoise); L = Lkf; % 正常运行时残差协方差的理论近似 P = dare(A', C', Qnoise, Rnoise); Sigma_r = C * P * C' + Rnoise; threshold = alpha * sqrt(diag(Sigma_r)); end在线运行的诊断函数就比较直白了:
function [r, f_hat, fault_detected] = run_fdd(A, B, C, D, L, y_meas, u_prev, z_prev, f_prev, threshold, N_confirm) % 预测输出和残差 y_hat = C * z_prev + D * u_prev; r = y_meas - y_hat; % 状态更新 z_new = A * z_prev + B * u_prev + L * r; % 故障检测:残差是否连续N拍超阈值 if all(abs(r) > threshold) f_prev = f_prev + 1; else f_prev = 0; end fault_detected = f_prev >= N_confirm; % 故障重构:使用增广状态或最小二乘估计 % 这里简化为比例形式的估计 f_hat = r; % 详细估计方法见3.3节 r = r; z_prev = z_new; end写代码时有一点务必小心:残差信号虽然由r(k)表示,但阈值判断不能简单对每个通道单独做,还要考虑通道之间的相关性。我在仿真中遇到过这样的情况:某个执行器故障导致的残差在两个传感器通道上方向相反,逐个通道检查时都没超阈值,但残差向量的范数已经明显超标。所以更稳妥的做法是对残差向量的加权范数或马氏距离设定阈值。
3. FT-MPC控制器设计
3.1 模型预测控制的基本原理回顾
MPC的核心可以用一句话概括:在每一控制周期,基于当前状态,在线求解一个有限时域的优化问题,得到未来Np步的最优控制序列,但只把第一步控制量施加到系统上,下一个周期重复这个过程。这种“滚动优化、逐步实施”的方式使得MPC能够有效处理多变量耦合和约束,同时天然具有反馈校正能力。
对于离散线性时不变系统,MPC的优化问题一般写成如下形式:
min J = Σ_{k=0}^{Np-1} [ (x_ref(k) - x(k))'Q(x_ref(k) - x(k)) + u(k)'Ru(k) + Δu(k)'SΔu(k) ]
约束条件包括状态方程约束、控制输入幅值约束(u_min ≤ u(k) ≤ u_max)和控制增量约束(Δu_min ≤ Δu(k) ≤ Δu_max)。在Matlab中,如果系统规模不大(状态数不超过10个,控制量不超过5个),可以直接用quadprog求解;如果规模很大或者要求快速求解,可以考虑用内点法改写或使用Embedded Coder自动生成代码,但研究阶段quadprog足够。
3.2 故障信息如何融入MPC优化问题
FT-MPC和标准MPC的区别就在“故障信息如何融入”这件事上。我做的是把故障信息分别注入预测模型和约束条件两个层面。
第一个层面是预测模型的修正。当执行器发生部分失效时,系统行为不再由标称模型描述,而是变为:
x(k+1) = Ax(k) + B_actual·u(k) + F_d·d(k)
其中B_actual = B·diag(β),β是执行器健康因子向量,β_j = 1表示第j个执行器完全健康,β_j = 0表示完全失效,0 < β_j < 1表示部分失效。故障诊断模块在线估计出β之后,MPC的预测模型实时修正,优化问题自动把控制指令更多地分配给健康的执行器,这就是容错能力的根本来源。
第二个层面是约束的动态调整。如果某个执行器发生了故障,即使它还有部分能力,我们也希望它的控制量不要输出过大,以防二次损伤。所以约束上下限要做动态收窄,比如原来u_j的范围是[-1, 1],检测到β_j = 0.5后,把约束调整为[-0.5, 0.5]。这个动态约束策略在实际系统中很有意义,因为约束收窄后MPC不会“压榨”故障执行器,系统整体更安全。
3.3 FT-MPC的优化问题表述
综合起来,我最终用的FT-MPC每拍求解如下优化问题:
min J = Σ_{i=0}^{Np-1} [ (x(k+i+1) - x_ref)'Q(x(k+i+1) - x_ref) + u(k+i)'R u(k+i) ] + 终端代价项
s.t. x(k+i+1) = Ax(k+i) + B·diag(β_hat)·u(k+i) u_min·β_hat ≤ u(k+i) ≤ u_max·β_hat Δu_min ≤ Δu(k+i) ≤ Δu_max x(k) = x_current
这里β_hat就是故障诊断模块给出的健康因子估计值。值得强调的一点是,当β_hat接近0(执行器几乎完全失效)时,约束范围变得很窄,优化器无论如何都难以满足性能目标,这时需要引入“软约束”机制,即允许一定的约束违反,但在目标函数中加惩罚项。我在代码里给约束违反加了一个很大权重的松弛变量ε,保证优化问题始终有解,避免出现“无解停机”的尴尬局面。
3.4 控制器的Matlab代码实现
FT-MPC的在线求解部分,我封装成了一个函数,每次控制周期调用一次。核心代码如下:
function [u_opt, opt_info] = ft_mpc_controller(A, B, C, D, x_cur, x_ref, beta_hat, param) % param: Np, Nc, Q, R, u_min, u_max, du_min, du_max Np = param.Np; Nc = param.Nc; Q = param.Q; R = param.R; % 修正输入矩阵 B_hat = B * diag(beta_hat); % 构建预测矩阵(使用层叠矩阵形式) [Phi, Gamma] = build_prediction_matrices(A, B_hat, C, Np, Nc); % 目标函数转成标准二次型 H = 2 * (Gamma' * kron(eye(Np), Q) * Gamma + kron(eye(Nc), R)); f = 2 * (x_ref_seq' * kron(eye(Np), Q) * Gamma)'; % 注意这里f需要根据x_ref和Phi*x_cur的组合进一步整理 % 约束矩阵:幅值约束和增量约束 Aieq = []; bineq = []; % 幅值约束 u_min*beta_hat <= u <= u_max*beta_hat Aieq = [Aieq; eye(Nc*m); -eye(Nc*m)]; bineq = [bineq; kron(ones(Nc,1), param.u_max .* beta_hat); -kron(ones(Nc,1), param.u_min .* beta_hat)]; % 调用quadprog求解 options = optimoptions('quadprog', 'Display', 'off', 'Algorithm', 'interior-point-convex'); [U, fval, exitflag] = quadprog(H, f, Aieq, bineq, Aeq, beq, [], [], [], options); % 取第一个控制量 u_opt = U(1:m); opt_info.exitflag = exitflag; end这段代码是一个浓缩骨架,真正运行时还需要处理几个细节:一是预测矩阵的构建方式,二是x_ref序列的生成,三是等式约束处理。第一次写的时候很容易在维度上出错,建议先在Matlab里用小规模随机矩阵验证一下维度是否匹配,再接入仿真主循环。
4. 仿真平台搭建与核心代码解析
4.1 仿真实例选择
为了验证FT-MPC的效果,我选了经典的卫星姿态控制模型作为仿真对象。卫星姿态动力学经过离散化后可以表示成四阶LTI系统,其中两个状态是姿态角和姿态角速度,两个状态是反作用飞轮的角动量。这个模型的好处是物理意义直观,而且系统和控制量的维度适中,方便展示容错控制的效果。
系统矩阵如下:
A = [1, dt, 0, 0; 0, 1, dt, 0; 0, 0, 1, dt; 0, 0, 0, 0.98];
B = [0, 0; dt, 0; 0, dt; 0, 1];
C = [1, 0, 0, 0; 0, 1, 0, 0];
采样时间dt取0.1秒,Np取10步,Nc取3步,加权矩阵Q取diag([100, 10, 1, 1]),R取diag([0.1, 0.1])。在这个参数下,系统能较快地跟踪参考输入,同时控制量不会过于激进。
4.2 仿真主循环结构
整个仿真程序的主循环结构如下:
% 初始化 x = x0; % 初始状态 u = zeros(m, 1); fault_time = 200; % 第200步注入故障 beta_true = ones(m, 1); % 真实健康因子 for k = 1:T % 1. 故障注入 if k == fault_time beta_true(1) = 0.4; % 第一个执行器失效到40% end % 2. 实际系统输出 u_apply = u; y_meas = C*x + D*u_apply + noise; % 3. 故障诊断 [r, beta_hat, fault_flag] = run_fdd(A, B, C, D, L, y_meas, u_prev, z_prev, threshold, N_confirm); % 4. FT-MPC求解 x_cur = x; % 这里假设状态可测,若不可测则用观测器估计值 [u, info] = ft_mpc_controller(A, B, C, D, x_cur, x_ref, beta_hat, param); % 5. 状态更新 x = A*x + B*diag(beta_true)*u_apply + process_noise; % 6. 数据记录 x_log(:, k) = x; u_log(:, k) = u; beta_hat_log(:, k) = beta_hat; end注意在第5步状态更新时,我使用的是带真实β的状态方程,而MPC求解时使用的是带估计β的模型,这一“真实模型”和“估计模型”的差异就模拟了故障诊断精度对控制性能的影响。诊断越准,预测模型越接近真实,控制效果自然越好。
4.3 主要参数的计算和调试过程
在仿真调试中,我花了不少时间调整三个关键参数,这里详细说下我的调参逻辑。
第一个是MPC的预测时域Np。Np太短时,预测无法充分反映系统未来动态,约束处理能力差,容易导致振荡;Np太长时,计算量增大,而且远期预测误差积累,性能未必提升。我试过Np从5到20的变化,发现对于这个四阶系统,Np在8到12之间控制性能基本收敛,取10是比较稳妥的选择。
第二个是故障诊断的阈值系数K。K取3时噪声误报率偏高,在仿真中大约每500步会出现一次误报;K取5时基本没有误报,但对小故障的检测延迟会增加到20步左右。最终我取K=4,配合连续3拍确认逻辑,兼顾了误报率和检测速度。
第三个是约束的动态收窄策略。直接让约束跟随β_hat线性收窄是最简单的方案,但实际仿真中发现,当β_hat在0.4附近时,约束变得很窄,MPC可选的控制空间太小,输出性能下降很快。改进方案是让约束按β_hat的非线性函数收窄,比如β_hat^0.7,这样在中度故障时仍保留一定的控制余量,整体性能更平滑。
4.4 仿真结果的关键指标对比
我跑了三组对比仿真:第一组是无故障工况,第二组是有故障但不启用容错(即MPC预测模型不修正),第三组是有故障且启用FT-MPC。关键性能指标如下表所示:
| 指标 | 无故障 | 有故障无容错 | 有故障+FT-MPC |
|---|---|---|---|
| 跟踪误差均方根 | 0.032 | 0.186 | 0.061 |
| 控制输入最大值 | 0.98 | 1.52 | 1.31 |
| 恢复时间(步) | - | 不恢复 | 约35步 |
| 闭环是否稳定 | 稳定 | 临界失稳 | 稳定 |
从表中可以看到,无容错时系统在故障发生后性能急剧恶化,跟踪误差增加了近5倍,控制输入幅值超过约束上限,系统已经处于临界失稳状态。而启用FT-MPC后,误差虽然比健康状态有所增加,但仍在可接受范围内,并且系统大约在35步后重新达到新的稳态。这个结果很直观地说明了容错控制的价值:它不一定能恢复到故障前水平,但能把系统从“失效边缘”拉回到“可用状态”。
5. 常见问题与调试经验
5.1 故障诊断的误报和漏报如何处理
误报和漏报是故障诊断里最头疼的一对矛盾。误报会导致容错控制被频繁触发,控制器反复切换,反而可能引起系统性能下降;漏报更严重,故障没有被检测到,容错机制根本不启动。在我调试过程中,最容易出现的问题是观测器极点位置和阈值选择不当共同导致的误报。
我的处理经验是分两步走:先调观测器极点,把观测器的带宽压下来,让残差对高频噪声不那么敏感;再调阈值和确认机制,阈值适当放宽,配合“连续N拍超限确认”逻辑。如果误报还是频繁,就要考虑是不是模型失配导致的,比如A、B矩阵参数和真实系统有偏差,这时候需要单独做模型校正,而不是一味调阈值。
5.2 quadprog求解无解或求解失败的处理
在实际运行中,quadprog偶尔会返回exitflag小于0,表示求解失败。最常见的原因是约束过紧导致可行域为空,特别是在故障严重、β_hat接近0时,控制幅度约束和增量约束相互冲突。
我的解决办法是引入松弛变量和软约束。具体做法是在目标函数中增加一项很大的惩罚系数乘以松弛变量,同时在约束中允许控制量有一个小范围的越限。这个策略在工程上非常实用,因为实际的执行器一般允许短时间小幅越限,这样可以避免系统在故障瞬间因为“无解”而失去控制。代码层面,我用的是:
% 软约束部分 soft_vars = sdpvar(1); % 若用YALMIP方便些,否则手动扩展H和f矩阵 % 在H矩阵对应位置加上大M,在f向量对应位置加上M*1如果不想引入额外变量,也可以把约束范围适当放大,比如在约束上乘以一个1.1的裕度系数,但这样不够严谨,还是推荐软约束方案。
5.3 故障估计延迟对控制性能的影响
故障诊断不是瞬时的,从故障发生到故障被检测,再到β_hat收敛到接近真实值,中间有一段时间窗口。在这段时间内,MPC用的还是旧的预测模型,系统处于“带故障运行但无容错”的状态。这个延迟窗口越长,系统偏离期望轨迹越远,甚至可能已经失稳。
我在仿真中测试过不同检测延迟下的表现,发现从10步延迟增加到40步时,恢复后的稳态误差几乎翻倍。所以一个系统的容错性能,实际上由诊断速度和控制器鲁棒性共同决定。想要缩短检测延迟,一方面可以设计更灵敏的残差,另一方面可以在β_hat还没收敛时先用一个保守的估计值(比如快速收窄约束),这样虽然会牺牲一点性能,但至少能限制故障恶化。
5.4 状态不可测情况下的输出反馈FT-MPC
很多实际系统并不是所有状态都可测,这时候需要把MPC中的状态x_cur替换成观测器估计值z,形成输出反馈FT-MPC。要注意的是,观测器本身的估计精度受到故障的影响——如果故障导致系统模型发生改变,观测器的估计就会出现偏差,进而影响MPC的性能。所以在输出反馈结构下,我建议把故障诊断用的增广观测器同时承担状态估计任务,这样可以保证故障发生后观测器的模型也是修正过的,防止“坏数据进、坏结果出”的恶性循环。
5.5 快速调试的检查清单
根据我踩过的坑,整理了一个排查清单,仿真结果不对时逐条检查:
- 检查A、B、C、D矩阵的维度匹配,特别是B_hat替换后维度是否变化
- 检查预测矩阵Phi和Gamma的构建是否使用了正确的Np和Nc
- 检查quadprog的变量维度是否与控制序列长度一致
- 检查故障发生后残差是否确实被激发,用plot画出残差曲线确认
- 检查β_hat估计值是否被正确传入MPC控制器,不会被意外清零
- 检查不同模块的采样时间是否一致,防止出现多速率问题
6. 扩展思考与个人体会
做这个FT-MPC研究的过程,让我对容错控制有了更实际的认识。教科书里的容错控制往往把故障诊断和控制重构分成两个独立的问题分别研究,但在真实系统里这两个模块是深度耦合的。诊断精度影响控制性能,控制效果又反过来影响诊断残差——比如MPC输出补偿后,系统输出可能又回到参考轨迹附近,残差信号随之减小,如果阈值设置不当,诊断模块可能会误判为“故障消失”。这种“控制掩盖故障”的现象在实际工程中很常见,解决思路是在容错控制激活后继续锁定诊断结果一段时间,或者监测控制量本身的变化作为故障持续存在的证据。
这个项目如果继续往下做,有几个方向我觉得很值得探索。一是把线性时不变系统扩展到线性参数变化系统,让FT-MPC在多个工作点之间自动切换;二是引入鲁棒MPC思想,使控制器对模型失配和诊断误差具有更强的容忍能力;三是将现在的仿真验证进一步移植到半实物仿真平台上,用真实的执行器模型检验算法的实时性和可行性。无论是做学术研究还是做工程项目,这套方案都提供了一个清晰的起点。