首页 / 第 03 篇

PX4 学习记录之生态

QGC 地面站进阶操作、MAVROS / UXRCE-DDS 上位机自主飞行、PX4 内部代码结构与自定义模块开发,以及 MBD 工作流的落地方式。

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

QGC初始界面

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

地面站设置

QGC初始界面

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

按照需求进行配置即可。在该界面所有设置均不影响飞控内部参数设置。

通用设置

配置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 中旧版 POSCTLALTCTL 模式已被移除,分别由 POSITIONALTITUDE 取代;固定翼原 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): MANUALACROSTEERINGHOLDMISSIONRETURNOFFBOARD

无人船(Boat): MANUALACROHOLDMISSIONRETURNOFFBOARD

此外,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数据流图 这里需要参考的是上位机产生的/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端配置

  1. QGC连接飞控 → 参数页搜索 UXRCE: - UXRCE_DDS_CFG:选与上位机物理相连的串口,如 TELEM1=101、TELEM2=102(以参数下拉框实际值为准); - 对应串口波特率调高:如 SER_TEL2_BAUD 设为 921600,带宽不足会掉话题。
  2. 重启飞控,接好TX/RX交叉串口线到上位机。
  3. 上位机按连接方式启动Agent:
# 串口直连(最常见,设备名按实际改)
MicroXRCEAgent uart4 -D /dev/ttyAMA0 -b 921600
# UDP(上位机与飞控在同一网段经数传/网口透传时)
MicroXRCEAgent udp4 -p 8888
  1. 验证:终端滚动输出 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才引入的特性),发布/接收时必须自己换轴;
  • 向量类(位置、速度、加速度)换轴公式相同:
$$x_{NED}=y_{ENU},\quad y_{NED}=x_{ENU},\quad z_{NED}=-z_{ENU}$$
  • 目标高度在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.txtset(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 testuorb 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/gpssrc/drivers/telemetry
PWM/DShot电机输出 src/drivers/pwm_outsrc/drivers/dshot
执行器混合与分配 src/modules/control_allocator
多旋翼姿态/位置控制器 src/modules/mc_rate_controlmc_att_controlmc_pos_control
解锁/模式管理 src/modules/commander

最小模块骨架

一个模块只需五个要素:main入口、继承ModuleBase、静态task_spawnrun()主循环、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
    )

再把模块"接入编制",共两步:

  1. 在模块目录建 Kconfig,声明 config MODULES_MY_MODULE bool "my module" default n
  2. 在所用板卡的 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.hsrc/spi.cppsrc/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

安装与硬件设置向导

  1. MATLAB主页 → 附加功能 → 获取硬件支持包,搜索安装 UAV Toolbox Support Package for PX4 Autopilots;
  2. 在附加功能管理器中点击 Setup,进入硬件设置向导;
  3. 向导自动clone对应版本的PX4-Autopilot源码、安装编译工具链(Windows下自动落在WSL2中),并完成一次试编译;
  4. 选板卡与CMake构建目标;
  5. 向导结束后,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卡数据回读 → 模型校准·参数辨识 ←───────────┘

算法无论改多少版都只动模型,代码永远自动生成——这正是"加速科研部署"的落点。

← 返回文章列表 回到顶部
文章目录19 节

目录