别再对着文档发愁了!手把手教你用MAVROS控制PX4无人机(附Python/ROS2代码)
·
MAVROS实战指南:用Python与ROS2构建无人机自主飞行系统
第一次看到无人机在代码控制下完成精准悬停时,那种成就感至今难忘。作为连接ROS与PX4飞控的桥梁,MAVROS让开发者能够用熟悉的ROS工具链操控无人机,但文档里零散的话题说明常让人无从下手。本文将用可运行的代码示例,带你跨越从理论到实践的鸿沟。
1. 环境配置与基础检查
在开始编码前,确保你的开发环境满足以下条件:
- PX4固件 :v1.13或更新版本
- ROS版本 :
- ROS Noetic(Ubuntu 20.04)
- ROS2 Humble(Ubuntu 22.04)
- MAVROS安装 :
# ROS1 sudo apt install ros-noetic-mavros ros-noetic-mavros-extras # ROS2 sudo apt install ros-humble-mavros ros-humble-mavros-extras
连接验证(确保飞控与地面站通信正常):
import rospy
from mavros_msgs.msg import State
def state_cb(msg):
print(f"Connected: {msg.connected}, Armed: {msg.armed}, Mode: {msg.mode}")
rospy.init_node('check_connection')
state_sub = rospy.Subscriber('/mavros/state', State, state_cb)
rospy.spin()
注意:如果显示未连接,检查USB/UART连接或MAVROS启动参数中的设备路径
2. 关键数据订阅与解析
无人机状态监控是自主控制的基础,这些核心话题需要特别关注:
2.1 姿态与位置数据融合
# ROS2示例
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import NavSatFix, Imu
from geometry_msgs.msg import PoseStamped
class SensorFusion(Node):
def __init__(self):
super().__init__('sensor_fusion')
# 创建订阅
self.gps_sub = self.create_subscription(
NavSatFix, '/mavros/global_position/global',
self.gps_cb, 10)
self.imu_sub = self.create_subscription(
Imu, '/mavros/imu/data',
self.imu_cb, 10)
self.local_pos_sub = self.create_subscription(
PoseStamped, '/mavros/local_position/pose',
self.local_pos_cb, 10)
def gps_cb(self, msg):
self.get_logger().info(
f"GPS: Lat={msg.latitude:.6f}, Lon={msg.longitude:.6f}, Alt={msg.altitude:.1f}")
def imu_cb(self, msg):
# 四元数转欧拉角略
pass
def local_pos_cb(self, msg):
x, y, z = msg.pose.position.x, msg.pose.position.y, msg.pose.position.z
self.get_logger().info(f"Local Position: X={x:.2f}, Y={y:.2f}, Z={z:.2f}")
if __name__ == '__main__':
rclpy.init()
node = SensorFusion()
rclpy.spin(node)
2.2 系统状态监控表
| 状态类型 | 话题路径 | 关键字段 | 典型用途 |
|---|---|---|---|
| 飞控状态 | /mavros/state | connected, armed, mode | 模式切换判断 |
| 电池状态 | /mavros/battery | voltage, remaining | 低电量预警 |
| RC输入 | /mavros/rc/in | channels | 手动接管检测 |
| 系统状态 | /mavros/statustext | text, severity | 故障诊断 |
3. 控制指令发布实战
3.1 安全进入Offboard模式
# ROS1完整示例
#!/usr/bin/env python
import rospy
from geometry_msgs.msg import PoseStamped
from mavros_msgs.msg import State
from mavros_msgs.srv import CommandBool, SetMode
current_state = State()
def state_cb(state):
global current_state
current_state = state
if __name__ == "__main__":
rospy.init_node("offboard_ctrl")
state_sub = rospy.Subscriber("/mavros/state", State, state_cb)
local_pos_pub = rospy.Publisher("/mavros/setpoint_position/local", PoseStamped, queue_size=10)
rospy.wait_for_service("/mavros/cmd/arming")
arming_client = rospy.ServiceProxy("/mavros/cmd/arming", CommandBool)
rospy.wait_for_service("/mavros/set_mode")
set_mode_client = rospy.ServiceProxy("/mavros/set_mode", SetMode)
rate = rospy.Rate(20)
# 必须先发送设定值,否则PX4会拒绝Offboard模式
pose = PoseStamped()
pose.pose.position.x = 0
pose.pose.position.y = 0
pose.pose.position.z = 2
for i in range(100):
local_pos_pub.publish(pose)
rate.sleep()
last_request = rospy.Time.now()
while not rospy.is_shutdown():
now = rospy.Time.now()
if current_state.mode != "OFFBOARD" and (now - last_request > rospy.Duration(5.)):
if set_mode_client(0, "OFFBOARD").mode_sent:
rospy.loginfo("Offboard enabled")
last_request = now
else:
if not current_state.armed and (now - last_request > rospy.Duration(5.)):
if arming_client(True).success:
rospy.loginfo("Vehicle armed")
last_request = now
local_pos_pub.publish(pose)
rate.sleep()
关键安全措施:在真实环境中测试时,务必配置RC遥控器作为Fallback,并设置电子围栏
3.2 多控制模式实现对比
位置控制 :
def send_position_target(x, y, z):
pose = PoseStamped()
pose.header.stamp = rospy.Time.now()
pose.pose.position.x = x
pose.pose.position.y = y
pose.pose.position.z = z
local_pos_pub.publish(pose)
速度控制 :
from geometry_msgs.msg import TwistStamped
def send_velocity_target(vx, vy, vz):
twist = TwistStamped()
twist.header.stamp = rospy.Time.now()
twist.twist.linear.x = vx
twist.twist.linear.y = vy
twist.twist.linear.z = vz
vel_pub.publish(twist)
姿态控制 :
import math
def send_attitude_target(roll, pitch, yaw, thrust):
q = quaternion_from_euler(roll, pitch, yaw)
pose = PoseStamped()
pose.pose.orientation.x = q[0]
pose.pose.orientation.y = q[1]
pose.pose.orientation.z = q[2]
pose.pose.orientation.w = q[3]
# 需要配合混控器话题发布推力
att_pub.publish(pose)
4. 典型应用场景实现
4.1 自动起飞与定点悬停
# ROS2实现类
class AutoTakeoff(Node):
def __init__(self):
super().__init__('auto_takeoff')
self.target_alt = 5.0 # 目标高度
self.current_pose = None
self.state = None
self.pose_sub = self.create_subscription(
PoseStamped, '/mavros/local_position/pose',
self.pose_cb, 10)
self.state_sub = self.create_subscription(
State, '/mavros/state',
self.state_cb, 10)
self.pos_pub = self.create_publisher(
PoseStamped, '/mavros/setpoint_position/local', 10)
self.arming_client = self.create_client(
CommandBool, '/mavros/cmd/arming')
self.set_mode_client = self.create_client(
SetMode, '/mavros/set_mode')
async def arm_and_takeoff(self):
# 等待服务可用
while not (self.arming_client.wait_for_service(timeout_sec=1.0) and
self.set_mode_client.wait_for_service(timeout_sec=1.0)):
self.get_logger().info('等待服务...')
# 发送初始位置
pose = PoseStamped()
pose.pose.position.z = self.target_alt
for _ in range(100):
self.pos_pub.publish(pose)
await asyncio.sleep(0.05)
# 切换模式
set_mode_req = SetMode.Request()
set_mode_req.custom_mode = 'OFFBOARD'
await self.set_mode_client.call_async(set_mode_req)
# 解锁
arm_req = CommandBool.Request()
arm_req.value = True
await self.arming_client.call_async(arm_req)
# 控制循环
while rclpy.ok():
if abs(self.current_pose.pose.position.z - self.target_alt) < 0.1:
break
self.pos_pub.publish(pose)
await asyncio.sleep(0.05)
self.get_logger().info("到达目标高度!")
4.2 航点任务规划
结合MAVROS的航点服务,我们可以构建完整的任务系统:
- 航点数据结构 :
from mavros_msgs.msg import Waypoint
def create_waypoint(x, y, z, cmd=16, frame=1):
wp = Waypoint()
wp.frame = frame # MAV_FRAME_LOCAL_NED
wp.command = cmd # MAV_CMD_NAV_WAYPOINT
wp.x_lat = x
wp.y_long = y
wp.z_alt = z
wp.autocontinue = True
return wp
- 航点任务上传 :
from mavros_msgs.srv import WaypointPush
async def upload_mission(self, waypoints):
wp_push = self.create_client(WaypointPush, '/mavros/mission/push')
req = WaypointPush.Request()
req.waypoints = waypoints
future = wp_push.call_async(req)
try:
response = await future
if response.success:
self.get_logger().info(f"上传成功,共{response.wp_transfered}个航点")
else:
self.get_logger().error("上传失败")
except Exception as e:
self.get_logger().error(f"服务调用失败: {str(e)}")
5. 调试技巧与异常处理
实际部署中常见问题解决方案:
-
GPS信号丢失 :
def handle_gps_loss(): # 切换至高度保持模式 set_mode_client(0, "ALTCTL") # 缓慢下降 send_velocity_target(0, 0, -0.5) -
通信中断应急处理 :
def check_heartbeat(): last_heartbeat = rospy.Time.now() while not rospy.is_shutdown(): if (rospy.Time.now() - last_heartbeat).to_sec() > 5.0: rospy.logerr("心跳丢失!触发应急措施") # 执行预设应急方案 break rospy.sleep(1) -
状态监控看板 (使用rqt_multiplot):
rosrun rqt_multiplot rqt_multiplot配置建议监控曲线:
- 本地位置XYZ
- 速度设定值与实际值
- 电池电压变化
- CPU负载与通信延迟
在Gazebo仿真中测试完整流程时,发现姿态控制需要特别注意四元数归一化问题。某次测试因未做归一化导致无人机异常旋转,后来在发布姿态指令前增加检查后问题解决。
更多推荐

所有评论(0)