Sobre a primeira parte
Na primeira parte, descrevi a parte física da construção e apenas um pequeno pedaço de código. Agora considere o componente de software - um aplicativo Android e um esboço do Arduino.
Primeiro, darei uma descrição detalhada de cada momento e, no final, deixarei links para todo o projeto + um vídeo do resultado, o que deve
desapontá- lo.
Aplicativo para Android
O programa para android é dividido em duas partes: a primeira é conectar o dispositivo via Bluetooth, a segunda é o joystick de controle.
Eu te aviso - o design do aplicativo não foi elaborado e foi feito de maneira errada, se funcionasse. A adaptabilidade e o UX não esperam, mas não devem sair da tela.
Layout
A atividade inicial se baseia no layout, nos elementos: botões e no layout de uma lista de dispositivos. O botão inicia o processo de localização de dispositivos com Bluetooth ativo. O ListView exibe os 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>
A tela de controle é baseada em um layout, no qual existe apenas um botão, que no futuro se tornará um joystick. Um botão é anexado ao botão através do atributo background, tornando-o redondo.
O TextView não é usado na versão final, mas foi originalmente adicionado para depuração: os números enviados via bluetooth foram exibidos. Na fase inicial, eu aconselho você a usar. Mas os números começarão a ser calculados em um fluxo separado, do qual é difícil acessar o 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>
O arquivo button_control_circle.xml (estilo), deve ser colocado na pasta extraível:
<?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>
Você também precisa criar o arquivo item_device.xml, que é necessário para cada item da 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>
Manifesto
Apenas no caso, darei o código completo do manifesto. Você precisa obter acesso total ao bluetooth por meio de permissão de uso e não se esqueça de indicar a segunda atividade por meio da tag de atividade.
<?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>
A atividade principal, emparelhamento Arduino e Android
Herdamos a classe de AppCompatActivity e declaramos as variáveis:
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; }
Vou descrever o método onCreate () linha por linha:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
As funções abaixo verificam se a permissão para usar o bluetooth é obtida (sem a permissão do usuário, não poderemos transferir dados) e se o bluetooth está ativado:
private boolean permissionGranted() {
Se todas as verificações forem aprovadas, a pesquisa do dispositivo será iniciada. Se uma das condições não for atendida, uma notificação será exibida, dizendo "permitir \ ativar?", E isso será repetido até que a verificação seja aprovada.
A pesquisa de dispositivos é dividida em três partes: preparando a lista, adicionando à lista de dispositivos encontrados, estabelecendo uma conexão com o dispositivo selecionado.
private void findArduino() {
Quando um módulo Bluetooth pendurado em um Arduino (mais sobre isso posteriormente) é encontrado, ele aparecerá na lista. Ao clicar nele, você começará a criar um soquete (pode ser necessário esperar 3-5 segundos após um clique ou clicar novamente). Você entenderá que a conexão é estabelecida pelos LEDs no módulo Bluetooth: sem conexão, eles piscam rapidamente; se houver uma conexão, a frequência diminui visivelmente.

Gerenciar e enviar comandos
Depois que a conexão é estabelecida, você pode prosseguir para a segunda atividade - ActivityControl. Haverá apenas um círculo azul na tela - o joystick. É feito a partir do botão usual, a marcação é dada acima.
public class ActivityControl extends AppCompatActivity {
No método onCreate (), toda a ação principal ocorre:
Preste atenção (!) - descobriremos quantos pixels o botão ocupa. Graças a isso, obtemos adaptabilidade: o tamanho do botão dependerá da resolução da tela, mas o restante do código se adaptará facilmente a isso, porque não corrigimos os tamanhos antecipadamente. Posteriormente, ensinaremos o aplicativo a descobrir onde estava o toque e, em seguida, traduzi-lo em valores compreensíveis para o arduinki de 0 a 255 (afinal, o toque pode estar a 456 pixels do centro e o MK não funcionará com esse número).
A seguir está o código para ControlDriveInputListener (), essa classe está localizada na classe da própria atividade, após o método onCreate (). Por estar no arquivo ActivityControl, a classe ControlDriveInputListener se torna uma criança, o que significa que ela tem acesso a todas as variáveis da classe principal.
Não preste atenção nas funções chamadas quando clicadas. Agora, estamos interessados no processo de captar detalhes: em que momento a pessoa colocou o dedo e quais dados obteremos sobre isso.
Observe que eu uso a classe java.util.Timer: permite criar um novo thread que pode ter um atraso e será repetido um número infinito de vezes após cada enésimo número de segundos. Deve ser usado para a seguinte situação: a pessoa colocou um dedo, o método ACTION_DOWN funcionou, as informações foram para o arduino e depois a pessoa decidiu não mover o dedo, porque a velocidade lhe convinha. Na segunda vez, o método ACTION_DOWN não funcionará, pois primeiro você precisa ligar para ACTION_UP (para levantar o dedo da tela).
Bem, começamos o loop da classe Timer () e começamos a enviar os mesmos dados a cada 10 milissegundos. Quando o dedo é deslocado (ACTION_MOVE funcionará) ou aumentado (ACTION_UP), o ciclo do temporizador deve ser interrompido para que os dados da impressora antiga não comecem a ser enviados novamente.
public class ControlDriveInputListener implements View.OnTouchListener { private Timer timer; @Override public boolean onTouch(View view, MotionEvent motionEvent) {
Preste atenção novamente: o x e o y contam com o método Touch (), a partir do canto superior esquerdo da tela. No nosso caso, o ponto (0; 0) está localizado no botão aqui:

Agora que aprendemos como obter a localização atual do dedo nos botões, descobriremos como converter pixels (porque xey são apenas a distância em pixels) para valores funcionais. Para fazer isso, eu uso o método calculAndSendCommand (x, y), que deve ser colocado na classe ControlDriveInputListener. Você também precisará de alguns métodos auxiliares, nós os escreveremos na mesma classe após o cálculo de CalculAndSendCommand (x, y).
private void calculateAndSendCommand(float x, float y) {
Quando os dados são calculados e transferidos, o segundo fluxo entra no jogo. Ele é responsável pelo envio de informações. Você não pode ficar sem ele; caso contrário, os dados de transmissão do soquete retardarão a captura de toques, uma fila será criada e o final inteiro será mais curto.
A classe ConnectedThread também está localizada na classe ActivityControl.
private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket btSocket) {
Resumindo o aplicativo Android
Resuma brevemente todo o incômodo acima.
- No ActivityMain, configuramos o bluetooth, estabelecemos uma conexão.
- No ActivityControl, anexamos o botão e obtemos dados sobre ele.
- Nós penduramos o botão OnTouchListener, ele capta toque, movimento e levanta um dedo.
- Os dados obtidos (ponto com as coordenadas x e y) são convertidos no ângulo e velocidade de rotação
- Enviamos dados, separando-os com caracteres especiais
E o entendimento final chegará a você quando você olhar o código inteiro -
github.com/IDolgopolov/BluetoothWorkAPP.git . Não há código de comentário, por isso parece muito mais limpo, menor e mais simples.
Sketch Arduino
O aplicativo Android é desmontado, escrito, entendido ... e aqui será mais fácil. Vou tentar considerar tudo em etapas e depois darei um link para o arquivo completo.
Variáveis
Primeiro, considere as constantes e variáveis que você precisará.
#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 Setup ()
No método setup (), definimos os parâmetros dos pinos: eles funcionarão na entrada ou na saída. Também definimos a velocidade de comunicação do computador com o arduino, bluetooth com o 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 () e funções adicionais
No método loop () de repetição constante, os dados são lidos. Primeiro, considere o algoritmo principal e, em seguida, as funções envolvidas nele.
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); } } }
Obtemos o resultado: no telefone, enviamos bytes no estilo "@ speed # angle #" (por exemplo, um comando típico "@ 200 # 60 #". Esse ciclo se repete a cada 100 milissegundos, já que no Android definimos esse intervalo para o envio de comandos. não faz sentido, pois eles começam a ficar na fila e, se você prolongar, as rodas começam a se mover rapidamente. Todos osatrasos pelo comando delay (), que você verá mais tarde, são selecionados não por meio de cálculos físicos e matemáticos, mas empiricamente. zadrezham, a máquina funciona sem problemas e, em Todas as equipes têm tempo para se exercitar (as correntes têm tempo para percorrer.)Duas funções laterais são usadas no ciclo: elas pegam os dados recebidos e fazem a máquina girar. 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); }
Por sua vez, quando o Android envia dados que o usuário fixou no ângulo de 60, 90, 120, não vale a pena, caso contrário você não será capaz de seguir em frente. Sim, talvez você não deva enviar imediatamente um comando de virada do androide se o ângulo for muito pequeno, mas isso é de alguma forma desajeitado na minha opinião.Resultados do esboço
Um esboço tem apenas três etapas importantes: ler um comando, processar os limites de rotação e fornecer corrente aos motores. Tudo parece simples e, na execução, é mais fácil do que fácil, embora tenha sido criado por um longo tempo e com embotamentos. A versão completa do esboço .No final
Um inventário completo de vários meses de trabalho terminou. A parte física é desmontada, o software ainda mais. O princípio permanece o mesmo - contato para fenômenos incompreensíveis, entenderemos juntos.E os comentários da primeira parte são interessantes, eles recomendaram uma montanha de dicas úteis, obrigado a todos.Resultado Vídeo