DUDuuu.studio

Back

简介#

在真机部分的 上篇文章 中,我们完成了机载电脑的刷机。本文基于已有工作,利用机载电脑和底层飞控,跑通 ROS2 + PX4 的真机起飞流程。如果你是第一次接触 PX4 仿真,建议先从 ROS2+PX4 仿真环境开发教程 开始。

笔者使用阿木实验室(AMOVLAB)的 JCV-600 科研实验四旋翼飞机(底层飞控为 CodevDynamics 基于 PX4 的 Codev-autopilot),使用 Nvidia Jetson Orin NX 作为机载电脑进行 ROS2 控制。读者可以仿照本教程,利用自己的硬件设备进行相似实验。

需要的硬件设备(根据自己的情况调整):

  • 无人机整机套件或自行刷入飞控的无人机
  • 一对数传
  • 本地电脑与机载电脑
  • 相应的连接线若干(参考机载电脑与无人机 I/O 口的具体说明)
  • 飞机相关接口的示意图/说明书
阿木实验室无人机各设备连接方式
图 1:阿木实验室无人机各设备连接方式(图源:阿木实验室)

流程总述#

下图是本文采用的真机起飞流程的简要示意图。首先通过数传建立地面站与飞控的稳定连接,确保传感器数据、GPS 信号和遥控指令正常传输。随后,机载电脑通过串口与飞控通信,完成驱动安装、权限配置及波特率匹配。在地面站进行飞行前检查通过后,通过 SSH 远程连接机载电脑,启动 MAVROS2 节点,建立 ROS2 与 PX4 的通信链路。最后,使用 Offboard 模式控制无人机。整个过程需全程连接数传,关注地面站数据,随时准备手动接管,确保飞行安全。

完整过程示意图
图 2:完整过程示意图

与地面站建立连接#

在整个实验过程中,PX4 飞控严格要求与地面站的稳定连接。笔者使用一对 RFD900x 数传进行连接。无人机上的数传与飞控的 I/O 口连接(GND、RX-TX、TX-RX 交叉连接),飞控预留了 5V 电源口为数传供电,这是内部已经完成了电源分配。

飞控I/O口示意图
图 3:飞控 I/O 口示意图

在本地电脑中,打开 QGroundControl 地面站,在 “Application Settings” → “Comm Links” → “Add New Link” 中,Type 选择 “Serial”,Serial Port 选择含 “USB0” 的选项,波特率默认 57600。连接后,两个数传 “ACT” 灯常绿(供电正常),“COM” 灯闪烁(连接成功),地面站可以看到飞机各项状态。

机载电脑与飞控连接#

首先,用 USB-TypeC 线将飞控连接到电脑地面站,在参数页面为机载电脑添加 MAVLINK 实例(一般默认为 TELEM2 串口):

MAV_1_CONFIG = TELEM2
MAV_1_MODE = Onboard
MAV_1_RATE = 115200
plaintext

以下介绍三种连接方式。

方式一:USB-TTL 线#

使用 PX4 的 TELEM2 接口,用于连接机载电脑、数传等,有 VCC(5V)、TX、RX、PWM、GPIO、GND 六个引脚可供选择。将 USB 连接在机载电脑端,用杜邦转端子线交叉连接飞控的 GND、TX、RX,不要进行供电。机载电脑使用预留的 12V 供电口单独供电。

预留的12V充/供电口
图 4:预留的 12V 充/供电口
# 检查 USB 连接
lsusb
ls -l /dev/tty*
# 应出现 CH340 USB-Serial adapter 和 /dev/ttyS0

# 将用户加入 dialout 组
sudo usermod -a -G dialout $USER
bash

下载并编译 CH340 驱动:

sudo apt install build-essential git linux-headers-$(uname -r)
git clone https://github.com/juliagoda/CH341SER
cd CH341SER
make && sudo make install
sudo insmod ch341.ko
sudo cp ch341.ko /lib/modules/$(uname -r)/kernel/drivers/usb/serial/
sudo depmod -a
echo "ch341" | sudo tee /etc/modules-load.d/ch341.conf
bash

创建 udev 规则 /etc/udev/rules.d/99-ch341.rules

SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0666", SYMLINK+="ttyCH341"
plaintext
sudo udevadm control --reload-rules && sudo udevadm trigger
ls -l /dev/ttyCH341  # 验证驱动安装
bash

测试通信(如出现 MAVLINK 乱码则说明成功):

stty -F /dev/ttyUSB0 115200 raw -echo
cat /dev/ttyUSB0
bash

如果上述 USB-TTL 连接方式出现问题,可以参考以下链接进行驱动安装:https://blog.csdn.net/qq_52102933/article/details/126839474

方式二:UART 串口线#

Jetson Orin NX 有 40 针 UART 引脚,其中 Pin8(TX)、Pin10(RX) 为 UART1 默认调试口。我们使用 UART2 进行连接:Pin 14(RX)、Pin 12(TX)、Pin 6(GND),不连 VCC,单独供电。

NX的40针引脚位置
图 5:NX 的 40 针引脚位置
# 查看可用 UART
ls /dev/ttyTHS*

# 如缺少 ttyTHS1,编辑 /boot/extlinux/extlinux.conf
# 在内核启动行末尾添加 console=ttyTHS1,115200
sudo reboot

# 配置权限和波特率
sudo usermod -aG dialout $USER
stty -F /dev/ttyTHS1 115200 raw -echo
cat /dev/ttyTHS1  # 测试通信
bash

方式三:USB-TypeC 线#

笔者的 JCV-600 的 TypeC 接口可直接连接机载电脑(仅供参考,不一定适用于所有设备)。

TypeC接口位置
图 6:TypeC 接口位置
lsusb  # 应出现 ID 26ac:0032 The Autopilot PX4 CODEV DP1000
sudo usermod -aG dialout $USER
stty -F /dev/ttyACM0 115200 raw -echo
cat /dev/ttyACM0  # 测试通信
bash

利用地面站做预检查#

在 “Vehicle Setup” 页面进行飞行前检查:

传感器校准:加速度计、陀螺仪、磁罗盘、水平校准;确认 GPS 信号质量(一般 6 颗星以上)。

参数检查

  • SYS_COMPANION = 921600(ROS2 通信波特率)
  • MAV_1_CONFIG = TELEM2(对应机载电脑连接的串口)
  • BAT_* 参数与实际电池规格匹配
  • 检查紧急停止开关功能正常
  • 验证遥控器各通道映射正确

重要:在实机飞行中使用 Offboard 模式时,需随时准备用遥控器手动接管飞行,以确保安全。检查完成后,QGC 地面站应显示 “Ready for Fly”。

使用 SSH 远程连接机载电脑#

SSH(Secure Shell)是一种网络安全协议,通过加密和认证机制实现安全的访问和文件传输等业务。由于机载电脑连接在飞控上,并且直接向飞控实时发布 ROS 控制指令,与无人机一起飞行,因此我们不能够直接在机载电脑上发布控制指令。在这里我们使用 SSH 协议将本地电脑与远程的机载电脑进行连接,实现本地电脑远程控制机载电脑从而实现对飞控的控制。

在机载电脑的终端启用 SSH 并且设置开机自启:

sudo systemctl start ssh
sudo systemctl enable ssh
sudo systemctl status ssh  # 应显示 active (running)
bash

确保两台电脑位于同一局域网内,使用个人热点避免公网的客户端隔离。查询 IP 地址:

ifconfig  # 查找形如 inet 10.192.90.46 netmask 255.255.0.0 broadcast 10.192.255.255 的输出
# 其中 10.192.90.46 为 IPv4 地址,netmask 和 broadcast 分别为子网掩码和广播地址
# 验证两台电脑的 IPv4 地址,确保在同一大子网下
ping 10.192.90.46  # 在本地电脑 ping 机载电脑,验证网络连通
bash

SSH 连接:

ssh jetson@10.192.90.46  # 首次连接需输入 yes 确认,然后输入密码
# 终端用户名变为 jetson@ubuntu:~$ 说明连接成功
# 输入 exit 退出 SSH 连接
bash

使用 MAVROS 进行 Offboard 模式控制#

在仿真篇章中我们使用 MicroXRCEAgent 进行 ROS2 与 PX4 的通信。笔者的飞控固件中刷入了 MAVROS(而非 XRCE 中间件)。MAVROS 是连接 ROS 与 MAVLINK 协议的重要工具。针对 ROS2 的 MAVROS2 功能包仍在开发维护中,但对于简单任务已可使用。

后续,笔者会针对 XRCE 中间件的开发进行探索,这需要重新刷写 PX4 固件。已更新:ROS2+PX4 无人机编队实机(三)UXRCE-DDS 中间件的部署(以 Pixhawk 6C 为例)

安装 MAVROS2#

sudo apt install ros-humble-mavros ros-humble-mavros-msgs

# 安装 GeographicLib 地理数据集(必需依赖)
wget https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh
chmod +x install_geographiclib_datasets.sh
sudo ./install_geographiclib_datasets.sh
bash

创建 Offboard 节点#

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python offboard_control \
    --dependencies rclpy geometry_msgs mavros_msgs
cd offboard_control/offboard_control
touch simple_offboard.py && chmod +x simple_offboard.py
bash

simple_offboard.py 完整源码(Offboard 起飞并悬停 5 米):

#!/usr/bin/env python3

import rclpy
from rclpy.node import Node
from rclpy.clock import Clock
from geometry_msgs.msg import PoseStamped
from mavros_msgs.msg import State
from mavros_msgs.srv import CommandBool, SetMode

class SimpleOffboard(Node):

    def __init__(self):
        super().__init__('simple_offboard')

        # mavros
        self.declare_parameter('mavros_ns', '/mavros')
        self.mavros_ns = self.get_parameter('mavros_ns').get_parameter_value().string_value

        # publishers
        self.local_pos_pub = self.create_publisher(
            PoseStamped,
            f'{self.mavros_ns}/setpoint_position/local',
            10
        )

        # subscribers
        self.state_sub = self.create_subscription(
            State,
            f'{self.mavros_ns}/state',
            self.state_callback,
            10
        )

        # service clients
        self.arming_client = self.create_client(
            CommandBool,
            f'{self.mavros_ns}/cmd/arming'
        )
        while not self.arming_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('mavros/arming service not available, waiting...')

        self.set_mode_client = self.create_client(
            SetMode,
            f'{self.mavros_ns}/set_mode'
        )
        while not self.set_mode_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('mavros/set_mode service not available, waiting...')

        self.current_state = State()  
        self.timer = self.create_timer(0.05, self.control_loop)  
        self.offboard_setpoint_counter = 0
        self.last_call_time = self.get_clock().now()
        self.target_altitude = 5.0 

        self.get_logger().info("Offboard Node Initialized. Waiting for MAVROS state...")

    def state_callback(self, msg):
        self.current_state = msg

    def arm_drone(self):
        self.get_logger().info("Attempting to ARM drone...")
        arm_request = CommandBool.Request()
        arm_request.value = True
        future = self.arming_client.call_async(arm_request)
        future.add_done_callback(self.arm_response_callback)

    def arm_response_callback(self, future):
        try:
            response = future.result()
            if response.success:
                self.get_logger().info("Arming successful!")
            else:
                self.get_logger().warn(f"Arming failed: {response.result}")
        except Exception as e:
            self.get_logger().error(f"Arming service call failed: {str(e)}")

    def set_offboard_mode(self):
        self.get_logger().info("Attempting to set OFFBOARD mode...")
        mode_request = SetMode.Request()
        mode_request.custom_mode = "OFFBOARD"
        future = self.set_mode_client.call_async(mode_request)
        future.add_done_callback(self.set_mode_response_callback)

    def set_mode_response_callback(self, future):
        try:
            response = future.result()
            if response.mode_sent:
                self.get_logger().info("OFFBOARD mode enabled!")
            else:
                self.get_logger().warn(f"Failed to set OFFBOARD mode.")
        except Exception as e:
            self.get_logger().error(f"SetMode service call failed: {str(e)}")

    def control_loop(self):
        now = self.get_clock().now()

        self.get_logger().info(
            f"[State] Connected: {self.current_state.connected}, "
            f"Armed: {self.current_state.armed}, "
            f"Mode: {self.current_state.mode}",
            throttle_duration_sec=1.0  
        )

        pose = PoseStamped()
        pose.header.stamp = now.to_msg()
        pose.header.frame_id = "map"  
        pose.pose.position.x = 0.0
        pose.pose.position.y = 0.0
        pose.pose.position.z = float(self.target_altitude)
        self.local_pos_pub.publish(pose)

        if not self.current_state.connected:
            return

        time_since_last_call = (now - self.last_call_time).nanoseconds / 1e9  

        if time_since_last_call > 5.0:  
            if not self.current_state.armed:  
                self.arm_drone()
                self.last_call_time = now
            elif self.current_state.mode != "OFFBOARD": 
                self.set_offboard_mode()
                self.last_call_time = now

def main(args=None):
    rclpy.init(args=args)
    offboard_node = SimpleOffboard()

    try:
        rclpy.spin(offboard_node)
    except KeyboardInterrupt:
        offboard_node.get_logger().info('Offboard control interrupted by user.')
    finally:
        offboard_node.destroy_node()
        rclpy.shutdown()
        print("Offboard Node Shutdown.")

if __name__ == '__main__':
    main()
python

setup.pyentry_points 中添加:

'console_scripts': [
    'simple_offboard = offboard_control.simple_offboard:main',
],
python

编译:

cd ~/ros2_ws
colcon build --symlink-install --packages-select offboard_control
source install/setup.bash
bash

起飞#

所有软硬件准备完成,执行完整起飞流程:

起飞流程示意图
图 7:起飞流程示意图
  1. 通过数传将无人机与地面站连接
  2. 任选一种方式将机载电脑与飞控连接
  3. 地面站完成飞行前预检查
  4. 使用 SSH 将本地电脑与机载电脑远程连接
  5. 在 SSH 终端中执行:
# 激活 ROS2 环境
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash

# 启动 MAVROS2 节点(串口和波特率需与飞控配置一致)
ros2 run mavros mavros_node --ros-args -p fcu_url:="serial:///dev/ttyACM0:115200"
# 出现 [INFO] [mavros]: FCU connection established 说明连接成功

# 启动 Offboard 控制节点
ros2 run offboard_control simple_offboard
bash

此时应看到无人机解锁、起飞、飞至 5 米高度。由于 Offboard 模式的危险性,需随时准备使用遥控器接管飞行。

总结#

勤能补拙,多动手尝试。本文给出了笔者自己的一种操作方式——从数传连接、机载电脑与飞控的三种硬件连接方式、飞行前检查、SSH 远程连接,到最终的 MAVROS2 Offboard 起飞。如有错漏请指正,感谢各位读者的耐心阅读!

参考资料#

使用机载电脑控制PX4无人机(基于MAVROS2)
https://duduuu.xyz/zh/posts/px4-ros2-mavros
Author dudu
Published at 2026年7月13日
阅读
总访问