本文章的基本思路是,通过App Inventor2开发一款手机控制程序,点击这个手机程序上的按键后,通过蓝牙发送命令给连接到Arduino的蓝牙模块,Arduino再将接收到的命令通过串口发送给Processing,来实现对Processing里的一个小坦克的运动控制。
所需硬件:
-
Arduino Uno
-
JDY-16 蓝牙模块(我的是蓝牙4.2版本)
-
电脑
-
手机(我用的是安卓,苹果手机应该也可以,最近其应用商店也有App inventor(AI)伴侣)
所需软件:
- Aduino IDE或者PlatfomIO(在Visual Studio Code)
- Mit App inventor2或者Wxbit App inventor2
App inventor2的地址
Wxbit App inventor2的地址 - Processing 3
步骤:
App Inventor2:
对于大部分非专业人士,开发一款手机APP并非易事,MIT的App inventer2给普通人开发自己的手机App提供了可能,对于国内无法登陆帐号的朋友可以用国内的Wxbit App inventor2(可以用QQ登录,不像前都要用梯子登录谷歌帐户)。
-
界面设计:添加垂直布局,水平布局,按钮,下拉表,可以在下图的右侧看到所有的组件内容,在组件界面设计中如下图所示,再添加一个不可见的对话框和BluetoothLE1。注意要在苹果手机运行App的朋友要在App Inventor2里选择苹果系统。
-
逻辑设计:
切换到Block界面,按照下图对逻辑部分进行设计。
a. 发送“F”,“B”,“L”,“R”和“S”给蓝牙模块,分别代表让坦克向前,向后,向左,向右和停止的命令
b. “断开连接”按钮在初始化时,是否启用(enabled)为假(False),该按键显示为灰色
-
如果有朋友觉得这部分比较麻烦可以在下面的链接里下载我共享的资源,在App Inventor里直接导入即可。
https://download.csdn.net/download/idfengming/85676035 -
通过AI伴侣或者WxBit调试助手把APK安装到手机上。
Arduino IDE:
-
连线是相当的简单,连接上5V, GND,再把JDY-16的RXD连接到UNO的2脚 ,TXD连接到UNO的3脚,因为Uno默认的串口0,1两脚要用来给Processing通讯,因为不能将蓝牙模块连到0,1脚。
-
在手机上先把JDY-16模块配对,默认的密码是"123456",不管是用的什么模块,都要先在手机上配对好,因为我们的app里是没有配对功能的。
本例要用到两个Arduino串口,因为要调用软串口库,IDE代码如下:
#include <Arduino.h>
#include <SoftwareSerial.h> //导入软串口库,以便进行多串口通讯
char moving_value='S'; //初始化移动命令值,S:停止
SoftwareSerial btSerial(2,3); //新串口(即本例的蓝牙串口)RX, TX分别连接到2,3脚
void setup() {
Serial.begin(9600);//内置串口波特率
btSerial.begin(9600);//软串口波特率
}
void loop() {
if(btSerial.available()>1) //蓝牙串口有命令传入时
{
moving_value=btSerial.read(); //赋值给moving_value, 为F,B,L,R,S之一
}
Serial.print(moving_value); //向内置串口(即本例中的processing串口)发送移动命令值
Serial.print("\n");
delay(50); //串口延时,不设置的话发送命令的速度太快
}
- 把程序上传到Uno。
Processing:
代码如下:
import processing.serial.*;
Serial myPort;
public static final short LF = 10; //数据帧尾(10即换行符)
int x= 300, y= 200; //坦克起始位置
int speed= 3; // 坦克移动的速度, 即每收到一个串口命令后移动几个像素
char Direction; // 方向
class Tank
{
Tank() {
}
void DisplayTank( int px, int py, char direction)
{
background(204);
switch(direction)
{
case 'F': //前进
fill( 255, 255, 0);
rect( px-5, py, 15, 40);
rect( px+30, py, 15, 40);
ellipse( px+ 20, py+ 20, 20, 25);
rect( px+17, py-10, 6, 19);
break;
case 'B': // 后退
fill( 255, 255, 0);
rect( px-5, py, 15, 40);
rect( px+30, py, 15, 40);
ellipse( px+20, py+20, 20, 25);
rect( px+ 17, py+ 30, 6, 19);
break;
case 'L': //往左行驶
fill( 255, 255, 0);
rect( px, py-5, 40, 15);
rect( px, py+30, 40, 15);
ellipse( px+20, py+ 20, 25, 20);
rect( px-10, py+17, 19, 6);
break;
case 'R': //往右行驶
fill( 255, 255, 0);
rect( px, py-5, 40, 15);
rect( px, py+30, 40, 15);
ellipse( px+20, py+20, 25, 20);
rect( px+30, py+17, 19, 6);
break;
case 'S': //停止
fill( 255, 255, 0);
rect( px-5, py, 15, 40);
rect( px+30, py, 15, 40);
ellipse( px+ 20, py+ 20, 20, 25);
rect( px+17, py-10, 6, 19);
default:
break;
}
}
void boundary( int px, int py) //防止坦克跑出边界
{
if (px< 10) x= 10;
if (py< 10) y= 10;
if (px>( width- 50)) x= width- 50;
if (py>( height- 50)) y= height- 50;
}
}
Tank tank;
void setup()
{
size( 640, 480);
tank= new Tank();
tank.DisplayTank(x, y, 'F');
myPort= new Serial(this, "COM7", 9600); //根据Arduino实际连接的COM更改
myPort.clear();
}
void draw() {
tank.DisplayTank(x, y, Direction);
}
void serialEvent(Serial myPort)
{
if (0<myPort.available()) {
String message = myPort.readStringUntil(LF);
char direction;
if (message !=null)
{
direction=message.charAt(0);
println(direction);
Direction=direction;
switch(direction)
{
case 'F':
y= y- speed;
tank.boundary(x, y);
tank.DisplayTank(x, y, 'F');
break;
case 'B':
y= y+speed;
tank.boundary(x, y);
tank.DisplayTank(x, y, 'B');
break;
case 'L':
x= x- speed;
tank.boundary(x, y);
tank.DisplayTank(x, y, 'L');
break;
case 'R':
x= x+speed;
tank.boundary(x, y);
tank.DisplayTank(x, y, 'R');
break;
case 'S':
tank.boundary(x, y);
tank.DisplayTank(x, y, 'S');
break;
default:
break;
}
}
}
}
最后就能通过手机上的五个控制按键控制Processing屏幕里的小坦克了,如果一切正常就会出现下面的画面