制作一个Tello无人机无线WiFi的遥控器-Arduino中文社区 - Powered by Discuz! Archiver

youxianke 发表于 2018-6-28 09:17

制作一个Tello无人机无线WiFi的遥控器

本帖最后由 youxianke 于 2018-6-28 11:09 编辑

这篇文章是我发表在简书的一个开源课程,原地址:https://www.jianshu.com/p/f8e2e8ceaa47,首先声明一下:本文为作者原创,未经作者书面同意,不得转载!

从开始接触Arduino,就找到了这个论坛,这是国内最好的Arduino学习交流论坛,给我帮助很多,感谢很多朋友,虽未谋面,确已成良师,感谢为开源贡献的人们!

首先声明一下:本文将要制作的Tello无人机遥控器是基于睿炽科技官网公开的Tello SDK,网址链接:https://www.ryzerobotics.com/cn/tello/downloads,Tello无人机是一款教育编程无人机,用户可以根据睿炽科技公开的SDK编程控制无人机,为了让读者更好的理解程序和基于本文能够自己动手自做一个遥控器,本文会对睿炽科技的SDK做一些必要的引用以对开源的程序做一些解释,如涉及版权问题,请第一时间联系作者,谢谢!

Tello无人机是大疆跟睿炽科技合作开发的一款教育编程无人机,针对STEAM(科学、技术、工程、艺术、数学)教育场景及需求。

//upload-images.jianshu.io/upload_images/1723059-5c83f73db6b365b0.png

首图.png

Tello支持Scratch、Python等语言进行编程控制,儿子最近在捣鼓Scratch编程,于是这个无人机变成了他六一儿童节礼物。

//upload-images.jianshu.io/upload_images/1723059-9b78930a124360e8.jpg



这个无人机其实非常小巧,室内都能飞行,给小孩玩是非常不错的。但是无人机并没有附带遥控手柄,而是在官网提供了遥控APP,安装到手机上,通过WiFi连接上无人机后进行控制(当然也可以通过Scratch编程进行控制,这部分内容我会在另一个系列《Scratch边玩边学:从动画、游戏到算法入门》中介绍)。
其实手柄是有的,不过要单独购买:

//upload-images.jianshu.io/upload_images/1723059-d9ce0a42ed90e731.png

手柄.png

多少钱?忘了,反正太贵(你有没有发现,随着学习Arduino的深入,你会发现市面上的电子产品给人的感觉越来越贵了,呵呵,开个玩笑!),既然觉得贵,那就自己做一个吧!Tello本身就是一款教育编程机器人,手柄贵,不就是要让我们自己动手来做一个吗?你说是不是?
好吧,今天我们要做的项目就是Tello无人机遥控手柄,通过遥控手柄实现Tello无人机起飞、降落,前后左右飞行以及上升下降。
在开始之前先介绍一下Tello无人机支持的无线连接方式,Tello无人机是基于WiFi UDP协议跟控制器(遥控手柄、电脑、手机APP)实现连接的,所以我们需要准备的组件就要包括一个WiFi模块。

1 本章您将学到
在这个项目中,您将学到的:

[*]学会基于第三方SDK文档进行简单项目开发
[*]通过ESP8266模块实现UDP消息透传
[*]JoyStick摇杆扩展板的使用

2 工具和组件2.1 工具列表

本项目不需要额外的工具。

2.2 元器件列表

元器件 型号 数量
主控板 UNO 1
JoyStick摇杆扩展板 1
ESP8266 12N 1
杜邦线 4
数据线 UNO数据线 1



2.3 工具和元器件介绍
2.3.1 JoyStick摇杆扩展板

JoyStick Shield游戏摇杆扩展板是我们在项目中第一次使用,我们简单介绍一下:

//upload-images.jianshu.io/upload_images/1723059-3f375ec6704fd3e9.jpg

003.jpg

这个扩展板是我偶然发现的,原先的设计是通过一个摇杆+4个按键进行设计,摇杆都买好了:

//upload-images.jianshu.io/upload_images/1723059-ab11a77740473901.jpg

004.jpg

在购买3D打印机主控的时候偶然发现了JoyStick Shield,这个太好了,省去了搭建电路的麻烦,爽!其实Arduino最吸引人的地方就是它的外围模块太丰富了,只有你想不到,呵呵!言归正传,我们还是来介绍这个扩展板吧!
Joystick Shield还添加了nRF24L01的RF接口和Nokia5110 LCD接口,这样非常方便二次的游戏开发。

2.3.1.1 技术参数

这个似乎没什么可介绍的,这个扩展板其实就是一个遥杆+六个按键,注意中间部位还有两个小按键,按键我们在本系列文章之前就有介绍,记得是交通灯那一篇,有不清楚的可以去翻翻那一篇文章看看。
另外,扩展板上还有一个开关可以在3.3V 和5V 之间切换,可以将此模块用于其它3.3V单片机平台,比如STM32,由于Arduino UNO支持5V和3.3V供电,所以这个开关在UNO上似乎没有什么意义,经测试也的确没什么用。

2.3.1.2 摇杆的原理

前面介绍过JoyStick Shield游戏摇杆扩展板就是一个双轴按键摇杆+6个按键,按键我们都清楚了,那这个双轴按键摇杆是个什么东东呢?其实它也很简单,就是两个电位计+一个按键。
那现在我们应该很清楚了,JoyStick Shield游戏摇杆扩展板就是7个按键+两个电位计。
我们知道了这个扩展板的组成,但是你也许还有疑惑,我们怎么能够知道摇杆到底朝那个方向摇动呢?其实就是通过模拟输入口读取两个电位计的电压值,摇杆朝不同的方向摇动会导致这两个值发生变化,根据这个变化,我们就能判断摇杆的方向。感兴趣的朋友可以自己测试一下。

2.3.1.3 跟UNO的连接
JoyStick Shield游戏摇杆扩展板在实际使用时直接插在UNO电路板上即可,不过我们还是需要了解一下它跟UNO的实际连接。
前面说过,JoyStick Shield游戏摇杆扩展板就是7个按键+两个电位计,如果你需要使用全部的7个按键,那么就需要7个数字输入引脚+2个模拟输入引脚,另外还需要连接5V和GND,7个数字引脚用的是(2、3、4、5、6、7、8),扩展板提供了从9到13数字引脚的接口,可以直接使用。
两个模拟口可以自定义,A0到A5都可以,后面我们在介绍JoyStickShield扩展库的时候会再介绍。

2.3.2 ESP-12F WiFi模块

我们重点介绍一下这个模块。
ESP-12F是一款超低功耗的UART-WiFi 透传模块,专为移动设备和物联网应用设计,可将用户的物理设备连接到Wi-Fi 无线网络上,进行互联网或局域网通信,实现联网功能。

//upload-images.jianshu.io/upload_images/1723059-23dbafe279fa8d7b.png

12F.png

这个模块使用之前需要焊接到转接板上,下图是转接板:

//upload-images.jianshu.io/upload_images/1723059-c7660e22650e61db.png

12F board.png

下面两张图是焊接完成后的样子:

//upload-images.jianshu.io/upload_images/1723059-0d7aa9cc168825ad.png

12F-01.png

//upload-images.jianshu.io/upload_images/1723059-6af42035d35abccc.png

12F-02.png

ESP-12F模块引脚间距是2mm的,焊接起来比较费劲。本来想采用ESP-01模块的,这个模块不需要焊接,有引脚直接可以用,不过ESP-01模块对供电要求比较高,而且Flash才8Mbit,可用引脚也比较少,可玩性跟12F差太多,所以就不推荐大家使用了,不过如果是做一个实际项目,有成本控制且只做无线透传,ESP-01就相对合适一些(其实ESP8266模块本身就是一个MCU,跟Arduino的主控板一样,也能在Arduino IDE下编程)。

2.3.2.1 产品特性

[*]支持无线802.11 b/g/n 标准
[*]支持STA/AP/STA+AP 三种工作模式
[*]内置TCP/IP协议栈,支持多路TCP Client连接
[*]支持丰富的Socket AT指令
[*]支持UART/GPIO数据通信接口
[*]支持Smart Link 智能联网功能
[*]支持远程固件升级(OTA)
[*]内置32位MCU,可兼作应用处理器
[*]超低能耗,适合电池供电应用
[*]3.3V 单电源供电
注意:最后一条,3.3V供电,建议由电池组或者电源模块单独供电,用一个降压模块,直接用UNO的3.3V供电很不稳定。

2.3.2.2 模块使用方法
这部分内容比较关键,ESP8266系列模块在使用前都需要进行调试和模式的设定,包括工作模式和串口通讯速率,如果模块烧录了非AT固件,还需要重新对模块进行烧录,好在如果你是新买的模块,或者买回来后没有对其进行过其它固件烧录,那么就没有烧录的必要,它出厂就默认烧录好了AT固件。
那么我们只需要设置一下它的串口通信速率即可,ESP8266模块默认的串口通信速率是:115200,这个速率对于UNO主控板的软串口来说太高了,不稳定,所以我们需要将其设定为:9600。
设定方法:通过串口模块跟ESP8266连接上电脑后,通过串口指令进行设定,指令如下:
AT+UART_DEF=9600,8,1,0,0

3 电路设计3.1 电路图
根据我们的项目需求,设计电路图如下:

//upload-images.jianshu.io/upload_images/1723059-907ec50572a84145.png

UNO Tello Controller_bb.png

3.2 电路原理
这个电路图其实比较简单,JoyStick Shield游戏摇杆扩展板直接安装到UNO板上,数字9、10口作为软串口的RX、TX引脚跟ESP8266-12N连接,图中ESP8266-12N模块由UNO直接供电,VCC接的是3.3V,但在实际项目中,采用的是单独供电,单独供电的时候,ESP8266-12N需要和UNO共地。

4 程序设计4.1 类库介绍

这个项目用的库比较多,有四个,我们分别介绍一下:

4.1.1 JoystickShield.h库

JoystickShield.h库下载地址:百度网盘链接:https://pan.baidu.com/s/187HyxX6GceP8rAtOIN9-eA。
下载解压缩后,直接放到Arduino项目文件夹(一般在:我的电脑 \ 文档 \ Arduino \)中的libraries子目录中。

4.1.2 WiFiEsp.h库

WiFiEsp.h库下载地址:百度网盘链接:https://pan.baidu.com/s/187HyxX6GceP8rAtOIN9-eA。
下载解压缩后,直接放到Arduino项目文件夹(一般在:我的电脑 \ 文档 \ Arduino \)中的libraries子目录中。
这个库其实包含好几个库:WiFiEsp.h、WiFiEspClient.h、WiFiEspServer.h、WiFiEspUdp.h,这个是一个非常优秀的ESP8266 AT指令封装库,在本文中会用到两个库:WiFiEsp.h、WiFiEspUdp.h,我们简单了解一下,其它两个我们在别的文章中还会继续介绍。

4.1.3 SoftwareSerial.h库
SoftwareSerial.h库为Arduino的自带核心库,无需下载,可直接引用。
这个函数作用是设置串口传送波特率,软串口波特率我们一般采用9600,这个波特率需要跟与串口通信的设备或者模块保持一致。

4.1.4 IPAddress.h库
IPAddress.h库为Arduino的自带核心库,无需下载,可直接引用。
这个类就是定义一个IP地址对象,说实话,直到开始写这部分内容时,我才意识到这里弄复杂了,其实IP地址可以用一个字符串定义,就像下面这样:
const char *telloAddr= "192,168,10,1";为什么可以这么做呢?
我们可以看一下这个IP地址对象在哪儿使用了(你可以先看一下主程序,找到这行代码):
Udp.beginPacket(telloAddr, telloPort);这行代码的作用就是对Udp对象进行初始化,这里会用到一个IP地址对象tellAddr和端口号telloPort,程序的开始都有定义。
但实际上beginPacket这个方法在WiFiEspUDP对象中是重载的,它定义了两个beginPacket方法,如下:
virtual int beginPacket(IPAddress ip, uint16_t port);virtual int beginPacket(const char *host, uint16_t port);所以beginPacket方法的第一个参数可以是一个IPAddress对象,也可以是一个字符数组指针变量。
这样你就很清楚了,其实我们的主程序可以更加简化的,不过咱们是为了学习而来,弄懂程序背后的意义才是重点。

关于UDP协议:
UDP是一个传输层协议,与之对应的还有TCP协议,它们都工作在IP协议上,它们之间区别就是TCP是面向连接的,而UDP不是,可能有的朋友还是不理解这一点,我简单的举个例子说明一下:
假设某个周末的下午,你到小区的院子里跟小朋友玩耍,你妈妈忙着做晚饭,不一会儿,妈妈的晚饭做好了,而你呢?玩得正嗨,忘了回家的时间。
好了,你妈妈需要叫你回家吃饭了,现在你妈妈有两种做法,一种是按照TCP的模式,一种是UDP的模式,假设你家的阳台正对着小区院子,阳台到院子之间可以通过声音交流(类似IP协议提供的服务),你的小名叫:阿福(类似IP地址),我们来看看这两种模式的区别:
TCP模式:
你妈妈在阳台对着院子大声的喊:“阿福、阿福!”
你听到了,赶紧回答:“妈妈,妈妈,干嘛!”
你妈妈又说:“回家吃饭了!”
你回答:“好的,马上就回来!”
你妈妈听到后,知道你一会儿就回来吃饭,然后开始去忙别的了。
UDP模式:
你的妈妈来到阳台,对着院子大喊一声:“阿福,回家吃饭了!”
你的妈妈觉得你肯定能够听到,反正回家吃饭这件事也没什么大不了,妈妈认为你一会儿就会回家吃饭,然后她就忙别的去了。
你呢?你可能听到了,也可能没听到,小朋友在一起玩耍时本身就是吵吵闹闹的,当你听到了,你肯定就会回家吃饭,这种情况发生的概率很大,毕竟小区院子就正对着你家阳台,你妈妈的声音也够响亮。
如果万一没听到呢?没关系,你妈妈隔一会发现你还没回家,又会跑到阳台,再喊一声:“阿福,回家吃饭了!”
现在你能理解这两种通信方式的区别了吗?
4.2 Tello SDK介绍这部分内容主要是对Tello SDK文档做一个简单的介绍。
4.2.1 WiFi连接Tello无人机IP地址:192.168.10.1;
Tello无人机UDP监听端口:8889。
4.2.2 命令参数
命令功能描述可能的响应
command进入命令模式OK 或者 FALSE
takeoff自动起飞OK 或者 FALSE
land自动降落OK 或者 FALSE
up xx向上飞xx厘米(xx范围20~500CM)OK 或者 FALSE
down xx向下飞xx厘米(xx范围20~500CM)OK 或者 FALSE
left xx向左飞xx厘米(xx范围20~500CM)OK 或者 FALSE
right xx向右飞xx厘米(xx范围20~500CM)OK 或者 FALSE
forward xx向前飞xx厘米(xx范围20~500CM)OK 或者 FALSE
back xx向后飞xx厘米(xx范围20~500CM)OK 或者 FALSE
注意:命令参数的单位为:距离是厘米、角度是度、速度为厘米/秒。
关于SDK暂时就介绍这些指令,这也是我们在后面程序中需要用到的,当然,官方给出的SDK文档还有更多的指令,感兴趣的朋友可以到官网下载。

4.3 主程序设计
/********************************
/********************************
Name:   Tello无人机遥控器
Module:   UNO + Joystick + ESP8266-12N
Author:   You xianke
Version:V1.0
Init:   2018-6-25
Modify:
*******************************/
#include <JoystickShield.h> // include JoystickShield Library
#include <WiFiEsp.h>
#include <WiFiEspUdp.h>

#include <SoftwareSerial.h>
#include <IPAddress.h>

char ssid[] = "TELLO-AA32D0";    // Tello SSID,这个需要根据无人机的实际值进行修改,启动Tello无人机后,用电脑扫描一下WiFi网络,以TELLO开头的热点即是
char pass[] = "";                // WiFi password is NULL

int status = WL_IDLE_STATUS;   // the Wifi radio's status

JoystickShield joystickShield; // create an instance of JoystickShield object

const int RXPin = 9;   //定义软串口针脚
const int TXPin = 10;

unsigned int localPort = 9000;      // local port to listen for UDP packets

const int UDP_TIMEOUT = 2000;    // timeout in miliseconds to wait for an UDP packet to arrive
char packetBuffer;          // buffer to hold incoming packet

// A UDP instance to let us send and receive packets over UDP
WiFiEspUDP Udp;
IPAddress telloAddr(192,168,10,1);   //Tello的UdpServer服务端的IP地址
const int telloPort = 8889;          //Tello UDP监听端口号

SoftwareSerial espSerial(RXPin,TXPin); // 定义连接ESP-12N串口

void PrintWifiStatus();
void SendCommand(const char* command);

void setup() {
Serial.begin(9600);
espSerial.begin(9600);

WiFi.init(&espSerial);
// WiFi.mode(WIFI_STA);

    // check for the presence of the shield:
    if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
      // don't continue:
      while (true);
    }

    // attempt to connect to WiFi network
    while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
      Serial.println(ssid);
      // Connect to WPA/WPA2 network
      status = WiFi.begin(ssid, pass);
    }
   
    delay(1000);   
    Serial.println("Connected to wifi");
    PrintWifiStatus();

    Serial.println("\nStarting listening a UDP port...");
    // if you get a connection, report back via serial:
    Udp.begin(localPort);   
    Serial.print("Listening on port ");
    Serial.println(localPort);

    SendCommand("command");   //Tello进入命令模式
delay(100);

joystickShield.calibrateJoystick();
}

void loop() {
//遥控指令的处理
joystickShield.processEvents(); // process events

if (joystickShield.isUp()) {
    Serial.println("Up") ;
    Serial.println("Tello forward 50CM!") ;
    SendCommand("forward 50");   //Tello向前50CM
    delay(1000);
}

if (joystickShield.isRightUp()) {
    Serial.println("RightUp") ;
}

if (joystickShield.isRight()) {
    Serial.println("Right") ;
    Serial.println("Tello turn right 50CM!") ;
    SendCommand("right 50");   //Tello向右50CM
    delay(1000);
}

if (joystickShield.isRightDown()) {
    Serial.println("RightDown") ;
}

if (joystickShield.isDown()) {
    Serial.println("Down") ;
    Serial.println("Tello turn back 50CM!") ;
    SendCommand("back 50");   //Tello向后50CM
    delay(1000);
}

if (joystickShield.isLeftDown()) {
    Serial.println("LeftDown") ;
}

if (joystickShield.isLeft()) {
    Serial.println("Left") ;
    Serial.println("Tello turn left 50CM!") ;
    SendCommand("left 50");   //Tello向左50CM
    delay(1000);
}

if (joystickShield.isLeftUp()) {
    Serial.println("LeftUp") ;
}

if (joystickShield.isJoystickButton()) {
    Serial.println("Joystick Clicked") ;
}

if (joystickShield.isUpButton()) {
    Serial.println("Up Button Clicked") ;
    Serial.println("Tello land!") ;
    SendCommand("up 50");   //Tello上升50CM
    delay(1000);
}

if (joystickShield.isRightButton()) {
    Serial.println("Right Button Clicked") ;
    Serial.println("Tello land!") ;
    SendCommand("land");   //Tello降落
    delay(2000);
}

if (joystickShield.isDownButton()) {
    Serial.println("Down Button Clicked") ;
    Serial.println("Tello land!") ;
    SendCommand("down 50");   //Tello下降50CM
    delay(1000);
}

if (joystickShield.isLeftButton()) {
    Serial.println("Left Button Clicked") ;
    Serial.println("Tello takeoff!") ;
    SendCommand("takeoff");   //Tello起飞
    delay(2000);
}

// new eventfunctions
if (joystickShield.isEButton()) {
    Serial.println("E Button Clicked") ;
}

if (joystickShield.isFButton()) {
    Serial.println("F Button Clicked") ;
}

if (joystickShield.isNotCenter()){
    Serial.println("NotCenter") ;
}

// new position functions
Serial.print("x ");   
Serial.print(joystickShield.xAmplitude());
Serial.print(" y ");
Serial.println(joystickShield.yAmplitude());

// 接收到Tello无人机消息后的处理
int packetSize = Udp.parsePacket();
if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From Tello ");
    IPAddress remoteIp = Udp.remoteIP();
    Serial.print(remoteIp);
    Serial.print(", port ");
    Serial.println(Udp.remotePort());

    // read the packet into packetBufffer
    int len = Udp.read(packetBuffer, 64);
    if (len > 0) {
      packetBuffer = 0;
    }
    Serial.println("Contents:");
    Serial.println(packetBuffer);
}
delay(500);
}

void PrintWifiStatus(){
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);

// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}

void SendCommand(const char* command){
Udp.beginPacket(telloAddr, telloPort);
Udp.write(command, strlen(command));
Udp.endPacket();
delay(1000);
}
主程序就不单独解释了,程序中的注释已经非常清楚了!

5 安装调试
下面我们根据电路图将两个模块跟UNO连接上:
//upload-images.jianshu.io/upload_images/1723059-b77c9774ae603d43.jpg

组装01.jpg

将Tello无人机开机,打开电脑串口,观察一下遥控器是否跟Tello连接上,连接上后,串口会有WiFi状态打印。
如果连接成功,您就可以通过遥控手柄控制Tello无人机的起飞、降落,上升、下降,前后左右飞行了。

5 总结扩展
因为时间的关系,我并没有将这个手柄做得更加完善,只是搭建了一个原型,您可以根据这个原型来自己设计一个更加完善的遥控手柄,增加外壳,用电池进行供电,甚至增加一个小的液晶屏,直接来显示连接状态和命令发送的相关信息。
另外这个手柄上还有两个小的按钮,我的想法是您可以增加两个自定义飞行动作系列,让无人机能够表演一连串的复杂动作,当然,程序您需要再修改一下,怎么修改?我相信您肯定能够办到,呵呵,实在不行就请关注我们的微信号留言吧!


页: [1]
查看完整版本: 制作一个Tello无人机无线WiFi的遥控器