关于第一部分
在第一部分中,我描述了构造的物理部分以及一小段代码。 现在考虑软件组件-一个Android应用程序和一个Arduino草图。
首先,我会详细介绍每个时刻,最后,我会留下整个项目的链接以及结果的视频,这
会让您
感到失望 。
Android应用
Android程序分为两部分:第一部分是通过蓝牙连接设备,第二部分是控制操纵杆。
我警告您-该应用程序的设计根本无法完成,如果可以的话,它会大失所望。 适应性和UX不会等待,但不应超出屏幕范围。
布局图
开始活动取决于布局,元素:按钮和设备列表的布局。 该按钮开始查找具有活动蓝牙的设备的过程。 ListView显示找到的设备。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="60dp" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_marginStart="40dp" android:layout_marginTop="50dp" android:text="@string/start_search" android:id="@+id/button_start_find" /> <Button android:layout_width="wrap_content" android:layout_height="60dp" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:id="@+id/button_start_control" android:text="@string/start_control" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true"/> <ListView android:id="@+id/list_device" android:layout_width="300dp" android:layout_height="200dp" android:layout_marginEnd="10dp" android:layout_marginTop="10dp" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" /> </RelativeLayout>
控制屏幕基于布局,其中只有一个按钮,将来会变成操纵杆。 通过background属性将一个按钮附加到该按钮,使其变为圆形。
最终版本中未使用TextView,但最初是为了调试而添加的:显示了通过蓝牙发送的数字。 在初始阶段,我建议您使用。 但是随后将开始在单独的流中计算数字,从该流中很难访问TextView。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="200dp" android:layout_height="200dp" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:layout_marginBottom="25dp" android:layout_marginStart="15dp" android:id="@+id/button_drive_control" android:background="@drawable/button_control_circle" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" android:minWidth="70dp" android:id="@+id/view_result_touch" android:layout_marginEnd="90dp" /> </RelativeLayout>
button_control_circle.xml文件(样式),必须将其放置在drawable文件夹中:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#00F" /> <corners android:bottomRightRadius="100dp" android:bottomLeftRadius="100dp" android:topRightRadius="100dp" android:topLeftRadius="100dp"/> </shape>
您还需要创建item_device.xml文件,每个列表项都需要它:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="150dp" android:layout_height="40dp" android:id="@+id/item_device_textView"/> </LinearLayout>
清单
为了以防万一,我将提供完整的清单代码。 您需要通过uses-permission获得对蓝牙的完全访问权限,并且不要忘记通过活动标签指示第二个活动。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.bluetoothapp"> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.arproject.bluetoothworkapp.MainActivity" android:theme="@style/Theme.AppCompat.NoActionBar" android:screenOrientation="landscape"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.arproject.bluetoothworkapp.ActivityControl" android:theme="@style/Theme.AppCompat.NoActionBar" android:screenOrientation="landscape"/> </application> </manifest>
主要活动,配对Arduino和Android
我们从AppCompatActivity继承该类并声明变量:
public class MainActivity extends AppCompatActivity { private BluetoothAdapter bluetoothAdapter; private ListView listView; private ArrayList<String> pairedDeviceArrayList; private ArrayAdapter<String> pairedDeviceAdapter; public static BluetoothSocket clientSocket; private Button buttonStartControl; }
我将逐行描述onCreate()方法:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
下面的功能检查是否获得了使用蓝牙的许可(在没有用户许可的情况下,我们将无法传输数据)以及是否打开了蓝牙:
private boolean permissionGranted() {
如果所有检查均通过,则设备搜索开始。 如果不满足其中一个条件,则将显示一条通知,说“允许\启用?”,并且将重复此操作,直到通过检查为止。
设备搜索分为三个部分:准备列表,添加到找到的设备列表中,与所选设备建立连接。
private void findArduino() {
当找到挂在Arduino上的蓝牙模块时(稍后会详细介绍),它将出现在列表中。 通过单击它,您将开始创建套接字(单击后可能需要等待3-5秒或再次单击)。 您会理解,连接是通过蓝牙模块上的LED建立的:没有连接,它们会快速闪烁,如果有连接,则频率会明显降低。

管理和发送命令
建立连接后,您可以继续执行第二个活动-ActivityControl。 屏幕上只有一个蓝色圆圈-游戏杆。 它是由通常的Button制成的,标记在上面给出。
public class ActivityControl extends AppCompatActivity {
在onCreate()方法中,所有主要操作都将发生:
注意(!)-我们将找出按钮占用了多少像素。 因此,我们获得了适应性:按钮的大小将取决于屏幕分辨率,但是其余代码将很容易适应此情况,因为我们没有预先确定大小。 稍后,我们将教应用程序找出触摸的位置,然后将其转换为arduinki可以理解的值(从0到255)(毕竟,触摸可以距中心456像素,而MK不适用于该数字)。
以下是ControlDriveInputListener()的代码,该类位于onCreate()方法之后的活动本身的类中。 在ActivityControl文件中,ControlDriveInputListener类成为子级,这意味着它可以访问主类的所有变量。
不要关注单击时调用的功能。 现在,我们对接触的过程感兴趣:该人在什么时候放下手指,以及从中获得什么数据。
请注意,我使用java.util.Timer类:它允许您创建一个可能有延迟的新线程,并且在每第n秒后将重复无数次。 应该在以下情况下使用它:该人放了一根手指,ACTION_DOWN方法起作用,信息到达了arduino,此后该人决定不移动手指,因为速度适合他。 第二次,ACTION_DOWN方法将不起作用,因为首先您需要调用ACTION_UP(将手指从屏幕上移开)。
好了,我们启动Timer()类循环,并开始每10毫秒发送相同的数据。 当手指移开(ACTION_MOVE将起作用)或抬起手指(ACTION_UP)时,必须终止计时器周期,以免旧按键的数据再次开始发送。
public class ControlDriveInputListener implements View.OnTouchListener { private Timer timer; @Override public boolean onTouch(View view, MotionEvent motionEvent) {
再次注意:x和y计数onTouch()方法从视图的左上角开始。 在我们的例子中,点(0; 0)位于Button的此处:

现在,我们学习了如何在按钮上获得手指的当前位置,我们将弄清楚如何将像素(因为x和y只是以像素为单位的距离)转换为工作值。 为此,我使用calculateAndSendCommand(x,y)方法,该方法必须放在ControlDriveInputListener类中。 您还需要一些辅助方法,我们在calculateAndSendCommand(x,y)之后将它们写在同一类中。
private void calculateAndSendCommand(float x, float y) {
计算并传输数据后,第二个流进入游戏。 他负责发送信息。 您不能没有它,否则传输数据的套接字将减慢触摸的捕获,将创建一个队列,并且整个端点将变短。
ConnectedThread类也位于ActivityControl类中。
private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket btSocket) {
总结Android应用程序
简要总结以上所有繁琐的工作。
- 在ActivityMain中,我们配置蓝牙,建立连接。
- 在ActivityControl中,我们附加按钮并获取有关它的数据。
- 我们挂在OnTouchListener按钮上,它可以捕捉触摸,移动和抬起手指。
- 将获得的数据(具有x和y坐标的点)转换为旋转角度和速度
- 我们发送数据,并用特殊字符分隔它们
当您查看整个代码
-github.com/IDolgopolov/BluetoothWorkAPP.git时,最终的理解将带给您。 没有注释代码,因此看起来更整洁,更小,更简单。
素描Arduino
Android应用程序是经过反汇编,编写,理解的,在这里会更加容易。 我将尝试分阶段考虑所有内容,然后提供指向完整文件的链接。
变数
首先,考虑所需的常量和变量。
#include <SoftwareSerial.h> // \ // SoftwareSerial BTSerial(8, 9); // int speedRight = 6; int dirLeft = 3; int speedLeft = 11; int dirRight = 7; // , int angleDirection = 4; int angleSpeed = 5; //, , // int pinAngleStop = 12; // String val; // int speedTurn = 180; //, // int pinRed = A0; int pinWhite = A1; int pinBlack = A2; // long lastTakeInformation; //, , boolean readAngle = false; boolean readSpeed = false;
设置()方法
在setup()方法中,我们设置引脚的参数:它们将在输入或输出上工作。 我们还设置了与arduino,蓝牙与arduino的通信速度。
void setup() { pinMode(dirLeft, OUTPUT); pinMode(speedLeft, OUTPUT); pinMode(dirRight, OUTPUT); pinMode(speedRight, OUTPUT); pinMode(pinRed, INPUT); pinMode(pinBlack, INPUT); pinMode(pinWhite, INPUT); pinMode(pinAngleStop, OUTPUT); pinMode(angleDirection, OUTPUT); pinMode(angleSpeed, OUTPUT); // HC-05 // , BTSerial.begin(38400); // Serial.begin(9600); }
Loop()方法和附加功能
在不断重复的loop()方法中,读取数据。 首先,考虑主要算法,然后考虑其中涉及的功能。
void loop() { // if(BTSerial.available() > 0) { // char a = BTSerial.read(); if (a == '@') { // @ ( ) // val val = ""; //, readSpeed = true; } else if (readSpeed) { // // val if(a == '#') { // , // Serial.println(val); //, readSpeed = false; // go(val.toInt()); // val val = ""; // , return; } val+=a; } else if (a == '*') { // readAngle = true; } else if (readAngle) { // , // , val if(a == '#') { Serial.println(val); Serial.println("-----"); readAngle = false; // turn(val.toInt()); val= ""; return; } val+=a; } // lastTakeInformation = millis(); } else { // , 150 // if(millis() - lastTakeInformation > 150) { lastTakeInformation = 0; analogWrite(angleSpeed, 0); analogWrite(speedRight, 0); analogWrite(speedLeft, 0); } } }
我们得到的结果是:从电话中我们以“ @ speed#angle#”的样式发送字节(例如,典型命令“ @ 200#60#”。此周期每100毫秒重复一次,因为在android上我们设置了发送命令的时间间隔。简而言之,这没有任何意义,因为它们开始排队,如果更长,轮子开始抖动。通过delay()命令的所有延迟(您将在稍后看到)不是通过物理和数学计算而是根据经验选择的。 zadrezham,机器运转平稳,所有团队都有时间进行锻炼(电流有时间来解决)。循环中使用了两个辅助功能,它们获取接收到的数据并使机器运转并旋转。 void go(int mySpeed) { // 0 if(mySpeed > 0) { // digitalWrite(dirRight, HIGH); analogWrite(speedRight, mySpeed); digitalWrite(dirLeft, HIGH); analogWrite(speedLeft, mySpeed); } else { // 0, digitalWrite(dirRight, LOW); analogWrite(speedRight, abs(mySpeed) + 30); digitalWrite(dirLeft, LOW); analogWrite(speedLeft, abs(mySpeed) + 30); } delay(10); } void turn(int angle) { // digitalWrite(pinAngleStop, HIGH); // , delay(5); // 150 , // 30 , // 31 149 if(angle > 149) { // , // , // return if( digitalRead(pinWhite) == HIGH && digitalRead(pinBlack) == LOW && digitalRead(pinRed) == LOW) { return; } // // digitalWrite(angleDirection, HIGH); analogWrite(angleSpeed, speedTurn); } else if (angle < 31) { if(digitalRead(pinRed) == HIGH && digitalRead(pinBlack) == HIGH && digitalRead(pinWhite) == HIGH) { return; } digitalWrite(angleDirection, LOW); analogWrite(angleSpeed, speedTurn); } // digitalWrite(pinAngleStop, LOW); delay(5); }
当android发送数据时,用户将其夹成60、90、120的角度是不值得的,请打开,否则,您将无法直行。是的,如果角度太小,也许您不应该立即从android发送转弯命令,但是我认为这有点笨拙。草绘结果
草图只有三个重要步骤:读取命令,处理旋转极限以及向电机供电。一切听起来很简单,尽管创建时间长且钝,但执行起来却比简单容易。草图的完整版本。最后
数月工作的完整清单已结束。物理部分被拆解,软件更是如此。原理保持不变-对于难以理解的现象,我们将一起理解。第一部分下面的评论很有趣,他们向大家提供了许多有用的技巧,多亏了所有人。结果视频