RflyExpCode无人机仿真实验:MATLAB解析PX4日志与调参实战
2026/9/13 16:09:57 网站建设 项目流程

简介:RflySim-RflyExpCode.zip是一套基于MATLAB/Simulink的无人机模拟与实验代码包,面向无人机控制、导航与算法验证方向的研究者和开发者,适用于飞行控制器设计、空气动力学建模、传感器数据处理等学习与二次开发场景。包内共409个文件,涵盖m脚本、slx模型、mat数据、pdf文档及png图片等类型:Simulink模型用于搭建飞行控制器与无人机动力学仿真环境,MATLAB脚本负责实验数据读取、滤波分析和结果可视化,pdf文档则提供原理与操作说明,整体约149.91MB。目前已有146人学习下载。资源内容覆盖空气动力学建模中的升力、阻力与扭矩计算,PID、状态反馈等飞行控制器设计,以及GPS、加速度计、陀螺仪等多源传感器数据处理;同时涉及路径规划、避障与自主导航等算法开发。通过对照模型与脚本,读者可以系统理解从理论建模到实验数据分析的完整流程,并基于提供的示例快速搭建自己的无人机实验平台。

1. 从一卷 RflyExpCode 开始:为什么 MATLAB 是无人机仿真的主战场

拿到RflySim-RflyExpCode.zip,先别急着解压后到处点开.m文件。这个包在 RflySim 生态里的位置,相当于把「Pixhawk 硬件在环、PX4 固件参数、MATLAB/Simulink 控制器模型、飞行日志」揉成了一组可对照的工程样本:里面有myownUAV.ac(自定义机型气动配置文件)、e4_ekf_A.bin(EKF 估计相关二进制)、e_acc1_A.bine4_A.bin(不同传感器/状态源的数据记录)、datalog_A.bin(飞行日志)、pixhawk_A.bin(硬件在环或实飞记录)、e3_m_A.bin(另一组电机/状态样本),以及同名出现三次的px4_simulink_app_params.c——这个重复出现的文件名在 Windows 解压时会被自动改名,但它恰恰说明 PX4 的 Simulink 应用层参数是这套实验的核心纽带。

对正在做无人机课程设计、毕设或飞控算法验证的人来说,这套代码的直接价值是:它把「理论上的姿态控制」变成「能跑在 Pixhawk 硬件上的二进制实验」。用 MATLAB 的原因也不复杂——Simulink 的代码生成工具链可以一键把控制器模型编译成 PX4 的模块,你在 Simulink 里拖出来的 PID 结构、状态观测器、导航逻辑,最终会变成px4_simulink_app_params.c里可配置的参数表。本文不会去复述工具箱介绍,而是顺着文件清单逐个拆解:每个.bin是什么实验留下的、.ac文件怎么描述一架自定义无人机、参数文件里哪些字段在真实调参时会被反复改动。

2. 文件清单里的实验脉络:从 myownUAV.ac 到 px4_simulink_app_params.c

2.1 myownUAV.ac:自定义机型的空气动力学入口

.ac后缀在 RflySim 中代表 Aircraft Configuration 文件,它不是 MATLAB 脚本,而是一个描述飞行器物理属性的文本配置。打开后常见字段包括机架类型(四旋翼/固定翼)、质量、转动惯量矩阵、电机位置、螺旋桨气动系数等。RflySim 的 Simulink 模块RflySim3DUAV Dynamics会读取这个文件,构建出你专属的被控对象模型。

我一般会先检查三个关键参数:转动惯量对角项Ixx/Iyy/Izz)、电机时间常数MotorTimeConstant)、螺旋桨推力系数ThrustCoeff)。转动惯量直接决定角加速度对控制力矩的响应快慢,如果实验机是 F450 机架,Ixx通常在0.01-0.03量级,但教学包里的myownUAV.ac可能已经针对某款自定义机架做了修改,所以不要照抄论文里的惯量值,而是用这个文件配合datalog做系统辨识。

提示:如果发现仿真中无人机高频振荡或响应迟缓,先怀疑.ac里的惯量是否和实际螺旋桨尺寸匹配,而不是急着调 PID。

2.2 一串 .bin 文件的实验语义:EKF、加速度计、电机与 Pixhawk 数据

.bin文件在 MATLAB 中通常用fopen+fread按字节读取,但 RflyExpCode 里的.bin更可能是 PX4 的 ULog 或自定义二进制日志。e4_ekf_A.bin名字里的e4多半对应实验编号(Experiment 4),ekf指明记录的是扩展卡尔曼滤波器的状态输出;e_acc1_A.bin保存的是加速度计通道原始数据或加计融合后的线加速度;e3_m_A.bin是第三组实验的电机控制信号样本;pixhawk_A.bin则是从 Pixhawk 硬件上导出的完整飞行记录,可能包含姿态、位置、IMU 原始数据。

要读懂它们,先在 MATLAB 里写一个通用读取框架:

function data = read_rfly_bin(filename) fid = fopen(filename, 'rb'); if fid == -1 error('无法打开文件: %s', filename); end % 读取头部 4 字节,判定是否为 PX4 ULog 格式 (magic: 0x55 0x4C 0x6F 0x67) magic = fread(fid, 4, 'uint8'); fseek(fid, 0, 'bof'); if isequal(magic', [0x55 0x4C 0x6F 0x67]) data = parse_ulog(fid); % 进入 ULog 解析流程 else % 自定义二进制格式:按固定结构体顺序读取 n_samples = fread(fid, 1, 'uint32'); data = struct('timestamp', [], 'acc', [], 'gyro', []); for i = 1:n_samples data.timestamp(i) = fread(fid, 1, 'uint64'); data.acc(i, 1:3) = fread(fid, 3, 'double'); data.gyro(i, 1:3) = fread(fid, 3, 'double'); end end fclose(fid); end

这段代码的逻辑分两级:先尝试验证文件头是否为 ULog 的特定魔数[0x55 0x4C 0x6F 0x67](对应 ASCII 字符 ULog),如果是就进入官方解析流程;否则回退到自定义结构体解析。freaduint64时间戳是 PX4 的微秒计数,换算成秒需要除以1e6。参数说明:第一参数filename是路径,第二参数'rb'表示二进制只读,这是 Windows 平台下避免换行符被错误转换的关键。

2.3 px4_simulink_app_params.c:控制器参数落地的关键文件

文件出现三次,说明解压时的重名导致了px4_simulink_app_params.cpx4_simulink_app_params - 副本.cpx4_simulink_app_params - 副本(2).c这样的组合。这些文件本质是同一份参数源,内容里是 PX4 自定义 Simulink 应用模块(比如姿态控制器)需要的参数结构体初始化代码。

看它时,我通常抓住两个宏定义:PARAM_*开头的float数组和px4_simulink_app_params结构体。结构体成员对应 Simulink 模型里的Simulink.Parameter,比如Kp_RollKi_PitchKd_Yaw。修改后要在 MATLAB 中重新生成代码,命令是:

% 打开模型 open_system('my_attitude_controller.slx'); % 修改参数 my_attitude_controller.Kp_Roll.Value = 6.5; my_attitude_controller.Ki_Pitch.Value = 0.05; % 生成代码到目标文件夹 slbuild('my_attitude_controller');

slbuild生成的是可嵌入 PX4 的 C 代码,而不是直接可执行文件。生成的*.c需要拷贝到 PX4 Firmware 的src/modules/simulink_app/目录下,再通过 PX4 的make px4_fmu-v5编译链编入固件。参数说明:open_system('my_attitude_controller.slx')第一个实参是模型名称,必须与当前工作目录下的.slx文件同名;slbuild默认生成ert_shrlib类型的共享库,若用于硬件编译,需要先配置 Embedded Coder 的目标硬件为 Pixhawk 系列。

提示:如果生成代码后报参数无法解析,优先检查 Simulink 模型里参数对象是否选中了Tunable属性,否则参数会被内联为常量,px4_simulink_app_params.c中对应的变量就不会生成。

3. MATLAB 中的数据回放与状态估计验证方法

3.1 用 datalog_A.bin 还原飞行状态

datalog_A.bin是这套实验里最有价值的调试素材,它保存的通常是同步对齐后的 IMU 数据、控制器输出、参考输入。还原时,第一步是把二进制指针移动到数据域起点,第二步是按列映射数据。假设文件存储格式是「时间戳(8 字节 uint64) + 三轴加速度(float32 的 3 列) + 三轴角速度(float32 的 3 列) + 四元数(float32 的 4 列)」,则读取代码为:

function flight = load_datalog(filename) fid = fopen(filename, 'rb'); fseek(fid, 0, 'eof'); file_size = ftell(fid); fseek(fid, 0, 'bof'); % 每行 8 + 3*4 + 3*4 + 4*4 = 48 字节 row_bytes = 8 + 12 + 12 + 16; n_rows = floor(file_size / row_bytes); flight.t = zeros(1, n_rows); flight.acc = zeros(n_rows, 3); flight.gyro = zeros(n_rows, 3); flight.quat = zeros(n_rows, 4); for i = 1:n_rows flight.t(i) = fread(fid, 1, 'uint64') / 1e6; flight.acc(i, :) = fread(fid, 3, 'float32')'; flight.gyro(i, :) = fread(fid, 3, 'float32')'; flight.quat(i, :) = fread(fid, 4, 'float32')'; end fclose(fid); flight.t = flight.t - flight.t(1); % 时间对齐到零 end

ftell配合fseek算出文件总字节数,再除以固定行宽得到行数,这种「先定长扫描再整块读取」的方法避免了循环中频繁判断文件末尾,解析速度更快。row_bytes = 8 + 12 + 12 + 16需要严格与数据生成端结构体#pragma pack(1)对齐,如果生成端有字节对齐,实际宽度可能是5664。遇到解析出乱码大概率是这里宽度算错——最稳妥的办法是先读一两行,直接打印十六进制对照结构体偏移。

3.2 EKF 估计结果与真实数据的对齐分析

e4_ekf_A.bin中记录的 EKF 状态与datalog_A.bin里直接测量量之间天然有相位差,因为 EKF 内部有预测-更新循环,滤波器输出是平滑后的最优估计,而加速度计样本是原始测量。把两组数据画在同一张图里时,我习惯先计算互相关来估计延迟:

[c, lag] = xcorr(data_ekf_roll, data_acc_roll, 50, 'normalized'); [~, idx] = max(abs(c)); time_delay = lag(idx) * dt; % dt 为采样周期 % 对齐后绘制对比曲线 t_aligned = data_ekf.time - time_delay; figure; plot(t_aligned, data_ekf_roll, 'b-', 'LineWidth', 1.5); hold on; plot(data_acc.time, data_acc_roll, 'r--', 'LineWidth', 1); legend('EKF 输出', '加速度计计算滚转角'); xlabel('时间 (s)'); ylabel('滚转角 (deg)');

xcorr的第三个参数50限制了最大滞后样本数,采样率 250 Hz 时对应 0.2 秒的最大延迟搜索范围;'normalized'选项让互相关值归一化到 [-1,1],避免振幅差异造成的假峰值。如果最大互相关值低于 0.6,先检查dt是否估算正确,再确认两个.bin文件的时间基准是否一致——RflySim 的仿真实验中,硬件时间戳和 Simulink 仿真时间戳常有固定偏移,需要先用第一段代码统一到秒。

3.3 用e_acc1_A.bin检查传感器饱和与振动

e_acc1_A.bin这类加速度计样本经常被忽略,但它反映的是飞控振动环境。多旋翼在接近共振转速时,加计读数会出现等幅振荡,而不是随机噪声。快速检测方法是计算功率谱密度:

fs = 250; % 采样率 [pxx, f] = pwelch(data_acc, hann(256), 128, 1024, fs); figure; plot(f, 10*log10(pxx)); xlabel('频率 (Hz)'); ylabel('功率谱密度 (dB/Hz)'); title('加速度计功率谱分析');

pwelchhann(256)是 256 点的汉宁窗,128是重叠样本数(50% 重叠),1024是 FFT 点数,频率分辨率约250/1024 ≈ 0.244 Hz。在 80-120 Hz 频段看到明显尖峰,说明机架振动传递到了 IMU;此时在 Simulink 模型里加低通滤波器的截止频率应设在这些尖峰频率的 1/2 以下,而不是随意选个 50 Hz。值得一提的是,e_acc1_A.bin里的数据可能已包含滤波器后的信号,若在低频段也看到整体抬升的底噪,说明滤波器带宽过宽或陷波器参数失配。

4. 从二进制日志到底层调参:RflyExpCode 的实战推演

4.1 参数辨识:从e3_m_A.bin提取电机响应曲线

e3_m_A.bin是电机控制输入与转速响应的记录。四旋翼模型中电机通常被建模为一阶惯性环节:G(s) = K / (τ s + 1),其中K是油门到转速的静态增益,τ是电机时间常数。读取文件后,用tfest直接辨识传递函数:

data_e3 = read_rfly_bin('e3_m_A.bin'); u = data_e3.control_cmd; % 控制输入(归一化 0~1) y = data_e3.motor_speed; % 电机转速(RPM) ts = 0.004; % 采样周期 250Hz % 构造 iddata 对象,输入输出单位时间 data_id = iddata(y, u, ts); sys_motor = tfest(data_id, 1, 0); % 一阶无零点模型 tau = sys_motor.Denominator(2) / sys_motor.Denominator(1); K = sys_motor.Numerator(1) / sys_motor.Denominator(1); fprintf('电机时间常数 tau = %.4f s, 增益 K = %.4f\n', tau, K);

tfest(data_id, 1, 0)中的1表示极点数,0表示零点数;一阶系统传函K/(τs+1)对应一个极点、无零点,因此选(1,0)。如果实际电机有信号延迟,尝试(1,1)模型加入一个零点来拟合相位。识别出的τ对控制频率设计很关键——如果 τ 超过 0.02 秒,姿态控制环的带宽就要适当下调,否则控制器输出的高频变化会被电机响应衰减掉,导致实际力矩跟不上指令。

4.2 把myownUAV.ac中的气动参数代入 Simulink 非线性模型

RflyExpCode 的 Simulink 模型模型顶层通常是RflyUAV模块,双击后可以关联myownUAV.ac。关联后,Simulink 内部会通过Load UAV Parameters回调函数读取文件中的惯量、质量、力臂长度等,生成一个总线结构体传给动力学模块。

我建议在模型初始化回调里加一行显式加载,避免手动点选遗漏:

% 在模型 Callback -> InitFcn 中添加 UAV_PARAM = load_ac_file('myownUAV.ac'); assignin('base', 'UAV_PARAM', UAV_PARAM);

load_ac_file是 RflySim 工具包提供的内置函数,返回结构体数组;assignin('base', ...)将结果放到 MATLAB 基础工作区,确保信号线中的UAV_PARAM总线可见。加载后,在 Simulink 的UAV Dynamics模块参数中确认MassInertia已经变成myownUAV.ac中的值,而不是默认的1.5 kgdiag([0.015 0.015 0.03])。如果模块显示还是默认值,多半是回调函数名拼写不一致——打开Model Explorer查看InitFcn是否真正执行了该行,必要时手动点击Edit -> Update Diagram强制执行回调。

4.3 联合仿真中的坑:Pixhawk 在环实验的配置顺序

把 Simulink 控制器编译进 Pixhawk 做硬件在环(HITL)时,容易踩三个顺序问题。第一,必须先运行 PX4 SITL 仿真,让 QGroundControl 与 RflySim3D 建立连接,再启动 Simulink 模型,否则 Pixhawk 端的 MAVLink 无法正确接收仿真姿态。第二,PX4 固件版本必须与e4_ekf_A.bin生成时的固件版本一致,不同 PX4 版本中 EKF 的状态量排列不同,直接拿旧.bin当参考会让 EKF 输出错位。第三,参数写入顺序要用px4_simulink_app_params.cPARAM_*宏配合 QGroundControl 的 Onboard Parameters 界面写入,不要直接在 C 文件里改硬编码值,那样每次编译都会静默覆盖。

具体编译命令在 RflySim 的脚本框架里常见两种方式:

# 方式一:利用 RflySim 的自动编译脚本 cd C:\PX4Firmware python Tools\setup\px4_simulink_generate.py my_controller.slx # 方式二:手动编译并上传 make px4_fmu-v5_default

px4_simulink_generate.py的核心工作是解析.slx里 Embedded Coder 生成的报告,找到model.cmodel.h后自动复制到src/modules/simulink_app/,同时更新 CMakeLists.txt。make px4_fmu-v5_default里的px4_fmu-v5是硬件型号,对应 Pixhawk 4;如果实际用的是 Pixhawk 6C,要改成px4_fmu-v6c_default,否则固件签名不匹配,上传后飞控不启动。

5. 实验学到的关键技巧:把 .bin 变成可交互的验证平台

5.1 用 MATLAB App Designer 做飞行数据快速查看器

RflyExpCode 里的处理脚本大多是一次性分析,但实际调试中反复读取不同.bin时,每次改路径和时间窗口很烦。我写过一个小工具,把读取、对齐、画图封装成一个交互面板。核心逻辑只有三步:选择文件、选择通道、绘制对比。

classdef FlightDataViewer < matlab.apps.AppBase properties (Access = public) UIFigure matlab.ui.Figure LoadButton matlab.ui.control.Button Ax1 matlab.ui.control.UIAxes Ax2 matlab.ui.control.UIAxes Data struct end methods (Access = private) function loadData(app, ~) [fname, pname] = uigetfile('*.bin', '选择飞行日志'); if isequal(fname, 0); return; end app.Data = read_rfly_bin(fullfile(pname, fname)); end function plotData(app, ~) t = app.Data.t; plot(app.Ax1, t, app.Data.acc(:, 1), 'r'); hold(app.Ax1, 'on'); plot(app.Ax1, t, app.Data.acc(:, 2), 'g'); plot(app.Ax1, t, app.Data.acc(:, 3), 'b'); legend(app.Ax1, {'Ax', 'Ay', 'Az'}); end end end

上面用了matlab.apps.AppBase作为基类,app.Data保存在属性里供多个回调共享,避免每次画图都重新读文件。uigetfile('*.bin')返回文件名与路径,注意返回顺序是[filename, pathname],初学者常写反导致fullfile拼接错误。两个坐标轴Ax1/Ax2可以一个画三轴加速度,一个画四元数,在飞行实验中快速定位异常时间段。

5.2 从 EKF 输出的协方差矩阵判断滤波发散

e4_ekf_A.bin中不仅保留状态量,通常还包括状态协方差对角的根号,视觉上表现为ekf_cov曲线。滤波发散时,协方差会呈指数上升,然后估计值开始漂移。检测策略是在 MATLAB 中扫描协方差序列:

cov_data = data_ekf.cov_diag; % Nx7 矩阵,对应位置、姿态等 7 维状态 figure; semilogy(data_ekf.t, cov_data); xlabel('时间 (s)'); ylabel('协方差 (log 尺度)'); legend({'x', 'y', 'z', 'q0', 'q1', 'q2', 'q3'}); ylim([1e-6, 1e2]);

semilogy使用对数坐标,是为了同时看清正常工况下1e-6量级的协方差和发散时1e1以上的跳变。如果滚转角对应协方差在 5 秒内从1e-5涨到1e-1,说明该轴量测噪声矩阵R设置过小——滤波器过于相信量测,位置稍微跳变就认为速度也跳变。此时回 Simulink 中调EKF 模块的 R 参数,把加速度计噪声从0.02调到0.08,重新生成代码再做一组e4_ekf_A.bin对比。协方差跳变沿也可用于检查 IMU 与 EKF 的时间戳是否错位,错位时协方差会先出现单点尖峰然后回落。

5.3 用 Simulink Test 自动化对比不同参数的飞行表现

当你在px4_simulink_app_params.c里改了Kp_Roll,想验证不同取值的效果,手工一次次起飞既费时又难以保证环境一致。RflyExpCode 这类工程包通常集成了 Simulink Test 的支持,可以写一个直接跑批的脚本,把参数数组串起来循环仿真并保存日志:

Kp_values = [4.0, 5.0, 6.0, 7.0]; for k = 1:length(Kp_values) set_param('my_controller/Kp_Roll', 'Value', num2str(Kp_values(k))); % 运行仿真 simOut = sim('rfly_uav_hitl_model', 'StopTime', '20'); roll_overshoot(k) = compute_overshoot(simOut.logsout.get('roll').Values.Data); disp(['Kp = ', num2str(Kp_values(k)), ' -> 超调 = ', num2str(roll_overshoot(k))]); end

set_param直接改模型内部 Gain 模块的Value,适用于参数没有封装成 Simulink.Parameter 的情况;如果是独立参数对象,更推荐用my_controller.Kp_Roll.Value = ...的方式。sim('rfly_uav_hitl_model', 'StopTime', '20')会在当前目录生成simOut,其中的logsout包含模型里配置的标记信号。compute_overshoot是自己写的简单函数:找到阶跃响应的最大值、减去终值、再除以终值变化量。跑完四组 Kp 后,直接在命令行打印超调与时域响应时长,然后回px4_simulink_app_params.c里修改你选中的那组参数重新部署硬件。

最后落到一个具体技巧:所有.bin日志的解析脚本前缀保持同一套read_rfly_bin函数。RflyExpCode 的实验数据格式虽然不统一,但用统一的读取入口可以快速适配;新增实验文件时只需要在parse_ulog分支里加一个if判断数据主题名,不必重写整段逻辑。这样做的好处是当你拿到第二个版本的RflyExpCode时,旧的解析工具不用推倒重来。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询