PX4除了是一个开源的飞控软件之外,还拥有一个庞大的生态系统,包括各种工具、库和社区资源。这些资源可以帮助开发者更高效地进行开发、测试和部署。 考虑到文章成稿后长时间不会更新,本章教学环境采用长期维护版本。
具体环境参考如下:
- Linux发行版:Ubuntu 22.04(长期支持版本,适合开发和部署)
- PX4版本:v1.14.3(PX4在1.14.0后迎来大幅度改动,在之后的1.15以及1.16中大多是对传感器、高级算法等进行改进,整体架构变化不大)
- ROS2版本:Humble(ROS2的长期支持版本,适合长期项目开发)
- Matlab版本:R2024b (Matlab版本不受限,只要支持ROS2 Humble即可)
无人机硬件参考如下:
- 飞控:微空micoair H743v2
- 电调:微空60A四合一电调(每路最高60A)
- 上位机:鲁班猫3(RK3576 8GB+64GB)
- 传感器:动作捕捉系统(自开发可以使用D435|Mid 360等传感器)
- 电机:2150kv 2006 无刷电机
- 螺旋桨:乾丰D90
- 机架:怪象35Pro(不建议使用,机架本身不存在合适上述飞控的定位孔,购前需确认)
- 电池:格氏6S 1800mAh 75C xt60(实际续航在5分钟左右)可以换为6s 2700mAh
- 其他:P23C稳压模块(5V3A为上位机供电)
- 遥控:天地飞ET16S(或类似型号,或直接微空套装,至少价格在500元以上)
QGC 使用
QGroundControl(QGC)是一个开源的地面控制站软件,支持多种无人机平台,包括使用PX4飞控的无人机。QGC提供了一个用户友好的界面,用于监视和控制无人机的飞行状态、规划飞行路径、调整飞行参数等功能。
QGC地面站可以在官网下载,该软件支持Windows/Linux以及安卓平台使用。
https://github.com/mavlink/qgroundcontrol/releases

地面站初始界面主要分为图中框选部分。
黄色框-主菜单选项:包含地面站设置,飞控参数设置,飞控固件烧录,飞控数据实时查看,nsh命令行。
红色框-连接无人机选项:QGC会自动查找已连接的无人机,但仍旧可以尝试手动连接,具体连接配置见手动连接
绿色框-实时UI显示:右键该区域可以更改仪表,实时数据变量等。
在连接无人机后,点击Not Ready部分,能够解锁。如果解锁按键为灰色,证明当前状态存在错误/致命风险,具体信息显示在Overall Status中。红色代表必须解决才能够起飞的严重警告,橙色表示可以不解决也能够起飞的警告。
地面站设置

点击该图标进入地面站设置。
设置界面如下图所示:

按照需求进行配置即可。在该界面所有设置均不影响飞控内部参数设置。
通用设置
配置APP语言、常用单位、常用飞控与机型等。 这会影响到地面站的部分数据显示,但不会影响飞控本身。
主界面飞行显示设置
主要配置无人机飞行时,主界面地图显示与检查单等是否开启。
航路规划显示设置
预设高度、距离,多机设置。
自动/手动连接配置
选择开关自动连接的不同的设备(Pixhawk/Sik数传/UDP/RTK GPS)。
配置手动连接的设备。

仿真常用UDP,实飞一般使用数传,部分飞控支持蓝牙。
飞控参数配置

连接飞控后,进入Configure界面。

飞控固件烧录
选择固件烧录界面,断开飞控连接后在此界面自动连接激活bootloader进入固件烧录模式。

固件可以选择官方提供的最新稳定版,也可选择自行上传固件,点击自定义固件文件即可。 历史PX4固件可在下方连接下载。
https://github.com/PX4/PX4-Autopilot/releases
机架设置
PX4提供了多种预设机型,通过选择这些机型,执行器设置界面会有不同的变化,如果预设都不是你想要的飞行器,需要你自行设置所有的执行器。

执行器设置
不同飞行器装有不同的执行器。
常见四旋翼、六旋翼、八旋翼和共轴四旋翼等的电机数量都不同,需要按照机型配置。
另外,直升机、倾转eVTOL、矢量四旋翼等具备变桨距、矢量推力的飞行器具备额外舵机执行器。
目前,PX4飞控支持CAN、PWM、Dshot协议控制电机,CAN、PWM协议控制舵机。

传感器校准
飞控硬件一般板载IMU、磁罗盘,这些传感器需要定期校准已达到收敛状态。这些传感器会随着温度、高频抖动、电磁干扰等外界因素导致精度逐渐失真。
在各个子菜单栏进入校准页面,按照屏幕提示完成校准。

安全参数设置
飞行器飞行需遵循 《无人驾驶航空器飞行管理暂行条例》 ,限飞区、适飞空域等地的最大限飞高度不同。需要设置飞行器最大的飞行高度与飞行半径。

PID自整定
在飞行过程中,PX4能够自行整定各控制环的PID。在姿态模式或高度模式下,无人机自身会周期性重复3-4次小阶跃指令,自身观察姿态反馈。小扰动下观测姿态响应能够推出低阶模型,进而实现PID的自整定。

电池设置
一般飞控套件具有电流计与电压检测,会实时反馈电池电压与输出电流(甚至有些能够反馈电池温度)。将你使用的电池电芯数量、最大与最小电压、强制降落电压进行配置,使得地面站能够实时显示当前电量(百分比显示),便于查看当前续航。

遥控器对频与校正
遥控器是无人机必不可少的设备,是紧急/手动控制的最后防线。 PX4支持主流协议:
| 协议名称 | 类型 / 备注 | 相关说明 |
|---|---|---|
| PPM | 通用串行协议 | 一种早期的模拟脉冲位置调制协议。 |
| SBUS | 通用串行协议 | Futaba公司开发的数字串行协议,是目前非常主流的协议之一。 |
| DSM / DSM2 / DSMX | 通用串行协议 | Spektrum公司使用的数字协议家族,DSMX是其最新版本。 |
| CRSF (Crossfire) | 特定厂商协议 | TBS(Team BlackSheep)公司的协议,用于其Crossfire系列远航遥控系统。 |
| GHST | 特定厂商协议 | ImmersionRC公司的Ghost系列遥控系统使用的协议。 |

飞行模式分配
PX4飞控支持遥控器切换飞行模式。 PX4飞行模式有:
注意:PX4 v1.14 中旧版
POSCTL与ALTCTL模式已被移除,分别由POSITION与ALTITUDE取代;固定翼原STABILIZED模式并入MANUAL。
多旋翼(Multicopter):
MANUAL/STABILIZED:手动 / 自稳模式ACRO:特技模式(角速率控制,不自稳)ALTITUDE:定高模式(保持高度,允许漂移)POSITION:定点模式(保持位置悬停)HOLD:悬停保持(定点刹车并悬停)ORBIT:环绕模式(绕目标点画圆飞行)TAKEOFF:起飞模式LAND:降落模式RETURN:返航模式(RTL)MISSION:任务模式(执行航点任务)OFFBOARD:外部控制模式(由上位机/MAVROS 发送位置、速度等指令)FOLLOW ME:跟随模式(跟随地面站位置)
固定翼(Fixed-Wing):
MANUAL:手动模式(v1.14 起替代原 Stabilized 自稳模式)ACRO:特技模式ALTITUDE:定高模式POSITION:定点模式HOLD:保持模式TAKEOFF:起飞模式LAND:降落模式RETURN:返航模式MISSION:任务模式OFFBOARD:外部控制模式
VTOL: 飞行模式为多旋翼与固定翼模式的合集,由 VTOL 状态机根据飞行状态自动切换(如多旋翼模式完成起降,固定翼模式巡航)。
小车(Rover): MANUAL、ACRO、STEERING、HOLD、MISSION、RETURN、OFFBOARD
无人船(Boat): MANUAL、ACRO、HOLD、MISSION、RETURN、OFFBOARD
此外,PX4 v1.14 还支持通过 MAVLink 注册的外部自定义模式(External Mode),例如 MAVSDK 等外部系统可以注册并使用自己的模式。

参数修改
PX4内部有诸多参数可供修改,如PID控制参数、串口配置参数等。 通过阅读源码注释/参数界面的注释可以详细了解每个参数的含义。 同时,如果对飞控进行了修改,部分自定义的变量/参数也会出现在此处,便于修改。

本质上,PX4内部运行着一个nuttx操作系统,其配置有nuttx shell命令行,简称nsh。如果不使用地面站进行在线参数更改,可以使用命令行进行访问。 sudo apt-get install -y cu echo "alias nsh='cu -l /dev/ttyACM0 -s 115200'" >> ~/.bashrc source ~/.bashrc 之后使用nsh命令即可进入nsh命令行,使用 param show * 查看全部参数,param set xxx xxx 更改某参数。
实时参数监测
地面站支持在飞行过程中对数据进行实时监看。
点击Analyze按钮,进入状态分析页面。
在该页面,我们能够下载过往飞行日志(日志每次解锁后开始记录,上锁后停止记录。),也可以查看部分状态变量。
选中部分变量,能够在地面站实时绘图,右上角的独立对话框能够挂起该界面。
MAVROS 自主飞行
MAVROS是一个ROS包,提供了与MAVLink协议兼容的飞行控制器通信接口。通过MAVROS,开发者可以使用ROS的工具和库来控制无人机,实现自主飞行、路径规划、状态监测等功能。
但ROS wiki已经很久没有进行更新,需要参考MAVLINK在github上的源码和文档,或者直接查看PX4的源码来了解MAVROS的具体实现细节。
我们先看一下MAVROS控制无人机飞行的数据流图:
这里需要参考的是上位机产生的/mavros/setpoint_position/local话题,这个话题是MAVROS提供的接口,允许开发者通过发布消息来控制无人机的飞行。MAVROS会将这些消息转换为MAVLink协议的命令,并通过串口或网络发送给飞控。
另外,无人机在室内飞行时由于没有GPS信号,通常会使用视觉惯性里程计(VIO)或动作捕捉系统来提供位置信息。MAVROS也支持从这些传感器获取数据,并将其发送给飞控,以实现更精确的飞行控制。他们的话题为/mavros/vision_pose/pose,通过MAVROS发送到飞控后,会进入EKF2滤波器进行融合,提供无人机的位置信息,产生/mavros/local_position/pose(在PX4里的uorb消息叫做vehicle_local_position)。
图右侧不是ROS2的话题,而是PX4源码的文件路径,每个代码块代表一个模块,模块之间通过uORB消息总线进行通信。从上至下是飞控四旋翼控制流图的代码表示。
上位机环境配置
ROS2 Humble 安装
使用FishROS一键安装ROS2
wget http://fishros.com/install -O fishros && . fishros
选择ROS2 Humble一键安装。
MAVROS 安装
安装MAVROS:
sudo apt-get install ros-humble-mavros
sudo apt-get install ros-humble-mavros-extras
mavlink需要geographiclib系列包,安装方法如下:
git clone -b ros2 https://github.com/mavlink/mavros.git
cd mavros/mavros/scripts
sudo ./install_geographiclib_datasets.sh
最后一句sudo ./install_geographiclib_datasets.sh需要根据实际网络环境等待一会儿,运行完终端显示如下
Installing GeographicLib geoids egm96-5
Installing GeographicLib gravity egm96
Installing GeographicLib magnetic emm2015
上位机控制无人机飞行
我们期望自己的控制算法能够排除人工干预,从而验证算法的先进性与实时性。进而使用上位机控制无人机自主飞行显得格外重要。 PX4提供了外部控制信号接口,且高度自由化,允许介入任何控制环中。
offboard模式
在使用该模式前,需要注意以下几点
- 该模式需要位置或位姿/姿态信息——例如 GPS、光流、视觉惯性里程计(VIO)、动作捕捉(mocap)等,具体取决于外部控制器发送的 Offboard 设定值类型。
- 除切换模式外,手动控制将被禁用(也可以通过将参数 COM_RC_IN_MODE 设置为 4:禁用遥控器,从而完全不使用手动遥控器飞行)。
- 在 Offboard 模式下解锁、或飞行中切换到 Offboard 模式之前,飞行器必须已经在持续接收 MAVLink 设定值消息流或 ROS 2 OffboardControlMode 消息流。
- 如果 MAVLink 设定值消息或 OffboardControlMode 消息停止接收的时间超过 COM_OF_LOSS_T 配置的超时时间,飞行器将退出 Offboard 模式。
- 并非所有 MAVLink 支持的坐标系和字段值都被设定值消息和飞行器支持。请仔细阅读下文,确保只使用受支持的值。
- 官方建议offboard模式仅在平飞时使用,起飞/降落/返航仍旧使用对应模式。
多旋翼/VTOL(Copter/VTOL) SET_POSITION_TARGET_LOCAL_NED
支持以下输入组合:
- 位置设定值(仅 x、y、z)
- 速度设定值(仅 vx、vy、vz)
- 加速度设定值(仅 afx、afy、afz)
- 位置设定值 + 速度设定值(速度设定值作为前馈:叠加到位置控制器的输出上,结果作为速度控制器的输入)。
- 位置设定值 + 速度设定值 + 加速度(速度与加速度设定值作为前馈:速度设定值叠加到位置控制器的输出上,结果作为速度控制器的输入;加速度设定值叠加到速度控制器的输出上,结果用于计算推力矢量)。 PX4 仅支持以下 coordinate_frame 值:MAV_FRAME_LOCAL_NED 与 MAV_FRAME_BODY_NED。
SET_POSITION_TARGET_GLOBAL_INT
支持以下输入组合:
- 位置设定值(仅 lat_int、lon_int、alt)
- 速度设定值(仅 vx、vy、vz)
- 推力设定值(仅 afx、afy、afz)
INFO
加速度设定值被映射为归一化的推力设定值(即加速度设定值并未被“真正”支持)。
- 位置设定值 与 速度设定值(速度设定值作为前馈:叠加到位置控制器的输出上,结果作为速度控制器的输入)。 PX4 仅支持以下 coordinate_frame 值:MAV_FRAME_GLOBAL_INT、MAV_FRAME_GLOBAL_RELATIVE_ALT_INT、MAV_FRAME_GLOBAL_TERRAIN_ALT_INT。
SET_ATTITUDE_TARGET
支持以下输入组合:
- 姿态/朝向(SET_ATTITUDE_TARGET.q)+ 推力设定值(SET_ATTITUDE_TARGET.thrust)。
- 机体角速度(SET_ATTITUDE_TARGET.body_roll_rate、.body_pitch_rate、.body_yaw_rate)+ 推力设定值(SET_ATTITUDE_TARGET.thrust)。
固定翼(Fixed-wing) SET_POSITION_TARGET_LOCAL_NED 支持以下输入组合(通过 type_mask 指定):
- 位置设定值(仅 x、y、z;速度与加速度设定值被忽略)。 在 type_mask 中指定设定值类型(如果这些位未设置,飞行器会飞出花瓣状轨迹):
INFO
以下部分设定值类型不属于 MAVLink 标准中 type_mask 字段的定义。
取值为:
- 292:滑翔设定值。
该值将 TECS 配置为优先维持空速而非高度,使飞行器在无推力时滑翔(即通过控制俯仰来调节空速)。
等效于将 `type_mask` 设为 `POSITION_TARGET_TYPEMASK_Z_IGNORE`、`POSITION_TARGET_TYPEMASK_VZ_IGNORE`、`POSITION_TARGET_TYPEMASK_AZ_IGNORE`。
- 4096:起飞设定值。
- 8192:降落设定值。
- 12288:盘旋设定值(绕设定值点画圆飞行)。
- 16384:怠速设定值(油门为零,滚转/俯仰为零)
PX4 支持以下坐标系(coordinate_frame 字段):MAV_FRAME_LOCAL_NED 与 MAV_FRAME_BODY_NED。
SET_POSITION_TARGET_GLOBAL_INT
支持以下输入组合(通过 type_mask 指定):
- 位置设定值(仅 lat_int、lon_int、alt) 在 type_mask 中指定设定值类型(如果这些位未设置,飞行器会飞出花瓣状轨迹):
INFO
以下设定值类型不属于 MAVLink 标准中 type_mask 字段的定义。
取值为:
- 4096:起飞设定值。
- 8192:降落设定值。
- 12288:盘旋设定值(绕设定值点画圆飞行)。
- 16384:怠速设定值(油门为零,滚转/俯仰为零)。
PX4 仅支持以下 coordinate_frame 值:MAV_FRAME_GLOBAL_INT、MAV_FRAME_GLOBAL_RELATIVE_ALT_INT、MAV_FRAME_GLOBAL_TERRAIN_ALT_INT。
SET_ATTITUDE_TARGET
支持以下输入组合:
- 姿态/朝向(SET_ATTITUDE_TARGET.q)+ 推力设定值(SET_ATTITUDE_TARGET.thrust)。
- 机体角速度(SET_ATTITUDE_TARGET.body_roll_rate、.body_pitch_rate、.body_yaw_rate)+ 推力设定值(SET_ATTITUDE_TARGET.thrust)。
实飞操作demo
创建一个demo,以每秒20次的频率向PX4发送位置信息,完成预设轨迹飞行。
使用C++语言(也可使用Python语言)完成代码编写。 伪代码如下(详细代码参阅Github):
初始化:
声明参数 grid_extent=1.0, height=2.0
订阅 /mavros/state, /mavros/local_position/pose (SensorDataQoS)
创建发布 /mavros/setpoint_position/local
创建定时器: setpoint_timer(20 Hz), trajectory_timer(1 Hz)
回调 onState(msg): # 飞控模式检测
is_offboard ← (msg.mode == "OFFBOARD")
回调 onPose(msg): # 位姿缓存
current ← msg.pose.position
if is_offboard ∧ ¬has_start: # OFFBOARD 下首次位姿即 home
home ← current; has_start ← true
回调 onSetpointTimer(): # 20 Hz setpoint 泵
if ¬has_pose: return # 没位姿先不发
if ¬mission_started: # 任务未启动 → 跟踪当前位置悬停
target ← (current.x, current.y,
min(current.z + H, 5.0))
publish(target) # 恒发缓存,保证 PX4 流不断
回调 onTrajectoryTimer(): # 1 Hz 轨迹步进
offboard ← is_offboard
if offboard ∧ ¬was_offboard # OFFBOARD 上升沿(仅一次)
∧ phase == WAITING:
home ← current # 锁定 home
planner.setHome(home); elapsed ← 0
mission_started ← true
was_offboard ← offboard
if ¬offboard: return # 非 OFFBOARD 不算
target ← planner.step(elapsed, current) # 推进状态机
elapsed ← elapsed + 1.0
按阶段输出中文日志
输入: elapsed(自 OFFBOARD 起的秒数), 当前位置 (cx, cy, cz)
输出: 本步目标 setpoint
home_point ← (hx, hy, min(hz + H, MAX_ALTITUDE))
switch phase:
case INITIAL_HOVER: # 起飞稳定 5 s
if elapsed ≥ 5.0:
phase ← FOLLOWING_TRAJECTORY # 向下穿透
else:
return home_point
case FOLLOWING_TRAJECTORY: # 飞向当前航点
target ← waypoints[index - 1]
dist ← ‖target − current‖ # 欧氏距离
if dist < 0.12: # 到达判定半径
hold_start ← elapsed
phase ← HOLD_AT_WAYPOINT
return target
case HOLD_AT_WAYPOINT: # 每航点悬停 2 s
if elapsed − hold_start ≥ 2.0:
index ← index + 1
if index > 25: # 全部航点完成
phase ← TRAJECTORY_DONE_HOLD
return waypoints[24] # 终点(右上角)
phase ← FOLLOWING_TRAJECTORY
return waypoints[index - 1]
case TRAJECTORY_DONE_HOLD: # 终点悬停 5 s
if elapsed − done_time ≥ 5.0:
phase ← RETURNING_HOME # 向下穿透
else:
return waypoints[24]
case RETURNING_HOME: # 返航
if ‖home_point − current‖ < 0.12:
phase ← MISSION_COMPLETE # 永久锁定,不可逆
break
case MISSION_COMPLETE / WAITING_FOR_OFFBOARD:
break # 不动
return home_point # 兜底:悬停在家
无人机实物照片见第四章《PX4学习记录之硬件》
【下一代】UXRCE-DDS 实现自主飞行
ROS2相比ROS1,重点在时延上进行了改进,使其实时性大幅增加。PX4为与ROS2对接,开发了UXRCE-DDS,使得uORB消息能够像ROS2的话题一样订阅和发布,省去了MAVROS的转换,进一步降低时延。
UXRCE-DDS环境配置
git clone https://github.com/eProsima/Micro-XRCE-DDS-Agent.git
cd Micro-XRCE-DDS-Agent
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig /usr/local/lib/
架构速览
| 组件 | 运行空间 | 作用 |
|---|---|---|
| uXRCE-DDS Client | PX4固件内(自v1.14起内置) | 把uORB消息搬进DDS网络 |
| Micro-XRCE-DDS-Agent | 上位机 | 把client的数据转成标准ROS2话题 |
| px4_msgs | 上位机工作空间 | 消息定义,必须与固件版本严格对应 |
数据流向:uORB ↔ Client(串口/UDP) ↔ Agent ↔ ROS2话题。
话题命名是固定的:PX4对外发布在 /fmu/out/<消息名>,下行控制写在 /fmu/in/<消息名>。
PX4端配置
- QGC连接飞控 → 参数页搜索
UXRCE: -UXRCE_DDS_CFG:选与上位机物理相连的串口,如 TELEM1=101、TELEM2=102(以参数下拉框实际值为准); - 对应串口波特率调高:如SER_TEL2_BAUD设为 921600,带宽不足会掉话题。 - 重启飞控,接好TX/RX交叉串口线到上位机。
- 上位机按连接方式启动Agent:
# 串口直连(最常见,设备名按实际改)
MicroXRCEAgent uart4 -D /dev/ttyAMA0 -b 921600
# UDP(上位机与飞控在同一网段经数传/网口透传时)
MicroXRCEAgent udp4 -p 8888
- 验证:终端滚动输出
created subscriber/publisher ... client key: xxx即通;再执行ros2 topic list应看到/fmu/out/vehicle_odometry等话题。
SITL仿真:
make px4_sitl gazebo-classic默认已自动拉起Client并指向127.0.0.1:8888,本机只启动MicroXRCEAgent udp4 -p 8888即可。若无输出,可在SITL的NSH里手动执行uxrce_dds_client start -t udp -h 127.0.0.1 -p 8888。
建立ROS2工作空间
px4_msgs 必须用与固件对应的分支(本文环境 v1.14 → release/1.14),否则类型不匹配直接报错:
mkdir -p ~/drone_ros/src && cd ~/drone_ros/src
git clone -b release/1.14 https://github.com/PX4/px4_msgs.git
cd .. && colcon build --symlink-install
echo "source ~/drone_ros/install/setup.bash" >> ~/.bashrc && source ~/.bashrc
坐标系约定
PX4内部全部使用 NED(北东地)+ FRD机体;ROS习惯 ENU(东北天)+ FLU机体。注意:
- 截至v1.14,Client不做任何坐标转换(自动转换是v1.15才引入的特性),发布/接收时必须自己换轴;
- 向量类(位置、速度、加速度)换轴公式相同:
- 目标高度在NED下为负数:飞高2m即
position.z = -2.0; - 姿态四元数换算建议直接使用 tf2 完成转换,不推荐手写分量映射;
- 使用动作捕捉/VIO做室内定位时,把外部定位发给
/fmu/in/vehicle_visual_odometry(pose须为NED系或自行转换)。
最小Offboard示例(悬停起飞)
逻辑很简单:先以 ≥2Hz 持续发流,PX4确认收到后才能切OFFBOARD/解锁。20Hz起步最稳:
#!/usr/bin/env python3
import rclpy, math
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data # PX4为BEST_EFFORT,订阅必须用它
from px4_msgs.msg import OffboardControlMode, TrajectorySetpoint, VehicleCommand, VehicleLocalPosition
class MinimalOffboard(Node):
def __init__(self):
super().__init__('minimal_offboard')
self.it = 0; self.pos = None
self.create_subscription(VehicleLocalPosition, '/fmu/out/vehicle_local_position',
lambda m: setattr(self, 'pos', (m.x, m.y, m.z)), qos_profile_sensor_data)
self.p_ocm = self.create_publisher(OffboardControlMode, '/fmu/in/offboard_control_mode', 10)
self.p_sp = self.create_publisher(TrajectorySetpoint, '/fmu/in/trajectory_setpoint', 10)
self.p_cmd = self.create_publisher(VehicleCommand, '/fmu/in/vehicle_command', 10)
self.create_timer(0.05, self.loop) # 20Hz发流泵
def now_us(self): return int(self.get_clock().now().nanoseconds() / 1000)
def cmd(self, c, p1):
msg = VehicleCommand(); msg.timestamp = self.now_us()
msg.command = c; msg.param1 = p1
msg.target_system = 1; msg.target_component = 1
self.p_cmd.publish(msg)
def loop(self):
# 1) 心跳:声明"我要做位置控制",断流PX4立即退出OFFBOARD
ocm = OffboardControlMode(); ocm.timestamp = self.now_us(); ocm.position = True
self.p_ocm.publish(ocm)
# 2) 目标点:NED系,z向下为正——原地升至高2m就写-2.0
sp = TrajectorySetpoint(); sp.timestamp = self.now_us()
nan = math.nan
sp.position = [0.0, 0.0, -2.0] # ENU场地请自行换轴!
sp.velocity = [nan, nan, nan]; sp.acceleration = [nan, nan, nan]
sp.yaw = nan; sp.yawspeed = nan
self.p_sp.publish(sp)
# 3) 流稳定约1s后切OFFBOARD,约2s后解锁(实测节奏,可调)
self.it += 1
if self.it == 25: self.set_offboard()
if self.it == 50: self.cmd(VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, 1.0)
def set_offboard(self): # OFFBOARD=custom_main 6
msg = VehicleCommand(); msg.timestamp = self.now_us()
msg.command = VehicleCommand.VEHICLE_CMD_DO_SET_MODE
msg.param1 = 1.0; msg.param2 = 1.0; msg.param3 = 6.0
msg.target_system = 1; msg.target_component = 1
self.p_cmd.publish(msg)
def main():
rclpy.init(); n = MinimalOffboard()
try: rclpy.spin(n)
finally: rclpy.shutdown()
if __name__ == '__main__':
main()
流程总结(所有ROS2直驱项目通用):订阅odometry确认估计有效 → 持续发OffboardControlMode+Setpoint → 发DO_SET_MODE(CustomMain=6)切OFFBOARD → 发ARM_DISARM解锁 → 之后循环更新Setpoint即可。降落/返航交给模式指令(如 VEHICLE_CMD_DO_LAND_START 或直接切LAND)更安全。
自定义暴露话题
默认只桥接常用uORB消息。想新增话题:在PX4源码 src/modules/uxrce_dds_client/dds_topics.yaml 中添加条目,重新编译烧录固件即可,无需改动Agent。
延伸阅读(坐标系细节、消息全表):https://docs.px4.io/main/en/ros2/user_guide#px4-ros-2-service-servers
ROS2直驱PX4控制
offboard支持如下变量与对象指令输入
# Off-board control mode
uint64 timestamp # time since system start (microseconds)
bool position #控制位置
bool velocity #控制速度
bool acceleration #控制加速度
bool attitude #控制姿态
bool body_rate #控制角速度
bool thrust_and_torque #控制每个执行器的力和力矩
bool direct_actuator #直接控制执行器
这些字段按优先级排序:位置优先于速度及其后的字段,速度优先于加速度,依此类推。第一个非零字段(从上到下)决定了使用 Offboard 模式需要哪种有效估计,以及可以使用哪种设定值消息。例如,如果加速度字段是第一个非零值,则 PX4 需要有效的姿态估计,且设定值必须使用 TrajectorySetpoint 消息来指定。
多旋翼
px4_msgs::msg::TrajectorySetpoint
支持以下输入组合:
- 位置设定(position 不为 NaN)。velocity 与 acceleration 的非 NaN 值作为内环控制器的前馈项。
- 速度设定(velocity 不为 NaN,position 设为 NaN)。acceleration 的非 NaN 值作为内环控制器的前馈项。
- 加速度设定(acceleration 不为 NaN,position 与 velocity 均设为 NaN) 所有值均在 NED(北、东、地)坐标系下解释,position、velocity、acceleration 的单位分别为 [m]、[m/s]、[m/s^2]。
WARNING
多旋翼的位置、速度与加速度控制均由 mc_pos_control 模块处理。只要 position、velocity、acceleration 字段中任意一个设为 true,该模块就会被启用。但具体运行三个控制器中的哪一个,仅由 TrajectorySetpoint 消息的内容决定。
这意味着,即使 OffboardControlMode 消息表达的是速度控制的意图(仅设置了 velocity 字段),但只要 TrajectorySetpoint 消息中发送了非 NaN 的 position 值,PX4 仍会继续运行位置控制器。
px4_msgs::msg::VehicleAttitudeSetpoint
支持以下输入组合:
- 四元数 $q_d$ + 推力设定值 thrust_body。yaw_sp_move_rate 的非 NaN 值作为前馈项,在地球坐标系下表示,单位为 [rad/s]。
- 四元数表示无人机机体 FRD(前、右、下)坐标系与 NED 坐标系之间的旋转。推力在无人机机体 FRD 坐标系下表示,为归一化的 [-1, 1] 值。
px4_msgs::msg::VehicleRatesSetpoint
支持以下输入组合:
- roll、pitch、yaw 与 thrust_body。
- 所有值均在无人机机体 FRD 坐标系下。角速度单位为 [rad/s],thrust_body 归一化为 [-1, 1]。
你知道吗? 浙大高飞的PX4_CTRL算法是直接输出四元数+推力设定,是能够直接控制内环的算法。
通用飞行器(Generic Vehicle) 以下 Offboard 控制模式绕过所有 PX4 内部控制环,使用时需格外小心。
px4_msgs::msg::VehicleThrustSetpoint + px4_msgs::msg::VehicleTorqueSetpoint
支持以下输入组合:
- 推力的 xyz 与力矩的 xyz。
- 所有值均在无人机机体 FRD 坐标系下,并归一化为 [-1, 1]。
px4_msgs::msg::ActuatorMotors + px4_msgs::msg::ActuatorServos
直接控制电机输出和/或舵机输出。 所有值归一化为 [-1, 1]。对于不支持负值的输出,负值条目映射为 NaN。 NaN 映射为停转(disarmed)状态。
PX4内部代码修改
前面章节都是在"不改一行代码"的前提下使用PX4。当需要新增算法、接入自研传感器或深度定制启动流程时,就要进入源码层面了。先总览一下源码根目录(以v1.14.3为准):
| 目录 | 内容 |
|---|---|
msg/ |
全部uORB消息定义(.msg),与上位机px4_msgs仓库同源 |
src/modules/ |
核心功能模块:commander、ekf2、mc_pos_control、mavlink等 |
src/drivers/ |
传感器/执行器驱动:imu、gps、pwm_out、dshot等 |
src/lib/ |
可复用库:matrix、pid、perf、mixer_module等 |
boards/ |
板级支持包(BSP)与板级配置(.px4board) |
ROMFS/ |
机载只读文件系统:启动脚本、机架配置 |
platforms/ |
操作系统抽象层(NuttX/Linux/RTEMS),含NuttX内核子模块 |
Tools/ |
构建、烧录、日志分析等配套工具 |
各模块之间没有直接函数调用关系,全部通过uORB消息总线松耦合通信。这决定了改代码的基本套路:要么新建一个模块发布/订阅话题,要么在现有模块里插入处理逻辑。
编译环境配置
本章统一在Ubuntu 22.04下进行。拉取源码必须带 --recursive,否则缺少NuttX等大量子模块:
git clone -b v1.14.3 https://github.com/PX4/PX4-Autopilot.git --recursive
cd PX4-Autopilot
bash ./Tools/setup/ubuntu.sh # 一键安装ARM交叉编译链/Ninja/Gazebo仿真等全部依赖
脚本同时安装 arm-none-eabi-gcc(STM32固件编译用)与Gazebo相关组件,执行完请重启终端使udev用户组规则生效。
make 目标命名为 平台_板卡_配置 三段式,常用命令如下:
| 命令 | 说明 |
|---|---|
make px4_fmu-v6x_default |
编译STM32H743固件(微空H743即此类FMUv6X方案) |
make px4_fmu-v5_default |
编译FMUv5固件 |
make px4_fmu-v6x_default upload |
编译并经USB上传(烧录)固件 |
make px4_sitl gazebo-classic |
编译SITL并自动打开Gazebo仿真 |
make px4_fmu-v6x_default boardconfig |
菜单式开关该板上集成的模块 |
make clean / make distclean |
清理当前目标 / 清理全部构建产物 |
H743类第三方板默认按FMUv6X配置烧录,若遇bootloader不兼容,再对照厂商文档调整。
修改过的固件用QGC"自定义固件文件"入口烧录(见前文固件烧录一节)。详细构建选项参见官方手册:https://docs.px4.io/v1.14/en/dev_setup/building_px4.html
uORB与msg
uORB是PX4内部的异步 publish()/subscribe() 消息API,用于跨线程/跨进程通信,飞控启动早期由 uorb start 拉起。它在某种程度上就是PX4内部的DDS——上一章UXRCE-DDS桥接器做的正是"把uORB搬进ROS2世界"。官方文档:https://docs.px4.io/v1.14/en/middleware/uorb.html
新增一条消息
消息就是普通的文本接口文件,位于 msg/ 目录,与上位机px4_msgs一一对应。新增分三步:
① 定义 msg/test.msg(注意类型名称用蛇形小写):
蛇形小写是一种命名约定,指用全小写字母、单词之间用下划线 _ 连接的命名风格,因形似蛇蜿蜒而得名。如:sensor_combined、vehicle_local_position、my_new_msg 此外,还有大驼峰 SensorCombined、VehicleLocalPosition 小驼峰 sensorCombined、vehicleLocalPosition 自v1.15开始,编译器会为每个消息额外生成
px4::msg::命名空间下的大驼峰别名(如px4::msg::Test,实际指向test_s),新代码建议直接使用该别名,详见下文v1.15+示例。
uint64 timestamp # 必须是第一个字段: 自系统启动的时间戳(微秒)
float32 value # 示例数据
bool valid # 数据有效标志
② 登记:把 Test.msg 加入 msg/CMakeLists.txt 的 set(msg_files ...) 列表(注意登记名为首字母大写的文件名)。
③ 编译生成头文件:任选目标编译一次,头文件出现在 build/<目标名>/uORB/topics/test.h,此后任何模块都可以 #include <uORB/topics/test.h> 使用。若希望ROS2侧也用到该消息,把同名 .msg 同步进对应分支的 px4_msgs 仓库并重新colcon编译即可。
发布与订阅(C++模板封装,v1.14推荐写法)
#include <uORB/topics/test.h>
// -------- 发布端 --------
uORB::Publication<test_s> _test_pub{ORB_ID(test)};
test_s t{};
t.timestamp = hrt_absolute_time(); // 别忘了时间戳
t.value = 1.23f; t.valid = true;
_test_pub.publish(t);
// -------- 订阅端(轮询有无新数据) --------
uORB::Subscription<test_s> _test_sub{ORB_ID(test)};
test_s buf;
if (_test_sub.update(&buf)) { /* 收到比上次更新的拷贝 */ }
// -------- 订阅端(限定读取周期,例如10ms) --------
uORB::SubscriptionInterval<test_s> _slow_sub{ORB_ID(test), 10000 /*us*/};
v1.15及以后版本的写法:
消息定义方式与上文完全一致(.msg 文件名用大驼峰如 Test.msg,生成的头文件与struct仍是蛇形小写 test.h / test_s)。区别在于v1.15起,编译器会在生成头文件中额外插入一条 px4::msg 命名空间下的大驼峰类型别名(等价于 using Test = test_s;),官方新代码(如各模块源码)正逐步统一到这种命名。发布/订阅的模板类API没有任何变化,只是模板参数换成了别名:
#include <uORB/topics/test.h> // 头文件名不变: CamelCase自动转蛇形
// -------- 发布端(v1.15+) --------
uORB::Publication<px4::msg::Test> _test_pub{ORB_ID(test)}; // ORB_ID内仍用小写话题名
px4::msg::Test t{};
t.timestamp = hrt_absolute_time();
t.value = 1.23f; t.valid = true;
_test_pub.publish(t);
// -------- 订阅端(v1.15+) --------
uORB::Subscription<px4::msg::Test> _test_sub{ORB_ID(test)};
px4::msg::Test buf;
if (_test_sub.update(&buf)) { /* 收到比上次更新的拷贝 */ }
// -------- 订阅端(限定读取周期) --------
uORB::SubscriptionInterval<px4::msg::Test> _slow_sub{ORB_ID(test), 10000 /*us*/};
要点提醒:
listener test、uorb top、日志里的话题名仍是蛇形小写,不受别名影响;- 两个版本写法可混用(别名与原struct是同一类型),从v1.14迁移代码时只需替换模板参数,逻辑零改动;
- 配套封装按需选用(v1.14即已存在,v1.15起新代码中使用更普遍):
uORB::PublicationMulti/uORB::SubscriptionMulti(多实例话题,如多个IMU各自一个instance)、uORB::PublicationData/uORB::SubscriptionData(把数据内嵌在封装对象里,省去临时buffer)。
老的C接口(orb_advertise/orb_publish/orb_subscribe/orb_copy)依然可用,但新代码一律建议上表的模板类写法,配合订阅回调(uORB::SubscriptionCallbackWorkItem)可以挂到work queue上高效调度。
上手调试最快的路径是在NSH(SITL终端或QGC的MAVLink Console)里:
listener test # 直接打印某话题最新数据
uorb top # 实时观察所有话题的产生频率,排查谁没出数
常用核心消息速查(几乎覆盖二次开发需要的全部数据流):
| 消息 | 说明 |
|---|---|
sensor_combined / vehicle_imu |
IMU原始与滤波后数据 |
vehicle_attitude / vehicle_attitude_setpoint |
姿态四元数及其设定值 |
vehicle_local_position / vehicle_local_position_setpoint |
NED系位置速度及其设定值 |
trajectory_setpoint |
Offboard下行设定值(与ROS2对接层同款) |
vehicle_control_mode |
当前飞行模式与控制权限 |
actuator_motors / actuator_servos |
控制分配后的电机/舵机指令 |
actuator_outputs |
物理输出通道(PWM数值) |
battery_status / vehicle_status |
电源状态与整机状态 |
完整清单直接翻 msg/ 目录,每个字段都有单位注释。
driver与module
PX4中没有"普通应用"与"服务进程"之分——所有后台任务统称模块(module),传感器驱动也是模块。改驱动其实就是改一个个标准化的模块。
| 开发需求 | 对应位置 |
|---|---|
| SPI/I2C传感器驱动 | src/drivers/imu/bosch/bmi088 等(照葫芦画瓢首选) |
| GPS/串口外设 | src/drivers/gps、src/drivers/telemetry |
| PWM/DShot电机输出 | src/drivers/pwm_out、src/drivers/dshot |
| 执行器混合与分配 | src/modules/control_allocator |
| 多旋翼姿态/位置控制器 | src/modules/mc_rate_control、mc_att_control、mc_pos_control |
| 解锁/模式管理 | src/modules/commander |
最小模块骨架
一个模块只需五个要素:main入口、继承ModuleBase、静态task_spawn、run()主循环、Kconfig开关。
#include <px4_platform_common/module.h>
#include <px4_platform_common/posix.h>
extern "C" __EXPORT int my_module_main(int argc, char *argv[]);
class MyModule final : public ModuleBase<MyModule>
{
public:
static int task_spawn(int argc, char *argv[])
{
_task_id = px4_task_spawn_cmd("my_module", SCHED_DEFAULT,
SCHED_PRIORITY_DEFAULT, 1800,
(px4_main_t)&run_trampoline,
(char *const *)argv);
return _task_id < 0 ? PX4_ERROR : PX4_OK;
}
static int custom_command(int argc, char *argv[]) { return print_usage(); }
static int print_usage(const char *reason = nullptr)
{
PX4_INFO("usage: my_module {start|stop|status}");
return 0;
}
void run() override // 主循环
{
while (!should_exit()) {
// 这里做周期性工作: 读传感器/跑算法/发话题
px4_usleep(100000);
}
}
private:
DEFINE_PARAMETERS(
(ParamFloat<px4::params::MY_PARAM>, _my_param) // 私有参数示例
)
};
int my_module_main(int argc, char *argv[])
{
return MyModule::main(argc, argv);
}
配套的 CMakeLists.txt 只有三句核心:
px4_add_module(
MODULE modules__my_module
MAIN my_module # 生成的可执行名,NSH中的启动命令
STACK_MAIN 1800
SRCS my_module.cpp
)
再把模块"接入编制",共两步:
- 在模块目录建
Kconfig,声明config MODULES_MY_MODULE bool "my module" default n; - 在所用板卡的
boards/px4/fmu-v6x/default.px4board中加CONFIG_MODULES_MY_MODULE=y,重新编译后NSH里即可my_module start/status。
nsh 中还有几个通用好用的小工具辅助调试:list_devices 查看字符设备节点、param show MY_* 查参数、work_queue status 观察队列负载。完整入门教程见官方"第一个应用":https://docs.px4.io/v1.14/en/modules/hello_sky.html ,更接近实战的工程化模板见:https://docs.px4.io/v1.14/en/modules/module_template.html
写驱动的几条规则
- I2C/SPI设备不要裸写总线读写,统一继承框架自带的
I2C/SPI基类组合I2CSPIDriver,周期任务交给work queue而非独立线程(省内存且不占优先级);官方对这块有一整套约定,移植前务必通读硬件移植篇。 - 支持总线的灵活选择:驱动初始化参数
-X(外部I2C)/-I(内部I2C)/-S(SPI)/-q... 由启动脚本传参决定同一份代码适配不同接线,这也是多机型固件能互相通用的原因。 - 优先抄近道:本文微空H743常见的ICM-42688P在
src/drivers/imu/invensense/icm42688p,气压计BMP388、磁力计IST8310等国产板常客都能找到现成实现。 - 模块私有参数仿照
src/modules/land_detector/land_detector_params.c的组织方式编写,编译期自动进入QGC参数界面(呼应前文"自定义变量也会出现在参数页")。
lib
src/lib 存放被多个模块共享的纯算法库,二次开发应优先复用而不是自己造轮子:
| 库 | 用途 |
|---|---|
mathlib |
限幅、角度归一等数学工具函数 |
matrix |
矩阵/欧拉角/四元数/方向余弦矩阵运算 |
controllib |
Block控制框架、低通滤波器等 |
pid |
v1.14重构后的PID控制器独立库 |
motion_planning |
offboard/smooth takeoff的速度规划器 |
perf |
性能统计计数器 |
mixer_module |
输出混合抽象,供新执行器输出复用 |
两个高频片段,开发时基本绕不开:
// 性能计数: 统计每圈耗时,既能在console里 perf print 查看又能进 ulog
perf_counter_t _cycle_perf = perf_alloc(PC_ELAPSED, MODULE_NAME": cycle");
perf_begin(_cycle_perf);
/* ...被测量代码... */
perf_end(_cycle_perf);
// 二阶低通: 原始信号 -> 截止频率50Hz @ 250Hz采样率
#include <mathlib/math/filter/LowPassFilter2p.hpp>
math::LowPassFilter2pf<float> lp;
lp.set_cutoff_frequency(250.0f, 50.0f);
float filtered = lp.apply(raw_value);
boards与ROMFS
这是最靠近硬件的一层,改动频率不高但出问题时最难查。
板级配置(.px4board)
每块板的构建配置本质是 kconfig 片段,例如 boards/px4/fmu-v6x/default.px4board:
CONFIG_BOARD_AUTOSTART=y # 运行ROMFS启动脚本
CONFIG_ARCH_CHIP_STM32H743VIT6=y
CONFIG_MODULES_COMMANDER=y # 以下逐个决定哪些模块编进固件
CONFIG_MODULES_EKF2=y
...
除了 default 目标外,多数板还有 bootloader.px4board 等特殊构建目标。除菜单开关外还可用 make px4_fmu-v6x_default boardconfig 进入图形化配置。SoC外设级的底层(引脚复用、SPI片选、定时器DMA映射等)则分布在同目录的 src/board_config.h、src/spi.cpp、src/timer_config.cpp 与 NuttX 的 nuttx-config/nuttx-defconfig 中。目录结构与全部可选板可浏览仓库 boards/ 子目录。
启动脚本与机架(ROMFS)
固件编译时把 ROMFS/px4fmu_common 整体打包成"C盘"(这里的C盘只是比喻)挂载到 /etc,上电流程为:
Bootloader → NuttX内核 → /etc/init.d/rcS 总入口 → 各rc.*分工加载模块
→ rc.autostart 按 SYS_AUTOSTART 加载机架脚本 → 进入待命状态
ROMFS/px4fmu_common/
├── init.d/
│ ├── rcS # 启动总入口: 文件系统挂载/参数加载/安全机制
│ ├── rc.serial # 各串口角色映射(遥测/GPS/RC...)
│ ├── rc.sensors # 板载IMU/罗盘/空速等驱动批量启动
│ ├── rc.logging # 日志记录策略
│ ├── rc.autostart # 构建期自动生成的机架分发脚本(勿手改)
│ └── airframes/ # 机架配置,"编号_机型"命名
│ ├── 4001_quad_x
│ └── 4052_holybro_qav250
└── init.d-posix/ # 仅SITL(Linux/macOS)使用的一套脚本
机架文件就是一段NSH shell,开头注释供QGC识别分类,正文source机型通用默认值再覆写参数:
#!/bin/sh
# @name Generic Quadcopter
# @type Quadrotor x
# @class Copter
. ${R}etc/init.d/rc.mc_defaults # 多旋翼公共默认(控制器参数/失效保护)
param set-default CA_ROTOR_COUNT 4 # 控制分配几何(电夹角布置)
param set-default CA_ROTOR0_PX 1
...
SYS_AUTOSTART 参数取值即文件名的编号部分,官方大致按机型分段:
| 编号段 | 机型 |
|---|---|
| 1000 ~ 1999 | 仿真(SITL/HIL/SIH) |
| 2000 ~ 2999 | 标准布局固定翼 |
| 3000 ~ 3999 | 飞翼 |
| 4000 ~ 4999 | 十字四旋翼(Quad X) |
| 5000 ~ 5999 | 加号四旋翼(Quad +) |
| 6000 ~ 7999 | 六旋翼系列 |
新增机架的标准姿势是复制最近似的现有文件改为下一个空闲编号,调好参数后随固件一起重新编译烧录——构建期会把这些文件的注释抽取为 airframes.xml 内嵌进固件,QGC的机架列表正是读它,所以只改SD卡不动固件是不会出现新条目的。官方指引:https://docs.px4.io/v1.14/en/dev_airframes/adding_a_new_frame.html ,启动机制详解:https://docs.px4.io/v1.14/en/concept/system_startup.html
MBD 加速科研部署
模型驱动开发(Model-Based Design,MBD)是一种软件开发方法,强调在开发过程中使用模型来设计、分析和验证系统。对于无人机开发来说,MBD可以帮助加速科研部署,提高开发效率和系统可靠性。 这种技术目前在汽车行业已经非常成熟,在无人机领域也逐渐得到应用。通过使用MBD工具,如Matlab/Simulink,开发者可以在仿真环境中设计和测试无人机的控制算法,然后直接生成代码部署到飞控上。这种方法不仅缩短了开发周期,还提高了系统的性能和稳定性。
MBD的核心思想是"模型即产物":算法用Simulink框图表达,仿真验证通过后一键生成C++代码,避免手写移植带来的偏差。落到无人机上,MathWorks给出了两条互补的技术路线:
| 路线 | 算法运行位置 | 与PX4的接口 | 特点 |
|---|---|---|---|
| Simulink ↔ ROS2 | 上位机(鲁班猫3) | uXRCE-DDS(见上一章) | 迭代快、算力大,改算法不用重烧固件 |
| Simulink ↔ PX4固件 | 飞控(H743)内部 | uORB直连,代码编入固件 | 时延最低、全自主,不依赖上位机 |
简单说:视觉、规划、学习类大算力算法走ROS2路线放在上位机;姿态/位置控制律、估计器等实时性敏感算法走固件路线放进飞控,两条路线可以同机混用。
Matlab/Simulink 与 ROS2 集成
环境与许可
MATLAB接入ROS2的官方组件是 ROS Toolbox,R2024b内置ROS2 Humble。运行时,与本章ROS2 Humble环境直接互通:MATLAB侧无需安装ROS2,装好工具箱即可与飞控、上位机同网通信。旧版本MATLAB支持的发行版以官方文档为准(Foxy已停止维护,不建议使用)。
许可说明:命令行/仿真验证只需要ROS Toolbox;要把模型生成为可执行节点才需要 MATLAB Coder(.m脚本)或 Simulink Coder(Simulink模型)。
导入px4_msgs自定义消息
PX4的DDS话题类型定义在px4_msgs仓库中,MATLAB默认并不认识,需要用 ros2genmsg 生成一次消息类:
git clone -b release/1.14 https://github.com/PX4/px4_msgs.git ~/px4_msgs_src
ros2genmsg('C:\你的路径\px4_msgs_src') % 生成并注册,等待编译完成
ros2msg show px4_msgs/TrajectorySetpoint % 验证是否注册成功
注意:分支必须与固件严格对应(本章 release/1.14,呼应上一章),类型不匹配会直接报错;
ros2genmsg需要本机有CMake与C++编译器(Windows装Visual Studio Build Tools,Linux装gcc),首次生成较慢。生成一次后常驻MATLAB路径,换工程不用重复生成。
MATLAB作为ROS2节点
消息注册完成后,MATLAB命令行即可像ros2 cli一样直接操作:
setenv('ROS_DOMAIN_ID','0') % 与PX4/Agent保持一致(默认0)
node = ros2node('matlab_ground'); % MATLAB本身就是一个ROS2节点
ros2topic list % 应能看到 /fmu/out/* 等话题
订阅飞控输出有一个新手必踩的坑:PX4发布端统一使用BEST_EFFORT QoS,订阅端必须显式匹配,否则一个字节都收不到:
sub = ros2subscriber(node, '/fmu/out/vehicle_local_position', ...
'px4_msgs/VehicleLocalPosition', 'Reliability','besteffort');
按上一章"最小Offboard示例"的节奏,用MATLAB实现20Hz发流泵(先发流→切OFFBOARD→解锁):
now_us = @() round(posixtime(datetime('now'))*1e6); % 时间戳(微秒)
node = ros2node('matlab_offboard');
pubOcm = ros2publisher(node,'/fmu/in/offboard_control_mode','px4_msgs/OffboardControlMode');
pubSp = ros2publisher(node,'/fmu/in/trajectory_setpoint','px4_msgs/TrajectorySetpoint');
pubCmd = ros2publisher(node,'/fmu/in/vehicle_command','px4_msgs/VehicleCommand');
for it = 1:200 % 20Hz x 10s
ocm = ros2message('px4_msgs/OffboardControlMode');
ocm.timestamp = now_us(); ocm.position = true;
send(pubOcm, ocm); % 心跳,断流即退出OFFBOARD
sp = ros2message('px4_msgs/TrajectorySetpoint');
sp.timestamp = ocm.timestamp;
sp.position = [0 0 -2]; % NED系:飞高2m,z为负
send(pubSp, sp);
if it == 25 % 切OFFBOARD(custom_main=6)
cmd = ros2message('px4_msgs/VehicleCommand');
cmd.timestamp = now_us(); cmd.command = 176; % DO_SET_MODE
cmd.param1 = 1; cmd.param2 = 1; cmd.param3 = 6;
cmd.target_system = 1; cmd.target_component = 1;
send(pubCmd, cmd);
end
if it == 50 % 解锁
cmd = ros2message('px4_msgs/VehicleCommand');
cmd.timestamp = now_us(); cmd.command = 400; % ARM_DISARM
cmd.param1 = 1; cmd.target_system = 1; cmd.target_component = 1;
send(pubCmd, cmd);
end
pause(0.05);
end
这段脚本与上一章Python示例逻辑完全同构,坐标系(NED)、QoS、发流节奏三要素一致。在此之上,MATLAB相对Python的独特优势是控制/辨识工具箱可以直接用于算法原型(频域辨识、MPC、卡尔曼滤波调参等),验证完再生成代码。
Simulink侧:把模型直接变成ROS2节点
- 库浏览器 → ROS Toolbox → ROS 2,常用块:
| 块 | 作用 | 关键参数 |
|---|---|---|
| Subscribe | 订阅话题 | 话题名/消息类型/QoS(选Best Effort) |
| Publish | 发布话题 | 同上 |
| Blank Message | 生成空消息帧 | 消息类型 |
| Bus Assignment | 修改消息字段 | 按消息结构选信号 |
| Current Time | 获取ROS2时间 | 用于填timestamp |
- 模型设置 → ROS Toolbox 面板配置域ID与节点名;
- 最舒服的一点:普通"运行"仿真模式下,Simulink模型就直接参与DDS通信——左边模型,右边Gazebo SITL(
make px4_sitl gazebo-classic+ Agent),点一下运行就是全链路闭环,改框图立即生效,这是MBD迭代最快的形态; - 验证完点Build,Simulink Coder把模型生成为独立ROS2节点(可执行文件),经SSH部署到远程Linux设备。官方一键部署面向x86_64主机与NVIDIA Jetson等支持设备;RK3576这类aarch64板子,把生成的C++源码套进colcon包在板上本地编译即可,工程组织与MAVROS项目一致。
小结
| 环节 | 工具 |
|---|---|
| 命令行算法原型/数据录包 | MATLAB + ros2node/ros2subscriber |
| 框图化算法 + 实时闭环 | Simulink + ROS 2 块 |
| 上位机部署 | Simulink Coder生成独立节点 |
延伸阅读:
- ROS Toolbox文档:https://ww2.mathworks.cn/help/ros/
- PX4 ROS2用户指南:https://docs.px4.io/main/en/ros2/user_guide
Matlab/Simulink 与 PX4 集成
为什么要走固件路线
ROS2路线的算法跑在上位机,控制指令要经"DDS→Agent→串口→飞控"多级转发,时延抖动不可控,实飞时上位机一旦死机整条控制链路就没了。科研中大量算法(内环增稳、新型估计器、在线辨识)必须跑在飞控的实时环境里。MathWorks为此提供了 UAV Toolbox Support Package for PX4 Autopilots:Simulink模型经Embedded Coder生成C++,以模块身份直接编进PX4固件——本质上是上一章"PX4内部代码修改"的图形化版本,只是模块不用手写了。
版本与环境
支持包对PX4源码版本是锁定的,这一点必须先对齐:
| MATLAB版本 | 支持包锁定的PX4源码版本 |
|---|---|
| R2024b | v1.14.3(与本章教学环境完全一致) |
| 更新的MATLAB | 随支持包升级(截至撰写时为v1.15.4) |
升级MATLAB前务必到支持包文档确认锁定版本,固件版本一变,参数、消息、机架配置都可能失配。
| 项目 | 要求 |
|---|---|
| 许可 | UAV Toolbox(支持包本体免费)+ Embedded Coder(编译部署) |
| Windows | Win10/11(推荐11),固件克隆与编译走WSL2,固件上传需Python 3.8.2 |
| Linux | Ubuntu 20.04 / 22.04 LTS |
安装与硬件设置向导
- MATLAB主页 → 附加功能 → 获取硬件支持包,搜索安装 UAV Toolbox Support Package for PX4 Autopilots;
- 在附加功能管理器中点击 Setup,进入硬件设置向导;
- 向导自动clone对应版本的PX4-Autopilot源码、安装编译工具链(Windows下自动落在WSL2中),并完成一次试编译;
- 选板卡与CMake构建目标;
- 向导结束后,Simulink模型设置中即可选择硬件板 PX4 Pixhawk Series。
官方完整测试过的板卡:Cube Blue H7、Cube Orange、Cube Orange+、CUAV X7+、Pixhawk 6c、Pixhawk 6x、Pixhawk 4、Cube(Pixhawk 2.1)、Pixhawk 1(mRo)、Pixracer、UVify IFO-S。
本章微空H743v2不在列表中,向导里选 My board is not listed here,手动指定CMake目标
px4_fmu-v6x_default——与上一章make px4_fmu-v6x_default完全同源,凡FMU架构方案板都能这样挂进来。
Simulink建模:块速查
模型要求:定步长求解器(如0.005s,200Hz)、代码生成模式。块库位置:Simulink Library Browser → PX4 UAV Controller Support from Embedded Coder。
| 类别 | 块 | 对应资源 |
|---|---|---|
| 数据采集 | Vehicle Attitude / Accelerometer / Gyroscope / Magnetometer / GPS / Battery / Radio Control Transmitter | vehicle_odometry、sensor_accel、sensor_gyro、sensor_mag、vehicle_gps_position、battery_status、input_rc |
| uORB通用 | PX4 uORB Read / PX4 uORB Write / PX4 uORB Message | 任意话题(自定义话题用 createPX4uORBMessage 函数生成) |
| 执行器输出 | PX4 PWM Output、PX4 Actuator Write(R2024b新增) | 直接驱动ESC/舵机 |
| 参数与调试 | Read Parameter、PX4 Timestamp、PX4 ULog | 读参数、取时间戳、信号按ULog格式写SD卡 |
| 总线通信 | Serial Receive/Transmit、I2C Controller Read/Write、PX4 CAN Receive/Transmit、MAVLink Bridge Source/Sink | 外设与地面站链路 |
部署机制:模型变模块
点击 Build, Deploy & Start 后发生的事,与上一章手写模块一一对应:
- Embedded Coder生成C++,作为新模块 px4_simulink_app 加入PX4源码树;
- 自动写入板卡的CMake构建配置,随固件一起编译;
- 自动写入启动脚本,上电自启(也可选择SD卡自定义启动脚本);
- 烧录同样一条龙:模型设置里指定COM口,Build完成即经bootloader上传,不需要QGC。
更有价值的是控制器级开关:向导/Simulink中可以启停PX4默认控制器——做新位置控制律时关掉原生位置环,让px4_simulink_app直接接管"位置设定值→姿态设定值"这一级;也可以整体禁用commander/navigator/mavlink做全托管。呼应上一章架构:你的模型与飞行栈之间仍然是uORB消息总线,只是发布者换成了Simulink生成的代码。
安全提示:接管控制环后,failsafe兜底逻辑全靠你的模型。建议初期保留commander与遥控器,只替换位置环,逐级下放控制权。
五级验证流程
MBD的价值在"同一份模型贯穿始终",从桌面到实飞逐级加真实度:
| 级别 | 环境 | 验证目标 |
|---|---|---|
| 桌面仿真 | 纯Simulink | 控制律数学正确性 |
| SITL(PX4 Host Target) | PC上跑含px4_simulink_app的固件 + jMAVSim/Gazebo | 全算法闭环、参数初值 |
| PIL | 模型 ↔ 生成代码 | 代码与模型一致性、代码耗时测量 |
| HITL | 真飞控 + Simulink做无人机动力学(或Speedgoat实时机做plant) | 真实固件在环、地面站/遥控全链路 |
| 实飞 | 整机 | Monitor & Tune在线调参 |
两件趁手的工具:
- Monitor & Tune(外部模式):固件部署后独立运行;连上Simulink即可在线观察信号、修改参数,不用重新烧录——等效于QGC调参,但改的是你自己模型里的变量;
- PX4 ULog块 + getMATFilesFromPixhawk:把Simulink信号按ULog格式写进SD卡,飞完取卡一条命令取回MAT文件,省去日志二次解析。
官方示例导航
| 示例 | 内容 |
|---|---|
| Getting Started with Actuator Control over PWM | 最小示例:模型直驱PWM输出 |
| Position Tracking for X-Configuration Quadcopter Using Rate Controller | 自写位置+角速度控制器,Host Target + jMAVSim验证 |
| PX4 Autopilot in HITL Simulation with UAV Dynamics in Simulink | Simulink做六自由度动力学,真飞控HITL |
| PX4 HITL Simulation with VTOL UAV | 垂起固定翼控制器模板,可接Unreal Engine三维场景可视化 |
延伸阅读:
- UAV Toolbox Support Package for PX4 Autopilots:https://ww2.mathworks.cn/help/uav/px4-spkg.html
- ROS Toolbox:https://ww2.mathworks.cn/help/ros/
至此,MBD闭环成型:
Simulink建模 → 桌面仿真 → SITL → PIL → HITL → 机载px4_simulink_app → 实飞
↑ |
└───── ULog/SD卡数据回读 → 模型校准·参数辨识 ←───────────┘
算法无论改多少版都只动模型,代码永远自动生成——这正是"加速科研部署"的落点。