树莓派+Pixhawk:无人机自主巡航与视觉精准降落实战
发布时间:2026/9/28 2:40:13来源:尧图网络
把树莓派塞进无人机当“副驾驶”这事儿我琢磨了很久。飞控自己能飞、能定高、能按航点走但真要让它自己精准落回一个起飞垫上光靠GPS是远远不够的。GPS的漂移在开阔地轻松晃悠好几米落偏是家常便饭。后来我把树莓派加上摄像头通过MAVLink协议和Pixhawk 2.4.8飞控对上话才算把“自主巡航”和“精准降落”这两件事真正打通。这篇文章我会把整套系统的分工、连线、巡航航点实现、视觉精准降落的原理和代码以及我实测踩过的坑一次讲清楚适合手里有Pixhawk 2.4.8和树莓派、想做无人机自主任务或者毕业设计的同学参考。1. 先理清分工树莓派在飞机上到底扮演什么角色1.1 飞控能飞但飞控“不聪明”Pixhawk 2.4.8这块板子到今天依然是很多开源无人机的绝对主力。它内置了三轴陀螺仪、加速度计、磁力计和气压计配合GPS模块能稳定输出姿态和位置控制。你把它拨到Loiter模式它能乖乖悬停给它上传一条航线它能一个点一个点地飞过去。这些能力足够完成日常航拍和简单任务但它有个天生的短板算力太弱跑不动视觉识别也没有办法实时处理摄像头画面。飞控的实时性要求极高姿态解算和控制回路通常跑在几百赫兹级别它所有的精力都用在“让飞机别摔下去”这件事上。你让它分心去识别地面上的ArUco码、计算目标降落点的位置它没有余力硬件也跟不上。所以常规做法就出来了把“思考”和“决策”交给树莓派把“飞行稳定”和“底层控制”继续留给Pixhawk。树莓派是一台完整的Linux小电脑跑OpenCV、跑Python脚本、读摄像头、算目标位置这些活儿它都干得动。两边通过串口连接用MAVLink协议通信。1.2 机载计算机和飞控的职责边界怎么划我做这个项目时给两边的分工定得很明确Pixhawk 2.4.8负责姿态控制、位置控制、速度控制、航点执行、降落油门控制、紧急返航。树莓派负责摄像头采集与ArUco码识别、目标距离与角度解算、任务级决策什么时候起飞、飞哪些点、什么时候进入降落流程、把视觉结果打包成MAVLink消息发给飞控。这套分工的本质是“飞控永远有最终决定权”。树莓派可以建议飞控往左偏一点往右偏一点但最终油门、姿态、舵面混控都由飞控自己算。一旦树莓派死机或者视觉识别中断飞控依旧能按普通GPS降落逻辑完成任务不会因为机载电脑挂了就失控。这个设计逻辑一定要守住后面所有精度优化都建立在安全兜底之上。1.3 为什么不用地面站远程接管有人会问QGroundControl或者Mission Planner不也能上传航线、控制飞机吗为什么还要在飞机上塞一台树莓派答案很简单通信链路不可控。远程地面站通过数传电台跟飞控通信带宽有限、延迟大飞行中稍微拉远一点或者有遮挡链路就可能中断。一旦链路断了地面站根本看不到视觉画面更不可能实时传回目标位置。精准降落要求视觉系统以至少10Hz的频率连续输出目标角度和距离靠数传电台那点带宽根本不现实。机载处理的好处是所有的图像数据在飞机本地就消化掉了不需要传回地面只把最终的角度偏移和距离数值通过MAVLink串口发给飞控数据量极小、实时性极高。树莓派跑在飞机上等于把“眼睛”和“大脑”都搬到了现场。2. 硬件连接与固件参数先把通信路打通2.1 连线TELEM2到树莓派UART树莓派和Pixhawk的物理连接我建议走Pixhawk 2.4.8的TELEM2口。TELEM1一般留给数传电台TELEM2正好空出来接机载电脑。接口顺序是按标准JST-GH 6针脚位排列的和Pixhawk 4等新板子一样但2.4.8出厂默认带的接口其实是杜邦线或者带焊盘的插针需要自己接线。连接关系如下表TELEM2引脚功能树莓派GPIO1VCC 5V不要接-2TX飞控发送GPIO14UART0_TXD3RX飞控接收GPIO15UART0_RXD4GNDGND5CTS可悬空6RTS可悬空这里要特别强调第一个坑TELEM2口上那个5V是给外接设备供电用的有些飞控教科书里会让你接但我不建议从飞控给树莓派供5V。树莓派4B满载时电流能到2A以上飞控的5V稳压模块大概率扛不住强行带载会导致电压跌落轻则树莓派频繁重启重则连累飞控掉电压。正确的做法是树莓派用独立的5V BEC模块或者单独电池供电TELEM2只接TX、RX和GND三根线。电平方面Pixhawk 2.4.8的TELEM串口逻辑电平是3.3V树莓派GPIO也是3.3V电平可以直接互联。接线时注意飞控TX接树莓派RX飞控RX接树莓派TX交叉连接最后一定要共地。2.2 树莓派串口配置这一步不做后面全白搭树莓派的系统我用的是Raspberry Pi OS Lite不带桌面节省资源。系统装好之后如果要让GPIO14/15作为通用串口使用必须做两件事。第一件事关闭串口控制台登录。默认树莓派开机时会让系统把启动日志和登录shell映射到串口上这个串口如果不关掉树莓派和飞控之间发MAVLink时会混入一堆乱码文本。运行sudo raspi-config进入Interface Options - Serial Port第一问“Would you like a login shell to be accessible over serial?”选No第二问“Would you like the serial port hardware to be enabled?”选Yes。第二件事禁用蓝牙把UART归位。树莓派4B上蓝牙默认占用了PL011 UART也就是硬件串口ttyAMA0GPIO14/15默认被接到mini UARTttyS0上mini UART波特率跟随GPU频率不稳定丢包严重。在/boot/config.txt末尾加上enable_uart1 dtoverlaydisable-bt重启后/dev/ttyAMA0就是那个稳定的硬件串口可以直接被Python程序使用。如果是树莓派3B或者老版本同样适用。确认配置成功可以执行ls -l /dev/serial*正常情况下能看到一个指向ttyAMA0的软链接。2.3 飞控固件与关键参数Pixhawk 2.4.8可以刷PX4和ArduPilot两套固件精准降落这套方案我强烈建议用ArduPilot Copter固件版本推荐4.3.x以上。ArduPilot的Precision Landing功能非常成熟文档齐全PLND_TYPE里专门有Companion模式可以直接对接机载计算机的视觉数据。PX4虽然也有landing_target功能但配置复杂坑比较多新手容易劝退。刷好Copter固件后用Mission Planner或者QGC连接飞控在参数表里改这几项SERIAL2_PROTOCOL 2 (MAVLink2) SERIAL2_BAUD 57 (57600波特率) PLND_ENABLE 1 (开启精准降落) PLND_TYPE 1 (Companion机载视觉输入) PLND_EST_TYPE 0 (卡尔曼滤波器估计目标位置)波特率这里我选了57600没上921600。串口通讯不是越快越好树莓派和Pixhawk之间距离很短但航模电机和电调会制造电磁干扰高速率下误码率明显上升。57600波特率足够承载MAVLink的heartbeat、航点命令和LANDING_TARGET消息稳定优先。全部配置完后连接树莓派和飞控上电在树莓派上跑一个最简单的heartbeat检测脚本能收到飞控的MAVLink心跳就说明通信链路通了。3. 自主巡航实现MAVLink消息从哪来到哪去3.1 巡航完整消息链路自主巡航这套流程说穿了就是一条MAVLink消息链先告诉飞控“我要接管”再解锁电机然后起飞到目标高度接着上传一串航点最后切到AUTO模式让它自己飞。我用的Python库是pymavlink它是MAVLink协议的官方Python实现底层封装得非常干净。巡航的完整流程是这样的树莓派通过串口和飞控建立MAVLink连接收到heartbeat确认链路。发送SET_MODE指令把飞控切到GUIDED模式表示“机载电脑接管”。发送MAV_CMD_COMPONENT_ARM_DISARM指令解锁电机。发送MAV_CMD_NAV_TAKEOFF指令带一个目标高度让飞机原地起飞。发送MISSION_COUNT和MISSION_ITEM_INT消息上传全部航点。发送SET_MODE指令切换到AUTO模式飞控开始执行航线。每一步都需要确认飞控返回的ACK消息不能连续粗暴地发一坨消息过去飞控的处理队列是有顺序的消息挤在一起容易丢失。我在脚本里每一步发送后都会调用recv_match(typeCOMMAND_ACK, blockingTrue)等待确认超时再重发。3.2 Python脚本实操这是巡航部分的精简核心代码完整代码里我还加了超时重连和日志记录from pymavlink import mavutil import time # 建立连接 master mavutil.mavlink_connection(/dev/ttyAMA0, baud57600) master.wait_heartbeat() print(飞控已连接) # 切换GUIDED模式 master.set_mode_apm(GUIDED) time.sleep(0.5) # 解锁 master.arducopter_arm() time.sleep(1) # 起飞到10米 master.mav.command_long_send( master.target_system, master.target_component, mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 0, 0, 0, 0, 0, 10 ) ack master.recv_match(typeCOMMAND_ACK, blockingTrue, timeout5) print(起飞指令ACK:, ack.result) # 准备航点起飞点先绕两个矩形点最后回到降落区附近 waypoints [ (31.230412, 121.474061, 10), (31.230582, 121.474391, 10), (31.231102, 121.474398, 10), (31.231085, 121.473885, 10), ] # 上传航点列表 master.mav.mission_count_send( master.target_system, master.target_component, len(waypoints) ) for i, (lat, lon, alt) in enumerate(waypoints): master.mav.mission_item_int_send( master.target_system, master.target_component, i, # 航点序号 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), mavutil.mavlink.MAVLINK_TYPE_MISSION_ITEM_INT ) ack master.recv_match(typeMISSION_ACK, blockingTrue, timeout5) print(航点上传ACK:, ack.type) # 切换AUTO飞控开始执行航线 master.set_mode_apm(AUTO)几个容易出现误会的地方航点上传用的是MISSION_ITEM_INT而不是老的MISSION_ITEM。新消息类型里经纬度都放大到1e7的整数避免浮点数精度问题。MAVLink2里推荐优先用INT版本。MAV_FRAME_GLOBAL_RELATIVE_ALT_INT表示航点高度是相对起飞点的海拔高度不是相对海平面。这样换场地飞也不用改航点高度。航点参数里第4个参数accept_radius我设为0表示让飞控使用默认的接收半径。如果你希望飞控精确飞到某个点再切向下一个点可以给它一个较小的数值比如2米。3.3 为什么用AUTO而不是GUIDED逐个发Pt来飞GUIDED模式下逐个地发SET_POSITION_TARGET_GLOBAL_INT也能让飞机巡航但这种方式有个致命的隐患如果树莓派程序崩了飞控会失去目标位置输入进入悬停或失控保护状态。AUTO模式下航线数据是存储在飞控内存里的树莓派只需要发送一次航点之后就算树莓派死机飞控照样能把整个航线飞完。这就是我强调的“飞控有最终决定权”的体现。AUTO模式让任务执行逻辑完全落在飞控内部树莓派只在初始阶段参与航点上传不参与实时导航可靠性大幅提升。精准降落阶段才需要树莓派实时介入因为那个阶段视觉信息必须是时实的。4. 精准降落从ArUco识别到Precision Landing状态机4.1 视觉识别的核心数学精准降落的第一步是让树莓派知道自己离目标降落点多远、偏了多少角度。我采用的方案是市面上最成熟、成本最低的ArUco码识别在一张A4纸上打印一个20cm边长的ArUco码放在降落点正中央树莓派用朝下安装的摄像头俯视识别它。这里面牵扯到一个关键转换像素坐标到飞控能读懂的角度坐标。假设相机内参已经标定过焦距fx、fy光心cx、cyArUco检测后得到目标中心在图像里的像素坐标(u, v)那么目标相对相机光轴的水平和垂直角度是angle_x atan((u - cx) / fx) angle_y atan((v - cy) / fy)另外用estimatePoseSingleMarkers可以直接从ArUco码的真实尺寸20cm解算出目标在相机坐标系下的三维坐标(tvec)其中tvec[0][0][2]就是垂直方向的距离这个距离值直接送给飞控作为降落过程中的高度参考。这里必须说明一个非常容易翻车的细节MAVLink标准里LANDING_TARGET消息的angle_x和angle_y单位是弧度。ArduPilot内部也是按弧度处理。我自己第一版代码里写过角度制结果飞机在降落时不停震荡数据分析才发现是单位错了。所有角度计算最终送进消息之前务必统一成弧度。4.2 树莓派侧发送LANDING_TARGET检测到ArUco码后树莓派要做的事情就是持续发送LANDING_TARGET消息给飞控。消息发送频率建议10Hz到15Hz太低飞控的卡尔曼滤波器跟不上目标变化太高树莓派CPU占用率上涨视觉帧率反而下降。参考代码import cv2 import numpy as np from pymavlink import mavutil import math import time master mavutil.mavlink_connection(/dev/ttyAMA0, baud57600) master.wait_heartbeat() MARKER_SIZE 0.20 # ArUco码边长单位米 CAMERA_MATRIX np.array([[fx, 0, cx], [0, fy, cy]], dtypefloat) DIST_COEFFS np.zeros((5, 1)) aruco_dict cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) params cv2.aruco.DetectorParameters() detector cv2.aruco.ArucoDetector(aruco_dict, params) cap cv2.VideoCapture(0) # 降低分辨率提高处理速度 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) def send_landing_target(angle_x, angle_y, distance_m): master.mav.landing_target_send( int(time.time() * 1e6), # time_usec 0, # target_num mavutil.mavlink.MAV_FRAME_BODY_FRD, angle_x, # 弧度 angle_y, # 弧度 distance_m, # 米 0.20, 0.20 # target尺寸x/y单位米 ) while True: ret, frame cap.read() if not ret: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) corners, ids, _ detector.detectMarkers(gray) if ids is not None: # 取出第一个ArUco码 rvecs, tvecs, _ cv2.aruco.estimatePoseSingleMarkers( corners, MARKER_SIZE, CAMERA_MATRIX, DIST_COEFFS) tvec tvecs[0][0] dx, dy, dz tvec angle_x math.atan2(dx, dz) angle_y math.atan2(dy, dz) distance dz send_landing_target(angle_x, angle_y, distance) time.sleep(0.1)这里用atan2(dx, dz)而不是直接用tvec里的dx、dy是因为ArUco的坐标系定义里当目标在相机正前方时dx和dy接近0但轻微偏移时我们需要测出真实角度直接取tvec的x、y值在目标偏移较大时会有误差。摄像头安装方式直接影响识别距离。朝下安装时摄像头距离地面越远ArUco码在画面里越小识别距离越远但精度越低。我测试下来4米高度以内用640x480分辨率识别20cm的ArUco码没有任何压力超过5米最好换更大尺寸的码。4.3 飞控侧Precision Landing的运作机制树莓派把视觉角度和距离发过去之后飞控这边做了一系列复杂的处理。ArduPilot的Precision Landing功能PLND本质上是一个卡尔曼滤波器状态机它把外界输入的视觉轨迹和飞控自身的IMU数据进行融合估算出目标降落点相对飞机的真实位置然后在LAND模式里驱动飞机往目标点飞行。整个降落的触发逻辑是这样的飞机执行到航线最后一个航点后飞控自动进入LAND模式。如果PLND_ENABLE1且树莓派持续发送LANDING_TARGET飞控进入Precision Landing状态。在较高高度飞控主要依靠视觉数据修正水平位置让飞机挪到降落点正上方。下降到一定高度后飞控逐步降低油门同时持续用视觉数据修正位置直到触地。如果视觉信号中途中断超过一定时间由PLND_TIMEOUT参数控制飞控自动退出精准降落逻辑切换到普通GPS降落保证飞机不会因为视觉丢失而在空中乱飘。PLND_EST_TYPE0代表卡尔曼滤波器模式也是我实测后推荐的。它会把单帧的识别抖动平滑掉稳定性远超直接用原始数据。PLND_EST_TYPE1是直接用裸数据响应快但噪声大不建议新手使用。降落速度也有讲究。我会把LAND_SPEED设为60到80单位cm/s太慢容易被风吹偏太快则留给飞控修正位置的时间窗口太短。理想状态是飞机在1.5米高度左右已经稳定在目标点正上方然后以平稳速度垂直落下最终精度可以控制在15到30厘米内。4.4 视觉识别不出来怎么办多高度冗余设计实际操作中飞机下降过程中摄像头视角会发生变化ArUco码可能在某个高度以下跑出画面边缘。我的做法是三级冗余高空3米以上用ArUco码粗定位只要识别到码就把飞机往码的投影点上方挪。中低空1到3米ArUco码已经在画面里占据较大面积用tvec测距和角度持续发送。低空1米以下飞机已经基本在目标点上空视觉数据主要防止侧风把飞机吹离目标此时如果识别中断飞控来不及大幅修正普通降落也不会偏太远。如果你要兼顾低空识别稳定性建议把摄像头做成可调节俯仰角或者直接选广角镜头模组。OV5647这颗树莓派官方CSI摄像头模组默认60度左右视场角实测在2.5米高度识别20cm的ArUco码没问题但如果场地风大、飞机晃动厉害最好换100度以上的广角镜头。5. 实测中的关键经验数据、调参、安全5.1 串口和供电的坑比想象中多这个项目前后测试了大概两个月硬件上踩过的坑比软件多得多。先说串口干扰问题。最初我把树莓派和飞控用排线连接排线从机身中部走线绕到机臂位置结果电机一推油门MAVLink链路就开始丢包heartbeat频繁超时。后来发现是电机三相线在高功率输出时产生了强电磁干扰排线离机臂太近被耦合进了噪声。解决方法是串口线全部换成带屏蔽层的杜邦线或者双绞线走线避开电机线和电调线尽量贴着机身中线走长度控制在15厘米以内。如果干扰依旧存在可以在树莓派的TX、RX线上加一个磁环效果立竿见影。供电问题同样隐蔽。我曾图省事把树莓派直接接到飞控的5V输出脚上结果每次解锁电机树莓派就重启一次。原因是解锁瞬间电调会从电池抽取大量电流飞控5V稳压模块输入电压被拉低直接触发了树莓派的欠压保护。后来改成独立BEC模块给树莓派供电这个问题彻底消失。切记树莓派和飞控的供电要隔离共地只是为了信号参考不是让你共用电源。5.2 联调顺序先仿真后实飞别拿螺旋桨当测试工具整个系统的联调我推荐按这个顺序来SITL仿真 - 桌面串口测试 - 手持抓机测试 - 系留测试 - 空旷场地实飞。SITL仿真阶段ArduPilot自带的Software In The Loop直接在电脑上模拟飞控树莓派上跑的Python脚本除了串口设备路径不同其他逻辑完全一样。我建议先在这里把航点上传、模式切换、LANDING_TARGET消息的发送逻辑全部调通再上真机。桌面串口测试时飞机不需要装桨把树莓派和Pixhawk连接好在树莓派上跑巡航脚本和视觉识别脚本用Mission Planner看飞控是否收到航点和LANDING_TARGET消息。这一步能验证串口电平、波特率、参数配置是否正确。手持抓机测试最有意思人拿着飞机在地上走让树莓派识别地面上的ArUco码观察Mission Planner地面站画面里飞机的目标位置是否随手中移动而变化。这能确认视觉算法和MAVLink输出链路是通的而且没有GPS参与飞机不会乱动非常安全。系留测试时把飞机用绳子系在地面锚点上测试完整的自起降流程。我建议系留高度控制在2米以内即使失控绳子也能兜住飞机。实飞场地选空旷、无人的地方周围尽量没有金属围栏和高压线。第一次实飞前我会在飞控里设置好地理围栏和失控保护参数设置值作用FENCE_ENABLE1开启围栏FENCE_TYPE3高度和水平围栏都启用FENCE_RADIUS120半径120米FENCE_ALT30最高30米RTL_ALT20失控时返航到20米FS_THR_ENABLE1油门失控保护以上参数只是一个安全起点具体数值要根据场地规模和当地法规调整。实飞那天遥控器务必充满电救机时切Stabilize或者Loiter都比RTL来得直接。5.3 数据分析是调参的终极武器每次实飞或系留测试结束后把飞控里的DataFlash日志导出来在Mission Planner里打开重点看四个关键数据PLND状态、目标角度、目标距离、修正位移。PLND状态下数值从0变到1以上就说明飞控确实在用视觉数据进行修正目标角度和目标距离能直接对比树莓派端发送的值确认数据链路真没丢包修正位移则是最终结果检验看飞机是否真的往目标方向挪动了。有次测试降落总是偏左半米左右排查半天发现是树莓派串口数据里混进了半字节垃圾数据导致angle_x偶发跳变。飞控的卡尔曼滤波器虽然能抑制单帧噪声但连续几帧跳变还是会让位置估计偏移。这个问题靠的是对树莓派端串口数据做校验和过滤确保进入飞控的数据一定是有效值。日志分析时还有一个很实用的小技巧同时看飞控里的GPS定位数据和PLND位置估计数据两者偏差超过1米时就要注意是不是GPS的磁偏角或安装方向影响到了飞控的坐标系对齐。视觉引导的坐标系是以飞机机头方向为基准的如果GPS航向和磁航向存在偏差飞控在融合这两路数据时就会打架。5.4 安全兜底永远是第一优先级精准降落是个锦上添花的功能安全兜底才是飞机能反复起降的根本。我的飞控配置里RTL返航永远比精准降落优先。就算树莓派完全死机只要飞控还在切RTL就能让飞机回到起飞点附近。如果没有开FENCE和FS_THR_ENABLE树莓派程序一旦异常飞机可能一直往一个方向飞出去那种场景我完全不想再经历第二次。实操中我用的兜底方式是在树莓派脚本里加一个看门狗机制。每次向飞控发送LANDING_TARGET时同时记录当前时间戳。如果连续3秒没有成功发送脚本自动退出并释放GPIO同时通过飞控的MAVLink消息触发RTL。这样即使视觉识别卡死飞机也不会在原地傻等而是自动执行预设的返航逻辑。精准降落进入最后阶段时尤其是高度低于1米后我会在遥控器上一直虚握油门摇杆一旦察觉飞机有任何异常的横向漂移马上切回Loiter或者手动模式接管。视觉系统再准也不如人眼的瞬间判断可靠。这套树莓派、MAVLink和Pixhawk 2.4.8的组合完成之后我最大的感受是飞控把底层飞行控制做到了极致树莓派把上层智能做到了灵活两者结合才是开源无人机该有的灵魂。哪怕你现在预算有限用一块2.4.8飞控加一台树莓派3B也能复现这套巡航加精准降落流程树莓派3B跑OpenCV的帧率低一些但只要把分辨率降到480p、牺牲一点识别距离效果依然可用。等你跑通整个链路之后自然会知道下一步该把算力升级到树莓派5还是Jetson视觉识别是该换YOLOv5还是保持ArUco——那个阶段的选择已经是另一个故事了。
网站建设高端定制企业官网