1、本项目基本遵循苍穹四轴DIY给出的教程,但是由于各个库的版本更新,在环境配置过程中遇到了很多问题,原有教程难以解决,所以整理出一份资料。
2、由于环境配置过于复杂且繁琐,我只在Dronekit环境搭建还有SITL仿真环境搭建这两步进行了全过程的记录,只要对ubuntu系统足够熟悉,配置起来很快。不熟悉ubuntu也没关系,ubuntu使用上的关键部分我都贴出了相应教程。
3、dwc老师原本建议用ROS控制无人机,但是我为了光速毕业主打一个方便快捷且省事,最后还是采用了Dronekit的方案。
4、树莓派上的ubuntu系统用户名我设置为HDUASL,密码hduasl(小写)。
5、在这个项目之前我从未接触过无人机,仅为自身经验总结,如有错误,欢迎指正。(邮箱:Vc_Ri@outlook.com)
代码:
https://github.com/VcRi/F450-Dronekit-Aruco.git
演示视频:
(F450+Dronekit)SITL + MavProxy 仿真演示
F450+树莓派Ubuntu24.04+Dronekit 悬停、移动测试
综述:
1、无人机组装
2、无人机调试(需要下载地面站软件MissionPlanner)
3、树莓派5搭载Ubuntu24.04系统,使用GPIO对机械爪的PWM舵机进行控制以实现开合;使用Dronekit库对无人机进行飞行控制
4、仿真使用SITL+MAVProxy(仿真环境在笔记本虚拟机里的Ubuntu18.04系统上)
5、两个Ubuntu系统下载的都是带图形界面的版本,平时使用让树莓派接上显示器及键盘鼠标,用vscode写代码。户外飞行时用笔记本SSH远程连接树莓派(两台都连接手机热点,或者连接同一wifi,总之在同一网络下),通过命令行的方式运行代码。也可以通过VNC连接让笔记本获取到树莓派的桌面。
硬件准备:
1、F450无人机 (2000r左右,我使用的这台是pixhawk2.4.8飞控 + AT9s Pro遥控器)
苍穹四轴DIY 处购买,出厂已经调试好。我们有三台无人机是在这家购买的,提供订单链接可以被拉到售后群
2、树莓派5 (4G) (500r左右)
- 树莓派5是23年新发售的,教程不多。我买它是因为买的时候什么也不知道,随便选了个最新的,不过使用上大差不差。
- 买的是带csi摄像头版本的。raspberry pi os自带了对这个csi摄像头支持的库,但是ubuntu系统下我始终没搞定这个库,且这个摄像头似乎不具备完成这个项目的能力
- 还额外购买了32G的SD存储卡装在树莓派上
- 这一套使用下来偶有死机或者卡顿现象,不知道是内存小了还是树莓派本身性能的问题。
3、机械爪 (100r左右)
我购买的是下图这一款:
安装在无人机上的方式也很潦草,直接用轧带绑在无人机下面的。
机械爪资料-百度网盘分享 店家提供的机械爪资料我没细看,实际上就三根线,接到树莓派上就完事了,如何接线及使用详见文章后面。
4、摄像头 (400r左右)
选择全局快门(而非卷帘快门),我描述了一下我要在离地两三米高度对地面上A4大小左右二维码进行识别的需求后,店家推荐我使用50-60度的摄像头:
该摄像头是USB口,在win上和ubuntu上即插即用,无需安装驱动。Ubuntu命令行里输入以下两个命令可以调用该摄像头看看效果:
#ffmpeg是一个多媒体处理库,也是sudo apt install ffmpeg一行命令就能下载好 #录制一段10s的视频 ffmpeg -f v4l2 -input_format mjpeg -framerate 30 -video_size 1920x1200 -i /dev/video0 -t 10 -c:v copy output.mp4 #拍摄一张照片 ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1200 -i /dev/video0 -frames:v 1 -q:v 2 image.jpg
但是这款摄像头的拍摄素质有点感人,我只能说完成该二维码识别任务还是可以成功的
店家提供的调试软件:摄像头资料-百度网盘分享
F450很耐造,器材坏了都可以买,单价1000以内的发票学校可以直接报销,总之买之前跟dwc老师讲一声。
无人机桨叶等都是耗材(我的遥控技术实在太烂了),桨叶上有标型号,买的时候注意正桨反桨,可以各买一两副备用。
M8n GPS模块的支架容易摔断,如果断的地方靠底部,把螺丝拧开,支架重新插进去再拧上螺丝还可以继续用,只是会短一截。淘宝上也有单支架可以买,型号基本上是通用的。
苍穹四轴DIY教程整理:
点击可直接跳转:
图文并茂详细教程之–用pixhawk飞控组装一台F450四轴无人机(上)
图文并茂详细教程之-- 用pixhawk飞控组装一台F450无人机(中)
图文并茂详细教程之-- 用pixhawk飞控组装一台F450无人机(下)
Pixhawk无人机扩展教程(1)—树莓派与pixhawk连接
Pixhawk无人机扩展教程(2)—树莓派安装ubuntu-mate系统及必要设置
Pixhawk无人机扩展教程(3)—树莓派安装Dronekit及读取飞控数据
Pixhawk无人机扩展教程(4)—使用Dronekit编写一个控制程序
Pixhawk无人机扩展教程(5)—SITL仿真模拟飞行:开发环境搭建
Pixhawk无人机扩展教程(6)—启动SITL+MAVProxy
Pixhawk无人机扩展教程(7)—SITL+MP/QGC运行
本文是对这份教程的补充和扩展,飞行控制部分也主要参考教程里的代码,只是额外加上了机械爪控制和二维码检测部分。
其中扩展教程(7)和扩展教程(9)我完全没有用到。
温馨提示:
和安全性有关的问题,一定注意注意再注意,不要对自己太自信。
这一小段的内容即使暂时不理解也没有关系,放在前面是因为它非常重要。
祝愿大家都不要炸机。
1、每次安装好桨叶后,都仔细核对一遍桨叶安装是否正确
初次接触无人机的我:不是,这么简单谁能装反
某次起飞侧翻了三次才发现是桨叶装错后的我:我是**
2、锂电池是一个比较危险的东西。电池不要过充及过放,充电时保证有人在场。我的是3s锂电池,即有三片电芯,只要其中有一片坏了就不要再用了,电压会有问题;电池鼓包也不要再用了。每次飞行完毕后,无人机的动力锂电池和遥控器里的电池都及时取下,放进电池防爆袋里,废电池更是需要妥善处理
有次遥控器里的电池忘记取下,隔了一晚上发现电池鼓包,没法用了。
3、针对本项目,需要熟悉定高模式、悬停模式和降落模式,三种模式都要手飞过,并且学会判断GPS信号。
上锁、解锁、BB响使用演示:
YH远航科技-F450无人机PIXHAWK飞控起飞教程(定点+自稳操作)
这个视频里的遥控器设置和我们的不一样。关注使用过程就行。
苍穹四轴DIY提供的飞行教程:
定高模式: 定高模式不需要GPS。飞上去后,高度虽然能固定,但是水平方向上会有较大偏移,要不断调整位置
悬停模式: 悬停模式需要GPS,飞上去后会定点悬停住。如果GPS信号不足,遥控器是解锁不了的。
降落模式: 降落模式下,无人机会自动降落。
在代码里面使用的都是GUIDED模式,降落时才会切换RTL返航或者LAND降落模式实现降落。
RTL模式下,会先回到航点上空固定高度(在missionplanner里可以查到RTL_ALT这个参数),然后再执行降落。这是教程里的代码提供的降落模式。
Land模式下,会让无人机直接降落。这是在我的项目采用的降落模式。
对于GUIDED模式,想让它飞上去定的住不漂移,也是需要足够的GPS信号的。
我的个人使用经验是,即使GPS信号不充足,在GUIDED模式下也会解锁。
那次的使用体验很恐怖, 我没有检查GPS灯,也没有在悬停模式下试验能否用遥控器解锁,结果无人机起飞后直接大漂移,最后撞到东西摔了。
在那次之后,我还遇到过代码运行中、等待无人机起飞时,LED灯忽然从绿变黄绿的情况。如果没及时暂停代码,恐怕就要梅开二度了。
当然如果你的场地足够空旷,就不用像我这么小心翼翼。我设置的飞行高度一般两三米,飞行时间也很短暂。
检查GPS信号我认为有两种方式:
1、直接连MissionPlanner,可以查到GPS信号是否充足。(实际上直接在遥控器里也能查到GPS搜星数量)
2、通过飞控上的LED灯判断GPS信号
我的数传用不了,每次地面站连飞控都要接线,很麻烦,所以我都用第二种方法,扫一眼就知道能不能起飞了。
如何识别LED灯?
LED灯在飞控上,这里仅说明明如何辨别pix的灯
我这里找到的是pix4的官方教程,与我使用的pixhawk2.4.8还是有点区别的
参考官方教程:LED灯含义 · PX4 User Guide
解锁: 在手飞情况下,遥控器“油门最低 + 方向舵右”这一步就是解锁操作,解锁完后,电机会转动,此时推油门无人机才会起飞。呈现在代码里,就是vehicle.armed = True
这一句。(注意,拨动安全开关那一步只是解锁的前置条件,但还不是解锁)
GPS锁定: 这里锁定的意思是GPS模块会输出有效的全球定位数据,所以GPS锁定才是我们需要的。
综上,
蓝色,意味着GPS未锁定,即GPS信号不足。解锁操作会使灯从蓝色闪烁状态变成蓝色常亮状态。
绿色,意味着GPS锁定,即GPS信号充足。解锁操作会使灯从绿色闪烁状态变成绿色常亮状态。
但是!实际上我最常遇到的是另外一种快速闪烁的黄绿色灯(毕竟这只是Pixhawk4的教程)
这个灯的意思我猜测是介于蓝色和绿色之间,代表有GPS信号,但是仍然不足,譬如悬停模式下是无法解锁的
等待一段时间后,当GPS信号充足时,黄绿色灯才会转变成为绿色。M8N GPS性能不是很好,无人机附近不要有遮挡物,包括人也离得远一点。
黄绿色灯:
绿色灯:
代码控制飞行时,请保证LED灯一定是绿色的。
4、据无人机售后描述,用代码控制飞行时,使用遥控器切换飞行模式可以剥夺控制权。例如飞行中使用悬停模式,遥控器来回拨动一下模式开关就可以得到控制权了。但是在切换时油门需要保持中位,低位会掉下来。
5、如果真的炸机,可以看这篇教程查一下飞行日志检查原因:APM/Pixhawk飞行日志分析入门(苍穹四轴)
一、组装
组装见教程:图文并茂详细教程之–用pixhawk飞控组装一台F450四轴无人机(上)
这一步我没有经验可以分享,我拿到的无人机是已经组装好的,但是想熟悉一下所有结构的话可以参考这个视频:
二、调试
图文并茂详细教程之-- 用pixhawk飞控组装一台F450无人机(中)
图文并茂详细教程之-- 用pixhawk飞控组装一台F450无人机(下)
调试过程完全遵循教程。 即使是已经安装使用过的无人机,在长期不用后一些仪器也需要校准。
一些注意事项如下:
1、推荐一个视频PIXHAWK2.4.8飞控调试教程,非常直观地展示了调试的步骤和一些细节。视频仅供参考,调试过程中所有的设置仍然以卖家的教程为准。
2、调试使用的软件是Mission Planner地面站,官网下载即可,教程里用的是版本1.3.62(archive文件夹里有历史版本)。注意下载.msi版本,前者安装完后能帮你装上驱动,后续用数据线连接时电脑可以成功识别到飞控
3、飞控和笔记本之间连接使用的是micro线,如果插上线后识别不到正确的端口,很有可能是线的问题。短的micro线极有可能只提供充电功能,无法进行数据传输;三合一充电线也不够稳定。这两种线我都试过,用不了。
4、第一次安装的无人机跟着教程一步步设置即可。已经安装使用过的无人机,跳过更新固件这一步(除非有特殊需求)
5、指南针校准这一步,只要三百六十度无死角来回晃让进度条走满就可以,不需要有严格的旋转方向
三、电池使用
如果实验室的电池或者充电器换新设备了,使用方式可能会略有不同。仅以我拿到手的为例:
共有两块锂电池,小的是控电,装在遥控器里;大的是动力锂电池,给无人机供电
电池接口:
左边白色的是平衡头,右边黄色的是XT60头。
不同充电器需要用到的接口可能不同,使用B3平衡充的话,将平衡头接到充电器上进行充电即可。放一个电池充电的教程:[航模入门]B3、B6充电器使用方法 航模锂电池充电教程(锂电池易燃,不耐摔,使用务必注意安全,充电时需有人在场)
遥控器电池安装方式:
打开遥控器后侧电池槽
将遥控器电池供电头(红色那个头,有红黑两条电线分别对应正极和负极)插在遥控器的电池槽最左侧的二口排插上,保证正极(红色电线)朝上。
四、起飞
在温馨提示部分,已经列出了手飞可以参考的教程。顺便再把温馨提示看一遍。
五、树莓派与Pixhawk连接
见教程:Pixhawk无人机扩展教程(1)—树莓派与pixhawk连接
六、树莓派镜像烧录器烧录Ubuntu24.04系统
对应教程:Pixhawk无人机扩展教程(2)—树莓派安装ubuntu-mate系统及必要设置
在系统选择上,我并没有遵循扩展教程(2)的配置。树莓派5是2023年发售的,买的时候没考虑太多,买完后发现关于树莓派5的使用教程比较少。而且用树莓派镜像烧录器烧录系统的时候,发现能选择的ubuntu系统都比较新(没有教程里的那么古老)。中途系统还被我搞坏了一两次,总之最后配置好环境的Ubuntu系统是24.04 LTS (是一个相当新的Ubuntu版本)
因此,针对这一步骤,我简单描述下我是如何进行配置的:
1、使用树莓派镜像烧录器烧录24.04 LTS系统。
参考教程: 【树莓派5】树莓派5安装Ubuntu 23.10桌面系统
2、安装vim编辑器。
忘了24.04有没有自带vim了,虽然平时用vscode写代码,看文件也是直接用图形界面操作多,但是vim偶尔还是会用到的。
sudo apt-get install vim
vim编辑器的基础用法是需要额外学一下的。
3、对Ubuntu进行换源。
ubuntu下载好时,官方源使用的是国外服务器,下载速度感人,要换成我们国内的源。常见的有清华源、阿里源、淘宝源等等,都可以使用(简单来说换源就是更改Ubuntu系统里某个文件里的某几行)。
换源教程:Ubuntu24.04换源方法(新版源更换方式,包含Arm64) 注意,树莓派是arm架构,要看最后的Arm64-ubuntu配置,即换源时要额外加上ports。
4、我没有用远程桌面的方案。我室内写代码时树莓派外接显示器鼠标键盘,户外测试时直接使用ssh连接树莓派。
- 扩展教程(2)提到要在笔记本上提前下载好ubuntu系统,让两个ubuntu系统互连。我觉得没有必要。笔记本之后确实需要下载ubuntu系统(下载在虚拟机里),但这个系统我只用来配置仿真环境。现在用ssh连接只是保证在没有显示器和鼠标键盘的户外时,仍然可以运行树莓派里的代码,所以在win上使用MobaXterm这个软件远程ssh连接到树莓派就可以了。
- 在笔记本windows系统上下载好MobaXterm(亲测很好用,推荐),树莓派上开启ssh远程服务,需要保证笔记本和树莓派在同一网络下(同一手机热点也可以),之后就可以连接到树莓派用命令行的方式对树莓派进行控制了。
对ubuntu24.04使用的提示:
ubuntu24.04下载好是自带python3的,下面两个命令可以查看python版本
python2 --version
python3 --version
本项目的代码都是在python3环境下运行的,在命令行里运行python代码使用的命令也是 python3 test.py
(不加3会默认使用Python2环境)
苍穹四轴DIY里给出的教程应该是在python2环境下,Dronekit库也是Python2版本的。因此用教程里的方法去配置Dronekit完全不成功。这点在讲到Dronekit配置时再展开。
在环境配置阶段,除了Dronekit库下载和SITL仿真环境搭建这两步我卡了很久之外,其他所有的下载靠下面的命令基本就可以完成:
sudo apt install xxxx(你需要的包的名称)
sudo pip3 install xxxx(你需要的包的名称) #pip默认为Python 2使用,而pip3则专门为Python 3使用
总之环境配置就是看提示信息,提示缺什么就下载什么,大部分问题一两个命令就可以解决。下载不成就问GPT。
有时候回车了一个命令后会提示权限不足,这种情况一般是命令开头忘记加sudo了。sudo是临时提升权限的方式,不想频繁输入sudo也可以查下永久提升权限的方式。
在ubuntu命令行下输入密码,密码是被隐藏掉的,没有*号占位,光标也不会后移。放心大胆的输完然后敲回车,输错了会提示你重来的。
在室内使用时,树莓派是接树莓派官方电源使用的(比较稳定,卡顿较少)
在户外测试时,树莓派和无人机都使用电池供电,无人机上有一根Type-C接口的线就是给树莓派供电用的
七、机械爪安装
机械爪上的舵机是PWM舵机,总共有三根线,一根接5V电源,一根接地(Ground),还有一根是信号线,不同功能的线可以根据颜色区分。
查阅树莓派引脚图可发现,树莓派上总共40个引脚,每个引脚功能不同。
其中我接的是4号(5V电源)、6号(接地)和32号引脚(PWM信号),在图中已圈出。
引脚参考文章:树莓派5-GPIO和40针引脚_树莓派5引脚定义-CSDN博客
里面有提到“GPIO12、GPIO13、GPIO18、GPIO19 上提供硬件 PWM。”
总之我选择了GPIO12(即32号引脚),测试下来可以正常使用。
RPI.GPIO这个库是要下载的,网上查一下ubuntu安装树莓派GPIO库的命令是什么,不赘述。
机械爪测试代码:control.py
import RPi.GPIO as GPIO
import time
# 设置GPIO模式为BCM
GPIO.setmode(GPIO.BCM)
# 定义GPIO引脚
PWM_PIN = 12 # GPIO12(对应32号引脚)
# 设置GPIO引脚为输出模式
GPIO.setup(PWM_PIN, GPIO.OUT)
# 创建PWM实例,频率设置为50Hz(适用于大多数舵机)
pwm = GPIO.PWM(PWM_PIN, 50)
# 启动PWM,初始占空比为0(电机停止)
pwm.start(0)
# 定义函数:打开爪子
def open_claw():
print("打开爪子")
pwm.ChangeDutyCycle(8) # 8%占空比(假设对应爪子打开)
time.sleep(1) # 等待1秒,确保动作完成
# 定义函数:闭合爪子
def close_claw():
print("闭合爪子")
pwm.ChangeDutyCycle(5) # 5%占空比(假设对应爪子闭合)
time.sleep(1) # 等待1秒,确保动作完成
try:
while True:
open_claw() # 打开爪子
time.sleep(5) # 等待5秒
close_claw() # 闭合爪子
time.sleep(5) # 等待5秒
except KeyboardInterrupt:
# 捕获Ctrl+C,停止程序
print("程序停止")
finally:
# 停止PWM并清理GPIO设置
pwm.stop()
GPIO.cleanup()
print("GPIO已清理")
八、树莓派安装Dronekit(修改Dronekit源码)
参考教程:Pixhawk无人机扩展教程(3)—树莓派安装Dronekit及读取飞控数据
之前提到过,我们的树莓派上安装的是Ubuntu24.04,自带python2和python3。
从教程里的Dronekit使用流程里看下来,它使用的应该是python2环境
python2实在太古老,我之后还要控制机械爪,还要使用OpenCV,所以Dronekit也必须在python3环境下使用
按照教程里的方式去下载Dronekit时,遇到了相当多的问题,查阅资料,甚至发现Dronekit 不支持Python3.0。不过都是比较古老的教程,2025年Dronekit还是支持python3的。
在树莓派里我也是误打误撞才装下来Dronekit,不可能为了复刻而重装一遍系统,所以决定在我的笔记本虚拟机里装一个Ubuntu24.04 LTS,复刻一下Dronekit安装流程(但树莓派是arm架构毕竟和笔记本还是有区别,不清楚会不会有影响)
1、查看python版本
python3 --version
输出Python 3.12.3,说明ubuntu24.04确实自带python3
2、下载pip3,下载完后查看
sudo apt install python3 python3-pip python3-dev
pip3 --version #查看pip3版本
3、下载dronekit
sudo pip3 install dronekit
报错:
言下之意就是和环境有冲突,但如果硬要下载,可以在后面加上–break-system-packages
sudo pip3 install dronekit --break-system-packages
下载成功,下一步测试一下这个库能不能在python代码里正常使用
我已经提前下载安装好vscode,ubuntu自带的火狐浏览器默认搜索引擎是谷歌,没开vpn是用不了的,初次使用记得去设置里改一下。如果不想额外下载代码编辑器,直接在命令行里用命令创建python文件然后写代码进去也行。就是真的难用。
from dronekit import connect, VehicleMode, LocationGlobalRelative #代码里放这一句就够了。
运行一下测试代码,发现出现了报错。看起来Dronekit确实下载并且引入成功了,问题出在Dronekit内部。
大致原因是,python3的高版本中,MutableMapping已经被弃用。解决方法要么给python版本降级,要么修改Dronekit源代码。
我们使用修改源代码的方式
其实并不复杂,仔细观察报错可以发现,问题出在文件的第2689行,改掉这一行里的一点内容就可以。
我们使用vim编辑器打开这个文件:
#注意,要给sudo权限,不然改不了这个文件
sudo vim /usr/local/lib/python3.12/dist-packages/dronekit/__init__.py
打开之后会发现vim编辑器没显示行号的,先按esc键,然后输入冒号,再输入set number,回车后会显示行号。
:set number
既然讲到这就多写一句,vim编辑器有保存退出和不保存退出,不小心改掉了不该改的记得不保存退出:
:wq #保存退出 :wq #强制保存退出 :q #不保存退出 :wq! #强制不保存退出
我们直接跳转2689行。同样是先按esc,再输入:
:2689
键盘上敲一个a,进入插入模式
把2689行的 class Parameters(collections.MutableMapping, HasObservers):
这一句替换成:
from collections.abc import MutableMapping
class Parameters(MutableMapping, HasObservers):
然后:wq
保存退出。
此时再运行测试代码,成功
接下来就是按照教程的步骤赋予端口权限。
需要提到的是,教程里面永久改写USB口读写权限我一直没有成功,因此每次都用下面这个命令临时赋予权限:
sudo chmod 666 /dev/ttyUSB0
如果环境配置都成功了话,给无人机供上电,在树莓派上运行connect.py代码看下是否能正常输出信息。
connect.py代码:
from dronekit import connect
# Connect to the Vehicle (in this case a UDP endpoint)
#vehicle = connect('/dev/ttyUSB0', wait_ready=True, baud=921600)
vehicle = connect('/dev/ttyUSB0', wait_ready=True, baud=57600) #降低波特率后成功
# vehicle is an instance of the Vehicle class
print("Autopilot Firmware version: %s" % vehicle.version)
print("Autopilot capabilities (supports ftp): %s" % vehicle.capabilities.ftp)
print("Global Location: %s" % vehicle.location.global_frame)
print("Global Location (relative altitude): %s" % vehicle.location.global_relative_frame)
print("Local Location: %s" % vehicle.location.local_frame)
print("Attitude: %s" % vehicle.attitude)
print("Velocity: %s" % vehicle.velocity)
print("GPS: %s" % vehicle.gps_0)
print("Groundspeed: %s" % vehicle.groundspeed)
print("Airspeed: %s" % vehicle.airspeed)
print("Gimbal status: %s" % vehicle.gimbal)
print("Battery: %s" % vehicle.battery)
print("EKF OK?: %s" % vehicle.ekf_ok)
print("Last Heartbeat: %s" % vehicle.last_heartbeat)
print("Rangefinder: %s" % vehicle.rangefinder)
print("Rangefinder distance: %s" % vehicle.rangefinder.distance)
print("Rangefinder voltage: %s" % vehicle.rangefinder.voltage)
print("Heading: %s" % vehicle.heading)
print("Is Armable?: %s" % vehicle.is_armable)
print("System status: %s" % vehicle.system_status.state)
print("Mode: %s" % vehicle.mode.name)
print("Armed: %s" % vehicle.armed)
对于波特率这一点我很迷惑。常见的波特率有1200、2400、4800、9600、19200、38400、57600和115200,我改了几次最后发现我的无人机波特率设置为57600时可以正常连接。至于为什么改成57600就可以不清楚。
注意,无人机记得接上电源。未接电源,仅连接地面站的情况下运行代码,我这里出现no heartbeat的报错
WARNING:dronekit:Link timeout, no heartbeat in last 5 seconds
ERROR:dronekit.mavlink:Exception in MAVLink input loop
九、使用Dronekit编写代码
参考教程:Pixhawk无人机扩展教程(4)—使用Dronekit编写一个控制程序
到这一步就可以编写自己需要的功能控制无人机移动了。
再放一下我的代码的实地测试视频:F450+树莓派Ubuntu24.04+Dronekit 悬停、移动测试
代码不能直接上无人机运行! 只要是控制飞行的代码都要提前做好仿真,下一部分才会讲解到仿真如何做。
hover.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import time
from dronekit import connect, VehicleMode, LocationGlobalRelative
import RPi.GPIO as GPIO
# 设置GPIO模式为BCM
GPIO.setmode(GPIO.BCM)
# 定义GPIO引脚
PWM_PIN = 12 # GPIO12(32号引脚)
# 设置GPIO引脚为输出模式
GPIO.setup(PWM_PIN, GPIO.OUT)
# 创建PWM实例,频率设置为50Hz(适用于大多数舵机)
pwm = GPIO.PWM(PWM_PIN, 50)
# 启动PWM,初始占空比为0(电机停止)
pwm.start(0)
# 定义函数:打开爪子
def open_claw():
print("爪子已打开")
pwm.ChangeDutyCycle(8) # 8%占空比(打开)
time.sleep(1) # 等待1秒s
# 定义函数:闭合爪子
def close_claw():
print("爪子已闭合")
pwm.ChangeDutyCycle(5) # 5%占空比(闭合)
time.sleep(1) # 等待1s
# 在起飞之前,打开爪子5秒,然后合上,等待3秒
print("起飞前操作:抓取物品")
open_claw() # 打开爪子
print("爪子已打开,请在5s内放上物品")
time.sleep(5) # 等待5秒
close_claw() # 闭合爪子
print("闭合后等待3秒")
time.sleep(3) # 等待3s
# 当前连接的 Pixhawk 飞控的端口
connection_string = '/dev/ttyUSB0' # 现在使用的是 USB 转 TTL 接口,连接 Pixhawk 飞控
print('连接到设备: %s' % connection_string)
vehicle = connect('/dev/ttyUSB0', wait_ready=True, baud=57600) #降低波特率后成功
# 定义 arm_and_takeoff 函数,使无人机解锁并起飞到目标高度
# 参数 aTargetAltitude 即为目标高度,单位为米
def arm_and_takeoff(aTargetAltitude):
# 起飞前检查
print("进行起飞前检查")
# vehicle.is_armable 会检查飞控是否启动完成
while not vehicle.is_armable:
print(" 等待设备初始化...")
time.sleep(1)
# 解锁无人机(电机将开始旋转)
print("解锁电机")
# 将无人机的飞行模式切换成 "GUIDED"(一般在 GUIDED 模式下控制无人机)
vehicle.mode = VehicleMode("GUIDED")
# 通过设置 vehicle.armed 状态变量为 True,解锁无人机
vehicle.armed = True
# 在无人机起飞之前,确认电机已经解锁
while not vehicle.armed:
print(" 等待解锁...")
time.sleep(1)
# 发送起飞指令
print("起飞!")
# simple_takeoff 将发送指令,使无人机起飞并上升到目标高度
vehicle.simple_takeoff(aTargetAltitude)
# 在无人机上升到目标高度之前,阻塞程序
while True:
print(" 当前高度: ", vehicle.location.global_relative_frame.alt)
# 当高度上升到目标高度的 0.95 倍时,即认为达到了目标高度,退出循环
if vehicle.location.global_relative_frame.alt >= aTargetAltitude * 0.95:
print("达到目标高度")
break
time.sleep(1)
# 调用上面声明的 arm_and_takeoff 函数,目标高度 3 米
arm_and_takeoff(2)
# 悬停 5 秒,并不断输出“正在悬停”和一些信息
# 这里是为了在悬停的时候输出信息才这么写。如果不需要输出信息,直接time.sleep(5)就可以
print("开始悬停")
start_time = time.time()
while time.time() - start_time < 5:
# 打印高度和完整GPS信息
print(" 正在悬停,当前高度: ", vehicle.location.global_relative_frame.alt)
print("当前模式:",vehicle.mode.name)
print("当前GPS:", vehicle.location.global_frame)
time.sleep(1)
# 发送 "降落" 指令
print("降落")
#这里根据需要选择降落的方式
# RTL模式,无人机会自动返回home点的正上方(RTL高度可以在地面站里更改),之后自动降落。但我不需要无人机返回航点,所以改用Land模式降落。
# vehicle.mode = VehicleMode("RTL")
vehicle.mode = VehicleMode("LAND") # 直接降落
# 在降落过程中,不断输出当前高度
print("开始降落")
while vehicle.armed: # 当无人机未降落完成时(电机仍在旋转)
print(" 正在降落,当前高度: ", vehicle.location.global_relative_frame.alt)
print("当前模式:",vehicle.mode.name)
print("当前GPS:", vehicle.location.global_frame)
time.sleep(1)
# 降落完成后,退出之前,打开爪子
print("降落完成,打开爪子")
open_claw() # 打开爪子
time.sleep(1) # 等待1秒,确保动作完成
# 退出之前,清除 vehicle 对象
print("关闭设备连接")
vehicle.close()
# 停止PWM并清理GPIO设置
pwm.stop()
GPIO.cleanup()
print("GPIO已清理")
代码中凡是涉及到GPIO、PWM、Claw相关的代码,都是与机械爪有关的,可以直接删掉。不影响无人机控制部分的使用。
move.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import time
from dronekit import connect, VehicleMode, LocationGlobalRelative
import RPi.GPIO as GPIO
from pymavlink import mavutil
# 设置GPIO模式为BCM
GPIO.setmode(GPIO.BCM)
# 定义GPIO引脚
PWM_PIN = 12 # GPIO12(32号引脚)
# 设置GPIO引脚为输出模式
GPIO.setup(PWM_PIN, GPIO.OUT)
# 创建PWM实例,频率设置为50Hz(适用于大多数舵机)
pwm = GPIO.PWM(PWM_PIN, 50)
# 启动PWM,初始占空比为0(电机停止)
pwm.start(0)
# 定义函数:打开爪子
def open_claw():
print("爪子已打开,请在5s内放上物品")
pwm.ChangeDutyCycle(8) # 8%占空比(打开)
time.sleep(1) # 等待1秒,确保动作完成
# 定义函数:闭合爪子
def close_claw():
print("爪子已闭合")
pwm.ChangeDutyCycle(5) # 5%占空比(闭合)
time.sleep(1) # 等待1秒,确保动作完成
# 在起飞之前,打开爪子5秒,然后合上爪子,等待3秒
print("起飞前操作:抓取物品")
open_claw() # 打开爪子
time.sleep(5) # 等待5秒
close_claw() # 闭合爪子
print("等待3秒,开始连接设备")
time.sleep(3) # 等待3秒
# 当前连接的 Pixhawk 飞控的端口
connection_string = '/dev/ttyUSB0' # 现在使用的是 USB 转 TTL 接口,连接 Pixhawk 飞控
print('连接到设备: %s' % connection_string)
vehicle = connect('/dev/ttyUSB0', wait_ready=True, baud=57600) #降低波特率后成功
# 定义 arm_and_takeoff 函数,使无人机解锁并起飞到目标高度
# 参数 aTargetAltitude 即为目标高度,单位为米
def arm_and_takeoff(aTargetAltitude):
# 起飞前检查
print("进行起飞前检查")
# vehicle.is_armable 会检查飞控是否启动完成
while not vehicle.is_armable:
print(" 等待设备初始化...")
time.sleep(1)
# 解锁无人机(电机将开始旋转)
print("解锁电机")
# 将无人机的飞行模式切换成 "GUIDED"(一般在 GUIDED 模式下控制无人机)
vehicle.mode = VehicleMode("GUIDED")
# 通过设置 vehicle.armed 状态变量为 True,解锁无人机
vehicle.armed = True
# 在无人机起飞之前,确认电机已经解锁
while not vehicle.armed:
print(" 等待解锁...")
time.sleep(1)
# 发送起飞指令
print("起飞!")
# simple_takeoff 将发送指令,使无人机起飞并上升到目标高度
vehicle.simple_takeoff(aTargetAltitude)
# 在无人机上升到目标高度之前,阻塞程序
while True:
print(" 当前高度: ", vehicle.location.global_relative_frame.alt)
# 当高度上升到目标高度的 0.95 倍时,即认为达到了目标高度,退出循环
if vehicle.location.global_relative_frame.alt >= aTargetAltitude * 0.95:
print("达到目标高度")
break
time.sleep(1)
# 调用上面声明的 arm_and_takeoff 函数,目标高度 3 米
arm_and_takeoff(2)
# 悬停 5 秒,并不断输出“正在悬停”和一些信息
# 这里是为了在悬停的时候输出信息才这么写。如果不需要输出信息,直接time.sleep(5)就可以
print("开始悬停")
start_time = time.time()
while time.time() - start_time < 5:
# 打印高度和完整GPS信息
print(" 正在悬停,当前高度: ", vehicle.location.global_relative_frame.alt)
print("当前模式:",vehicle.mode.name)
time.sleep(1)
print("向左缓慢飞行")
left_speed = 0.5 # 设置向左飞行速度为0.5m/s
flight_time = 2 # 飞行时间4秒(0.5m/s × 4s = 2米)
# 获取起始位置
start_position = vehicle.location.global_relative_frame
# 发送向左速度指令
msg = vehicle.message_factory.set_position_target_local_ned_encode(
0, # 时间戳
0, 0, # 目标系统ID和目标组件ID
mavutil.mavlink.MAV_FRAME_BODY_NED, # 坐标系
0b0000111111000111, # 控制速度的位掩码
0, 0, 0, # 位置参数(忽略)
0, -left_speed, 0, # 速度参数(X,Y,Z)- Y为负表示向左
0, 0, 0, # 加速度参数(忽略)
0, 0) # 偏航参数(忽略)
# 发送指令并保持飞行状态
start_time = time.time()
while time.time() - start_time < flight_time:
vehicle.send_mavlink(msg)
vehicle.flush()
print(" 正在向左飞行,当前高度: ", vehicle.location.global_relative_frame.alt)
print("当前GPS:", vehicle.location.global_frame)
time.sleep(0.1)
# 停止移动(发送零速度指令)
msg = vehicle.message_factory.set_position_target_local_ned_encode(
0, 0, 0,
mavutil.mavlink.MAV_FRAME_BODY_NED,
0b0000111111000111,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0)
vehicle.send_mavlink(msg)
vehicle.flush()
print("向左飞行完成,准备降落")
# 发送 "降落" 指令
print("降落")
#这里根据需要选择降落的方式
# RTL模式,无人机会自动返回home点的正上方(RTL高度可以在地面站里更改),之后自动降落。但我不需要无人机返回航点,所以改用Land模式降落。
# vehicle.mode = VehicleMode("RTL")
vehicle.mode = VehicleMode("LAND") # 直接降落
# 在降落过程中,不断输出当前高度
print("开始降落")
while vehicle.armed: # 当无人机未降落完成时(电机仍在旋转)
print(" 正在降落,当前高度: ", vehicle.location.global_relative_frame.alt)
print("当前模式:",vehicle.mode.name)
time.sleep(1)
# 降落完成后,退出之前,打开爪子
print("降落完成,打开爪子")
open_claw() # 打开爪子
time.sleep(1) # 等待1秒,确保动作完成
# 先停止PWM
pwm.stop()
# 然后清理GPIO设置
GPIO.cleanup()
print("GPIO已清理")
# 最后退出之前,清除 vehicle 对象
print("关闭设备连接")
vehicle.close()
十、SITL仿真
仿真环境的搭建还是耗了我不少时间的,所以扩展教程(5)和(6)两篇教程里配置环境的步骤我会重新在文章里写一遍
之前已经说过了,仿真环境是需要搭建在另一台电脑上的Ubuntu系统上。
教程里用的是16.04系统。
我最后在笔记本Vmware虚拟机里配置了18.04系统(18.04本身就够古老了)
双系统是在一台计算机上装两个操作系统,每次开机时选择一个操作系统,两个系统是平级的。
虚拟机是在一个操作系统上通过虚拟机软件(如Vmware)模拟出1台或N台计算机,每台计算机上都可以装一个系统。
Vmware虚拟机安装ubuntu系统的帖子网上的教程很多,流程并不复杂,没有什么困难点。
唯一要注意的是,安装好系统后,如果虚拟机和主机之前无法复制粘贴,可以参考这篇:
Vmware虚拟机和主机之间复制、粘贴内容、拖拽文件的详细方法_主机如何复制粘贴到vmware虚拟机上-CSDN博客
我一般都是用这篇最后的三条命令解决这个问题:
sudo apt-get autoremove open-vm-tools
sudo apt-get install open-vm-tools
sudo apt-get install open-vm-tools-desktop
以下没有特别说明,默认是在笔记本端的ubuntu系统进行操作。
搭建Ardupilot开发环境
Pixhawk无人机扩展教程(5)—SITL仿真模拟飞行:开发环境搭建
更新系统,安装git:
sudo apt-get update
sudo apt-get install git
sudo apt-get install gitk git-gui
印象中这三个命令都是可以顺利运行的。不顺利的话自己找下原因
git clone这一步:
git clone https://github.com/ArduPilot/ardupilot #github服务器在国外,不会魔法的还是老实使用下面的镜像站吧
git clone https://kkgithub.com/ArduPilot/ardupilot #使用这个命令
这里的github.com换成镜像站kkgithub.com。镜像站的下载会很慢,请耐心等待一下,有能力也可以选择科学上网。
进入ardupilot文件夹:
cd ardupilot
之后,按照教程原本是要用git submodule update --init --recursive
这个命令的
git clone那一步只是下载了仓库,还需要通过submodule 把module里的一些模块给下载下来。
但是我下载的时候总是下载不全,有时候提示下载完了,但是点进modules文件夹里,发现里面好多文件夹是空的。
查阅了很多资料,试了很多方法,最后成功的方法依然是换源。
以下是换源步骤:
首先在ardupilot文件夹下,用ls -a
查看所有文件,其中有一个.gitmodules文件
我们用vim编辑器打开这个文件:
vim .gitmodules
把文件里面所有的github.com换成kkgithub.com,然后保存退出。
输入:
git submodule sync
因为少输了这一句我卡了两天…
此时,镜像站已经更换完成。我们可以回到正常方式下载模块了:
git submodule update --init --recursive
完毕后点进modules文件夹,里面文件夹一个一个查看,发现全部下载成功。
接下来就按照教程,依次输入这几句就可以:
chmod +x Tools/environment_install/install-prereqs-ubuntu.sh
Tools/environment_install/install-prereqs-ubuntu.sh -y
. ~/.profile
到此环境配置成功了。
对于教程里接下来提到的例子,说实话我没明白它的作用,但还是把命令放上来:
cd ardupilot
./waf configure--board Pixhawk1
./waf copter
印象中我一开始以为我的飞控是pixhwak4,所以对pixhwak4也编译过一次,反而没有针对pixhawk2.4.8进行编译。但是可以正常使用。
启动仿真环境
Pixhawk无人机扩展教程(6)—启动SITL+MAVProxy
首先进入ArduCopter目录:
cd ardupilot/ArduCopter
接下来,教程里直接在ArduCopter目录下输入sim_vehicle.py -w
(第一次仿真的时候一般先输入这个命令,初始化一些设置)
但是实际上这个文件挪到别的文件夹去了,所以在ArduCopter目录下应当输入的是:
../Tools/autotest/sim_vehicle.py -w
正式仿真的时候,运行的是下面的命令:
../Tools/autotest/sim_vehicle.py --console --map
正常情况下会弹出一个terminal,一个console,和一个map
console和map没有弹出来?正常。
在一堆输出里面我找到了一些信息:
Failed to load module: No module named 'console'. Use 'set moddebug 3' in the MAVProxy console to enable traceback
Failed to load module: No module named 'map'. Use 'set moddebug 3' in the MAVProxy console to enable traceback
言下之意就是这两个模块加载不出来。
教程里面给出的解决方法是这个:
sudo -H pip2 install --upgrade MAVProxy pymavlink future lxml
但是运行后,问题并没有解决
看下面这个帖子最终解决了
Pixhawk无人机-ArduPilot 软件SITL仿真模拟飞行(SITL+MAVProxy)_sitl仿真找不到’mavproxy.exe-CSDN博客
我是先试了解决方法二,没用,卡了好几天,回过头来又找到这个帖子,试了下解决方法一,最后成功了。
除了上述console和map模块的两个failed信息之外,我记得当时上面一行还有一行warning,言下之意是要卸载一个什么manager。当时网上搜了一下,反正最后sudo uninstall掉了。
### 连接树莓派运行仿真
再放一次自己录的演示视频:(F450+Dronekit)SITL + MavProxy 仿真演示
这里主要做两件事:
1、在笔记本端,启动仿真后,把树莓派的IP接口加进去
教程里的做法是使用output add命令增加树莓派IP接口。提醒一下,添加接口是每一次重启系统后都要重新做的。
但是有时候仿真启动后,需要等半天才能输入output add命令,不想等的话可以使用这个命令,将启动仿真+添加接口一次性完成:
# 记得把命令里的ip改成自己的树莓派的ip
# 冒号是英文冒号,不要打成中文冒号
../Tools/autotest/sim_vehicle.py -v ArduCopter --console --map --out=udp:192.168.2.239:14550
2、在树莓派端,运行仿真代码
其中仿真代码与实际代码相比,仅仅只是三行有改变而已。
左为hover.py代码示例,右边为配套的hover_sitl.py代码示例
connection_string = '192.168.2.239:14550' # 这里的ip地址记得改为你的树莓派的地址
print('连接到设备: %s' % connection_string)
vehicle = connect(connection_string, wait_ready=True)
树莓派运行仿真代码后,在笔记本端的仿真界面里就会出现相应的变化了。
十一、(openCV)Aruco二维码降落
Aruco二维码实现无人机降落是一个比较成熟的方案了,需要下载opencv库
扩展教程(9)里提供了下载openCV的方法,但它选择的是从源码编译安装,非常繁琐。
我没记错的话,我直接使用万能的sudo apt install就下载成功了,同时openCV下载好后自带Aruco模块,无需再额外下载Aruco。
下载好后到python代码里用一行import cv2
测试一下,没报错就说明安装成功。
我最后的实现方案非常傻瓜,就是让无人机在空中根据二维码调整自身位置,对准后直接降落。实际测试能不能落到二维码上比较看运气。
Aruco二维码生成网站:Online ArUco markers generator
我用的是它默认的配置,可以根据需要自行更改:
字典选择4*4,ID选择0,size选择100
相机检测二维码camera_test.py:
import cv2
import numpy as np
import time
# 摄像头参数
CAMERA_DEVICE = "/dev/video0"
FRAME_WIDTH = 1920
FRAME_HEIGHT = 1200
FPS = 30
# ArUco参数
DICTIONARY = cv2.aruco.DICT_4X4_50
MARKER_ID = 0
# 初始化摄像头
cap = cv2.VideoCapture(CAMERA_DEVICE)
if not cap.isOpened():
print(f"无法打开摄像头 {CAMERA_DEVICE}!")
exit()
cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT)
cap.set(cv2.CAP_PROP_FPS, FPS)
# ArUco初始化
aruco_dict = cv2.aruco.Dictionary_get(DICTIONARY)
parameters = cv2.aruco.DetectorParameters_create()
def get_frame_center(frame):
h, w = frame.shape[:2]
return (w // 2, h // 2)
# 主程序
try:
last_detection_time = 0
detection_interval = 2.5 # 检测间隔(秒)
while True:
ret, frame = cap.read()
if not ret:
print("摄像头读取失败!")
break
# 显示画面
cv2.imshow("Camera View - 按ESC退出", frame)
# 按固定间隔检测
current_time = time.time()
if current_time - last_detection_time >= detection_interval:
last_detection_time = current_time
# 检测ArUco标记
corners, ids, _ = cv2.aruco.detectMarkers(frame, aruco_dict, parameters=parameters)
if ids is not None and MARKER_ID in ids:
idx = np.where(ids == MARKER_ID)[0][0]
frame_center = get_frame_center(frame)
marker_center = np.mean(corners[idx][0], axis=0).astype(int)
offset_x = marker_center[0] - frame_center[0]
offset_y = marker_center[1] - frame_center[1]
# 输出调整指令
if abs(offset_x) > 100:
print(f"[{time.strftime('%H:%M:%S')}] 向右移动" if offset_x > 0 else f"[{time.strftime('%H:%M:%S')}] 向左移动")
if abs(offset_y) > 100:
print(f"[{time.strftime('%H:%M:%S')}] 向上移动" if offset_y < 0 else f"[{time.strftime('%H:%M:%S')}] 向下移动")
if abs(offset_x) <= 100 and abs(offset_y) <= 100:
print(f"[{time.strftime('%H:%M:%S')}] 已居中,准备降落")
else:
print(f"[{time.strftime('%H:%M:%S')}] 未检测到标记")
# 按ESC退出
if cv2.waitKey(1) == 27:
break
finally:
# 确保资源被正确释放
cap.release()
cv2.destroyAllWindows()
print("程序结束")