Communiquer sans fil avec un module nRF24L01, la bibliothèque Mirf et une carte Arduino / Genuino

Allo? Quelqu'un m'entend ?

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Tutoriels Arduino | Mots clefs : Arduino Genuino Sans fil nRF24L01 Nordic RF

Cet article a été modifié pour la dernière fois le


L'époque des communications câblées est révolue. Aujourd'hui, de plus en plus de communications se font sans fil, que ce soit pour le meilleur ou pour le pire. Dans ce tutoriel, nous allons aborder ensemble un sujet complexe : la communication entre cartes électroniques. Nous nous intéresserons à un duo de choc, aussi classique qu'efficace : le module radio nRF24L01 et la bibliothèque Arduino Mirf. En bonus, nous verrons comment fabriquer une sonnette rudimentaire, pour le plus grand bonheur des amateurs de DIY et de domotique.

Sommaire

Bonjour à toutes et à tous !

Quand on entreprend de mettre en relation plusieurs cartes électroniques, on se pose toujours la même question : avec ou sans câble ?

La mise en réseau de plusieurs cartes électroniques au moyen de câbles est une solution fiable, peu coûteuse et relativement simple à mettre en oeuvre. Un simple faisceau de fils permet de mettre en relation plusieurs cartes au sein d'un même montage. Pour de longues distances, un module RS485, un câble blindé et quelques borniers suffisent à couvrir de (très) longues distances.

Cependant, les câbles ont un gros défaut. Il faut les câbler. Câbler deux cartes électroniques au sein d'un même montage est trivial, car les longueurs de câbles sont réduites. Certes, parfois le résultat final n'est pas très propre, mais avec un peu de patience, on arrive généralement à tout câbler et à tout faire passer dans une jolie boite.

Maintenant imaginez-vous devoir creuser une tranchée de plus 50cm de profondeur, sur plusieurs dizaines de mètres pour simplement relier un capteur de température de votre jardin à votre station météo dans le salon. On est d'accord, ce n'est pas terrible comme activité familiale pour le week-end.

Dans ce genre de situation, les modules radio sont un pur bonheur. Pas de câble, pas de tranchée, pas de prise de tête. Certes, il y a de nombreux points négatifs comme la consommation électrique, la sécurité, les interférences, le temps de propagation et la portée du signal, etc. Mais entre se creuser la tête quelques heures pour mettre en place un système sans fil relativement propre et creuser littéralement pendant plusieurs heures, le choix est vite fait. Surtout que parfois, il n'est tout simplement pas possible de faire passer le moindre câble.

Dans cet article, nous allons-nous intéresser à un duo bien connu des amateurs d'Arduino : le module radio nRF24L01 et la bibliothèque Mirf.

Le module nRF24L01

Avant de commencer ce tutoriel, intéressons-nous d'abord au module radio lui-même.

Le module radio nRF24L01 est un module radio tout intégré du fabricant Nordic Semiconductor. Il s'agit d'un module radio intégrant tout le nécessaire pour émettre et recevoir des données sur la gamme de fréquences de 2.4GHz (comme le WiFi ou le Bluetooth) en utilisant le protocole de communication propriétaire de Nordic nommée "ShockBurst". Ce protocole de communication permet au nRF24L01 d'être considéré comme un modem complet, avec adressage, gestion des erreurs de transmission et retransmission automatique en cas de non-réponse du destinataire.

N.B. Le module nRF24L01 utilise la même gamme de fréquences que le WiFi et le Bluetooth, mais n'est pas compatible avec ceux deux protocoles !

PS L'utilisation du protocole ShockBurst est intégrée dans le hardware du module et n'est pas désactivable. Ceci n'est pas un problème pour 99,9999% des utilisateurs qui cherchent juste à transmettre des données et/ou des commandes. C'est même dans ce cas un énorme avantage. Si vous souhaitez faire un espion radio ou un module de transmission avec votre propre protocole de communication radio (couche physique du modèle OSI), passez votre chemin, le nRF24L01 n'est pas conçu pour cela.

Il existe deux versions du module nRF24L01 : la version classique et la version "+". Le nRF24L01+ est la nouvelle révision du chipset radio. Cette nouvelle version apporte énormément d'amélioration au module radio.

N.B. Si vous avez le choix entre une version classique et une version "+", choisissez toujours la version "+". Les deux versions sont compatibles d'un point de vue logiciel, mais d'un point de vue physique, la version "+" est bien plus intéressante. La version "+" a l'avantage d'avoir plus de mémoire, plusieurs canaux de communication simultanés et une fonction de renvoi automatique en cas de non-réponse du destinataire. La version "+" a aussi une plus grande portée, une meilleure sensibilité en réception et un débit plus important en transmission. Bref, utilisez la version "+" sans vous poser de question.

PS Vous trouverez peut-être sur Internet des références au module nRF2401A. Il s'agit d'une très vieille version du module radio nRF24L01 qui n'est plus du tout d'actualité aujourd'hui.

Module nRF24L01+ de Sparkfun avec antenne céramique intégrée

Module nRF24L01+ de Sparkfun avec antenne céramique intégrée

Parmi la multitude de cartes embarquant le module nRF24L01+, j'ai décidé d'utiliser une carte avec une antenne céramique intégrée de Sparkfun, à la fois pour sa simplicité de câblage, son antenne et son régulateur 3.3 volts intégré. Ce régulateur de tension intégré permet de relier le module directement sur une carte compatible Arduino, 3.3 volts ou 5 volts sans se poser de question.

Avec cette carte, il est possible de communiquer sur une distance de ~100 mètres à 250Kbps (bits par seconde, en effectif cela donne un débit de quelques kilo-octets par seconde). De plus, avec le régulateur de tension intégré, il est possible d'alimenter la carte avec une tension d'entrée comprise entre 3.3 et 7 volts (maximum), ce qui rend ce module utilisable avec n'importe quelle carte Arduino ou compatible d'un point de vue électrique.

PS Le débit maximum du nRF24L01+ est de 2Mbps, mais à cette vitesse la portée n'est que de quelques mètres. La portée dépend beaucoup de l'antenne. Une antenne "trace" (directement gravé sur la carte) n'a pas une grande portée, une antenne céramique a plus de portée, le mieux est bien évidemment d'utiliser une "vraie" antenne avec un connecteur SMA (pas de vis plaqué or).

Pour conclure cette petite introduction technique, j'ajouterai que le nRF24L01+ dispose de 32 octets de mémoire en émission et en réception par canaux de communication, pour un total de 6 canaux de communication simultanés. La taille maximum d'un paquet est de 32 octets, ce qui est assez faible, mais largement suffisant pour transmettre des mesures, des commandes ou des informations de télémétries pour un robot par exemple.

Les modules nRF24L01 fonctionnent sur la plage 2400 ~ 2525MHz, découpée en 128 canaux différents. Attention donc aux bornes WiFi ou Bluetooth trop puissantes ou beaucoup trop proche, il peut y avoir des interférences. Dans la pratique, à moins de mettre le module nRF24L01 au-dessus de sa box Internet, il n'y a pas de problème.

Module nRF24L01+ chinois avec antenne trace

Module nRF24L01+ chinois avec antenne "trace"

Autre petite précision, sur eBay et autres sites de vente "made in china", vous trouverez en grande quantité des modules nRF24L01+ comme celui en photo ci-dessus.

Ces modules sont assez standard, mais ne disposent pas de régulateur de tension intégré, il faut donc les connecter obligatoirement à une alimentation 3.3 volts. De plus, l'antenne "trace" n'est pas des plus efficace. Mais à quelques dollars pièce, frais de port compris, cela vaut le coup.

N'ayant pas trouvé de composant Fritzing pour les schémas des chapitres un peu plus bas, j'ai dû utiliser un de ces modules chinois pour l'explication du câblage. Cependant, le composant Frtizing n'est pas de moi, je l'ai trouvé sur github.

J'espère que vous ne me tiendrez pas rigueur pour cette petite différence au niveau du croquis de câblage. Les broches ne sont pas aux mêmes endroits, mais sur le module de Sparkfun, chaque broche est annotée avec son nom, contrairement aux modules chinois. Il suffit donc de faire preuve d'un peu de bon sens pour retrouver son chemin ;)

Pour les utilisateurs de module chinois, coup de chance pour vous, le croquis de câblage vous sera servi sur un plateau d'ici quelques chapitres.

Brochage du module nRF24L01+ de Sparkfun

Brochage du module nRF24L01+ de Sparkfun

Le module radio nRF24L01 communique avec son maitre au moyen d'un bus SPI. On retrouve donc au niveau du brochage les trois broches communes au bus SPI, à savoir : MISO (données esclaves -> maitre), MOSI (données maitre -> esclaves) et SCK (horloge), de même que deux broches pour la sélection du module sur le bus SPI et son activation / passage en veille, respectivement : CSN et CE.

Le module radio nRF24L01 dispose aussi d'une broche d'interruption nommée IRQ (active à LOW), permettant d'avertir le microcontrôleur maitre qu'un nouveau paquet de données vient d'être reçu. Avec cette fonctionnalité, il est possible de faire des applications basse consommation fonctionnant sur batterie ou de la programmation événementielle. Cela sera surement le sujet d'un prochain article.

N.B. Les broches du nRF24L01 sont compatibles 5 volts, même si le module lui-même fonctionne uniquement en 3.3 volts. N'ayez pas d'inquiétude, il n'est pas nécessaire d'ajouter une quelconque forme d'adaptateur de tension ou autre.

La bibliothéque Mirf

Le chapitre sur le module nRF24L01 ayant quelque peu débordé, je vais essayer de faire au plus court pour ce chapitre.

La bibliothèque Mirf permet de contrôler rapidement et facilement des modules nRF24L01. Celle-ci est conçue de manière très simpliste, sans extra. Le gros du travail est laissé à la charge du module radio lui même.

La bibliothèque Mirf se contente juste d'exposer des fonctions de haut niveau pour communiquer avec le module radio sans se prendre la tête.

La bibliothèque Mirf est disponible sur GitHub. Les instructions d'installation (en anglais) sont disponibles sur le site arduino.cc.

Attention à la compatibilité

La bibliothèque Mirf est un peu ancienne est a quelque peu vieillie (4 ans sans mise à jour, ça fait un certain temps).

Dans un prochain article (celui-ci sera disponible en bas de page le moment venu), je vous présenterai une autre bibliothèque nommée RF24, plus complexe et plus "lourde", mais compatible avec beaucoup plus de cartes Arduino (mais pas que).

Pour les utilisateurs de cartes Aduino Due, vous pouvez utiliser la bibliothèque Mirf simplement en modifiant la ligne :

1
SPI.setClockDivider(SPI_2XCLOCK_MASK); 

Dans le fichier MirfHardwareSpiDriver.cpp par :

1
2
3
4
5
#ifndef DUE
SPI.setClockDivider(10);
#else
SPI.setClockDivider(SPI_2XCLOCK_MASK);
#endif 

Le montage de démonstration

Afin de tester la bibliothèque Mirf, nous allons réaliser ensemble un petit montage de démonstration. Celui-ci servira aussi de base pour le chapitre bonus ;)

Photographie du matériel nécessaire à la réalisation du montage de démonstration de la bibliothèque Arduino Mirf

Matériel nécessaire

Pour réaliser ce montage, il va nous falloir :

  • Deux cartes Arduino UNO (et deux câbles USB),

  • Deux modules nRF24L01+,

  • Deux plaques d'essai et des fils pour câbler notre montage.

Vue prototypage du montage de démonstration de la bibliothèque Arduino Mirf

Vue prototypage du montage

Vue schématique du montage de démonstration de la bibliothèque Arduino Mirf

Vue schématique du montage

N.B. Le montage expliqué ci-dessous est à réaliser en deux exemplaires pour pouvoir tester l'envoi et la réception simultanée de données.

Pour commencer notre montage, nous allons câbler la broche VCC du module radio à l'alimentation de la carte Arduino au moyen d'un fil. Dans le cas d'un module radio avec régulation de tension intégré (comme c'est le cas avec le module de Sparkfun), il convient de relier la broche VCC du module radio à la broche 5V de la carte Arduino. Dans le cas d'un module radio sans régulateur de tension intégré (comme c'est le cas avec les modules chinois), il convient de relier la broche VCC du module radio à la broche 3V3 de la carte Arduino.

Attention aux Arduino pro et pro mini

Certaines cartes Arduino, comme les cartes Arduino Pro 5v et Arduino Pro mini 5v ne disposent pas de régulateur 3.3 volts, seulement d'un régulateur 5 volts. Avec ces cartes, la broche 3V3 est reliée directement à la broche 5V.

Alimenter un module radio 3.3 volts avec une tension de 5 volts le détruira immédiatement. Vérifiez la tension sur la broche 3V3 avec un multimètre si vous avez un doute avant de câbler votre module radio.

On relie ensuite la broche GND du module radio à la broche GND de la carte Arduino.

Photographie du montage de démonstration de la bibliothèque Arduino Mirf

Le montage fini

Pour terminer, il faut relier les autres broches du port SPI, en suivant le tableau ci-dessous :

Module radio

Carte Arduino

IRQ

D8 (ou non connecté, peu importe pour ce tutoriel)

CE

D9

CSN

D10

MOSI

D11

MISO

D12

SCK

D13

N.B. Les broches IRQ, CE et CSN peuvent être câblées sur d'autres broches que D8, D9 et D10 en fonction des besoins du projet. J'ai choisi d'utiliser une série de broches qui se suivent par purs soucis de simplicité. Cependant, les autres broches sont obligatoirement câblées sur D11, D12 et D13.

Photographie des deux cartes finies du montage de démonstration de la bibliothèque Arduino Mirf

Et de deux !

Une fois le câblage fini, il ne reste plus qu'à tout recommencer une seconde fois pour avoir au moins deux cartes de test.

Astuce de bricoleur : les cartes Arduino UNO font exactement la même largeur qu'une plaque de prototypage classique. Un bête élastique permet donc d'obtenir une carte de test tout-en-un qui tient dans la main et que l'on peut balader avec soi pour faire des tests de portée par exemple.

PS Si vous voulez augmenter la durée de vie du module radio, vous pouvez mettre des résistances de 10K ohms entre les broches CE, CSN, MOSI et SCK du module radio et la carte Arduino. Ces résistances limiteront le courant en entrée du module radio et permettront au module de supporter plus facilement la tension de 5 volts sur les broches en provenance de la carte Arduino. Cela est cependant totalement optionnel.

Utilisation de la bibliothèque Mirf

Une fois la bibliothèque Mirf installée, il suffit d'ajouter ces quelques lignes en début de programme pour l'utiliser :

1
2
3
4
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI (ne cherchez pas à comprendre)

Ensuite dans la fonction setup(), il suffit d'appeler quelques fonctions pour initialiser le module radio (on verra le détail dans le chapitre suivant) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void setup() {

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = 32; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
}

Le premier bloc de code permet d'initialiser la bibliothèque elle-même en fournissant les numéros de broches des broches CE et CSN.

Le second bloc de code permet de choisir le canal de fréquence et la taille des messages qui vont être transmis. Cette taille est fixe et doit être fournie à l'initialisation du module radio.

Le troisième et dernier bloc de code permet de choisir l'adresse d'émission (TADDR) et de réception (RADDR) du module.

Chaque adresse fait 5 octets de long, cela donne 1 099 511 627 776 adresses possibles par canal de fréquence. Autant dire qu'il y a de la marge.

Les variables et fonctions de la bibliothèque Mirf

Afin de comprendre comment marche la bibliothèque Mirf, nous allons voir ensemble les différentes variables et fonctions qu'elle contient.

Mirf.cePin : Cette variable permet de définir la broche a utiliser pour la broche CE du module radio (par défaut D8).

Mirf.csnPin : Cette variable permet de définir la broche à utiliser pour la broche CSN du module radio (par défaut D7).

Mirf.channel : Cette variable permet de définir le canal de fréquence à utiliser pour l’émission et la réception (de 0 à 127, par défaut 1).

Mirf.payload : Cette variable permet de définir la taille des « payload » (messages) à transmettre, au maximum 32 octets (par défaut 16 octets).

N.B. Cette constante doit être la même pour tous les modules qui dialogueront ensemble.

Mirf.spi : Toujours assigner la valeur &MirfHardwareSpi à cette variable. Ne cherchez pas à comprendre, il s'agit d'une fonctionnalité avancée que personne n'utilise.

Mirf.init() : Initialise la bibliothèque Mirf avec les valeurs des variables Mirf.cePin, Mirf.csnPin et Mirf.spi définies ci-dessus.

Mirf.config() : Configure le module radio avec les valeurs de Mirf.channel et Mirf.payload. Active la partie réception du module et vide la mémoire du buffer de réception.

Mirf.send(valeur) : Permet de transmettre un message à un module radio distant. La valeur donnée en paramètre de la fonction doit être un tableau d'octets (bytes*) d'une taille égale à la valeur assignée dans Mirf.payload (voir chapitres suivants pour les exemples d'utilisation).

Mirf.setRADDR(adresse) : Permet de choisir l'adresse (tableau de 5 octets) de réception de ce module radio. C'est via cette adresse qu'il sera possible d'envoi un message à la carte Arduino depuis une autre carte Arduino distante.

N.B. Il est possible de changer l'adresse de réception dynamiquement à tout moment lors de l'exécution du code. Cela rend possible l'implémentation de topologie réseau complexe.

Mirf.set TADDR(adresse) : Permet de choisir l'adresse (tableau de 5 octets) d'émission pour le prochain message qui sera transmis avec la fonction Mirf.send().

N.B. Comme pour l'adresse de réception, il est possible de changer l'adresse d'émission dynamiquement à tout moment lors de l'exécution du code. Cela rend possible la communication avec un nombre arbitrairement grand de module radio sur la même fréquence.

Mirf.dataReady() : Retourne vrai (true) si un nouveau message a été reçu, faux (false) sinon.

Mirf.isSending() : Retourne vrai (true) si un message est en cours d’émission, faux (false) sinon.

Mirf.rxFifoEmpty() : Retourne vrai (true) si le buffer de réception est vide, faux (false) sinon.

Mirf.txFifoEmpty() : Retourne vrai (true) si le buffer de transmission est vide, faux (false) sinon.

Mirf.getData(data) : Copie le dernier message reçu dans le tableau d'octets data passé en paramètre. Le tableau d'octets doit faire la même taille que Mirf.payload.

Mirf.getStatus() : Retourne la valeur brute du registre status du module radio.

N.B. Le registre status du nRF24L01 donne énormément d’informations additionnelles qui ne sont pas disponibles via des fonctions de la bibliothèque Mirf, par exemple la gestion des renvois automatiques, la gestion des échecs d’envoi, l’encombrement du canal de fréquence, etc. Comme il s'agit de fonctionnalités vraiment avancées, je ne donnerai pas plus de détails dans ce tutoriel.

Mirf.powerUpRx() : Active la partie réception du module radio si celle-ci a été désactivée manuellement avec Mirf.powerDown().

Mirf.powerUpTx() : Active la partie émission du module radio si celle-ci a été désactivée manuellement avec Mirf.powerDown().

Mirf.powerDown() : Met le module radio en veille. Celui-ci ne sera plus capable de recevoir ou d'émettre de messages, mais il ne consommera quasiment plus de courant. Cette fonction est très pratique pour des applications "basse consommation", comme des systèmes de mesure ou des boutons sans fil.

PS Un exemple de code utilisant cette fonctionnalité est disponible dans les codes d'exemples fournis avec la bibliothèque Mirf.

Émission et réception

Le plus compliqué avec les modules nRF24L01 est la gestion des tailles de paquets en émission et en réception, car la taille des paquets est fixe.

Pour que tout fonctionne correctement, il faut choisir une taille de paquet et s'y tenir pour tout les modules nRF24L01 susceptibles de communiquer entre eux. Si vous ne savez pas quelle taille va faire les données, utilisez la taille maximum de 32 octets comme valeur pour la variable Mirf.payload.

L'envoi d'un paquet de données se fait en trois étapes :

1
2
3
4
5
6
byte paquet[32];
strcpy(paquet, "Hello World!"); // Préparation du paquet

Mirf.send(paquet); // Envoi du paquet

while(Mirf.isSending()); // Attente de la fin de l'envoi
  1. Mettre en forme les données pour que le tout donne un joli tableau de Mirf.payload octets.

  2. Envoyer le paquet avec Mirf.send().

  3. Attendre la fin de l'envoi avec une boucle autour de Mirf.isSending().

La réception est plus simple :

1
2
3
4
5
6
7
byte paquet[32];

while(!Mirf.dataReady()){ // On attend de recevoir quelque chose
  // On attend ...
}

Mirf.getData(paquet); // Réception du paquet
  1. On prépare un tableau d'octets suffisamment grand pour recevoir un paquet de données.

  2. On attend de recevoir un paquet avec une boucle autour de Mirf.dataReady().

  3. On copie les données du paquet reçu dans le tableau d'octets avec Mirf.getData().

Codes d'exemples

Comme je sais que beaucoup d'entre vous doivent se poser des questions du style "mais comment j'envoie un long / int / char / float ?", "comment je peux faire pour allumer une LED à distance", etc. Je vous propose quelques codes d'exemples qui devraient couvrir une grande majorité des cas d'usage classiques de la bibliothèque Mirf.

Ping pong

Voici un code de test ultra simple : le client envoie un message que le serveur reçoit puis renvoie au client.

Le message n'est rien d'autre que le temps en millisecondes au moment de l'envoi du message, cela permet de savoir combien de temps il faut au message pour faire l'allez retour client-serveur.

Ce code permet de tester trois points :

  • le bon fonctionnement du montage,

  • la qualité du signal (via le temps d'aller-retour),

  • la portée de l'antenne (simplement en regardant revenir les réponses).

Le client :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
 * Exemple de code pour la bibliothèque Mirf – Client Ping Pong
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI
 
void setup() {
  Serial.begin(9600);
   
  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = sizeof(long); // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
   
  Serial.println("Go !"); 
}
 
void loop() {
  unsigned long time_message = millis(); // On garde le temps actuel retourné par millis()
   
  Serial.print("Ping ... ");
  Mirf.send((byte *) &time_message); // On envoie le temps actuel en utilisant une astuce pour transformer le long en octets
  while(Mirf.isSending()); // On attend la fin de l'envoi
   
  // Attente de la réponse
  while(!Mirf.dataReady()) { // On attend de recevoir quelque chose
    if (millis() - time_message > 1000 ) { // Si on attend depuis plus d'une seconde
      Serial.println("Pas de pong"); // C'est le drame ...
      return;
    }
  }
  
  // La réponse est disponible
  Mirf.getData((byte *) &time_message); // On récupère la réponse
  
  // On affiche le temps de latence (sans division par deux)
  Serial.print("Pong: ");
  Serial.print(millis() - time_message); 
  Serial.println("ms");
  
  // Pas besoin de tester plus d'une fois par seconde
  delay(1000);
} 

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Le code de la fonction setup() permet d'initialiser le port série à une vitesse de 9600 bauds. Ce code initialise aussi le module radio pour utiliser les broches D9 et D10, ainsi que le port SPI matériel de la carte Arduino. Le code est configuré pour utiliser le canal de fréquence n°1 et des paquets de la taille d'un long (nombre entier sur 4 octets).

Le code de la fonction loop() récupère le temps courant en millisecondes, l'envoi à la carte Arduino distante et attend une réponse. Si la réponse arrive en moins d'une seconde, le temps de l'allez retour est affiché. Si la réponse met plus d'une seconde à arriver, un message d'erreur est affiché.

PS Certains auront remarqué cette étrange chose dans le programme : (byte *) &time_message. Il s'agit d'un cast de pointeur, sans entrer dans les détails, il s'agit d'une méthode pour dire au compilateur "oui je sais ce n'est pas un tableau d'octets, mais fait comme ci c'était le cas".

Le serveur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * Exemple de code pour la bibliothèque Mirf – Serveur Ping Pong
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI

void setup() {
  Serial.begin(9600);

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = sizeof(long); // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf01"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf02"); // Adresse de réception

  Serial.println("Go !"); 
}

void loop() {
  byte message[sizeof(long)];

  if(!Mirf.isSending() && Mirf.dataReady()){
    Serial.println("Ping !");
    Mirf.getData(message); // Réception du paquet
    Mirf.send(message); // Et on le renvoie tel quel
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Du côté serveur, le code de la fonction setup() est quasiment identique à celui du client. Seules les adresses d'émission et de réception sont inversées.

La fonction loop() par contre est complètement différente. Elle attend un message en faisant attention de ne pas traiter un nouveau message pendant qu'un autre est en cours d'envoi, récupère le message et le renvoi à son expéditeur dans la foulée.

Si l'on teste ce code avec le montage de démonstration, sans grande surprise, le montage marche et le ping-pong dure moins d'une milliseconde (signe d'une très bonne qualité de signal, ce qui est normal vu que les cartes sont à moins de 10cm l'une de l'autre) :

1
2
3
4
5
6
7
8
9
Go !
Ping ... Pong: 1ms
Ping ... Pong: 1ms
Ping ... Pong: 1ms
Ping ... Pong: 1ms
Ping ... Pong: 1ms
Ping ... Pong: 1ms
Ping ... Pong: 1ms
Ping ... Pong: 1ms

Envoi de texte

Deuxième code d'exemple, le but ici est d'envoyer et de recevoir une chaine de caractères de taille variable.

Le texte saisi dans le moniteur série du client se retrouve affiché dans le moniteur série du serveur. La taille maximum du texte est limitée par la taille maximum d'un paquet, soit 32 octets.

Le client (envoi) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Exemple de code pour la bibliothèque Mirf – Client d'envoi de texte
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI
 
void setup() {
  Serial.begin(9600);
   
  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = 32; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
   
  Serial.println("Go !"); 
}
 
void loop() {
  byte message[32];
  
  // Lit un message de maximum 32 caractères depuis le port série
  int len = Serial.readBytesUntil('\n', (char*) message, 31);
  if (!len) {
    return; // Pas de message
  }
  message[len] = '\0'; // Ferme la chaine de caractères
  
  Mirf.send(message); // On envoie le message
  while(Mirf.isSending()); // On attend la fin de l'envoi
} 

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Le code de la fonction setup() est identique au code du client du code précédent. La seule différence est la taille d'un paquet qui est fixé à 32 octets (le maximum).

Le code de la fonction loop() fait trois choses :

  • le code attend que l'utilisateur finisse de saisir une chaine de caractères dans le moniteur série.

    N.B. La chaine de caractères est lue en mode octets, il est donc nécessaire de fermer cette chaine de caractère par un caractère vide '\0' en fin de chaine.

  • le code envoie la chaine de caractères,

  • Pour finir, le code attend que la transmission se termine.

Le serveur (réception) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Exemple de code pour la bibliothèque Mirf – Serveur d'envoi de texte
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI

void setup() {
  Serial.begin(9600);

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = 32; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf01"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf02"); // Adresse de réception

  Serial.println("Go !"); 
}

void loop() {
  byte message[32];

  if(Mirf.dataReady()){
    Mirf.getData(message); // Réception du paquet
    Serial.println((char*) message); // Affiche le message
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Même code pour la fonction setup(), à l'exception de l'inverse des adresses d'émission et de réception.

Pour le code de la fonction loop(), rien de bien extraordinaire. Le code attend un paquet de données, le copie en mémoire avec Mirf.getData() et l'affiche sous forme de texte dans le moniteur série.

Envoi de variable

Pour ce code d'exemple, imaginons que l'on veut transmettre un nombre à virgule (un float) qui provient d'une mesure d'un capteur. Le but est uniquement de transmettre ce float, rien d'autre.

N.B. J'utilise un float dans cet exemple, mais cela peut être n'importe quel type de variable. Cela inclut tous les types de base (int, long, char, etc.), les tableaux de valeurs, mais aussi les structures.

Le code du client :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Exemple de code pour la bibliothèque Mirf – Client d'envoi de variable
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI
 
void setup() {
  Serial.begin(9600);
   
  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = sizeof(float); // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
   
  Serial.println("Go !"); 
}
 
void loop() {
  
  // Lit un nombre depuis le port série
  float valeur = Serial.parseFloat();
  
  Mirf.send((byte *) &valeur); // On envoie le message
  while(Mirf.isSending()); // On attend la fin de l'envoi
} 

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Dans la fonction setup(), on utilise la fonction sizeof() pour avoir un paquet de la bonne taille pour le type de variable que l'on souhaite transmettre.

Dans la fonction loop(), on génère une valeur quelconque à transmettre, ici en lisant le port série, puis on envoi la valeur et on attend la fin de l'envoi.

Pour envoyer une variable comme s'il s'agissait d'un tableau d'octets, il est nécessaire de faire un cast de pointeur (une façon de dire au compilateur que l'on sait ce que l'on fait). Le & permet d'avoir un pointeur sur la variable cible (pas besoin de faire cela si la variable est déjà un pointeur ou un tableau de valeurs). On utilise ensuite un cast (byte*) pour ordonner au compilateur de traiter le pointeur fraichement obtenu comme un pointeur sur un tableau d'octets. En mémoire, il n'y a que des octets, cette astuce permet donc d'obtenir une représentation sous forme de tableau d'octets de n'importe quelle variable.

N.B. Cette façon de faire est dépendante de l'architecture processeur. Si vous tentez d'utiliser cette méthode pour transmettre un float ou un simple int entre deux cartes utilisant des processeurs d'architectures différentes, vous aurez des erreurs d'interprétations à la réception.

Le code du serveur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Exemple de code pour la bibliothèque Mirf – Serveur d'envoi de variable
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI

void setup() {
  Serial.begin(9600);

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = sizeof(float); // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf01"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf02"); // Adresse de réception

  Serial.println("Go !"); 
}

void loop() {
  float valeur;

  if(Mirf.dataReady()){
    Mirf.getData((byte *) &valeur); // Réception du paquet
    Serial.println(valeur); // Affiche le message
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Du côté serveur, la fonction loop() utilise cette même astuce du cast pour faire passer la variable comme un tableau d'octets aux yeux de la fonction Mirf.getData(). Une fois la valeur copiée par dans la variable par Mirf.getData(), il suffit d'utiliser la variable normalement.

Afin d'être complet, sachez qu'il est aussi possible de copier une variable dans un tableau d'octets au moyen de la fonction memcpy(). Cela n'apporte aucun avantage par rapport à la solution du cast, mais c'est parfois plus simple et plus lisible pour des débutants.

Voilà ce que donnerait le code des fonctions loop() du client et du serveur en utilisant memcpy() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void loop() {
  byte message[32];
  
  // Lit un nombre depuis le port série
  float valeur = Serial.parseFloat();
  
  // Copie le float dans le message
  memcpy(message, &valeur, sizeof(valeur));
  
  Mirf.send(message); // On envoie le message
  while(Mirf.isSending()); // On attend la fin de l'envoi
} 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void loop() {
  byte message[32];
  float valeur;

  if(Mirf.dataReady()){
    Mirf.getData(message); // Réception du paquet
    
    memcpy(&valeur, message, sizeof(valeur));
    Serial.println(valeur); // Affiche le message
  }
}

Envoi de structure

Dans le chapitre précédent, j'ai précisé que l'astuce du cast fonctionne avec n'importe quel type de données. Cela est aussi vrai pour les types de données complexes réalisés au moyen de structure de données.

Exemple de code client :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Exemple de code pour la bibliothèque Mirf – Client d'envoi de structure
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI
 
typedef struct {
  char commande;
  int valeur;
} MaStructure;
 
void setup() {
  Serial.begin(9600);
   
  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = sizeof(MaStructure); // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
   
  Serial.println("Go !"); 
}
 
void loop() {
  MaStructure message;
  
  // Lit un nombre depuis le port série
  while(!Serial.available()); // Attend un caractère
  message.commande = Serial.read();
  message.valeur = Serial.parseInt();
  
  Mirf.send((byte*) &message); // On envoie le message
  while(Mirf.isSending()); // On attend la fin de l'envoi
} 

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Et de code serveur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Exemple de code pour la bibliothèque Mirf – Serveur d'envoi de structure
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI

typedef struct {
  char commande;
  int valeur;
} MaStructure;

void setup() {
  Serial.begin(9600);

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = sizeof(MaStructure); // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf01"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf02"); // Adresse de réception

  Serial.println("Go !"); 
}

void loop() {
  MaStructure message;

  if(Mirf.dataReady()){
    Mirf.getData((byte*) &message); // Réception du paquet
    
    Serial.print("commande="); // Affiche le message
    Serial.print(message.commande);
    Serial.print(" valeur=");
    Serial.println(message.valeur);
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Comme vous pouvez le voir, il est possible d'envoyer des données structurées assez facilement. En utilisant une structure bien pensée, il est possible de transmettre à peu près n'importe quelle information ou commande.

Les pointeurs mordent

Attention aux pointeurs dans les structures, lors de l'envoi, seule l'adresse du pointeur est transmise et non la valeur pointée.

Je vous invite à lire ma remarque sur ce sujet dans l'article sur le stockage de structure en mémoire EEPROM pour plus de détails.

Envoi de tableau de valeurs

Toujours dans le même esprit, l'astuce du cast fonctionne aussi avec des tableaux de valeurs, comme par exemple un tableau de float, int, char, etc.

Exemple de code client :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Exemple de code pour la bibliothèque Mirf – Client d'envoi de tableau de valeurs
 */

#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI
 
void setup() {
  Serial.begin(9600);
   
  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothéque

  Mirf.channel = 1; // Choix du cannal de communication (128 canaux disponible, de 0 à 127)
  Mirf.payload = sizeof(int) * 6; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
   
  Serial.println("Go !"); 
}
 
void loop() {
  int valeurs[6];
  
  // Lit les broches analogiques
  valeurs[0] = analogRead(0);
  valeurs[1] = analogRead(1);
  valeurs[2] = analogRead(2);
  valeurs[3] = analogRead(3);
  valeurs[4] = analogRead(4);
  valeurs[5] = analogRead(5);
  
  Mirf.send((byte *) &valeurs); // On envoi le message
  while(Mirf.isSending()); // On attend la fin de l'envoi
  
  delay(1000);
} 

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Et de code serveur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * Exemple de code pour la bibliothèque Mirf – Serveur d'envoi de tableau de valeurs
 */

#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI

void setup() {
  Serial.begin(9600);

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothéque

  Mirf.channel = 1; // Choix du cannal de communication (128 canaux disponible, de 0 à 127)
  Mirf.payload = sizeof(int) * 6; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf01"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf02"); // Adresse de réception

  Serial.println("Go !"); 
}

void loop() {
  int valeurs[6];

  if(Mirf.dataReady()){
    Mirf.getData((byte *) &valeurs); // Réception du paquet
    
    Serial.print("valeurs[0]=");
    Serial.println(valeurs[0]); // Affiche le message
    Serial.print("valeurs[1]=");
    Serial.println(valeurs[1]); 
    Serial.print("valeurs[2]=");
    Serial.println(valeurs[2]); 
    Serial.print("valeurs[3]=");
    Serial.println(valeurs[3]); 
    Serial.print("valeurs[4]=");
    Serial.println(valeurs[4]); 
    Serial.print("valeurs[5]=");
    Serial.println(valeurs[5]);
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Envoi de commande simple

En modifiant quelques lignes du code d'exemple du serveur d'envoi de texte, il est possible de faire un système de commande à distance simpliste :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void loop() {
  byte message[32];

  if(Mirf.dataReady()){
    Mirf.getData(message); // Réception du paquet
    
    if (strcmp((char*) message, "on") == 0) {
      // Fait quelque chose si le message est "on"

    } else if (strcmp((char*) message, "off") == 0) {
      // Fait quelque chose d'autre si le message est "off"
    }
  }
}

La fonction strcmp() retourne 0 quand deux chaines de caractères sont identiques. Il suffit donc d'appeler strcmp() dans une série de if() pour tester la valeur de la commande et réaliser les actions nécessaires en conséquence.

Envoi de commandes complexes

Dans le même principe que le chapitre précédent, en modifiant quelques lignes du code d'exemple du serveur d'envoi de structure, il est possible de faire un système de commande à distance relativement complet, avec paramètres :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
void loop() {
  MaStructure message;

  if(Mirf.dataReady()){
    Mirf.getData((byte*) &message); // Réception du paquet
    
    switch (message.commande) {
      case 'A':
         // Fait quelque chose
        break;

      case 'B':
         // Fait autre chose 
        break;

      // ...

      default:
          // Fait quelque chose quand la commande n'est pas comprise
    } 
  }
}

Dans le code d'exemple, j'avais utilisé une structure avec deux champs : commande (un caractère) et valeur (un nombre entier). En utilisant un switch() (équivalent à une série de if() sur des valeurs numériques), il est possible de faire plusieurs actions avec un paramètre. Exemple : A = allumer, a = éteindre, avec valeur = le numéro de la broche à allumer / éteindre à distance.

Libre à vous de faire la structure de données qui convient à votre projet ;)

Bonus : une sonnette DIY

Pour terminer cet article en beauté, je vous propose de fabriquer une sonnette sans fil.

Cet exemple pratique pourra servir de base pour des projets domotiques ou de télécommandes à distance ;)

Le montage

Montage sonnette DIY - partie bouton

Montage de l'émetteur

Montage sonnette DIY - partie haut parleur

Montage du récepteur

Le montage est le même que celui en début d'article. Seulement, sur une des cartes, un bouton poussoir est câblé sur la broche D2, alors que sur l'autre il s'agit d'un haut-parleur.

Je vous renvoie vers mon article sur les entrées sorties numériques pour le câblage du bouton poussoir et vers mon article sur comment faire de la musique en Arduino pour le câblage du haut-parleur.

Le code

Tout d'abord l'émetteur avec son bouton :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Exemple de code pour la bibliothèque Mirf – Sonnette DIY (émetteur)
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI
 
const byte PIN_BUTTON = 2;
 
void setup() {
  Serial.begin(9600);
  
  pinMode(PIN_BUTTON, INPUT_PULLUP);
   
  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = 8; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf02"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf01"); // Adresse de réception
   
  Serial.println("Go !"); 
}
 
void loop() {
  
  // Lit l'état du bouton
  if (digitalRead(PIN_BUTTON) == LOW) {
    byte message[8] = "DRIIING";
    Mirf.send(message); // On envoie le message
    while(Mirf.isSending()); // On attend la fin de l'envoi
  }
} 

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Le code est à peu près le même que celui de l'exemple pour envoyer des chaines de caractères. La seule différence est l'ajout du code pour le bouton poussoir en début de programme et dans setup().

Le code est configuré pour envoyer un message de huit octets contenant la chaine de caractères "DRIIING" (7 caractères + le caractère vide de fin de chaine) quand le bouton est appuyé (état LOW).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Exemple de code pour la bibliothèque Mirf – Sonnette DIY (récepteur)
 */
#include <SPI.h>      // Pour la communication via le port SPI
#include <Mirf.h>     // Pour la gestion de la communication
#include <nRF24L01.h> // Pour les définitions des registres du nRF24L01
#include <MirfHardwareSpiDriver.h> // Pour la communication SPI

const byte PIN_BUZZER = 2;

void setup() {
  Serial.begin(9600);
  
  pinMode(PIN_BUZZER, OUTPUT);

  Mirf.cePin = 9; // Broche CE sur D9
  Mirf.csnPin = 10; // Broche CSN sur D10
  Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
  Mirf.init(); // Initialise la bibliothèque

  Mirf.channel = 1; // Choix du canal de communication (128 canaux disponibles, de 0 à 127)
  Mirf.payload = 8; // Taille d'un message (maximum 32 octets)
  Mirf.config(); // Sauvegarde la configuration dans le module radio

  Mirf.setTADDR((byte *) "nrf01"); // Adresse de transmission
  Mirf.setRADDR((byte *) "nrf02"); // Adresse de réception

  Serial.println("Go !"); 
}

void loop() {
  byte message[8];

  if(Mirf.dataReady()){
    Mirf.getData(message); // Réception du paquet
    
    if (strcmp((char*) message, "DRIIING") == 0) {
      Serial.println("Ding dong !");
      tone(PIN_BUZZER, 440, 1000);
    }
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Le code du récepteur est similaire au code de l'exemple de commande texte simple. Quand la chaine de caractères "DRIIING" est reçue, le haut-parleur émet un bip pendant une seconde.

Bonus : diminuer la vitesse de communication pour augmenter la portée

Pour les personnes qui voudraient sacrifier de la vitesse au profit d'une portée plus grande, voici une petite ligne de code à mettre après Mirf.config() qui permet de passer le module radio à une vitesse de 250Kbps et une puissance de sortie de 0dBm (le maximum) :

1
Mirf.configRegister(RF_SETUP, 0x26); 

Avec cette configuration et une antenne de qualité, il est normalement possible d'atteindre des distances relativement longues.

Conclusion

Ce tutoriel est désormais terminé.

Si ce tutoriel vous a plu, n'hésitez pas à le commenter sur le forum, à le diffuser sur les réseaux sociaux et à soutenir le site si cela vous fait plaisir.