Sobre la primera parte
En la primera parte, describí la parte física de la construcción y solo un pequeño fragmento de código. Ahora considere el componente de software: una aplicación de Android y un boceto de Arduino.
Primero, daré una descripción detallada de cada momento, y al final dejaré enlaces a todo el proyecto + un video del resultado, lo que debería
decepcionarte .
Aplicación de Android
El programa para Android se divide en dos partes: la primera es conectar el dispositivo a través de Bluetooth, la segunda es el joystick de control.
Te advierto: el diseño de la aplicación no se resolvió en absoluto y se hizo con un error, si solo funcionó. La adaptabilidad y la experiencia de usuario no esperan, pero no deberían salir de la pantalla.
Diseño
La actividad inicial se basa en el diseño, elementos: botones y diseño para una lista de dispositivos. El botón inicia el proceso de búsqueda de dispositivos con Bluetooth activo. ListView muestra los dispositivos encontrados.
<?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>
La pantalla de control se basa en un diseño, en el que solo hay un botón, que en el futuro se convertirá en un joystick. Se adjunta un botón al botón a través del atributo de fondo, haciéndolo redondo.
TextView no se usa en la versión final, pero se agregó originalmente para la depuración: se mostraban los números enviados a través de Bluetooth. En la etapa inicial, te aconsejo que uses. Pero luego los números comenzarán a calcularse en una secuencia separada, desde la cual es difícil acceder a 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>
El archivo button_control_circle.xml (estilo), debe colocarse en la carpeta dibujable:
<?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>
También debe crear el archivo item_device.xml, es necesario para cada elemento de la lista:
<?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>
Manifiesto
Por si acaso, le daré el código de manifiesto completo. Debe obtener acceso completo al bluetooth a través de permisos de uso y no olvide indicar la segunda actividad a través de la etiqueta de actividad.
<?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>
La actividad principal, emparejar Arduino y Android
Heredamos la clase de AppCompatActivity y declaramos las variables:
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; }
Describiré el método onCreate () línea por línea:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
Las funciones a continuación verifican si se obtiene el permiso para usar el bluetooth (sin el permiso del usuario, no podremos transferir datos) y si el bluetooth está activado:
private boolean permissionGranted() {
Si se pasan todas las verificaciones, comienza la búsqueda del dispositivo. Si no se cumple una de las condiciones, se mostrará una notificación que diga "¿permitir \ habilitar?", Y esto se repetirá hasta que se pase la verificación.
La búsqueda de dispositivos se divide en tres partes: preparar la lista, agregar a la lista de dispositivos encontrados, establecer una conexión con el dispositivo seleccionado.
private void findArduino() {
Cuando se encuentra un módulo Bluetooth colgado en un Arduino (más sobre esto más adelante), aparecerá en la lista. Al hacer clic en él, comenzará a crear un socket (puede que tenga que esperar 3-5 segundos después de un clic o haga clic nuevamente). Comprenderá que la conexión se establece mediante los LED en el módulo Bluetooth: sin conexión, parpadean rápidamente, si hay una conexión, la frecuencia disminuye notablemente.

Administrar y enviar comandos
Una vez establecida la conexión, puede continuar con la segunda actividad: ActivityControl. Solo habrá un círculo azul en la pantalla: el joystick. Está hecho del botón habitual, el marcado se da arriba.
public class ActivityControl extends AppCompatActivity {
En el método onCreate (), se lleva a cabo toda la acción principal:
Presta atención (!): Descubriremos cuántos píxeles ocupa el botón. Gracias a esto, obtenemos adaptabilidad: el tamaño del botón dependerá de la resolución de la pantalla, pero el resto del código se adaptará fácilmente a esto, porque no arreglamos los tamaños por adelantado. Más tarde, le enseñaremos a la aplicación a averiguar dónde estaba el toque y luego lo traduciremos a valores que sean comprensibles para arduinki de 0 a 255 (después de todo, el toque puede estar a 456 píxeles desde el centro y MK no funcionará con ese número).
El siguiente es el código para ControlDriveInputListener (), esta clase se encuentra en la clase de la actividad en sí, después del método onCreate (). Al estar en el archivo ActivityControl, la clase ControlDriveInputListener se convierte en secundaria, lo que significa que tiene acceso a todas las variables de la clase principal.
No preste atención a las funciones que se invocan al hacer clic. Ahora estamos interesados en el proceso de capturar toques: en qué punto la persona puso su dedo y qué datos obtendremos al respecto.
Tenga en cuenta que uso la clase java.util.Timer: le permite crear un nuevo hilo que puede tener un retraso y se repetirá un número infinito de veces después de cada enésimo número de segundos. Debe usarse para la siguiente situación: la persona puso un dedo, el método ACTION_DOWN funcionó, la información fue al arduino, y luego la persona decidió no mover el dedo, porque la velocidad le conviene. La segunda vez, el método ACTION_DOWN no funcionará, ya que primero debe llamar a ACTION_UP (para levantar el dedo de la pantalla).
Bueno, comenzamos el bucle de clase Timer () y comenzamos a enviar los mismos datos cada 10 milisegundos. Cuando se mueve el dedo (ACTION_MOVE funcionará) o se levanta (ACTION_UP), el ciclo del Temporizador debe cancelarse para que los datos de la prensa anterior no comiencen a enviarse nuevamente.
public class ControlDriveInputListener implements View.OnTouchListener { private Timer timer; @Override public boolean onTouch(View view, MotionEvent motionEvent) {
Preste atención nuevamente: el método x e y cuenta con Touch () conduce desde la esquina superior izquierda de la Vista. En nuestro caso, el punto (0; 0) se encuentra en el botón aquí:

Ahora que hemos aprendido cómo obtener la ubicación actual del dedo en los botones, descubriremos cómo convertir píxeles (porque x e y son solo la distancia en píxeles) a valores de trabajo. Para hacer esto, uso el método CalculateAndSendCommand (x, y), que debe colocarse en la clase ControlDriveInputListener. También necesitará algunos métodos auxiliares, los escribimos en la misma clase después de CalculateAndSendCommand (x, y).
private void calculateAndSendCommand(float x, float y) {
Cuando los datos se calculan y transfieren, la segunda transmisión ingresa al juego. Él es responsable de enviar la información. No puede prescindir de él, de lo contrario, los datos de transmisión del socket ralentizarán la captura de toques, se creará una cola y todo el final será más corto.
La clase ConnectedThread también se encuentra en la clase ActivityControl.
private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket btSocket) {
Resumiendo la aplicación de Android
Resuma brevemente todos los incómodos anteriores.
- En ActivityMain configuramos bluetooth, establecemos una conexión.
- En el ActivityControl adjuntamos el botón y obtenemos datos al respecto.
- Colgamos el botón OnTouchListener, se toca, toca y levanta un dedo.
- Los datos obtenidos (punto con coordenadas xey) se convierten al ángulo de rotación y la velocidad.
- Enviamos datos, separándolos con caracteres especiales.
Y la comprensión final le llegará cuando vea todo el código:
github.com/IDolgopolov/BluetoothWorkAPP.git . No hay código de comentario, por lo que se ve mucho más limpio, más pequeño y más simple.
Sketch Arduino
La aplicación de Android está desmontada, escrita, entendida ... y aquí será más fácil. Trataré de considerar todo por etapas, y luego daré un enlace al archivo completo.
Variables
Primero, considere las constantes y variables que necesitará.
#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;
Método de configuración ()
En el método setup (), establecemos los parámetros de los pines: funcionarán en entrada o salida. También establecemos la velocidad de comunicación de la computadora con el arduino, bluetooth con el 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); }
Método Loop () y funciones adicionales
En el método loop () que se repite constantemente, los datos se leen. Primero, considere el algoritmo principal, y luego las funciones involucradas en él.
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); } } }
Obtenemos el resultado: desde el teléfono enviamos bytes al estilo de "@ speed # angle #" (por ejemplo, un comando típico "@ 200 # 60 #". Este ciclo se repite cada 100 milisegundos, ya que en Android establecemos este intervalo para enviar comandos. En resumen, haga no tiene sentido, ya que comienzan a hacer cola, y si lo haces más largo, las ruedas comienzan a moverse bruscamente. Todos losretrasos a través del comando delay (), que verás más adelante, se seleccionan no mediante cálculos físicos y matemáticos, sino empíricamente. zadrezham, la máquina funciona sin problemas, y en Todos los equipos tienen tiempo para hacer ejercicio (las corrientes tienen tiempo de atravesarlas).Se utilizan dos funciones secundarias en el ciclo, toman los datos recibidos y hacen que la máquina funcione y gire. 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); }
Gire cuando el Android envíe datos que el usuario fijó el ángulo de 60, 90, 120, no vale la pena, de lo contrario no podrá ir directamente. Sí, tal vez no debería enviar inmediatamente un comando de giro desde el androide si el ángulo es demasiado pequeño, pero en mi opinión, esto es de alguna manera torpe.Resultados de croquis
Un boceto tiene solo tres pasos importantes: leer un comando, procesar los límites de rotación y suministrar corriente a los motores. Todo suena simple, y en ejecución es más fácil que fácil, aunque fue creado durante mucho tiempo y con embotamientos. La versión completa del boceto .Al final
Se acabó un inventario completo de varios meses de trabajo. La parte física se desmonta, el software aún más. El principio sigue siendo el mismo: contacto para fenómenos incomprensibles, lo entenderemos juntos.Y los comentarios en la primera parte son interesantes, aconsejaron una montaña de consejos útiles, gracias a todos.Video de resultados