Réaliser une liaison Bluetooth entre un Microcontrôleur et Android

24 avril 2011
Voici une méthode pour faire communiquer un microcontrôleur avec un smartphone Android pour seulement quelques euros.

La liaison Bluetooth se fait par le profil Bluetooth SPP (Serial Port Profile), qui permet d'émuler une connexion série.
Cette méthode est donc compatible avec n'importe quel microcontrôleur ayant une interface série (PIC, MSP430, Atmel AVR (Arduino), ...).


Matériel

Votre smartphone (ou tout autre dispositif sous Android) doit avoir le bluetooth bien évidement et doit tourner sur Android 2.0 minimum : les fonctions pour l'utilisation bluetooth ne sont présente dans le SDK qu'à partir de l'API Level 5.

Le microcontrôleur doit pouvoir communiquer par une liaison série, aussi appelée RS232, ou UART. Quelques exemples de microcontrôleur : PIC18F2550, PIC18F14K50, PIC18F1320, PIC16F886, ... ou bien chez TI : MSP430G2231, MSP430G2452, ...

Exemple de module bluetooth Enfin, il vous faut un transmetteur Bluetooth, qui sera connecté au microcontrôleur. Vous devez choisir un transmetteur prenant en charge le profil SPP et compatible avec une liaison série. Il existe de nombreux modules, avec une connexion DB9, DIP ou CMS.
Schéma PIC - MAX232 Cliquez pour agrandir

Attention, selon votre microcontrôleur et le module que vous choisissez, vous aurez peut être besoin d'une interface du type MAX232 afin de rendre les tensions compatibles entre elles. Le schéma de droite est un exemple pour interfacer un module bluetooth prévu pour une connexion série sur un PC (prise DB9) qui envoie un signal entre -10V et 10V et un PIC (signaux 0-5V).

Vous pourrez en trouvez à partir de 5€ sur eBay (en recherchant 'module bluetooth rs232'), SparkFun, Lextronic et d'autres revendeurs.


Du côté du microcontrôleur

Un Module Bluetooth relié à un MSP430G2231 Tout d'abord il faut relier le microcontrôleur au module, n'oubliez pas, le câblage est croisé : il faut relier la pin TX du microcontrôleur à la pin RX du module bluetooth et inversement la pin RX avec la TX du module.

Il faut ensuite configurer le microcontrôleur et/ou le module bluetooth afin qu'ils utilisent la même vitesse de connexion, le même baud rate.
Cette vitesse n'intervient pas pour la suite (pour Android) car la liaison bluetooth entre le module et Android se fait par le protocole RFCOMM, qui va beaucoup plus vite que la liaison série. Le module bluetooth se charge de faire la 'conversion' entre ces deux protocoles. Vous pouvez donc choisir le baud rate que vous voulez.

Il n'y a pas de règle spéciale / de protocole pour l'envoi ou la réception des données pour le microcontrôleur, il suffit d'envoyer les données simplement, comme pour une liaison série.


Le programme Android Android

Interface Android-Bluetooth-Microcontrôleur Nous allons créer une classe BtInterface qui regrouperas les fonctions pour envoyer, recevoir, ouvrir et fermer la connexion, ... Pour simplifier la programmation, nous n'allons pas gérer l'association du périphérique dans cette classe, ni l'activation du bluetooth. Il faudra donc passer par les paramètres bluetooth pour associer le périphérique avec Android (Paramètres -> Sans fil et réseaux -> Paramètres Bluetooth -> Rechercher des appareils), et ne pas oubliez d'activer le bluetooth.

Prérequis

Il vous faudra ajouter les permissions BLUETOOTH et BLUETOOTH_ADMIN afin de pouvoir utiliser le bluetooth dans votre application. BLUETOOTH_ADMIN n'est nécessaire que pour la découverte, l'association de périphériques et l'activation/désactivation du bluetooth.
Ajoutez ceci dans l'AndroidManifest.xml :
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />

3 classes spécifiques du SDK sont nécessaires :
  • BluetoothDevice : Classe qui représente un périphérique bluetooth
  • BluetoothAdapter : Classe qui représente le module bluetooth de votre Android, elle permet de scanner les périphériques présents, d'activer / désactiver le bluetooth, ...
  • BluetoothSocket : Classe qui permet d'obtenir les canaux d'écriture (émission) et de lecture (réception) du périphérique
Pour pouvoir utiliser ces classes, il faut donc les importer :
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;

La classe va donc contenir le 'périphérique', sa 'connexion', et ses canaux d'entrée et sortie :
public class BtInterface {
    private BluetoothDevice device = null;// le périphérique (le module bluetooth)
    private BluetoothSocket socket = null;
    private InputStream receiveStream = null;// Canal de réception
    private OutputStream sendStream = null;// Canal d'émission

Première étape : récupérer le BluetoothDevice de notre périphérique, pour cela, on utilise la fonction getBondedDevices() de BluetoothAdapter qui permet d'obtenir la liste des périphériques associés à Android. On récupère le module bluetooth par défaut d'Android avec la fonction BluetoothAdapter.getDefaultAdapter().
Il suffit de chercher dans cette liste notre périphérique. On récupère ensuite le BluetoothSocket de notre périphérique avec la fonction createRfcommSocketToServiceRecord(UUID uuid); Cette fonction prend en paramètre un UUID, on utilise ici l'UUID 00001101-0000-1000-8000-00805F9B34FB, qui indique une connexion 'port série'.
Les canaux d'émission/réception sont ensuite récupérés avec les fonctions getInputStream et getOutputStream.

Le module bluetooth n'a pas besoin d'être sous tension pour utiliser ces fonctions, les canaux seront seulement invalides tant que celui-ci n'est pas connecté.


public BtInterface() {
    
    // On récupère la liste des périphériques associés
    Set<BluetoothDevice> setpairedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
    BluetoothDevice[] pairedDevices = (BluetoothDevice[]) setpairedDevices.toArray(new BluetoothDevice[setpairedDevices.size()]);
    
    // On parcourt la liste pour trouver notre module bluetooth
    for(int i=0;i<pairedDevices.length;i++)
    {
        // On teste si ce périphérique contient le nom du module bluetooth connecté au microcontrôleur
        if(pairedDevices[i].getName().contains("ModuleBluetooth")) {
        
            device = pairedDevices[i];
            try {
                // On récupère le socket de notre périphérique
                socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
                
                receiveStream = socket.getInputStream();// Canal de réception (valide uniquement après la connexion)
                sendStream = socket.getOutputStream();// Canal d'émission (valide uniquement après la connexion)
                
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        }
    }
}

Connexion

Pour se connecter, on utilise la fonction connect(); de BluetoothSocket. La connexion pouvant prendre du temps, il est nécessaire de la lancer dans un autre thread pour éviter de bloquer l'application.
public void connect() {
    new Thread() {
        @Override public void run() {
            try {
                socket.connect();// Tentative de connexion
                // Connexion réussie
            } catch (IOException e) {
                // Echec de la connexion
                e.printStackTrace();
            }
        }
    }.start();
}

Envoi de données

Pour envoyer des données, on va tout simplement utiliser les fonctions write(...) de OutputStream
public void sendData(String data) {
    try {
        // On écrit les données dans le buffer d'envoi
        sendStream.write(data.getBytes());
        
        // On s'assure qu'elles soient bien envoyées
        sendStream.flush();
        
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Réception de données

Pour recevoir des données, le plus simple est de faire un Thread qui va vérifier l'arrivé de données en permanence. On utilise les fonctions read(...) et available() de InputStream pour lire ces données et vérifier si de nouvelles sont disponibles.
Problème : on ne peut afficher les données depuis ce nouveau thread : Android impose de les envoyer dans le thread de l'UI. On utilise donc la classe Handler. On doit alors modifier le constructeur pour récupérer un Handler du thread de l'UI.

private ReceiverThread receiverThread;

public BtInterface(Handler h) {
    ...
    receiverThread = new ReceiverThread(h); // On crée le thread de réception des données avec l'Handler venant du thread UI
    ...
}

public void connect() {
    new Thread() {
        @Override public void run() {
            try {
                socket.connect();// Tentative de connexion
                // Connexion réussie
                
                receiverThread.start(); // On démarre la vérification des données
                
            } catch (IOException e) {
                // Echec de la connexion
                e.printStackTrace();
            }
        }
    }.start();
}

private class ReceiverThread extends Thread {
    Handler handler;
    
    ReceiverThread(Handler h) {
        handler = h;
    }
    
    @Override public void run() {
        while(true) {
            try {
                // On teste si des données sont disponibles
                if(receiveStream.available() > 0)
                {
                    byte buffer[] = new byte[100];
                    
                    // On lit les données, k représente le nombre de bytes lu
                    int k = receiveStream.read(buffer, 0, 100);
                    
                    if(k > 0) {
                        // On convertit les données en String
                        byte rawdata[] = new byte[k];
                        for(int i=0;i<k;i++)
                            rawdata[i] = buffer[i];
                        
                        String data = new String(rawdata);
                        
                        // On envoie les données dans le thread de l'UI pour les afficher
                        Message msg = handler.obtainMessage();
                        Bundle b = new Bundle();
                        b.putString("receivedData", data);
                        msg.setData(b);
                        handler.sendMessage(msg);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Et dans le thread de l'UI, il suffit de créer un Handler et de le faire passer dans le constructeur de notre classe.
final Handler handler = new Handler() {
    public void handleMessage(Message msg) {
        String data = msg.getData().getString("receivedData");
        ... // Affichage de data
    }
};

Déconnexion

Enfin, pour fermer la connexion, il y a la fonction close() de BluetoothSocket :
public void close() {
    try {
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Le programme complet

Voici le code complet (non commenté) de la classe BtInterface et d'une UI très simple pour envoyer et recevoir des données : Télécharger


Catégories