Comment utiliser les modules nRF24L01 en communication bidirectionnelle

12771

Aujourd’hui on va apprendre à comment utiliser les modules nRF24L01 en communication bidirectionnelle avec arduino.
J’ai mis pas mal de temps pour concevoir ce tutoriel, il y a peut être certaines choses que je n’ai pas bien comprise, mais il vous fournit une bonne base pour concevoir un projet bidirectionnel compréhensible pour la plupart des débutants.

CircuitRF24_circuit

 
//RetroEtGeek.com RF24
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include "printf.h"

// module 0
#define PIN_BUTTON1 2
#define PIN_BUTTON2 3

//module 1
#define PIN_SENSOR  A0
#define LED1 4
#define PIN_X A1


RF24 radio(7, 8); // CE, CSN
const byte addresses[][6] = {"00001","00002"}; //tuyaux d'adresse 
bool mode = 1 ;// mode pour definir qui fait quoi 


char receivedData[32] ;
String dataToSend = "" ;

long temporisation = 200;


// booleen pour controler que l'on a reçu un message en retour mode0
int lumiereBool = 0;
bool button1 = 0;// eviter d'envoyer plusieurs fois l'ordre du bouton 1
bool button2 = 0;// idem

// mode 1
bool ledBool = 0;// pour allumer ou eteindre la led
int x;// variable potentiometre


void setup() {
  Serial.begin(115200);
  printf_begin();
  
  radio.begin();
  radio.setChannel(100);// canal de communication de 0 a 125
  radio.setDataRate(RF24_250KBPS); // vitesse de communication RF24_250KBPS , RF24_1MBPS , ou RF24_2MBPS 
  //radio.enableAckPayload();
  radio.enableDynamicPayloads();
  // ouverture des tuyaux d'ecoute et d'ecriture , on peut ouvrir jusqu'a 6 tuyaux pour l'ecoute
  if(mode == 0 ) {
    radio.openWritingPipe(addresses[1]);
    radio.openReadingPipe(1,addresses[0]);
  }
  else{
    radio.openWritingPipe(addresses[0]);
    radio.openReadingPipe(1,addresses[1]);    
  }
  radio.setPALevel(RF24_PA_HIGH);// amplification RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH et RF24_PA_MAX
  radio.startListening();// commencer a ecouter
  radio.printDetails(); // affiche les details du module RF
  radio.stopListening();//arreter d'ecouter
  //module 0
  pinMode( PIN_BUTTON1, INPUT_PULLUP);
  pinMode( PIN_BUTTON2, INPUT_PULLUP);

  //module 1
  pinMode( LED1, OUTPUT);
  pinMode( PIN_X, INPUT);

  // initialisation du potentiometre
  x = analogRead(PIN_X);

  
}
// fonction qui envoie avec rf24
bool sendData(String dataToSend){
  delay(50);
  Serial.print("envoie de "); 
  Serial.println(dataToSend);  
  char inputChar[dataToSend.length()+1] ;
  dataToSend.toCharArray(inputChar,dataToSend.length()+1);
  //Serial.println(inputChar);
  return radio.write(&inputChar, sizeof(inputChar));
}


void loop() {



// mode 0
  if(mode == 0 ) {
    
    // debut bouton 1
    // ici on utilise des variables pour garder en memoire l'execution
    // l'ordre du message et on attendra en retour pour annuler l'envoi du message  
    if(digitalRead(PIN_BUTTON1) == LOW && button1== 0){
      lumiereBool = 5;// variable pour obtenir un retour, ici 5 essais max
      button1 = 1; // variable pour prendre en consideration 1 seul appui sur bouton    
    }
    else if(digitalRead(PIN_BUTTON1) == HIGH){
      button1 = 0;
    }

    // condition qui va boucler tant que le retour n'est aps recu , lumiereBool = 0 pour stop
    if(lumiereBool>0){
      //on stop l'ecoute et on envoie l'information D:LUMIERE avec sendData;
      radio.stopListening();
      sendData("D:LUMIERE");
      delay(5);
      lumiereBool--;// on decremente le nombre d'essais
      radio.startListening();// on ecoute       
      temporisation = 1000;// on temporise un peu plus
    }
    //fin bouton1
    
    // debut bouton2
    // on ne controle pas que le message a ete recu
    if(digitalRead(PIN_BUTTON2) == LOW && button2== 0){
      radio.stopListening();
      Serial.println(sendData("D:LED"));
      delay(5);
      radio.startListening();      
      temporisation = 1000;
      button2 = 1; // variable pour prendre en consideration 1 seul appui sur bouton    
    }
    else if(digitalRead(PIN_BUTTON2) == HIGH){
      button2 = 0;
    }
    // fin bouton 2 
  }
  else{
    //mode 1/////////////////////////////////////////////////////////////////////mode1
    if(analogRead(PIN_X)>(x+30) || analogRead(PIN_X)<(x-30)){ x=analogRead(PIN_X); radio.stopListening(); positionPot();// appelle de la fioction positionPot qui envoie la valeur du potentiometre delay(5); radio.startListening(); temporisation = 1000; } } radio.startListening(); // on attends que le module soit pret unsigned long start_time = millis(); bool timeout = false ; while (!radio.available()){ if (millis() - start_time > temporisation ){
          timeout = true ;
          break;
      }
  }  
    if(temporisation != 200){
        temporisation = 200;
    }
    // radio prete  
    if (radio.available()) {
      while (radio.available()){ 
      radio.read(&receivedData, sizeof(receivedData));
            
      Serial.print("Data recue : ");
      Serial.println(receivedData);
      if(receivedData!=""){
        // ici on commence l'aiguillage des messages reçu
        char* command = strtok(receivedData, "&");
        // boucle sur toutes les commandes
        while (command != 0)
        {
          // decoupe command en deux valeurs , separateur :
          // Split the command in two values
          char* valueCommand = strchr(command, ':');
          if (valueCommand != 0)
          {
            *valueCommand = 0;
            ++valueCommand;     
            Serial.println(String(command));    
            // aiguillage par rapport a la valeur de command ICI   D:MAVALEUR
            if(String(command) == "D"){
              // ici on recupere les ordres
              // ma valeur est LUMIERE   (D:LUMIERE)
              if(String(valueCommand) == "LUMIERE"){
                Serial.println("Recue pour LUMIERE");
                // on arrete d'ecouter et on va transmettre une data, ici on appelle la fonction dataLumiere
                radio.stopListening();
                dataLumiere();
                delay(5);
                radio.startListening();
                // on se remet a ecouter startListening
              }//ici on a reçu D:LED
              else if(String(valueCommand) == "LED"){
                Serial.println("Recue pour LED");
                // si ma led eteinte j'allume et inversement
                if(ledBool == 0){
                  ledBool=1;
                  digitalWrite(LED1,HIGH);   
                }
                else{
                  ledBool=0;
                  digitalWrite(LED1,LOW); 
                }
              }
            //ici on a recu LUM:UNEVALEUR
            }else if(String(command) == "LUM"){
              // on recupere la valeur avec String(valueCommand) et on en fait ce que l'on veut ici simplement un affichage
              Serial.print("Valeur recue pour lum");
              Serial.println(String(valueCommand)) ;
              lumiereBool=0;// je met cette variable a 0 car c'est mon indicateur pour dire que l'on attendait un retour suite a un envoie d'information LUMIERE attend un retour LUM:...    
            }
            else if(String(command) == "POT"){
              //ici recupetation de la valeur de pot mais pas de variable a modifier car pas de retour attendu
              Serial.print("Valeur recue pour pot");
              Serial.println(String(valueCommand)) ;         
            }
          }
          // Recherche une nouvelle commande separes pas un &
          // Find the next command in input string
          command = strtok(0, "&");
        }
      }


      }

      
    } 
    radio.stopListening();
}

// fonction qui renvoie la valeur de la photoresistance
void dataLumiere(){
  int val;
  val = analogRead(PIN_SENSOR);
  dataToSend = "";        
  dataToSend = dataToSend + "LUM:" + val;
  sendData(dataToSend);
}
// fonction qui renvoie la valeur du potentiometre
void positionPot(){
  int val;
  val = analogRead(PIN_X);
  dataToSend = "";        
  dataToSend = dataToSend + "POT:" + val;
  Serial.println(sendData(dataToSend));
}


Il peut arriver que nous ayons besoin de communiquer sans fil entre deux arduino.
Plusieurs solutions existent mais aujourd’hui on parle du module RF24.
J’ai eu beaucoup de mal à produire ce tutoriel car je souhaitais communiquer de manière bidirectionnelle et il y a certaines surprises qui arrivent, comme beaucoup de “bug”, le module envoie l’information mais le module récepteur ne reçoit rien, ou reçoit mais le retour ne fonctionne pas, bref énormément d’acharnement et de boulot pour vous proposer ce tutoriel !

En matériel il nous faut :

  • 2 arduino (ici j’ai un uno et un mega, branchement différent sur mega)
  • 2 modules nRF24L01
  • Tout le reste ci dessous est optionnel et est destiné au tuto
  • 2 boutons
  • 1 led et la résistance qui va avec
  • 1 photo résistance et 1 résistance de la même valeur que la photo résistance
  • 1 potentiomètre

Le branchement des modules nRF24L01 suit un schéma différent si arduino uno et mega
ATTENTION LES MODULES FONCTIONNENT EN 3.3V, donc brancher sur le 3.3V

MOSIMISOSCKCSCE
Uno11121387
Mega51505287

Et sur les nRF24L01 les correspondances pin sont

|M
|O
|D
|U
|L
|E
VCC   3.3VCSMOSIIRQ
GNGCESCKMISO

Donc si vous suivez ces deux tableaux vous avez votre branchement avec les modules, ensuite si vous souhaitez reproduire le tutoriel sur l’arduino “mode 0” ( on verra dans le code pourquoi) j’ai un bouton branché sur le pin 2 et 3, l’autre partie des boutons sont branché sur le Gnd.
Pour le deuxième arduino “mode 1” j’ai le pin 4 connecté à une led qui est associé à une résistance et à la masse.
Le pin A0 est connecté à l’intersection de la photorésistance et de la résistance, l’autre côté de la photorésistance est connectée au 5V et l’autre côté de la résistance au Gnd.
Le pin A1 est connecté au pin central du potentiomètre, les deux autres pin sont connecté au 5V et Gnd.

Voila normalement vous avez tout bon sur le câblage du circuit, attention au module qui fonctionne en 3.3Volt.

Parlons du code, ici mon but est de transmettre une ou plusieurs informations et de pouvoir avoir un retour d’information suite à une demande.
J’utilise la bibliothèque RF24 qui se trouve dans gérer les bibliothèque RF24 (by THRh20) de votre arduino.

Premièrement nos include pour le SPI , nRF24L01, RF24 (et printf optionnel si on enlève “radio.printDetails();” // affiche ).
La bibliothèque RF24 by TMRh20 dans le gestionnaire de bibliothèque ( doc : http://tmrh20.github.io/RF24/ ).
J’ai défini 2 boutons sur le pin 2 et 3 pour mon module 0 , puis pour mon module 1 je défini le pin pour la photo résistance A0 ,  une led pin 4 et le potentiomètre en A1.

J’appelle ma librairie RF24 avec RF24 radio(7, 8); pour les pin CE, CSN des modules ce sont les seuls pin que l’on peut modifier tout le reste est fixe.
Ensuite on définit des tuyaux de communication avec “addresses[][6] = {“00001″,”00002”};
Un booleen mode defini à 0 ou 1 qui va aiguiller le programme suivant le module utilisé, j’ai regroupé ici les codes des deux modules en un seul, il est conseillé d’utiliser un programme pour chaque module mais ici j’ai tout regroupé et j’active la partie nécessaire avec mode.

J’initialise des variables “receivedData[32] “, “dataToSend” et “temporisation” qui vont être utilisés dans mon programme.
Les variables “lumiereBool” , “button1”, “button2” sont utilisées pour mes actions (mode 0) , “lumiereBool” contiendra le nombre d’essais d’envois d’information avant abandon et les button évitent que le bouton reste appuyé et envoie des données tout le temps.Et pour le mode 1 “ledBool”  pour vérifier l’état de la led et “x” pour une variable de mesure potentiomètre.

On commence dans le setup.
“Serial.begin” pour vérifier nos valeurs et “printf_begin” (optionnel) pour afficher les valeurs du module nRF24L01.
On démarre le module “radio radio.begin();” et on choisit le channel (0 à 125) avec “radio.setChannel(100);”.
Puis on définit la vitesse de communication avec “radio.setDataRate(RF24_250KBPS); // ( valeurs possibles RF24_250KBPS , RF24_1MBPS , ou RF24_2MBPS )”.
On active “radio.enableDynamicPayloads();” pour laisser gérer au module des tailles dynamiques.
Et la partie importante j’ai un if avec “mode == 0” , si le mode = 0 alors j’active les pipes avec des adresses, les pipes sont inversé entre le mode 0 et le mode 1 ce qui permet d’avoir un tuyaux pour communiquer dans un sens et dans l’autre.
On règle ensuite l’amplification avec “radio.setPALevel(RF24_PA_MAX);” //  ( valeurs possibles RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH et RF24_PA_MAX ).
On commence à écouter avec “radio.startListening();” , on affiche les informations modules si l’on souhaite avec “radio.printDetails();” et on arrête l’écoute avec “radio.stopListening();”
Je définit le mode des pin pour le mode 0 et mode 1 ” pinMode( PIN_BUTTON1, INPUT_PULLUP); pinMode( PIN_BUTTON2, INPUT_PULLUP); et pinMode( LED1, OUTPUT); pinMode( PIN_X, INPUT);x = analogRead(PIN_X);”
Le setup est terminé.

J’ai ensuite une fonction que j’ai nommée “sendData(String dataToSend)” dans laquelle on envoie un String et je récupère une valeur bool. Cette fonction convertie le string en char et envoie l’information.

Ensuite ma boucle ou tout ce passe suivant si j’ai mon mode à 0 ou 1 je vais effectuer certaines actions.
Premières conditions est sur le bouton 1 , si j’appuie et que mon bouton a bien était à l’état bas à un moment alors “lumiereBool = 5; “et “button1 = 1; “sinon si bouton relâché “button1 = 0; “.
Deuxième condition, si “lumiereBool>0” c’est que l’on a demandé une information a envoyer, donc on va envoyer une information, donc on arrête d’écouter avec “radio.stopListening();” , j’envoie mon information avec “Serial.println(sendData(“D:LUMIERE”));” , ici on peut simplement mettre le “sendData(“D:LUMIERE”);” pour ne pas afficher la valeur de retour.
Je décrémente le nombre de “lumiereBool–; “//( nombre d’essais ) , et j’active à nouveau l’écoute avec “radio.startListening();” et je met une temporisation plus longue d’écoute avec temporisation = 1000; . Pour le bouton 1 on attends un message de retour c’est pour cela que l’on est passé par “lumiereBool” que l’on retrouvera plus tard.

Pour le bouton2 on n’attend pas de retour de l’autre module donc on prend le même type de condition pour éviter d’envoyer le message X fois mais on envoie directement le message “Serial.println(sendData(“D:LED”));” , ici j’envoie D:LED que l’on va pouvoir trier plus tard.

J’ai après mon else qui est pour le mode 1 , sur le mode 1 on a seulement la valeur du potentiomètre à mesurer  et à comparer à l’ancienne mesure pour ne pas se déclencher tout le temps et si on détecte un changement de + ou – 30 alors on appelle la fonction  “positionPot();” qui va envoyer la valeur du potentiomètre.

Hors des conditions de mode, on à notre programme général qui ne fait qu’écouter et faire les actions nécessaires au message reçu.
Donc on écoute avec “radio.startListening();”, suivi d’une boucle d’attente si module disponible, on remet la temporisation en valeur plus classique par la suite.
Si ma radio est prête  “if (radio.available()) {” alors je récupère ce que j’ai reçu avec “radio.read(&receivedData, sizeof(receivedData));”.
Si mes datas reçues sont différentes de vide alors on a notre “découpeuse” d’information. Elle est faite pour couper les messages, exemple je veux envoyer 3 info X Y et Z je peux faire “X:5&Y:4&Z:9” donc je peux récupérer ses 3 valeurs séparément, mais ici on envoie une info exemple “D:LUMIERE”.
La partie qui nous intéresse est au niveau de “if(String(command) == “D”){” j’ai décidé d’envoyer des ordres avec D:MONORDRE donc tout ce qui est apres D est un ordre, si on reçoit LUMIERE “if(String(valueCommand) == “LUMIERE”){” alors j’arrête l’écoute radio et j’appelle “dataLumiere();”,je n’oublie pas d’activer l’écoute ensuite (mode 0).
J’ai une condition sinon si, la commande reçue est LED alors j’effectue l’allumage ou l’extinction de ma led (mode1).
Partie suivante on à une condition sinon si “}else if(String(command) == “LUM”){” , ici pas d’information commençant par “D:” mais par “LUM:” et cette information est importante car elle dit a mon module mode 0 que j’ai reçue l’information attendue à “LUMIERE”. En envoyant “LUMIERE” je voulais un retour “LUM” avec une valeur, ici je l’ai reçue donc je met “lumiereBool=0;” pour que le module n’essaye plus d’envoyer “LUMIERE” au début du programme.
J’ai un autre sinon si, avec comme valeur attendue “POT” et ici j’affiche simplement la valeur reçue.
Et puis on termine nos boucles et conditions et notre loop.

J’ai deux fonctions ensuite , celle qui envoie la valeur de la photorésistance et celle qui envoie la valeur du potentiomètre, il faut créer la valeur à envoyer en String et l’envoyer avec “sendData(dataToSend);”

On en à terminé avec le code, ici le programme ne sert pas a grand chose mais te permettra de faire ce que tu veux avec soit une information ou tu attends un retour soit un envoie simple.
Voici le code NRF24

N’hésitez pas à poser vos questions sur les réseaux sociaux de la chaîne instagramtwitter , facebook ,youtube ; si vous ne comprenez pas certaines parties du tutoriel n’hésitez pas , me dire ce que vous aimeriez que je crée pour en faire des vidéos tutoriel  et à partager les projets que vous aimeriez créer etc…

Comme toujours allez sur la page de C’est quoi Retro et Geek pour connaître tout ce que je recherche à faire sur la chaîne.

Merci les RetroGeeker et RetroGeekeuse