做无人机自主飞行项目Pixhawk和树莓派这套组合真的是绕不开的经典方案。Pixhawk2.4.8虽然已经不算新硬件但在DIY圈子和毕业设计里依旧是出镜率极高的飞控树莓派作为机载小电脑承担视觉处理、任务调度这些飞控干不了的重活。中间用MAVLink协议把两边串起来就能实现自主巡航、精准降落这类“飞控原生逻辑搞不定”的需求。这篇博文就从整体方案、通信链路、巡航代码和视觉降落四个角度把整个项目从0到1的搭建过程完整拆一遍适合刚接触树莓派飞控组合的爱好者也适合拿这套东西做毕设或比赛项目的同学。1. 整体方案设计与核心思路先说清楚这套系统的分工逻辑。Pixhawk2.4.8的本质是一个实时性要求很高的飞控它负责姿态解算、电机控制、导航状态机这些底层逻辑。但它的计算资源有限跑不了OpenCV、跑不了复杂的路径规划算法也没法接摄像头做实时视觉识别。树莓派4B则正好补齐这个短板它有四核A72处理器、2GB起步的内存、USB 3.0接口和40Pin GPIO跑Linux系统Python生态随便用天生就是干“机载计算”的料。所以整体架构分三层决策层树莓派上运行的Python脚本负责解析任务、规划航点、调用视觉算法做目标识别。通信层MAVLink协议通过串口或UDP在树莓派与Pixhawk之间传递心跳包、航点指令、姿态数据和位置信息。执行层Pixhawk飞控根据接收到的模式切换指令和运动目标指令实时驱动电机保持飞行姿态稳定。第二层的MAVLink是这套系统的信息主动脉。它本质上是一个轻量级、基于消息的数据封装协议定义了诸如心跳包HEARTBEAT、姿态数据ATTITUDE、全局位置GLOBAL_POSITION_INT、任务指令MISSION_ITEM等上百种标准消息。飞控和地面站之间、飞控和机载电脑之间都靠这些结构化的消息格式交换数据。好处是生态统一同一个协议既能接QGroundControl地面站也能被Python的pymavlink库解析开发效率很高。我自己踩过的最大的一个坑是把任务全塞给飞控的编译器去跑结果可扩展性极差。后来改成“决策在树莓派、执行在飞控”的分工方式视觉识别和航点规划放在树莓派飞控只响应MAVLink指令做底层运动控制整个系统的迭代速度一下子快了很多出了问题也容易定位是算法层还是控制层的问题。2. 关键硬件选型与基础环境准备硬件选型决定了后面开发和调试的顺畅程度这部分我列一份自己验证过的清单也标注了哪些地方容易踩坑。2.1 核心硬件清单与连接方式组件推荐型号连接方式注意事项飞控Pixhawk2.4.8直接作为主控注意IMU方向校准机载电脑树莓派4B2GB/4GB/8GB均可飞控TELEM2口接树莓派UART或USB-TTL推荐4GB以上内存跑视觉GPSu-blox M8N模块接飞控GPS口推荐有源天线搜星更快数传模块3DR Radio V2 433MHz/915MHz接飞控TELEM1口用于地面站实时监控摄像头树莓派OV5647官方摄像头或USB摄像头CSI接口或USB 2.0视觉降落推荐CSI延迟低电源模块Pixhawk专用PM模块动力电池分电板接入同时给飞控和树莓派供电遥控器乐迪AT9S Pro或同级别接收机接飞控RC口手动模式备用安全兜底树莓派与飞控的连接方式优先选USB-TTL转串口模块或者直接用树莓派的UART引脚。用UART时要注意树莓派默认把串口分配给了Linux控制台登录必须先把串口控制台关掉否则飞控的数据会被系统日志刷掉。我见过很多人飞控连接一直没数据排查半天发现是系统还在占着串口。2.2 树莓派系统刷写与基础配置系统选择我用过两个方案一个是树莓派官方Raspberry Pi OS Lite 64位另一个是Ubuntu Server 22.04 LTS。Raspberry Pi OS Lite更轻量开机内存占用约300MB留着给Python视觉进程用更充裕Ubuntu Server的驱动兼容性和ROS集成性更好如果后续要上ROS2就选它。刷系统流程用树莓派官方Imager工具选择对应镜像写入SD卡。在Imager里预配置好有线/WiFi网络、SSH和用户名密码。插入树莓派开机用路由器后台或arp -a查IP然后SSH登录。配环境时一定要先换源。树莓派默认官方源在国内拉包极慢换成清华源或阿里源后apt update速度能快几十倍。换源操作我放在后面避坑章节细讲。串口配置是另一个关键点在/boot/config.txt里确认enable_uart1并通过raspi-config关闭串口登录然后重启/dev/serial0才会留给飞控通信。2.3 Pixhawk固件选择与参数校准Pixhawk2.4.8可以刷ArduPilot或PX4固件。做自主巡航精准降落这个项目我更推荐ArduPilot。原因很实际ArduPilot的Mission Planner地面站里配置航点任务非常简单而且它的Precision Landing精准降落模块对视觉外设的接入有原生支持不需要自己从零写控制逻辑。刷固件用Mission Planner选好COM口和固件版本点闪烁即可。刷完之后有几个必调的参数ARMING_CHECK建议先开基础项避免乱报错导致无法解锁。PILOT_SPEED_UP、PILOT_SPEED_DN设默认值即可做任务模式不受影响。FS_EKF_ACTION设成1Land防止室内或弱GPS环境下EKF异常炸机。RTL_ALT设成30~50米失控时先爬升到安全高度再自动返航。再把加速度计、罗盘、水平校准走一遍。特别是罗盘校准最好在户外无磁干扰场地做完整的两圈旋转否则后面航向会漂GPS模式下飞机会画圈。3. MAVLink通信链路搭建与验证MAVLink链路是整套系统的数据传输通道。这部分最容易出问题但也是最能通过日志定位问题的地方。先说清楚链路的两端和接线方式再给出可以直接跑的验证代码。3.1 串口连接与波特率配置我的接线方案是树莓派的GPIO中的TX物理引脚8接飞控TELEM2口的RX树莓派RX物理引脚10接飞控TX共地GND接GND。TELEM2默认波特率是57600如果想提高速率在Mission Planner里把SERIAL2_BAUD改成921或对应的波特率值。注意这里的参数单位是“个位表示×1000”所以921表示921600。树莓派侧要确认设备名。USB-TTL转接通常是/dev/ttyUSB0用GPIO内置串口则是/dev/serial0。我建议直接用GPIO内置串口少一个USB转接环节稳定性更好。测试链路是否通畅的Python脚本from pymavlink import mavutil # 根据实际情况改串口号和波特率 connection mavutil.mavlink_connection(/dev/ttyUSB0, baud57600) connection.wait_heartbeat(timeout10) print(收到飞控心跳包飞控系统ID:, connection.target_system) # 读取一帧姿态数据验证 msg connection.recv_match(typeATTITUDE, blockingTrue, timeout5) if msg: print(Roll: {:.2f}°, Pitch: {:.2f}°, Yaw: {:.2f}°.format( __import__(math).degrees(msg.roll), __import__(math).degrees(msg.pitch), __import__(math).degrees(msg.yaw) ))看到心跳包说明MAVLink链路已经打通。如果卡在wait_heartbeat优先级最高的排查项是飞控是否通电、波特率是否匹配、串口是否被系统占用。这三个问题占了九成概率。3.2 UDP转发与地面站协同机载调试时树莓派上没有显示器直接看MAVLink原始数据不方便。我的习惯是用mavproxy.py做UDP转发把串口数据转成UDP数据包这样同一个局域网内的电脑就能用QGroundControl或Mission Planner实时监控飞行状态。mavproxy.py --master/dev/ttyUSB0 --baudrate57600 --outudp:192.168.1.100:14550电脑端QGroundControl连接UDP端口14550即可。这个方案还有另一个好处树莓派上跑的Python脚本同样可以通过UDP去收飞控数据不影响串口那边的其他程序。不过要注意同一串口被多个程序打开会冲突推荐mavproxy占用串口Python程序通过UDP连mavproxy的转发端口来收发消息。3.3 MAVLink常用消息类型速查消息名作用使用场景HEARTBEAT心跳包表明飞控在线链路检测、超时判断ATTITUDE姿态角Roll/Pitch/Yaw飞行状态监控GLOBAL_POSITION_INT经纬度高度位置航点记录、位置判断GPS_RAW_INTGPS原始数据和定位质量定位评估MISSION_ITEM / MISSION_ACK航点任务上传与确认自主巡航SET_POSITION_TARGET_GLOBAL_INT指定目标经纬度高度位置offboard控制SET_MODE切换飞行模式任务流程控制COMMAND_LONG通用指令通道解锁、起飞等通信链路是后续一切功能的基础建议在开发早期就把日志打全串口收到每条消息都打时间戳。飞行结束后用Mission Planner的Flight Review功能查看完整数据曲线很多问题一眼就能看出来。4. 自主巡航的路径规划与代码实现自主巡航的本质是让飞控按照预设的一组航点自动起飞、飞向目标点、完成拍照或巡线任务、最后降落。ArduPilot本身支持在Mission Planner里直接规划航点但机载树莓派的优势在于可以在飞行中根据实时情况动态生成航点或者调整路径这在巡检、测绘、搜索这类任务里特别有用。4.1 两种自主巡航实现路径对比我把实现方式分为“任务模式”和“引导模式/offboard模式”两种各有优劣实现方式优点缺点适用场景Mission模式任务模式飞控自主执行稳定性高不依赖机载电脑在线航点固定中途改点麻烦常规巡检、固定航线测绘Guided/offboard模式树莓派实时控制目标位置动态性强链路断开或脚本崩溃会导致失控风险动态目标追踪、视觉引导降落我的推荐是混合使用长航线的固定航点用Mission模式最后的精准降落和避障改航用Guided/offboard模式。这样既保留了任务模式的可靠性又能利用树莓派的计算能力做动态调整。4.2 Mission模式上传航点下面这段代码演示如何用pymavlink创建一个包含起飞点和两个航点的任务并执行from pymavlink import mavutil connection mavutil.mavlink_connection(/dev/ttyUSB0, baud57600) connection.wait_heartbeat() # 切换至GUIDED模式准备平台解锁 mode GUIDED mode_id connection.mode_mapping()[mode] connection.mav.set_mode_send( connection.target_system, mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED, mode_id ) # 清空旧任务 connection.mav.mission_clear_all_send(connection.target_system, connection.target_component) # 航点列表: 起飞点、航点1、航点2 # 每个航点包含序列号、坐标系、命令、参数 missions [ (0, 47.123456, 8.543210, 20), # 起飞点相对高度20米 (1, 47.124567, 8.545678, 25), # 航点125米 (2, 47.125678, 8.547890, 25), # 航点225米 ] for seq, lat, lon, alt in missions: connection.mav.mission_item_send( connection.target_system, connection.target_component, seq, mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT, mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 1, # current, autocontinue 0, 0, 0, 0, # 参数 int(lat * 1e7), int(lon * 1e7), int(alt) # 纬度和经度用整数微度表示 ) # 切换到AUTO模式执行任务 mode AUTO mode_id connection.mode_mapping()[mode] connection.mav.set_mode_send( connection.target_system, mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED, mode_id )注意经纬度在GLOBAL_POSITION_INT和MISSION_ITEM消息里用的是整数表示单位是°×10⁷所以存进去前要乘1e7再转int。包括后面的起飞高度也是整数厘米。任务上传完成后用mission_request_list轮询飞控的任务列表确认每一条航点都收到且ACK通过避免漏传航点。4.3 Guided模式动态控制目标位置Guided模式是精准降落最关键的飞行模式。它的原理是树莓派通过SET_POSITION_TARGET_GLOBAL_INT消息直接告诉飞控“现在飞到哪个经纬度、多高”。飞控内部的导航控制器会自动计算横向速度和垂直速度去靠这个目标点。def goto_position(lat, lon, alt): 发送目标位置到飞控 connection.mav.set_position_target_global_int_send( 0, # 时间戳0表示立即执行 connection.target_system, connection.target_component, mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT, mavutil.mavlink.MAV_NAV_ALT_HOLD, # 类型掩码这里表示只控制位置和高度 int(lat * 1e7), int(lon * 1e7), int(alt), # 目标经纬度和高度 0, 0, 0, 0, 0, 0, # 速度和加速度等参数全部置0 0, 0 # yaw和yaw_rate )类型掩码字段的MAV_NAV_ALT_HOLD表示只关注位置与高度不关心速度和加速度。如果要用速度控制就把类型掩码改成对应位为0并填入vx/vy/vz。实际上掩码里的每个bit位决定了某个数值是否“有效”这一点务必花时间看官方文档否则指令发了飞控可能不响应。还有一个容易被忽略的细节每次发送位置指令前先确认飞控当前已经处于Guided模式。ArduPilot在收到位置指令但不在Guided/offboard模式时通常会拒绝执行或者直接忽略。5. 精准降落方案与实现细节GPS精度再好在没有RTK差分的情况下也就2~5米误差这样的精度对于降落圈和停机坪来说太粗了。精准降落我采用的方案是“视觉初定位GPS粗定位融合”远距离时靠GPS把飞机引导到降落区域附近近距离切换视觉引导用摄像头识别ArUco标记类似于简化版的二维码标记计算偏移量再通过MAVLink发送修正指令实现厘米级的落点控制。5.1 ArUco标记识别原理ArUco是OpenCV内置库支持的一种二进制方形标记物。它由黑边和内部黑白网格组成每个标记物有独立ID。通过检测四个角点可以计算出标记物在相机坐标系下的位置姿态即旋转向量和平移向量从而估计飞机相对标记物的水平偏移和距离。我在代码里的实现步骤是从CSI摄像头读取一帧720p图像。用cv2.aruco.detectMarkers检测图像中的ArUco标记。用cv2.solvePnP根据相机内参和标记物尺寸求解位姿。从平移向量中获得相对于相机的x/y/z偏移结合当前飞行高度和GPS坐标解算出无人机需要修正的横向速度。5.2 视觉引导降落核心代码核心逻辑放在树莓派上识别到标记物后计算出偏移转换成速度指令通过MAVLink发给飞控。飞控自己执行高度下降但水平速度由树莓派修正。import cv2 import numpy as np from pymavlink import mavutil # 相机内参需要先用棋盘格标定自己的摄像头 CAMERA_MATRIX np.array([[520.0, 0, 640], [0, 520, 360], [0, 0, 1]], dtypefloat) DIST_COEFFS np.array([0.02, -0.05, 0.0, 0.0]) # ArUco标记实际尺寸单位米 MARKER_SIZE 0.20 # 先切到Guided模式并降低到一个安全高度 connection mavutil.mavlink_connection(/dev/ttyUSB0, baud57600) connection.wait_heartbeat() connection.set_mode(GUIDED) time.sleep(1) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame cap.read() if not ret: continue # 检测ArUco标记 parameters cv2.aruco.DetectorParameters() dictionary cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250) detector cv2.aruco.ArucoDetector(dictionary, parameters) corners, ids, _ detector.detectMarkers(frame) if ids is not None and len(ids) 0: # 这里取第一个检测到的标记实际应用可以锁定某个ID rvec, tvec, _ cv2.aruco.estimatePoseSingleMarkers( corners, MARKER_SIZE, CAMERA_MATRIX, DIST_COEFFS) tvec tvec[0][0] # tvec[0]是左右偏移tvec[1]是前后偏移tvec[2]是距离 x_offset tvec[0] # 正值表示标记在相机右侧 y_offset tvec[1] # 正值表示标记在相机下方 distance tvec[2] # 垂直距离约等于高度 # 简化控制偏移量换算成速度指令 # x方向前向速度用y_offset控制y方向右向速度用x_offset控制 vx max(min(0.8 * y_offset, 1.5), -1.5) vy max(min(0.8 * x_offset, 1.5), -1.5) # 发送速度控制指令 connection.mav.set_position_target_local_ned_send( 0, connection.target_system, connection.target_component, mavutil.mavlink.MAV_FRAME_BODY_NED, 0b0000111111000111, # 只启用速度控制 distance, 0, 0, # 位置分量 vx, vy, 0, # 速度分量 0, 0, 0, 0, 0 # 加速度和偏航 ) # 当距离足够近时切换LAND由飞控完成最后触地 if distance 1.5: connection.set_mode(LAND) break else: # 丢失视觉目标时发送悬停指令原地保持等待 connection.mav.set_position_target_local_ned_send( 0, connection.target_system, connection.target_component, mavutil.mavlink.MAV_FRAME_BODY_NED, 0b0000111111000111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) cap.release()代码里速度控制类型掩码0b0000111111000111表示位置、加速度、偏航全部忽略只启用速度控制。如果掩码配置错了飞控可能会把速度当成位置指令飞机会猛冲出去极其危险我强烈建议在地面上用一个假的tvec值先仿真验证指令效果。5.3 多阶段降落策略与参数调优实战中不要指望一个视觉算法从头带到尾。我习惯把降落过程拆成三个阶段GPS粗引导阶段距离标记物10米以上时用GPS引导飞控飞到降落区域上空高度设置在8~10米。视觉进近阶段距离5米左右高度降到5米以下开始视觉识别解算水平偏移用速度指令把飞机调整到标记物正上方。垂直触地阶段距离标记物1米左右直接切换LAND模式同时持续发送水平修正速度直到落地。这里有几个调参经验视觉识别的频率控制在10Hz就够了太高会占用树莓派CPU影响其他进程。控制增益不要太大速度控制在±1.5m/s以内否则容易引起飞控姿态振荡。摄像头安装方向要和机头指向一致并且做相机参数标定否则输出的偏移量是歪的。6. 常见问题与排错速查表做这个项目我遇到了不少问题大部分原因都能归到硬件接线、参数配置和协议理解这三类。整理一份速查表方便遇到问题直接对号入座。现象可能原因排查方法树莓派收不到心跳包串口接线错误 / 波特率不匹配 / 串口被系统占用用串口调试助手查看原始数据检查config.txt和raspi-config确认SERIAL2_BAUD参数GPS搜星慢或不定位天线朝向被遮挡 / 罗盘未校准 / GPS参数错误室外开阔地开机等1分钟以上在Mission Planner校准罗盘确认GPS_TYPE参数飞行中航迹明显偏移EKF未收敛 / 气压计受干扰 / 磁罗盘干扰查看EKF状态日志重启飞控远离大电流线缆重新校准上传航点后飞机不执行未切换AUTO模式 / 当前无人解锁 / 航点序号错误确认模式指示灯状态解锁后起飞检查mission_item_send的参数LAND模式降落点偏移大气流干扰 / GPS精度不足 / 视觉识别丢失加视觉修正调整视觉任务触发距离压低进场速度Python脚本启动后CPU占用过高OpenCV处理帧率过高 / 没有释放历史资源限制帧率或降低分辨率使用单例摄像头对象数传连接掉线频繁数传模块供电不足 / 干扰严重 / 天线位置不对换独立稳压供电确认433MHz模块天线垂直放置最后一个常见误区是很多人以为MAVLink只能在串口上跑。实际上MAVLink可以在串口、UDP、TCP、甚至USB虚拟串口上传输。树莓派和飞控距离较远时也可以考虑USB延长线的方案或用WiFi数传前提是链路可靠且做好加密实测下来UDP在局域网内转发效率很高丢包很少。系统集成流程与整体调试建议从零到飞我建议按下面的顺序分阶段走不要把自主巡航和精准降落一次全上阶段一验证数传链路。树莓派能收到心跳包QGC地面站能看到姿态和GPS数据。阶段二手动飞行确认飞控调参无误。使用遥控器起飞降落确认增稳模式正常。阶段三脱离遥控器用树莓派控制飞机在安全高度做定点悬停Guided模式。确认位置指令表现稳定后再尝试简单航点。阶段四上传完整任务航点执行自主巡航。这一步全程用遥控器做好接管准备发现异常立即切回增稳模式。阶段五最后再单独调试精准降落。把降落目标放在空旷草坪上先低空悬停测试视觉偏移量是否准确再逐步提高进场高度。每一次飞行后都导出日志用Mission Planner的Flight Review复盘几个关键指标EKF状态、GPS卫星数、位置误差、遥控信号RSSI、电流电压。养成看日志的习惯能帮助你从“试飞玄学”变成“故障科学”。最后再分享一点个人体会做完整套系统最让我意外的是真正的难点其实不在代码而在工程组织。MAVLink协议本身很成熟pymavlink文档也很全但要把树莓派、飞控、视觉算法、电源管理、通信链路这些子系统串成一个稳定整体得反复做系统联调。我建议至少留出两周的调试时间别指望一次试飞就成功。如果后续想提升整套系统的上限可以往两个方向走。一个是在树莓派上跑YOLOv5或YOLOv8做物体识别把“落到某个二维码上”升级成“落到某个动态目标上”这就能做动态追降。另一个是把RTK定位模块接进来用厘米级GPS替代视觉粗定位适合大面积巡检场景。这套架构的可扩展性足够强换传感器、换算法都在树莓派那一层解决飞控部分几乎不用动这正是MAVLink生态的最大价值。