Communication série en Bluetooth

L’ensemble des sketchs de notre projet peuvent être téléchargés ici.

Nous souhaitons à présent disposer d’une connexion sans fil à courte portée pour envoyer des commandes à notre station météo. La technologie toute désignée pour cela est le Bluetooth, avec le module HC-06. La mise en œuvre de ce module est très simple : une fois connecté sur la carte NUCLEO et alimenté, il signale sa présence à tous les récepteurs Bluetooth proches. Il devrait donc être visible depuis votre ordinateur personnel ou smartphone sous le nom que vous lui aurez donné1. Vous devrez alors appairer le module avec votre terminal, en précisant son code PIN, qui par défaut et selon les fabricants sera très probablement soit « 0000 » soit « 1234 ».

Par exemple, un module portant le nom « STATION01 », une fois appairé avec un terminal sur un ordinateur sous Windows2 apparait dans Paramètres / Appareils Bluetooth et autres (figure 2.2).

Figure 2.2 : Les paramètres Bluetooth sous Windows 10 Figure 2.3 : Le gestionnaire de périphériques de Windows 10
Bluetooth Windows 10 Device manager


Le système d’exploitation attribue au module un port de communication virtuel. Nous pouvons consulter le Gestionnaire de périphériques, dans la section Ports (COM et LPT) pour obtenir le port de communication virtuel attribué au HC-06.

La figure 2.3 montre comment se présente la liste des ports COM et LPT. Sous la branche « Ports (COM et LPT) » apparaissent trois entrées :

  1. Lien série sur Bluetooth standard (COM4)
  2. Lien série sur Bluetooth standard (COM5)
  3. STMicroelectronics ST-Link Virtual COM Port (COM6)

La troisième entrée correspond à la connexion physique entre l’USB du PC et l’USB du ST-Link de la carte NUCLEO. Les deux premières entrées sont des connexions Bluetooth exposées par le module HC-06.

Pour identifier sur lequel de ces deux COM est effectivement connecté le module, testez les tour-à-tour avec un logiciel terminal RS232 tel que Termite.

Le sketch « bluetooth_connection.ino » ajoute à « openweather_querying.ino » les fonctions nécessaires pour interagir avec le module HC-06.

Dans les déclarations globales nous avons ajouté les lignes suivantes :

0198 // Déclarations du port série du HC-06
0199 HardwareSerial BT_Serial(PD2, PC12);
0200 // Débit du port série du HC-06 (9600 bds recommandé)
0201 #define BT_BDRATE (9600)
0202 // Nombre maximum de caractères dans les chaînes envoyées au HC-06
0203 #define MAX_LEN (80)
0204 // Buffer d'émission et réception pour BT_Serial
0205 char BT_Buffer[MAX_LEN] = {0};
0206 
0207 // Switchs de gestion des commandes par interruptions
0208 volatile bool sd_card_listing = false;
0209 volatile bool measure_now = false;

Bien que le module HC-06 soit en principe capable de supporter des débits différents de 9600 bauds tous nos tests ont montré qu’il corrompait les données lorsqu’on décidait de le configurer autrement. Nous vous conseillons donc de conserver la valeur de 9600 bauds.

Dans la fonction setup, nous avons ajouté les lignes suivantes :

0269   // Initialise le port série du module HC-06
0270   BT_Serial.begin(BT_BDRATE);
0271   while (!BT_Serial) delay(100);
0272 
0273   Serial.print("Bluetooth serial available, baud rate set to ");
0274   Serial.println(BT_BDRATE);
0275 
0276   // Autorise le module Bluetooth à interrompre le mode veille
0277   LowPower.enableWakeupFrom(&BT_Serial, Bluetooth_Serial_ISR);
0278   Serial.println("Bluetooth module ready to process commands");
0279   BT_Serial.println("Bluetooth module ready to process commands");

La méthode enableWakeupFrom de la classe LowPower (ligne 277) est une extension de l’API Arduino qui permet de sortir le microcontrôleur du mode sommeil3 lorsque l’UART5 correspondant à BT_Serial reçoit des caractères.

La routine de service de l’interruption en réception de BT_Serial est la fonction Bluetooth_Serial_ISR :

1311 void Bluetooth_Serial_ISR() {
1312 
1313   // Si des données arrivent depuis le port série associé au HC-06
1314   if (processSerial(&BT_Serial, BT_Buffer)) {
1315 
1316     Serial.print("\nUser command is ");
1317     Serial.println(BT_Buffer);
1318 
1319     // Si le message est "SD_LIST"...
1320     if (!strcmp(BT_Buffer, "SD_LIST")) {
1321       sd_card_listing = true;
1322     }
1323     // Si le message est "MEASURE"...
1324     else if (!strcmp(BT_Buffer, "MEASURE")) {
1325       measure_now = true;
1326     }
1327     // Autrement, affiche la liste des commandes autorisées
1328     else {
1329       BT_Commands_Listing(&Serial);
1330     }
1331   }
1332 }

A la ligne 1314 est appelée la fonction processSerial qui va agréger les caractères reçus par le HC-06 dans le tableau BT_Buffer, jusqu’à ce que son contenu corresponde à une commande utilisateur. Dans notre exemple, nous avons défini deux commandes utilisateurs : “SD_LIST” et “MEASURE”. Ces commandes sont traitées exactement comme l’appui sur le bouton utilisateur tel que nous l’avons précédemment expliqué ici. Lorsqu’elles sont reçues l’ISR va assigner la valeur « true » à des variables booléennes déclarées avec l’attribut volatile, qui seront ensuite testées dans la fonction loop, laquelle effectuera les opérations attendues.

Pour les cas où la chaîne de caractères envoyée par l’utilisateur depuis son terminal RS232 ne correspond pas à l’une des deux commandes prédéfinies (ligne 1328) on appelle la fonction BT_Commands_Listing qui imprime sur le port série du ST-LINK la liste des fonctions prévues :

1373 void BT_Commands_Listing(HardwareSerial *serial) {
1374   serial->println("\nAllowed commands : ");
1375   serial->println(" MEASURE : force a measurement right now");
1376   serial->println("    Input example : \"MEASURE\"");
1377   serial->println(" SD_LIST : list SD card log file");
1378   serial->println("    Input example : \"SD_LIST\"");
1379 }

Passons à présent à la fonction la plus délicate, processSerial. Voici son code complet :

1339 uint16_t processSerial(HardwareSerial *serial, char* serial_buffer) {
1340 
1341   // Nombre de caractères reçus
1342   uint16_t strLen = 0;
1343 
1344   // Le premier caractère est '\0' (terminaison de chaîne)
1345   serial_buffer[0] = '\0';
1346 
1347   // Aussi longtemps que serial reçoit des caractères...
1348   while (serial->available()) {
1349 
1350     // Lis un caractère
1351     char inChar = (char)serial->read();
1352 
1353     // Si ce caractère est LF ('\n')...
1354     if (inChar == '\n') {
1355       //... ajoute un caractère '\0' à la fin du buffer
1356       serial_buffer[strLen - 1] = '\0';
1357       break; //... quitte la boucle while (fin de réception)
1358     }
1359 
1360     // Autrement, aussi longtemps qu'on ne dépasse pas la taille
1361     // du buffer
1362     else if ((strLen < (MAX_LEN - 1))) {
1363       // ajoute au buffer le dernier caractère reçu
1364       serial_buffer[strLen++] = inChar;
1365     }
1366   }
1367   return strLen; // Renvoie le nombre de caractères lus.
1368 }

Cette fonction, qui accumule les caractères reçus par l’UART dans leur ordre d’arrivée, considère qu’une commande est complète lorsqu’elle reçoit les deux caractères ‘\r’ et ‘\n’ dans cet ordre. A ce moment elle complète le tableau serial_buffer par le caractère de terminaison de chaîne ‘\0’ (ligne 1356). Le tableau serial_buffer qui sera ensuite renvoyé à BT_Buffer dans Bluetooth_Serial_ISR contiendra donc tous les caractères en provenance du terminal RS232 ainsi que le caractère ‘\0’ de terminaison de chaîne en dernier.

Pour conclure notre explication, intéressons-nous au mode opératoire pour envoyer des commandes au module HC-06 avec un terminal RS232. Nous considérons à nouveau le cas d’un utilisateur de Windows et du logiciel de terminal RS232 Termite (figure 2.4).


Terminal RS232 Termite


Le suffixe des trames doit obligatoirement être CR-LF. Toutes les commandes tapées dans le terminal RS232 se verront donc ajouter les deux caractères ‘\r’ et ‘\n’, une séquence qui correspond à la convention pour les sauts de lignes sur les systèmes d’exploitation Windows.

La séquence de transmission entre le terminal RS232 et la station météo est la suivante :


Séquence de transmission 1



Séquence de transmission 2



Séquence de transmission 3


La fonction loop, comporte peu de modifications, juste les lignes 340 à 351 qui traitent les commandes arrivant par le Bluetooth :

0286 void loop() {
0287 
0288   if (rtc_mod == SET_RTC_MOD) { // Toutes les SET_RTC_MOD itérations...
0289 

0307   } // Clôture de if (rtc_mod == SET_RTC_MOD)
0308 
0309   rtc_mod++;
0310 
0311   if (rec_mod == SET_REC_MOD) { // Toutes les SET_REC_MOD itérations...

0329     }
0330 
0331     // Eteint la LED utilisateur
0332     digitalWrite(LED_BUILTIN, LOW);
0333 
0334   } // Clôture de if (rec_mod == SET_REC_MOD)
0335 
0336   rec_mod++;
0337 
0338   // Gestion des commandes envoyées par Bluetooth
0339 
0340   // Liste des trames MQTT enregistrées sur la carte SD
0341   if (sd_card_listing) {
0342     LogFile_List(logfilename, &Serial);
0343     sd_card_listing = false;
0344   }
0345 
0346   // Force la station à effectuer une mesure
0347   else if (measure_now) {
0348     Serial.println("User triggered measurement");
0349     rec_mod = SET_REC_MOD;
0350     measure_now = false;
0351   }


0361   LowPower.deepSleep(main_loop_delay);
0362 }
  1. Vous trouverez sur cette page des explications pour changer le nom du module avec la commande « AT+NAME ». 

  2. En l’occurence, Windows 10 version 20H2, l’interface ayant quelque peu changé avec le plus réçent Windows 11. Nous laissons aux utilisateurs de MAC OS, Android ou Linux le soin trouver la procédure adéquate pour leur terminal. 

  3. Comme le fait déjà l’interruption externe du bouton utilisateur.