资讯详情

资讯详情

四旋翼无人机PID控制Matlab仿真:从零搭建完整模型与调参指南

不管是刚接触四旋翼还是准备调真正的飞控板我的建议始终是同一个先在电脑里把控制逻辑跑通再谈上天的事。四旋翼这东西油门一推、姿态一偏炸机就在几秒之内但Matlab仿真里你可以随便炸参数随便调成本就是重跑一次脚本。这篇文章就是带你从零搭一个完整的四旋翼无人机PID控制仿真不依赖任何硬件纯软件环境跑完你就能看到无人机从起飞点自动飞到目标位置、姿态稳定、响应可控的全过程。整个仿真做下来你会接触到几个绕不开的核心点四旋翼的动力学模型怎么建、位置环和姿态环的串级PID怎么设计、四个电机的推力怎么分配、以及为什么有些参数调大会直接让仿真发散。这些搞明白了后续不管是换Simulink搭模型、还是真机上写Pixhawk固件逻辑都是通用的。文章里给的代码是完整可运行的复制到Matlab里直接跑就行建议配合2020以后的版本使用。1. 为什么新手要先做仿真整体思路拆解1.1 仿真解决的核心问题很多新手拿到四旋翼的第一反应是先把桨装上、把遥控器对好频、推油门试飞。这个流程不能说错但如果你的目的是学控制、学调参那真机的试错成本太高了。飞控参数没调好轻则机身抖振、姿态漂移重则直接翻机。而在Matlab仿真里你可以反复试同一组参数观察每一步的状态变化甚至故意把参数调到发散去体会那种“因果关系”被放大的感觉。仿真还有一个好处是状态全透明。真机上你只能通过日志看数据而且数据有延迟、有噪声仿真里你想看什么变量就看什么变量位置、速度、姿态、角速度、每个电机的推力全部可以实时打印成曲线。这种“上帝视角”对于理解四旋翼的工作原理帮助非常大。1.2 四旋翼控制系统的整体架构四旋翼的控制逻辑从上到下分好几层新手最容易犯的错误是想一步到位写一个“总控制器”——输入是目标位置输出直接是四个电机转速。这在数学上不是不行但调参会调到崩溃。更主流的做法是串级控制外层管位置内层管姿态最内层管角速度。位置环根据当前位置与目标位置的误差计算出期望的加速度再换算成期望的横滚角和俯仰角。姿态环跟踪位置环给出的期望姿态角输出期望的角速度。角速度环跟踪姿态环给出的期望角速度输出三个轴的力矩指令。控制分配把总推力和三个力矩换算成四个电机的推力。每一层各司其职调参时也可以分层调试。这个架构在真机上同样是主流方案。1.3 为什么用Matlab而不是直接上Python我知道不少人会问现在Python也有numpy、scipy为什么教程还是用Matlab。原因很实际Matlab的矩阵运算、绘图、调试可视化是一体化体验尤其对于控制领域的学生和从业者Matlab/Simulink就是行业标准工具。而且后面你想做更复杂的验证比如Simulink建模、Carsim联合仿真、代码生成烧录到飞控Matlab的生态是现成的。当然仿真的核心思路放到Python里也完全成立文章里代码逻辑是语言无关的你理解了之后用Python复刻一遍也不是难事。2. 核心基础四旋翼动力学模型2.1 坐标系统与状态变量四旋翼的运动描述需要定义一个状态向量。这里我采用最常见的12维状态状态含义如下变量含义单位x, y, z无人机在世界坐标系中的位置mvx, vy, vz世界坐标系下的线速度m/sphi, theta, psi横滚角、俯仰角、偏航角radp, q, r机体坐标系下的角速度rad/s这里要特别注意姿态角的顺序问题。我使用的是Z-Y-X欧拉角先偏航再俯仰再横滚这是航空领域最常见的定义方式。旋转矩阵就是从机体坐标转换到世界坐标的关键桥梁。2.2 牛顿-欧拉方程推导四旋翼的动力学分为平动和转动两部分。平动部分由牛顿第二定律描述外力包括重力、四个旋翼产生的升力、以及空气阻力。在仿真中我可以把空气阻力忽略或者加入一个简单的线性阻尼项。转动部分由欧拉方程描述力矩包括横滚力矩、俯仰力矩、偏航力矩以及陀螺效应项。平动方程m * a_world [0; 0; -m*g] R * [0; 0; F_total]其中R是机体到世界的旋转矩阵F_total是四个电机推力的总和。这条公式看着简单但它包含了无人机最核心的耦合关系机身倾斜即改变姿态角会让推力产生水平分量从而实现水平运动。转动方程p_dot (tau_phi - (Iz - Iy) * q * r) / Ix q_dot (tau_theta - (Ix - Iz) * p * r) / Iy r_dot (tau_psi - (Iy - Ix) * p * q) / Iz里面的(q*r)项就是陀螺效应耦合项。新手看到这里不需要太紧张在代码里我们只是把它作为数学计算的一部分写进去。2.3 控制分配四个电机如何实现六自由度运动四旋翼有四个输入但要控制六个输出三个位置、三个姿态是因为四旋翼本身就是一个欠驱动系统——水平位置是通过倾斜机身来实现的而不是直接靠水平推力。四个电机和三个力矩之间的关系总推力F f1 f2 f3 f4横滚力矩tau_phi L * (f4 - f2)俯仰力矩tau_theta L * (f3 - f1)偏航力矩tau_psi cm * (f1 - f2 f3 - f4)这里假设电机1在前、2在右、3在后、4在左的十字型布局L是机臂长度。偏航力矩依赖的是电机之间的扭矩差即对角电机同向旋转、相邻电机反向旋转带来的反扭矩差。这个分配关系在代码里就是一个4x4矩阵的求逆非常直观。3. PID控制原理与四旋翼控制架构设计3.1 PID控制器的核心逻辑PID三个字母分别代表比例、积分、微分。比例项让系统朝误差减小的方向运动积分项消除稳态误差微分项抑制超调和振荡。对于四旋翼这种系统P和D的作用比I明显得多尤其在内环积分用不好反而会导致振荡。为什么四旋翼的偏航和横滚控制里积分的权重通常很小因为四旋翼的稳态误差主要来自模型误差和外部扰动比如风。在仿真中模型是精确的没有电机老化、没有重心偏移所以积分项可有可无。但在真机中积分项还是必要的只是需要限幅防止积分饱和。3.2 串级PID四旋翼为什么用内外环串级PID的设计思路是用内环控制响应更快的物理量角速度用外环控制响应更慢的物理量位置。角速度的响应速度比位置快一个数量级所以内环带宽要大于外环带宽否则内外环会互相干扰系统很容易振荡。形象地理解外环是“指挥官”它决定无人机应该向哪个方向倾斜、倾斜多少角度内环是“执行者”它负责快速把机身姿态稳定到指挥官要求的角度上。如果执行者动作太慢指挥官不断下发新指令两者就会打架。3.3 参数整定思路与初始值新手拿到PID参数最容易做的事是到处找“现成参数”其实参数整定是有套路的先调内环角速度环给定一个期望角速度看跟踪效果。再调姿态环角度环给定一个期望角度看姿态响应。最后调位置环看整机的位置跟踪效果。每层调试时先只加P让系统不振荡再加D抑制超调最后根据稳态误差决定是否加I。4. 完整代码实现与逐段解析4.1 主程序框架与参数设置先看最基础的部分。以下代码可以直接保存为quadrotor_sim.m运行。%% 四旋翼无人机PID控制仿真 - 主程序 clear; clc; close all; %% 1. 无人机物理参数 m 1.2; % 质量 (kg) g 9.81; % 重力加速度 (m/s^2) L 0.25; % 机臂长度 (m) Ix 0.012; % X轴转动惯量 (kg*m^2) Iy 0.012; % Y轴转动惯量 Iz 0.025; % Z轴转动惯量 cm 0.01; % 偏航力矩系数简化这里的关键是可调参数质量、机臂长度、转动惯量。不同型号的四旋翼这些参数差异很大如果你后面想仿真自己组的无人机直接把这几行改成你实测的参数即可。4.2 PID参数初始化%% 2. PID参数 % 位置环PD Kp_x 1.5; Kd_x 2.0; Kp_y 1.5; Kd_y 2.0; Kp_z 3.0; Kd_z 3.5; % 姿态角度环P Kp_phi 30; Kp_theta 30; Kp_psi 10; % 角速度环PID Kp_p 5; Ki_p 0.8; Kd_p 0.1; Kp_q 5; Ki_q 0.8; Kd_q 0.1; Kp_r 3; Ki_r 0.5; Kd_r 0.05;再强调一次这套参数是我在默认机型参数下调好的如果你改了飞机质量或者转动惯量参数就要重新整定。实战经验是参数整定的目标不是让响应曲线“完美”而是让系统在合理范围内快速稳定。过度追求零超调会导致响应很慢这在四旋翼上不太实用。4.3 主循环控制器与动力学更新这是整个仿真最核心的一段。%% 3. 仿真设置与初始化 dt 0.005; t_end 20; t 0:dt:t_end; N length(t); state zeros(12,1); % x y z vx vy vz phi theta psi p q r pos_des [2; 1; 3]; % 目标位置单位米 yaw_des 0; % 目标偏航角弧度 pos_log zeros(N,3); att_log zeros(N,3); int_p 0; int_q 0; int_r 0; prev_rate_err zeros(3,1); for k 1:N % 状态提取 pos state(1:3); vel state(4:6); att state(7:9); rate state(10:12); % 位置环外环 pos_err pos_des - pos; vel_err -vel; acc_des [Kp_x*pos_err(1) Kd_x*vel_err(1); Kp_y*pos_err(2) Kd_y*vel_err(2); Kp_z*pos_err(3) Kd_z*vel_err(3)]; F_des m * (acc_des(3) g); F_des max(F_des, 0.1); phi_des ( acc_des(1)*sin(yaw_des) - acc_des(2)*cos(yaw_des)) / g; theta_des ( acc_des(1)*cos(yaw_des) acc_des(2)*sin(yaw_des)) / g; phi_des max(min(phi_des, deg2rad(20)), -deg2rad(20)); theta_des max(min(theta_des, deg2rad(20)), -deg2rad(20));外环这段代码的逻辑位置误差通过PD控制计算出期望加速度然后再推算出总推力。z方向的期望推力要加上重力分量否则飞机会一直往下掉。这也是新手最容易漏掉的地方——别忘了抵消重力。水平和垂直方向的加速度通过小角度假设换算成期望姿态角。小角度假设在倾斜角小于20度时精度足够这也是我在这里对期望角做限幅的原因。% 姿态环与角速度环内环 att_err [phi_des - att(1); theta_des - att(2); yaw_des - att(3)]; att_err(3) atan2(sin(att_err(3)), cos(att_err(3))); rate_des [Kp_phi*att_err(1); Kp_theta*att_err(2); Kp_psi*att_err(3)]; rate_err rate_des - rate; int_p int_p rate_err(1)*dt; int_q int_q rate_err(2)*dt; int_r int_r rate_err(3)*dt; int_p max(min(int_p, 2), -2); int_q max(min(int_q, 2), -2); int_r max(min(int_r, 2), -2); rate_der (rate_err - prev_rate_err)/dt; prev_rate_err rate_err; tau_phi Kp_p*rate_err(1) Ki_p*int_p Kd_p*rate_der(1); tau_theta Kp_q*rate_err(2) Ki_q*int_q Kd_q*rate_der(2); tau_psi Kp_r*rate_err(3) Ki_r*int_r Kd_r*rate_der(3);内环这段是串级PID的核心。姿态误差经过P控制器得到期望角速度角速度误差再经过PID控制器得到期望力矩。注意偏航角的误差用了atan2做了归一化避免出现“目标偏航角是0度实际偏航角是359度”时误差计算为359度这种蠢问题。积分限幅也非常重要。不限制积分项的话电机推力的积分项可能会累积到很大导致响应出现大幅超调。% 控制分配 A [1 1 1 1; 0 -L 0 L; -L 0 L 0; cm -cm cm -cm]; b [F_des; tau_phi; tau_theta; tau_psi]; f_motor A \ b; f_motor max(f_motor, 0); f_motor min(f_motor, 10); % 动力学更新 F_actual sum(f_motor); tau_phi_actual L*(f_motor(4) - f_motor(2)); tau_theta_actual L*(f_motor(3) - f_motor(1)); tau_psi_actual cm*(f_motor(1) - f_motor(2) f_motor(3) - f_motor(4)); phi att(1); theta att(2); psi att(3); R [cos(theta)*cos(psi), sin(phi)*sin(theta)*cos(psi) - cos(phi)*sin(psi), cos(phi)*sin(theta)*cos(psi) sin(phi)*sin(psi); cos(theta)*sin(psi), sin(phi)*sin(theta)*sin(psi) cos(phi)*cos(psi), cos(phi)*sin(theta)*sin(psi) - sin(phi)*cos(psi); -sin(theta), sin(phi)*cos(theta), cos(phi)*cos(theta)]; acc_world [0;0;-g] R*[0;0;F_actual/m]; p_dot (tau_phi_actual - (Iz-Iy)*rate(2)*rate(3)) / Ix; q_dot (tau_theta_actual - (Ix-Iz)*rate(1)*rate(3)) / Iy; r_dot (tau_psi_actual - (Iy-Ix)*rate(1)*rate(2)) / Iz; phi_dot rate(1) rate(2)*sin(phi)*tan(theta) rate(3)*cos(phi)*tan(theta); theta_dot rate(2)*cos(phi) - rate(3)*sin(phi); psi_dot (rate(2)*sin(phi) rate(3)*cos(phi)) / cos(theta); state(1:3) state(1:3) vel*dt; state(4:6) state(4:6) acc_world*dt; state(7:9) state(7:9) [phi_dot; theta_dot; psi_dot]*dt; state(10:12) state(10:12) [p_dot; q_dot; r_dot]*dt; pos_log(k,:) state(1:3); att_log(k,:) state(7:9); end控制分配矩阵中的物理意义再展开讲一下矩阵第一行让四个电机共同提供总推力第二行根据左右电机的差产生横滚力矩第三行根据前后电机的差产生俯仰力矩第四行根据对角电机的综合效果产生偏航力矩。A矩阵就是控制效率矩阵通过它你可以明确知道每个电机对各个通道的贡献大小。动力学更新部分用到了旋转矩阵它的作用是把机体系下的推力转换到世界系。最后是状态积分这里用的是最朴素的欧拉法。欧拉法的精度虽然不如四阶龙格库塔但只要仿真步长足够小本代码为5ms完全满足教学演示需求。4.4 结果可视化与代码解读%% 4. 画图 figure(Position, [100 100 800 900]); subplot(3,1,1); plot(t, pos_log(:,1), r, t, pos_des(1)*ones(N,1), r--); ylabel(x (m)); title(位置响应); legend(x, 目标x, Location, best); grid on; subplot(3,1,2); plot(t, pos_log(:,2), g, t, pos_des(2)*ones(N,1), g--); ylabel(y (m)); legend(y, 目标y, Location, best); grid on; subplot(3,1,3); plot(t, pos_log(:,3), b, t, pos_des(3)*ones(N,1), b--); ylabel(z (m)); xlabel(时间 (s)); legend(z, 目标z, Location, best); grid on; figure(Position, [950 100 800 600]); plot(t, rad2deg(att_log)); xlabel(时间 (s)); ylabel(角度 (deg)); legend(roll, pitch, yaw, Location, best); grid on; title(姿态角响应);运行这段代码后你会看到两个图第一个是位置响应曲线可以看到无人机在大概5秒左右到达目标位置(2, 1, 3)第二个是姿态角响应可以看到起飞阶段俯仰角和横滚角有短暂倾斜然后逐渐回到0度。这个行为完全符合物理直觉无人机要水平移动必须先倾斜机身。5. 进阶用Simulink搭建同样的系统5.1 Simulink模型结构脚本仿真的优点是逻辑清晰、适合单步调试但如果你想以后对接更复杂的场景比如Carsim联合仿真、硬件在环测试迟早要转移到Simulink。用Simulink搭建四旋翼模型的思路和脚本完全一致只是把代码块变成了模块控制器子系统包含位置环、姿态环、角速度环的PID模块。控制分配子系统用增益矩阵实现4x4矩阵运算。动力学子系统用S函数或者简单的积分模块搭出状态方程。可视化模块用Scope或To Workspace输出曲线。5.2 模块配置与参数设置在Simulink中搭建时有几个关键点要特别注意。PID模块里要勾选“Integrator anti-windup”选项否则当执行器饱和时积分项会不断累积。期望姿态角的限幅模块也别忘了加这和脚本代码里的deg2rad(20)限幅是一个作用。另外推荐使用powder风格的配色和合理命名别笑很多人在Simulink里找信号线找到崩溃。把每个环路的信号用不同颜色标记出来对排查错误效率提升很大。5.3 脚本仿真与Simulink仿真的对比两者的计算结果应该是一致的。脚本仿真的优势是代码即文档、容易版本管理Simulink的优势是可视化程度高、便于扩展信号流。我的建议是先跑通脚本理解逻辑再搭Simulink理解信号流。两条腿走路对于控制工程师来说是基本功。6. 常见问题与排查技巧实录6.1 仿真发散多半是参数问题新手跑仿真时最常遇到的情况是曲线直接冲到无穷大。发散的原因通常有三个一是参数设置过大特别是内环PID的P值过大导致高频振荡二是仿真步长过大导致数值积分不稳定三是控制分配矩阵写错了导致正反馈。排查方法很简单先把内环的D和I都归零只保留很小的P确认系统稳定后再逐步增加参数。如果你的P值从1加到5系统就震荡了说明这个轴的转动惯量很小需要的P值本来就不大强行加大会让系统进入正反馈区。6.2 位置到达不了目标点怎么处理如果无人机最终稳定在一个偏离目标点的位置通常是因为位置环缺少积分项。仿真中模型是精确的所以P和D的组合理论上可以消除稳态误差——因为误差趋近于零时加速度指令也趋近于零推力刚好等于重力。如果还不行检查一下期望姿态角的限幅是否限制得太小导致水平加速度分量不够。真机上位置环一般也用PIDI项主要用于补偿风等扰动。仿真中如果位置到不了目标优先检查动力学方程里重力是否抵消干净了。6.3 新手容易忽略的几个细节初始高度z0时期望高度z3无人机会先加速上升再减速如果你把Kp_z调得很大会出现明显的超调然后回稳这是正常的。仿真步长dt不要太随意脚本里用的5ms在大多数情况下是安全的如果你改成50ms大概率会因为数值积分误差导致结果失真。电机推力不能为负很多新手忽略了这一点控制分配得到负数后直接带入动力学结果无人机“往下吸”完全违背物理。代码里用了max(f_motor, 0)就是干这个用的。这些看起来都是小细节但每一个都是我在实际仿真中踩过的坑。尤其电机推力限幅这一步漏掉之后整个系统的运动轨迹会变得很奇怪你还会误以为是PID参数的问题白白浪费半天时间。最后再分享一个调试技巧不要只盯着位置曲线和姿态曲线建议顺手把四个电机的推力也打印出来。当你的无人机“悬停”时四个电机推力应该稳定在mg/4附近如果某个电机的推力一直顶在饱和值上不掉下来说明控制分配或者参数配置有逻辑问题优先排查那个方向。把这个习惯养成你后面做真机调试也会顺利很多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →