quinta-feira, maio 08, 2014

Comunicação Bluetooth entre Arduino e Android - Um exemplo completo

Para fechar esta série, vejamos um exemplo completo. No lado do Arduino, alguns periféricos e um software que aceita comandos pelo Bluetooth. No lado do Android, uma tela que permite procurar os dispositivos Bluetooth no alcance, se conectar a um deles, enviar comandos ao Arduino e apresentar a resposta.


Arduino - Hardware

Usei um Arduino Nano para facilitar a montagem na protoboard. Além do módulo Bluetooth, coloquei dois LEDs, um sensor de temperatura LM35 e um servomotor.  Os LEDs estão ligados a saídas digitais comuns, o servomotor a uma saída digital com PWM e o sensor de temperatura a uma entrada analógica.

O módulo Bluetooth está ligado à serial normal do Arduino. Isto permite testar o software retirando o módulo, ligando o Arduino ao PC via USB e enviando os comandos através de um programa de comunicação normal (como o monitor serial da IDE do Arduino). Por outro lado, é preciso desconectar o módulo para fazer a carga do software e desconectar do micro para a operação com o módulo.


Arduino - Software

Nada de muito especial por aqui. Os comandos consistem em um único dígito, de 0 a 6. A resposta é a leitura do sensor de temperatura, em décimos de grau, sempre com três dígitos (seguidos de CR e LF). A leitura do sensor está descrita no meu post sobre o LM35. Os LEDs são controlados por digitalWrite e o servo pela biblioteca que vem com a IDE do Arduino.
  1. // Exemplo de comunicação por Bluetooth  
  2.   
  3. #include <Servo.h>  
  4.   
  5. // Alguns dispositivos  
  6. static const int pin_ledr  = 2;  
  7. static const int pin_ledg  = 3;  
  8. static const int pin_servo = 5;  
  9. static const int pin_lm35 = A0;  
  10. static Servo servo;  
  11.   
  12. // Iniciação  
  13. void setup ()  
  14. {  
  15.   // Módulo BT está ligado à serial  
  16.   Serial.begin (9600);  
  17.     
  18.   // Portas digitais  
  19.   pinMode (pin_ledr, OUTPUT);  
  20.   pinMode (pin_ledg, OUTPUT);  
  21.   servo.attach(pin_servo);  
  22.   servo.write(0);  
  23.     
  24.   // Sensor de temperatura LM35  
  25.   analogReference (INTERNAL);  
  26. }  
  27.   
  28. // Loop Principal  
  29. void loop()  
  30. {  
  31.   int cmd;  
  32.     
  33.   if (Serial.available())  
  34.   {  
  35.     // recebeu um comando  
  36.     cmd = Serial.read();  
  37.     if ((cmd < '0') | (cmd > '6'))  
  38.       return;  // ignora comando inválido  
  39.         
  40.     // Trata o comando  
  41.     switch (cmd)  
  42.     {  
  43.       case '0':    // nop  
  44.         break;  
  45.       case '1':    // acende led vermelho  
  46.         digitalWrite (pin_ledr, HIGH);  
  47.         break;  
  48.       case '2':    // apaga led vermelho  
  49.         digitalWrite (pin_ledr, LOW);  
  50.         break;  
  51.       case '3':    // acende led verde  
  52.         digitalWrite (pin_ledg, HIGH);  
  53.         break;  
  54.       case '4':    // apaga led verde  
  55.         digitalWrite (pin_ledg, LOW);  
  56.         break;  
  57.       case '5':    // Coloca o servo na posição 0  
  58.         servo.write(0);  
  59.         break;  
  60.       case '6':    // Coloca o servo na posição 180  
  61.         servo.write(180);  
  62.         break;  
  63.     }  
  64.       
  65.     // Le o sensor de temperatura  
  66.     int vSensor = analogRead(pin_lm35);  
  67.     long temp = (vSensor*1100L)/1024L;  
  68.       
  69.     // Monta e envia a resposta  
  70.     char resp[6];  
  71.     resp[0] = (char) (((temp / 100) % 10) + '0');  
  72.     resp[1] = (char) (((temp / 10) % 10) + '0');  
  73.     resp[2] = (char) ((temp % 10) + '0');  
  74.     resp[3] = '\r';  
  75.     resp[4] = '\n';  
  76.     resp[5] = '\0';  
  77.     Serial.print (resp);  
  78.   }  
  79. }  

Android - UI

Para simplificar, a interface se resume a uma única tela. Nada de muito elaborado (ou bonito), apenas o essencial para testarmos o funcionamento.


Android - Software

O ponto de partida foi o meu exemplo de procura de dispositivos. O principal acréscimo é a thread que cuida da conexão e comunicação. Para fazer a interface entre esta thread e os eventos da UI, usei uma BlockingQueue. O código possui uma detecção de erros bem básica.
  1. private BlockingQueue<byte[]> filaCmd;  
  2.   
  3. // Trata botão Acende Led Vermelho  
  4. private final OnClickListener acendeLedR = new OnClickListener() {  
  5.     @Override  
  6.     public void onClick(View v) {  
  7.         try {  
  8.             filaCmd.put (new byte[] { '1' });  
  9.         } catch (InterruptedException e) {  
  10.         }  
  11.     }  
  12. };  
  13.   
  14. // Trata a conexão e comunicação com o Arduino  
  15. private class ConexaoThread extends Thread {  
  16.     private final BluetoothSocket mmSocket;  
  17.     private InputStream mmInStream;  
  18.     private OutputStream mmOutStream;  
  19.   
  20.     public ConexaoThread(BluetoothDevice device) {  
  21.         BluetoothSocket tmp = null;  
  22.         // Obtem o socket  
  23.         try {  
  24.             tmp = device.createRfcommSocketToServiceRecord(serialUUID);  
  25.         } catch (IOException e) {   
  26.             // tmp ficará com null  
  27.         }  
  28.         mmSocket = tmp;  
  29.     }  
  30.   
  31.     public void run() {  
  32.         // Confirma que obteve o socket  
  33.         if (mmSocket == null) {  
  34.             return;  
  35.         }  
  36.   
  37.         // Garante que não tem um discovery sendo executado  
  38.         mBtAdapter.cancelDiscovery();  
  39.   
  40.         // Tenta conectar  
  41.         try {  
  42.             mmSocket.connect();  
  43.         } catch (IOException connectException) {  
  44.             return// não conseguiu  
  45.         }  
  46.   
  47.         // Obtem os streams de entrada e saída  
  48.         try {  
  49.             mmInStream = mmSocket.getInputStream();  
  50.             mmOutStream = mmSocket.getOutputStream();  
  51.         } catch (IOException e) {   
  52.             try {  
  53.                 mmSocket.close();  
  54.         } catch (IOException closeException) { }  
  55.             return;  
  56.         }  
  57.         bBtConectado = true;  
  58.         acertaTela();  
  59.        
  60.         // Trata a comunicação  
  61.         while (true) {  
  62.             byte[] cmd;  
  63.             try {  
  64.                 cmd = filaCmd.take(); // Aguarda um comando  
  65.                 if ((cmd == null) || (cmd[0] == '*')) {  
  66.                     break;  
  67.                 }  
  68.                 // Envia o comando  
  69.                 mmOutStream.write(cmd);  
  70.                 // Le a resposta (timeout de 3 segundos)  
  71.                 int available = 0;  
  72.                 long limite = System.currentTimeMillis() + 3000;  
  73.                 while(((available = mmInStream.available()) < 5) &&   
  74.                       (System.currentTimeMillis() < limite)) {  
  75.                     Thread.sleep(250);  
  76.                 }  
  77.                 if (available >= 5) {  
  78.                     byte[] resp = new byte[available];  
  79.                     mmInStream.read(resp);  
  80.                     mostraTemp (resp);  
  81.                  }  
  82.             } catch (InterruptedException e) {  
  83.                 break;  
  84.             } catch (IOException e) {  
  85.                 break;  
  86.             }  
  87.         }  
  88.   
  89.         // Encerra a comunicação  
  90.         try {  
  91.             mmInStream.close();  
  92.             mmOutStream.close();  
  93.             mmSocket.close();  
  94.         } catch (IOException closeException) { }  
  95.         bBtConectado = false;  
  96.         acertaTela();  
  97.     }  
  98. }      

Funcionamento

O vídeo abaixo mostra o sistema em funcionamento.



Fontes

Os fontes dos softwares estão nos Arquivos do Blog (veja o link lá no alto à direita), no arquivo DemoBT.zip.

5 comentários:

Unknown disse...
Este comentário foi removido pelo autor.
Unknown disse...

Amigo obrigado pelo esquema, parabéns.
Quero saber o porque dos resistores no rx e tx. É pra garantir nivel logico 0?
E no pwm tambem é necessario?

Daniel Quadros disse...

Leonardo, como descrito em um post anterior o módulo opera a 3.3V, daí o divisor resistivo no pino Rx. O resistor no pino Tx é um pull-up que garante o nível 1. O PWM está ligado direto a um servo e não precisa de nenhum resistor.

Unknown disse...


Muito legal, obrigado.

Foi voce quem fez o programa?

Daniel Quadros disse...

Eduardo: sim.