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 |
---|---|
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 :
- Lien série sur Bluetooth standard (COM4)
- Lien série sur Bluetooth standard (COM5)
- 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).
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 :
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 }
-
Vous trouverez sur cette page des explications pour changer le nom du module avec la commande « AT+NAME ». ↩
-
En l’occurrence, Windows 10 version 20H2, l’interface ayant quelque peu changé avec le plus récent Windows 11. Nous laissons aux utilisateurs de MAC OS, Android ou Linux le soin trouver la procédure adéquate pour leur terminal. ↩
-
Comme le fait déjà l’interruption externe du bouton utilisateur. ↩