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.
// Exemplo de comunicação por Bluetooth

#include <Servo.h>

// Alguns dispositivos
static const int pin_ledr  = 2;
static const int pin_ledg  = 3;
static const int pin_servo = 5;
static const int pin_lm35 = A0;
static Servo servo;

// Iniciação
void setup ()
{
  // Módulo BT está ligado à serial
  Serial.begin (9600);
  
  // Portas digitais
  pinMode (pin_ledr, OUTPUT);
  pinMode (pin_ledg, OUTPUT);
  servo.attach(pin_servo);
  servo.write(0);
  
  // Sensor de temperatura LM35
  analogReference (INTERNAL);
}

// Loop Principal
void loop()
{
  int cmd;
  
  if (Serial.available())
  {
    // recebeu um comando
    cmd = Serial.read();
    if ((cmd < '0') | (cmd > '6'))
      return;  // ignora comando inválido
      
    // Trata o comando
    switch (cmd)
    {
      case '0':    // nop
        break;
      case '1':    // acende led vermelho
        digitalWrite (pin_ledr, HIGH);
        break;
      case '2':    // apaga led vermelho
        digitalWrite (pin_ledr, LOW);
        break;
      case '3':    // acende led verde
        digitalWrite (pin_ledg, HIGH);
        break;
      case '4':    // apaga led verde
        digitalWrite (pin_ledg, LOW);
        break;
      case '5':    // Coloca o servo na posição 0
        servo.write(0);
        break;
      case '6':    // Coloca o servo na posição 180
        servo.write(180);
        break;
    }
    
    // Le o sensor de temperatura
    int vSensor = analogRead(pin_lm35);
    long temp = (vSensor*1100L)/1024L;
    
    // Monta e envia a resposta
    char resp[6];
    resp[0] = (char) (((temp / 100) % 10) + '0');
    resp[1] = (char) (((temp / 10) % 10) + '0');
    resp[2] = (char) ((temp % 10) + '0');
    resp[3] = '\r';
    resp[4] = '\n';
    resp[5] = '\0';
    Serial.print (resp);
  }
}

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.
    private BlockingQueue<byte[]> filaCmd;

    // Trata botão Acende Led Vermelho
    private final OnClickListener acendeLedR = new OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                filaCmd.put (new byte[] { '1' });
            } catch (InterruptedException e) {
            }
        }
    };

    // Trata a conexão e comunicação com o Arduino
    private class ConexaoThread extends Thread {
        private final BluetoothSocket mmSocket;
        private InputStream mmInStream;
        private OutputStream mmOutStream;
  
        public ConexaoThread(BluetoothDevice device) {
            BluetoothSocket tmp = null;
            // Obtem o socket
            try {
                tmp = device.createRfcommSocketToServiceRecord(serialUUID);
            } catch (IOException e) { 
                // tmp ficará com null
            }
            mmSocket = tmp;
        }
  
        public void run() {
            // Confirma que obteve o socket
            if (mmSocket == null) {
                return;
            }

            // Garante que não tem um discovery sendo executado
            mBtAdapter.cancelDiscovery();

            // Tenta conectar
            try {
                mmSocket.connect();
            } catch (IOException connectException) {
                return; // não conseguiu
            }
  
            // Obtem os streams de entrada e saída
            try {
                mmInStream = mmSocket.getInputStream();
                mmOutStream = mmSocket.getOutputStream();
            } catch (IOException e) { 
                try {
                    mmSocket.close();
            } catch (IOException closeException) { }
                return;
            }
            bBtConectado = true;
            acertaTela();
         
            // Trata a comunicação
            while (true) {
                byte[] cmd;
                try {
                    cmd = filaCmd.take(); // Aguarda um comando
                    if ((cmd == null) || (cmd[0] == '*')) {
                        break;
                    }
                    // Envia o comando
                    mmOutStream.write(cmd);
                    // Le a resposta (timeout de 3 segundos)
                    int available = 0;
                    long limite = System.currentTimeMillis() + 3000;
                    while(((available = mmInStream.available()) < 5) && 
                          (System.currentTimeMillis() < limite)) {
                        Thread.sleep(250);
                    }
                    if (available >= 5) {
                        byte[] resp = new byte[available];
                        mmInStream.read(resp);
                        mostraTemp (resp);
                     }
                } catch (InterruptedException e) {
                    break;
                } catch (IOException e) {
                    break;
                }
            }

            // Encerra a comunicação
            try {
                mmInStream.close();
                mmOutStream.close();
                mmSocket.close();
            } catch (IOException closeException) { }
            bBtConectado = false;
            acertaTela();
        }
    }    

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:

Leonardo Rodrigues Ventura disse...
Este comentário foi removido pelo autor.
Leonardo Rodrigues Ventura 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.

Eduardo Castellani disse...


Muito legal, obrigado.

Foi voce quem fez o programa?

Daniel Quadros disse...

Eduardo: sim.