
简介面向自动化、机器人与智能车辆等领域的控制算法学习者和工程师这份资源提供了LQR、H∞鲁棒控制、神经网络控制等多种控制率算法的MATLAB/Simulink实现案例。包内共12个文件以.m脚本和.fis模糊推理系统文件为主体另含.mat数据文件与示例图片兼顾仿真运行、结果保存与可视化对照压缩包仅2.65MB轻量便捷。已有249人学习下载。案例中LQR利用动态规划求解状态反馈矩阵适用于无人机姿态稳定等最优控制场景H∞方法通过最小化传递函数无穷范数增强系统鲁棒性可用于主动振动抑制神经网络控制器则结合反向传播与强化学习逼近系统动态并实现自适应决策贴合自动驾驶环境感知与路径规划需求。通过源码可清晰看到算法从理论公式到仿真实现的完整映射并理解不同控制率在稳定裕度、抗干扰能力与自适应性能上的取舍。文件命名按算法模块组织便于读者对照实验、二次开发。1. 控制率算法为什么值得在工程里重新实现一遍仿真里调好的LQR一换到实物上就开始抖H∞把不确定性写进模型求出来的控制器阶数却高得让小单片机喘不过气神经网络控制率看起来不需要被控对象模型可真机试跑时你根本猜不到它下一拍会把控制量推到哪。控制率算法的本质不是“算出u-Kx”而是从建模、权重设计、离散化到最终部署的整条链路。这篇文章用LQR、H∞和神经网络三种被讨论最多的控制率算法给出可复现的最小案例、参数整定方法和工程落地中的实际坑。适合做平衡车、无人机、电机驱动的嵌入式工程师和自动化算法工程师也适合刚学完现代控制理论但不想只停留在课本推导的人。2. LQR控制率算法的离散化实现与权重整定2.1 为什么LQR控制率必须从连续时间转到离散域LQR线性二次型调节器的目标是找一个状态反馈控制率u-Kx使二次型性能指标 J∫(xᵀQxuᵀRu)dt 最小。求解核心是连续Riccati方程AᵀPPA-PBR⁻¹BᵀPQ0KR⁻¹BᵀP。理论推导干净但数字控制器在每个采样周期只能看到离散时刻的状态所以必须用离散LQR也就是dlqr直接作用于系统矩阵的离散版本。常见的错误是先在MATLAB里用连续lqr算出K再直接把这个K放进STM32的中断里跑。当采样周期在1ms以内时连续K和离散K的差别还能忍一旦采样周期到5ms以上或者被控对象本身特征根靠近虚轴连续K很容易让闭环系统在离散化后出现极限环甚至发散。我一般用零阶保持器做离散化因为它精确匹配DAC输出保持一个周期的物理行为c2d默认的zoh是最稳的起点。离散LQR的Riccati方程是离散代数Riccati方程Python Control库的dlqr直接解它返回的K已经对应你给定的离散系统矩阵A_d、B_d。实现时最需要确认的是A_d、B_d来自哪个dtK就必须用于那个dt不能换个采样周期继续用。2.2 用Python Control跑通一个可复现的LQR最小案例下面以自平衡车的线性化模型为例。小车在平衡点附近的角度θ和角速度ω作为状态控制量是轮子加速度u模型可近似为二阶积分加不稳定极点。import numpy as np from control import dlqr, ss, c2d # 倒立摆在平衡点附近的连续时间线性化模型 # 状态 x [角度, 角速度]控制输入 u 车轮加速度 A_c np.array([[0.0, 1.0], [9.8, 0.0]]) B_c np.array([[0.0], [1.0]]) C np.eye(2) D np.zeros((2, 1)) # 构建连续系统并按 5ms 采样周期零阶保持离散化 sys_c ss(A_c, B_c, C, D) dt 0.005 sys_d c2d(sys_c, dt, methodzoh) # 权重矩阵Q惩罚状态偏差R惩罚控制能量 Q np.array([[100.0, 0.0], [0.0, 1.0]]) R np.array([[0.01]]) # 求解离散LQR返回状态反馈增益 K、Riccati解 S、闭环极点 E K, S, E dlqr(sys_d.A, sys_d.B, Q, R) print(K , K) print(closed-loop poles , E)这段代码里A_c的9.8来自重力加速度与摆杆长度/质量的简化比值实际调试时要根据实物重心位置修正。Q矩阵的(0,0)项惩罚角度偏差取100意味着角度偏1弧度比角速度偏1rad/s要“贵”100倍R取0.01表示控制量相对便宜控制器会更大胆地输出加速度。dlqr返回的K是一个1×2矩阵控制率直接写为u -K[0]*x0 - K[1]*x1。跑通这段代码后建议看一眼闭环极点E的模长。所有极点的模长小于1是稳定的必要条件模长越接近0响应越快但控制量峰值也会更大。如果你的极点模长出现大于1的情况先检查Q、R是否给了正定值再检查离散化结果sys_d.A、sys_d.B是否为有限值。2.3 LQR权重Q和R的整定法则与STM32落地Q和R没有唯一标准答案工程上最常用的是Bryson法则Q_ii取状态最大允许偏差平方的倒数R_ii取最大控制量平方的倒数。比如角度最大允许0.1rad则Q(0,0)1/0.1²100控制加速度最大限幅2m/s²则R1/2²0.25。这个法则只给初始值之后的调整方向按下面表格来。参数调整对闭环极点的影响对阶跃响应的影响对控制能量的影响增大Q(0,0)极点模长变小响应更快上升时间缩短超调变大u峰值明显增大增大R极点向单位圆内靠近上升时间变长超调减小u峰值减小更保守增大Q(1,1)对角速度惩罚加强阻尼感更强振荡减少整体控制量更平滑上表的三个方向基本覆盖平衡车和无人机悬停场景的调参需求。STM32落地时我建议把K算好之后直接以常数数组写进代码不要在板子上跑Riccati求解。控制率本体就一行u -(K0 * state[0] K1 * state[1])。但有两个必须处理的问题一是状态估计实际传感器只能拿到角度角速度要用陀螺仪或状态观测器观测器带宽要高于LQR闭环带宽的3到5倍二是积分项LQR本身没有积分作用模型误差和摩擦力会让稳态存在偏差常见做法是在控制率外面加一个误差积分项u_int ki * error * dt并用抗饱和钳位。提示LQR权重中Q和R的相对大小只影响闭环性能不影响稳定性判定。调参数时一次只改一个元素否则很难判断是哪一项引起的振荡。3. H∞控制率算法从加权灵敏度到可求解的标准问题3.1 H∞控制率要解决的是LQR没有建模的那部分不确定性LQR的前提是模型精确、扰动可被高斯噪声近似。真实系统里存在未建模动态、参数摄动、传感器噪声和外部阶跃扰动这些不确定性写成模型时往往是范数有界而不是方差有界。H∞控制把控制率设计变成一个最小化闭环传递函数峰值增益的问题目标是让从扰动w到期望输出z的闭环传递函数T_wz满足‖T_wz‖∞ γ。工程中常用的是混合灵敏度框架同时约束三个传递函数灵敏度函数S(IGK)⁻¹控制灵敏度KS补灵敏度函数TGK(IGK)⁻¹。加权函数W1、W2、W3分别压在S、KS、T上要求‖W1·S‖∞1、‖W2·KS‖∞1、‖W3·T‖∞1。W1在低频段取高增益用来抑制低频扰动W2限制控制量峰值W3在高频段取高增益用来压制未建模动态。这套设计思路直接回答了“LQR鲁棒性不够怎么办”的问题。加权函数的具体形式一般选一阶或二阶有理函数。W1常写成(s/Mω_B)/(sω_B·A)的形式其中ω_B是期望的带宽M是峰值上界A是低频增益上限。调权重时看闭环γ值γ小于1说明满足指标大于1说明至少有一个加权函数没有达到需要修改权重或降低性能预期。3.2 用mixsyn求解H∞控制器的最小可复现案例Python Control库的mixsyn函数直接解决加权混合灵敏度问题不需要手工构造广义被控对象。下面的代码对同一台倒立摆模型设计H∞控制器。import numpy as np from control import tf, mixsyn, c2d, ss # 被控对象与LQR案例相同的二阶不稳定系统 G tf([1], [1.0, 0.0, -9.8]) # W1: 低频扰动抑制期望带宽2rad/s低频增益上限50 W1 tf([1.0, 0.0], [1.0, 0.02]) # (s0)/(s0.02)近似低频高增益 # W2: 限制控制量取常数0.8 W2 tf([0.8], [1.0]) # W3: 高频未建模动态抑制从100rad/s开始增益上升 W3 tf([1.0, 10.0], [1.0, 2000.0]) # mixsyn求解H∞控制器gamma为最终达到的范数水平 K_hinf, CL, gamma, rcond mixsyn(G, W1, W2, W3) print(gamma , gamma) print(K_hinf , K_hinf)这里W1取了纯积分形式让低频段增益接近无穷意味着对阶跃扰动有完全抑制能力。W2取常数0.8限制控制量权重。W3的高频转折点设到100rad/s代表我们对被控对象在100rad/s以上的动态完全没信心希望控制器在高频段迅速截止。mixsyn返回的K_hinf是一个传递函数gamma是综合后的实际范数水平小于1说明所有加权都满足。实际使用时这段代码跑通的关键在于W1要是一个真的有理函数而非纯积分纯积分器的状态在数值求解时容易出现奇异建议写成(seps)/(somega_L)的形式eps取0.01即可。gamma如果大于5先检查W3的转折频率是否压到了比闭环带宽低的位置这是最常见的失败原因。3.3 控制器阶数、离散化与实现时的三个坑mixsyn求解出的控制器阶数等于增广系统的阶数也就是G的阶数加上W1、W2、W3的阶数之和。上面的例子算下来是4阶实际工程里如果权重取得复杂控制器冲到8阶以上很正常。STM32上直接实现高阶连续控制器非常痛苦第一步要做降阶处理。常用方法是对K_hinf做平衡截断降阶到2阶或3阶然后重新检查闭环系统的γ值通常γ上升在10%以内都可以接受。离散化H∞控制器时我习惯用双线性变换也就是tustin方法而不是zoh。控制器是一个连续的动态补偿器双线性变换能把s域的左半平面映射到z域单位圆内保持频率响应形状。zoh离散控制器会产生额外的相位滞后对鲁棒性有负面作用。代码上写K_d c2d(K_hinf, dt, methodtustin)即可但要注意双线性变换在接近Nyquist频率时频率轴有畸变控制器设计时带宽最好保持在采样频率的1/10以下。第三个坑是执行器限幅与H∞控制器输出之间的配合。H∞控制器在设计时假设控制量无饱和一旦实际输出被限幅整个鲁棒性保证就会失效。工程上需要在控制器后端接一个积分抗饱和结构或者把W2设成大一点的值让控制器在求解时就“知道”控制量不能太大。如果调试时发现控制器输出频繁打到限幅值优先增大W2而不是降低W1。提示H∞控制器调参的起点是W1决定带宽W3决定鲁棒裕度W2决定控制量忍耐度。三者互相关联建议每次只改一个转折频率跑一遍仿真看gamma变化趋势。4. 神经网络控制率算法的数据生成、训练与边缘部署4.1 神经网络控制率的三种范式与选型理由神经网络控制率指的是用参数化网络直接或间接生成控制量uf_θ(x)。目前工程里能落地的主要是三条路线行为克隆、强化学习和在线自适应。行为克隆把LQR、H∞或人类示教产生的状态-动作对当作训练集让网络用监督学习拟合控制率强化学习通过奖励函数在仿真中直接搜索控制策略在线自适应则是在实机运行中持续更新网络参数。三者的风险差异很大。行为克隆上限受限于示范数据的质量但下限有保证适合控制周期在1ms到10ms的嵌入式系统强化学习在仿真环境成熟时可以自动发现新颖的控制策略但奖励函数设计不当会出现“奖励黑客”而且训练出的网络往往对仿真参数极其敏感在线自适应效果上限最高却最容易在实机上出现灾难性遗忘。做工程时我最常用的路线是行为克隆先拿一个成熟控制器生成数据再把网络部署上去这让整个系统从第一天起就有明确的性能基线。选择哪种路线取决于模型精度和算力预算。如果被控对象有比较可靠的仿真模型优先强化学习如果没有精细仿真但已经有可工作的LQR控制器行为克隆是成本最低的升级路径只有硬件支持模型预测控制且算力充足时才值得考虑在线自适应架构。4.2 用LQR示范数据生成神经网络训练集的通用做法以下代码是用LQR控制器在仿真环境里滚动采集数据。核心思路是在每个控制周期记录当前状态和执行的控制量并加入小幅度探索噪声避免网络学到的控制率过拟合到精确的单一轨迹。import numpy as np from control import dlqr, c2d, ss # 复用第2章离散化后的系统 A_d, B_d sys_d.A, sys_d.B K, _, _ dlqr(A_d, B_d, Q, R) # 数据采集参数 T 5.0 # 每次仿真时长 dt 0.005 # 控制周期 n_episodes 20 # 不同初始条件的条数 state_dim 2 act_dim 1 explore_std 0.3 # 探索噪声标准差 u_max 2.0 # 控制量限幅 data_x [] data_u [] # 在角度偏差和角速度偏差的范围内随机撒初始状态 initial_range [[-0.3, 0.3], [-1.0, 1.0]] for ep in range(n_episodes): x np.array([np.random.uniform(*initial_range[0]), np.random.uniform(*initial_range[1])]) steps int(T / dt) for k in range(steps): # 用LQR控制率计算基准控制量 u -K x # 加入探索噪声模拟未建模扰动并增强数据覆盖面 u_noisy u np.random.randn(act_dim) * explore_std u_noisy np.clip(u_noisy, -u_max, u_max) data_x.append(x.copy()) data_u.append(u_noisy.copy()) # 用离散状态方程推进仿真注意这里用的是线性模型 x A_d x B_d u_noisy # 转换为numpy数组 X_data np.array(data_x) U_data np.array(data_u).reshape(-1, act_dim) print(X_data.shape, U_data.shape)这段代码有几个关键参数需要注意。explore_std取0.3表示噪声幅度达到正常控制量的30%左右太小网络学不到鲁棒性太大则训练数据里会出现大量饱和样本网络会学着输出最大控制量。数据分布要覆盖你实际部署时可能遇到的状态范围尤其是初始角度偏差大的场景如果只在0.05rad附近采样实机一旦出现大扰动网络的输入就会落在训练分布之外。生成完成后要把数据做去重和异常值过滤把LQR输出被限幅的样本删掉或降权这些样本对应的控制量不是最优的。4.3 用PyTorch训练MLP控制率并导出到边缘设备训练网络的结构不宜复杂控制率网络本质上是一个从状态到控制量的回归函数。输入是2维状态输出是1维控制量两层隐藏层加Tanh激活就足够。激活函数不要用ReLUReLU在输入越过零点时会突然切换斜率对控制信号来说是引入高频噪声。import torch import torch.nn as nn import numpy as np from sklearn.model_selection import train_test_split # 转换为PyTorch张量 X_train, X_val, U_train, U_val train_test_split( X_data, U_data, test_size0.2, shuffleTrue ) X_t torch.tensor(X_train, dtypetorch.float32) U_t torch.tensor(U_train, dtypetorch.float32) X_v torch.tensor(X_val, dtypetorch.float32) U_v torch.tensor(U_val, dtypetorch.float32) class ControlNet(nn.Module): def __init__(self, n_state, n_act): super().__init__() self.net nn.Sequential( nn.Linear(n_state, 64), nn.Tanh(), nn.Linear(64, 64), nn.Tanh(), nn.Linear(64, n_act) ) def forward(self, x): return self.net(x) model ControlNet(X_t.shape[1], U_t.shape[1]) loss_fn nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(150): model.train() pred model(X_t) loss loss_fn(pred, U_t) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 30 0: model.eval() val_loss loss_fn(model(X_v), U_v).item() print(fepoch {epoch}, loss{loss.item():.5f}, val_loss{val_loss:.5f})训练中重点关注验证集loss和高频抖动。验证集loss在训练后期还在下降说明模型容量不够或数据量偏少增加隐藏层维度比加深网络更有效。高频抖动可以通过对输出做一阶低通滤波来缓解但更根本的解决方案是在损失函数里加一个输出变化量的惩罚项torch.mean((pred_seq[1:] - pred_seq[:-1])**2)这能直接压缩控制率的时间导数。训练完成后导出ONNX再转成STM32Cube.AI或TensorFlow Lite Micro能加载的模型格式。导出时输入维度必须固定为1×state_dim不要开动态batch。# 导出为ONNX供STM32Cube.AI或ONNX Runtime部署 dummy_input torch.randn(1, X_t.shape[1], dtypetorch.float32) torch.onnx.export(model, dummy_input, control_net.onnx, input_names[state], output_names[action], opset_version11)ONNX导出的一个关键点是网络层名称不要包含特殊字符量化时需要逐层统计激活函数的值域。部署后先在纯仿真环境里把神经网络控制率和LQR控制率的输出做逐拍对比误差超过5%就说明导出过程或量化造成精度损失需要检查是否有算子被替换成高误差版本。部署时控制率网络外部保留一个硬限幅参考LQR案例中的u_max值。5. 三种控制率算法的验证方法、常见坑与组合使用5.1 用同一套指标给三种算法做对照实验三种控制率算法算出来的控制器形态差异很大LQR是一组常数H∞是传递函数神经网络是一堆权重。验证时不能只看能不能稳住要把它们放进同一个仿真协议里做对照。我习惯用三个指标阶跃响应上升时间、扰动抑制峰值偏差、控制量峰值。给定同样的初始角度偏差0.2rad各跑一次仿真记录三条曲线。LQR胜在实现简单H∞胜在模型参数偏差20%时依然稳定神经网络胜在非线性区域响应平滑但三者需要分别验证才能得出你所在场景的结论。5.2 三个实现中最容易翻车的点LQR的翻车点集中在离散化不一致和权重维度无意义。很多实现直接把连续K用在离散系统上还以为是采样频率足够高所以没问题这个“足够高”很难量化不如从一开始就用dlqr。H∞的翻车点集中在加权函数与闭环带宽的匹配上gamma值调不到1以下时先检查W3的转折频率是否远大于W1的转折频率差距不足十倍时鲁棒性指标会互相打架。神经网络控制率的翻车点集中在数据分布和部署精度上训练集里的状态范围一定要覆盖实机的初始状态范围量化到int8之后要做逐拍对比不能只看稳态误差。提示验证神经网络控制率时先用纯仿真跑1000条随机轨迹任何一条轨迹的发散都算失败。只跑两三条调试轨迹无法证明安全性。5.3 把三种算法组合成一套控制率基线加补偿三种算法不是只能三选一。最稳妥的架构是让LQR或H∞输出基线控制量神经网络输出一个补偿增量两者相加后再进限幅器。基线控制器保证标称稳定性神经网络只学习残差负责修正模型误差带来的稳态偏差。组合时要给神经网络的补偿增量加一个限幅比如不超过基线控制量峰值的20%这样即使神经网络输出异常也不会把系统推出稳定域。调试这个架构的顺序是先用LQR单独跑通并记录误差曲线然后冻结LQR权重只训练神经网络拟合u_lqr与理想控制量的差值。最后验证时同时打印u_lqr、u_nn和总控制量三条曲线观察神经网络是否在需要补偿的频段上起作用。如果神经网络输出接近0说明基线控制器已经把问题解决得差不多这个场景不需要加网络如果神经网络输出持续饱和说明基线控制器本身性能不足应该回头调LQR而不是加大补偿系数。这个“先固定基线、再补偿残差、最后约束补偿幅度”的路径是三种算法配合时最省时间的落地方式。本文还有配套的精品资源点击获取