作为一名从事无人机开发多年的工程师,我深知一个稳定可靠的开发环境对于项目推进的重要性。本文将详细介绍基于ROS 2、Gazebo、PX4和QGC的完整开发环境搭建流程,以及如何实现Offboard控制模式。这套环境适用于无人机仿真、算法验证和实际飞行测试,是当前业界最主流的开发方案之一。
在开始之前,我们需要明确几个核心组件的作用:
推荐使用Ubuntu 22.04 LTS作为开发环境,这是目前ROS 2 Humble和Gazebo Harmonic官方支持的最佳平台。安装完成后,首先执行常规系统更新:
bash复制sudo apt update && sudo apt upgrade -y
注意:建议使用英文版系统,避免中文路径可能带来的兼容性问题。同时确保系统有足够的磁盘空间(至少50GB可用),因为后续的仿真环境和工具链会占用大量空间。
ROS 2是无人机开发的核心框架,提供丰富的工具和库支持。以下是安装步骤:
bash复制# 设置locale
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
# 添加ROS 2仓库
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
# 添加源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# 安装ROS 2
sudo apt update
sudo apt install ros-humble-desktop -y
# 环境配置
source /opt/ros/humble/setup.bash
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
安装完成后,可以通过以下命令验证:
bash复制ros2 run demo_nodes_cpp talker
# 另开终端
ros2 run demo_nodes_py listener
QGC是无人机开发不可或缺的地面站软件,提供飞行监控、参数配置和任务规划等功能。安装步骤如下:
bash复制# 安装依赖
sudo apt install gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-gl -y
sudo apt install libfuse2 -y
sudo apt install libxcb-xinerama0 libxkbcommon-x11-0 libxcb-cursor-dev -y
# 下载并运行AppImage
wget https://d176tv9ibo4jno.cloudfront.net/latest/QGroundControl.AppImage
chmod +x QGroundControl.AppImage
./QGroundControl.AppImage
提示:如果遇到图形驱动问题,可以尝试添加
--appimage-extract-and-run参数运行。建议将QGC添加到系统PATH,方便后续使用。
Gazebo Harmonic是Ubuntu 22.04官方支持的版本,相比Classic版本有更好的性能和功能支持:
bash复制# 卸载旧版本(如有)
dpkg -l | grep gazebo | awk '{print $2}' | xargs sudo apt remove -y
sudo apt autoremove -y
# 安装Harmonic
sudo apt-get update
sudo apt-get install curl lsb-release gnupg
sudo curl https://packages.osrfoundation.org/gazebo.gpg --output /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] https://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/gazebo-stable.list > /dev/null
sudo apt-get update
sudo apt-get install gz-harmonic
验证安装:
bash复制gz sim
常见问题解决:
LIBGL_ALWAYS_SOFTWARE=1 gz sim启动PX4是当前最流行的开源飞控系统,支持多种硬件平台和仿真环境:
bash复制# 克隆仓库(建议使用国内镜像加速)
git clone https://gitee.com/mirrors/PX4-Autopilot.git --recursive
cd PX4-Autopilot
# 初始化子模块(网络问题可能导致失败,需多次尝试)
git submodule update --init --recursive
# 安装依赖
bash ./Tools/setup/ubuntu.sh
# 验证安装
make px4_sitl list_vmd_make_targets
编译常见问题:
.gitmodules文件中的URL为国内镜像源DDS是实现ROS 2与PX4通信的关键组件,配置步骤如下:
bash复制# 安装依赖
sudo apt install -y cmake build-essential
# 编译安装Micro XRCE-DDS Agent
git clone -b v2.4.3 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/
启动代理:
bash复制MicroXRCEAgent udp4 -p 8888
注意:如果遇到Fast-DDS兼容性问题,可以尝试降级Fast-DDS版本或使用PX4推荐的特定版本组合。
首先创建一个工作空间:
bash复制mkdir -p ~/px4_ros2_ws/src
cd ~/px4_ros2_ws/src
git clone https://github.com/PX4/px4_msgs.git
git clone https://github.com/PX4/px4_ros_com.git
cd ..
colcon build
source install/local_setup.bash
测试通信:
bash复制# 终端1:启动仿真
cd ~/PX4-Autopilot
make px4_sitl gz_x500
# 终端2:启动DDS代理
MicroXRCEAgent udp4 -p 8888
# 终端3:监听传感器数据
ros2 topic echo /fmu/out/sensor_combined
Offboard模式允许外部系统(如ROS 2)直接控制无人机。以下是基础实现:
cpp复制// offboard_control.cpp
#include <px4_msgs/msg/offboard_control_mode.hpp>
#include <px4_msgs/msg/trajectory_setpoint.hpp>
#include <px4_msgs/msg/vehicle_command.hpp>
#include <rclcpp/rclcpp.hpp>
using namespace std::chrono_literals;
class OffboardControl : public rclcpp::Node {
public:
OffboardControl() : Node("offboard_control") {
// 初始化发布器
offboard_control_mode_publisher_ = this->create_publisher<px4_msgs::msg::OffboardControlMode>(
"/fmu/in/offboard_control_mode", 10);
trajectory_setpoint_publisher_ = this->create_publisher<px4_msgs::msg::TrajectorySetpoint>(
"/fmu/in/trajectory_setpoint", 10);
vehicle_command_publisher_ = this->create_publisher<px4_msgs::msg::VehicleCommand>(
"/fmu/in/vehicle_command", 10);
// 定时器回调
timer_ = this->create_wall_timer(100ms, [this]() {
if (offboard_setpoint_counter_ == 10) {
this->publish_vehicle_command(
px4_msgs::msg::VehicleCommand::VEHICLE_CMD_DO_SET_MODE, 1, 6);
this->arm();
}
publish_offboard_control_mode();
publish_trajectory_setpoint();
if (offboard_setpoint_counter_ < 11) {
offboard_setpoint_counter_++;
}
});
}
private:
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<px4_msgs::msg::OffboardControlMode>::SharedPtr offboard_control_mode_publisher_;
rclcpp::Publisher<px4_msgs::msg::TrajectorySetpoint>::SharedPtr trajectory_setpoint_publisher_;
rclcpp::Publisher<px4_msgs::msg::VehicleCommand>::SharedPtr vehicle_command_publisher_;
uint64_t offboard_setpoint_counter_ = 0;
void publish_offboard_control_mode() {
px4_msgs::msg::OffboardControlMode msg{};
msg.position = true;
msg.velocity = false;
msg.acceleration = false;
msg.attitude = false;
msg.body_rate = false;
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
offboard_control_mode_publisher_->publish(msg);
}
void publish_trajectory_setpoint() {
px4_msgs::msg::TrajectorySetpoint msg{};
msg.position = {0.0, 0.0, -5.0}; // NED坐标系,Z向下为正
msg.yaw = -3.14; // 朝向北方
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
trajectory_setpoint_publisher_->publish(msg);
}
void publish_vehicle_command(uint16_t command, float param1 = 0.0, float param2 = 0.0) {
px4_msgs::msg::VehicleCommand msg{};
msg.param1 = param1;
msg.param2 = param2;
msg.command = command;
msg.target_system = 1;
msg.target_component = 1;
msg.source_system = 1;
msg.source_component = 1;
msg.from_external = true;
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
vehicle_command_publisher_->publish(msg);
}
void arm() {
publish_vehicle_command(px4_msgs::msg::VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM, 1.0);
RCLCPP_INFO(this->get_logger(), "Arm command send");
}
};
int main(int argc, char* argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<OffboardControl>());
rclcpp::shutdown();
return 0;
}
相比话题方式,服务调用提供了更可靠的命令确认机制:
cpp复制// offboard_control_srv.cpp
#include <px4_msgs/msg/offboard_control_mode.hpp>
#include <px4_msgs/msg/trajectory_setpoint.hpp>
#include <px4_msgs/srv/vehicle_command.hpp>
#include <rclcpp/rclcpp.hpp>
using namespace std::chrono_literals;
class OffboardControl : public rclcpp::Node {
public:
OffboardControl() : Node("offboard_control_srv") {
// 初始化服务客户端
vehicle_command_client_ = this->create_client<px4_msgs::srv::VehicleCommand>(
"/fmu/vehicle_command");
// 等待服务可用
while (!vehicle_command_client_->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(this->get_logger(), "Interrupted while waiting for service");
return;
}
RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
}
// 定时器
timer_ = this->create_wall_timer(100ms, std::bind(&OffboardControl::timer_callback, this));
}
private:
enum class State { INIT, OFFBOARD_REQUESTED, ARM_REQUESTED, ARMED } state_;
rclcpp::Client<px4_msgs::srv::VehicleCommand>::SharedPtr vehicle_command_client_;
rclcpp::TimerBase::SharedPtr timer_;
uint8_t service_result_;
bool service_done_ = false;
void timer_callback() {
switch (state_) {
case State::INIT:
request_vehicle_command(
px4_msgs::msg::VehicleCommand::VEHICLE_CMD_DO_SET_MODE, 1, 6);
state_ = State::OFFBOARD_REQUESTED;
break;
case State::OFFBOARD_REQUESTED:
if (service_done_) {
if (service_result_ == 0) {
request_vehicle_command(
px4_msgs::msg::VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM, 1.0);
state_ = State::ARM_REQUESTED;
}
}
break;
case State::ARM_REQUESTED:
if (service_done_ && service_result_ == 0) {
RCLCPP_INFO(this->get_logger(), "Vehicle armed and in offboard mode");
state_ = State::ARMED;
}
break;
case State::ARMED:
// 执行控制逻辑
break;
}
}
void request_vehicle_command(uint16_t command, float param1 = 0.0, float param2 = 0.0) {
auto request = std::make_shared<px4_msgs::srv::VehicleCommand::Request>();
px4_msgs::msg::VehicleCommand cmd{};
cmd.command = command;
cmd.param1 = param1;
cmd.param2 = param2;
cmd.target_system = 1;
cmd.target_component = 1;
cmd.source_system = 1;
cmd.source_component = 1;
cmd.from_external = true;
cmd.timestamp = this->get_clock()->now().nanoseconds() / 1000;
request->request = cmd;
service_done_ = false;
auto result_future = vehicle_command_client_->async_send_request(
request, [this](rclcpp::Client<px4_msgs::srv::VehicleCommand>::SharedFuture future) {
auto response = future.get();
service_result_ = response->reply.result;
service_done_ = true;
RCLCPP_INFO(this->get_logger(), "Command result: %d", service_result_);
});
}
};
bash复制cd ~/PX4-Autopilot
make px4_sitl gz_x500
bash复制./QGroundControl.AppImage
bash复制MicroXRCEAgent udp4 -p 8888
bash复制ros2 run px4_ros_com offboard_control
无人机不响应Offboard命令:
ROS 2节点无法连接PX4:
px4_ros_com是否正确编译Gazebo模型异常:
~/.gazebo缓存后重新启动可以根据项目需求扩展PX4消息接口:
px4_msgs/msg目录下的消息定义文件通过修改启动参数可以实现多无人机仿真:
bash复制make px4_sitl gz_x500 -j8 instance 0
make px4_sitl gz_x500 -j8 instance 1
每个实例需要使用不同的MAVLink端口和DDS命名空间。
将系统部署到真实无人机的步骤:
在实际项目中,我发现这套环境最大的优势在于仿真和实机可以无缝切换。通过精心设计的硬件抽象层,90%的算法代码可以直接从仿真环境移植到真实无人机上运行。