Télécommande à base d'Arduino et de nRF24L01+

Que ce soit pour se déplacer, pour attaquer ou se défendre, il est nécessaire de contrôler les moteurs et autres actionneurs de votre robot. Pour cela, il est nécessaire de recourir à un minimum d’électronique.
Pour réagir aux différentes commandes, le robot peut donc embarquer un ou plusieurs microcontrôleurs. Dans ce cas, un peu de programmation est nécessaire pour s’assurer du bon comportement du robot.
Le règlement impose que les robots soient pilotés par un ou plusieurs membres de l’équipe. La transmission sera exclusivement de type sans fil. Il est possible d’utiliser des modules semblables au modélisme ou de hacker un autre système comme une manette de console de jeux.

Modérateur : Guillaume

Guillaume
Messages : 11
Inscription : 31 oct. 2014, 00:56
Localisation : Saint-Louis

Télécommande à base d'Arduino et de nRF24L01+

Message par Guillaume » 14 févr. 2016, 23:55

Voici une alternative à la manette sans fil de Gamecube pour piloter un robot.

La partie manette se compose d'un Arduino Uno et d'un Joystick Shield de chez Funduino, et la communication se fait via des modules 2.4GHz à base de nRF24L01+.
Elle est alimentée par une batterie au Lithium de 3.7V.
2016-02-14 22.53.17.jpg
Manette
Le récepteur est lui aussi construit autour d'une carte Arduino.
Celui qui est présenté ici est basé sur une Arduino Nano, et trois ponts en H à base de BTS7960. Ces derniers sont idéaux pour alimenter les moteurs de visseuse électrique.
2016-02-14 22.52.56.jpg
Récepteur
Tout ce matériel se trouve facilement et à vil prix sur eBay :mrgreen:

Voici le code de la manette :

Code : Tout sélectionner

#include <SPI.h>
#include "RF24.h"
#include "printf.h"

/** Brochage de la radio nRF24L01+ sur le Joystick Shield
 *  Signal  nRF pin     Arduino pin
 *  GND     1           GND
 *  VCC     2           3V3
 *  CE      3           9
 *  CSn     4           10
 *  SCK     5           13
 *  MOSI    6           11
 *  MISO    7           12
 *  IRQ     8           -
 */
const byte nRF_CE = 9;
const byte nRF_CSn = 10;
RF24 nRF(nRF_CE, nRF_CSn);

/* Configuration des adresses pour la communication radio
 *  Ces adresses doivent être définies identiques entre la
 *  manette et le robot pour que la communication fonctionne...
 */
/* Adresse de la radio montée sur le robot */
const byte nRF_robot_address[6] = "1Node";
/* Adresse de la radio montée sur la télécommande */
const byte nRF_joystick_address[6] = "2Node";

/* Valeur des axes et boutons de la manette
 *  Voir le code du robot pour le détail des champs...
 */
struct joystick_state {
  byte  buttons;
  int   axis_x;
  int   axis_y;
} joystate;

const byte button_mask_up = 0x01;
const byte button_mask_right = 0x02;
const byte button_mask_down = 0x04;
const byte button_mask_left = 0x08;
const byte button_mask_start = 0x10;
const byte button_mask_select = 0x20;
const byte button_mask_joystick = 0x40;

void read_joystick()
{
  /* Lecture des boutons du joystick
   *  Les boutons sont attribués de la façon suivante :
   *  Bouton    Broche Arduino  Broche Atmega
   *  ---------------------------------------
   *  UP        2               PD2
   *  RIGHT     3               PD3
   *  DOWN      4               PD4
   *  LEFT      5               PD5
   *  START     6               PD6
   *  SELECT    7               PD7
   *  JOYSTICK  8               PB0
   *  
   *  Comme les 6 premiers boutons sont situés sur le même port,
   *  on lit la valeur de celui-ci et on décale de deux bits
   *  vers la droite, comme ça le bouton UP se retrouve sur le 
   *  bit 0, le bouton RIGHT sur le bit 1, et ainsi de suite.
   *  Les boutons sont actifs à l'état bas (reliés à GND), ainsi
   *  on inverse la valeur du port pour convertir en logique
   *  positive. Ainsi, le bit correspondant à un bouton est mis à
   *  1 dans <buttons> si le bouton est pressé
   */
  joystate.buttons = (~PIND >> 2) & 0x7F;
  /* Le bouton situé sous le joystick est relié à la broche 0
   *  du port B. On le traite donc séparément des précédents.
   *  Si la broche physique est à 1, on met le bit correspondant
   *  à 0 dans <buttons>, car le bouton n'est pas pressé.
   */
  if (PINB & 0x01)
    joystate.buttons &= ~button_mask_joystick;
  /* Lecture de la position des sticks analogiques */
  /* Axe horizontal du joystick */
  joystate.axis_x = analogRead(A0);
  /* Axe vertical du joystick */
  joystate.axis_y = analogRead(A1);
}

void setup() {
  Serial.begin(115200);
  Serial.println("nRF24L01+ Joystick");
  printf_begin();
  
  /* Initialisation des ports du joystick
   *  Ports en entrées, activer les pull-ups
   */
  DDRD &= 3;
  PORTD |= 0xFC;
  DDRB &= 0xFE;
  PORTB |= 0x01;
    
  /* Initialisation de la radio nRF24L01 */
  nRF.begin();
  nRF.openWritingPipe(nRF_robot_address);
  nRF.openReadingPipe(1, nRF_joystick_address);
  nRF.printDetails();
}

void loop() {
  /* Lecture de l'état du joystick */
  read_joystick();

  /* Ecrit l'état du joystick dans la console, pour info */
  Serial.print("X = ");
  Serial.print(joystate.axis_x);
  Serial.print("\tY = ");
  Serial.print(joystate.axis_y);
  Serial.print("\tButtons = ");
  if (joystate.buttons & button_mask_up) Serial.print("UP ");
  if (joystate.buttons & button_mask_right) Serial.print("RIGHT ");
  if (joystate.buttons & button_mask_down) Serial.print("DOWN ");
  if (joystate.buttons & button_mask_left) Serial.print("LEFT ");
  if (joystate.buttons & button_mask_start) Serial.print("START ");
  if (joystate.buttons & button_mask_select) Serial.print("SELECT ");
  if (joystate.buttons & button_mask_joystick) Serial.print("JOYSTICK ");
  
  /* Envoie l'état du joystick au robot
   *  Si le robot ne répond pas dans le délai imparti,
   *  affiche un message d'erreur dans la console
   */
  if (!nRF.write(&joystate, sizeof(struct joystick_state))) {
    Serial.println(F("(Not Ack'd from bot)"));
  }
  else
    Serial.println("(OK)");

  /* Attente de 20ms avant le prochain envoi */
  delay(20);
}
Ce programme lit l'état des boutons et du joystick à une fréquence de 50Hz et envoie le tout vers le robot.
Mis à part les adresses (voir plus bas), il n'est pas nécessaire de modifier le reste du code pour se construire un ensemble manette/récepteur

Le code du récepteur est le suivant :

Code : Tout sélectionner

#include <SPI.h>
#include "RF24.h"
#include "printf.h"

/* Brochage de la radio nRF24L01+ */
const byte nRF_CE = 9;
const byte nRF_CSn = 10;
RF24 nRF(nRF_CE, nRF_CSn);

/* Adresse de la radio montée sur le robot */
const byte nRF_robot_address[6] = "1Node";
/* Adresse de la radio montée sur la télécommande */
const byte nRF_joystick_address[6] = "2Node";

/* Valeur des axes et boutons de la manette */
struct joystick_state {
  /* <buttons> stocke l'état des 7 boutons de la manette dans 1 octet.
   *  chaque bit de <buttons> est mis à 1 si le bouton correspondant est pressé
   *  Par exemple, pour vérifier si le bouton du haut est pressé, on utilise :
   *  if (joystate.buttons & button_mask_up)
   *  {
   *    ...
   *  }
   */
  byte  buttons;
  /* <axis_x> est la valeur analogique de l'axe horizontal du joystick
   *  La valeur est comprise entre 0 et 1023
   *  0    : stick à gauche
   *  1023 : stick à gauche
   *  ~512 : stick au centre
   */
  int   axis_x;
  /* <axis_y> est la valeur analogique de l'axe vertical du joystick
   *  La valeur est comprise entre 0 et 1023
   *  0    : stick en bas
   *  1023 : stick en haut
   *  ~512 : stick au centre
   */
  int   axis_y;
} joystate;

/* Masques binaires pour décoder la valeur <buttons> de <joystate> */
const byte button_mask_up = 0x01;         /* Bouton haut ou A */
const byte button_mask_right = 0x02;      /* Bouton droit ou B */
const byte button_mask_down = 0x04;       /* Bouton bas ou C */
const byte button_mask_left = 0x08;       /* Bouton gauche ou D */
const byte button_mask_start = 0x10;      /* Bouton start ou E */
const byte button_mask_select = 0x20;     /* Bouton select ou F */
const byte button_mask_joystick = 0x40;   /* Bouton sous le stick */

/* Classe permettant de piloter simplement un moteur relié via un pont en H */
class motor {
  private:
  byte pin_A, pin_B, pin_PWM;
  public:
  /* Initialise une instance de la classe <moteur>
   *  <pin_A> correspond à la commande du moteur dans un sens
   *  <pin_B> correspond à la commande du moteur dans l'autre sens
   *  <pin_PWM> est le signal de modulation de largeur d'impulsion
   *    et permet de faire varier la vitesse du moteur
   * Pour la connexion à un pont chinois à base de BTS7960, le cablage 
   * est le suivant :
   *  <pin_A> et <pin_B> sont respectivement reliés à L_PWM et R_PWM
   *    (ou R_PWM et L_PWM si on souhaite inverser le sens de rotation)
   *  <pin_PWM> est reliée à L_EN et R_EN mis ensemble
   */
  motor(byte pin_A, byte pin_B, byte pin_PWM){
    this->pin_A = pin_A;
    this->pin_B = pin_B;
    this->pin_PWM = pin_PWM;
  }

  /* Initialise les broches de commande du moteur en tant que sorties
   *  Cette fonction est de préférence appelée dans setup()
   */
  void begin() {
    pinMode(pin_A, OUTPUT);
    pinMode(pin_B, OUTPUT);
    pinMode(pin_PWM, OUTPUT);
    stop();
  }

  /* Arrête le moteur
   *  Les deux branches du pont sont déconnectées, le moteur est en roue libre  
   */
  void stop() {
    digitalWrite(pin_A, 0);
    digitalWrite(pin_B, 0);
    digitalWrite(pin_PWM, 0);
  }


  /* Définit la vitesse de rotation du moteur
   *  <value> est un entier compris entre -255 et +255
   *  Si <value> dépasse ces valeurs, <value> sera borné à -255 ou +255
   */
  void setPower(int value) {
    if (value > 0) {
        digitalWrite(pin_A, HIGH);
        digitalWrite(pin_B, LOW);
        analogWrite(pin_PWM, min(value, 255));
    } else if (value < 0) {
        digitalWrite(pin_A, LOW);
        digitalWrite(pin_B, HIGH);
        analogWrite(pin_PWM, min(-value, 255));
    } else {
        digitalWrite(pin_A, LOW);
        digitalWrite(pin_B, LOW);
        analogWrite(pin_PWM, 0);
    }
  }
};

/* Moteur gauche
 *  pin_A sur broche 2
 *  pin_B sur broche 4
 *  pin_PWM sur broche 3
 */
motor left_motor(2,4,3);
/* Moteur droit
 *  pin_A sur broche 7
 *  pin_B sur broche 8
 *  pin_PWM sur broche 6
 */
motor right_motor(7,8,6);

void setup() {
  Serial.begin(115200);
  Serial.println("nRF24L01+ Robot");
  printf_begin();

  /* Initialisation de la radio nRF24L01 */
  nRF.begin();
  nRF.openWritingPipe(nRF_joystick_address);
  nRF.openReadingPipe(1, nRF_robot_address);
  nRF.printDetails();
  nRF.startListening();

  /* Initialisation des commandes de moteur */
  left_motor.begin();
  right_motor.begin();
}

/* Crée une zone morte sur <inval>
 *  si <inval> est compris entre -<thres> et +<thres>, renvoie 0
 *  sinon, retranche <thres> à <inval> (ajoute si <inval> < 0)
 */
int deadZone(int inval, int thres)
{
    if (inval > thres) {
        return inval - thres;
    } else if (inval < -thres) {
        return inval + thres;
    } else {
        return 0;
    }
}

void loop() {
  /* Date à laquelle on a reçu le dernier message de la télécommande.
   *  Cette valeur est en millisecondes depuis le démarrage de l'Arduino
   *  (obtenu avec la fonction millis())
   */
  static uint32_t last_joystick_time = 0;

  /* Teste si l'on a reçu un message provenant de la télécommande */
  while (nRF.available()) {
    /* Oui, alors on extrait la structure de donnée du message reçu */
    nRF.read(&joystate, sizeof(struct joystick_state));
    
    /* Ecrit l'état de la manette dans la console, pour info */
    Serial.print("X = ");
    Serial.print(joystate.axis_x);
    Serial.print("\tY = ");
    Serial.print(joystate.axis_y);
    Serial.print("\tButtons = ");
    if (joystate.buttons & button_mask_up) Serial.print("UP ");
    if (joystate.buttons & button_mask_right) Serial.print("RIGHT ");
    if (joystate.buttons & button_mask_down) Serial.print("DOWN ");
    if (joystate.buttons & button_mask_left) Serial.print("LEFT ");
    if (joystate.buttons & button_mask_start) Serial.print("START ");
    if (joystate.buttons & button_mask_select) Serial.print("SELECT ");
    if (joystate.buttons & button_mask_joystick) Serial.print("JOYSTICK ");
    Serial.println("");

    /* Met à jour la date de dernière bonne réception */
    last_joystick_time = millis();

    /* Vitesse d'avance/recul du robot
     *  Définie à partir de l'axe vertical du joystick
     *  On retire 512 à <axis_y> pour avoir 0 au centre
     *  On divise par 2 pour avoir au maximum +/- 256
     */
    int throttle = deadZone(joystate.axis_y - 512, 16) / 2;
    /* Vitesse de rotation du robot
     *  Définie à partir de l'axe horizontal du joystick
     *  On retire 512 à <axis_x> pour avoir 0 au centre
     *  On divise par 3 pour limiter la vitesse de rotation du robot
     */
    int steering = deadZone(joystate.axis_x - 512, 16) / 3;

    /* Définit la vitesse de rotation des moteurs gauche et droit
     *  Le signal <throttle> est une action commune 
     *  Le signal <steering> est une action différentielle
     */
    left_motor.setPower(throttle + steering);
    right_motor.setPower(throttle - steering);
  }

  /* Si l'on n'a pas reçu de message de la télécommande depuis plus de 500 ms */
  if (millis() - last_joystick_time > 500)
  {
    Serial.println("No joystick data !");
    
    /* On arrête les moteurs */
    left_motor.stop();
    right_motor.stop();
    
    delay(100);
  }
}
Ce programme attend la réception d'une trame provenant de la télécommande, puis pilote les moteurs en fonction des consignes de la télécommande.
En l'absence de données reçues (perte de signal, plus de batterie dans la télécommande, etc.), les moteurs sont arrêtés pour ne pas avoir un robot hors de contrôle.

Pour compiler le code, il est nécessaire d'installer la bibliothèque RF24 dans l'environnement Arduino. On peut la trouver à l'adresse suivante :
http://tmrh20.github.io/RF24/
Ou dans les versions récentes de l'IDE Arduino :
Croquis -> Inclure une bibliothèque -> Gérer les bibliothèques, puis rechercher "RF24" et l'installer.
Pour le câblage du module nRF24L01+, se référer à la documentation de la bibliothèque RF24.

Les adresses utilisées par les modules nRF24L01+ sont définies par l'extrait de code suivant :

Code : Tout sélectionner

/* Adresse de la radio montée sur le robot */
const byte nRF_robot_address[6] = "1Node";
/* Adresse de la radio montée sur la télécommande */
const byte nRF_joystick_address[6] = "2Node";
Ces adresses doivent être définies de façon identique dans le code de la manette et dans le code du récepteur :!:

Dans le code du récepteur, une classe motor permet de gérer facilement les moteurs. Elle s'utilise de la façon suivante :
  • On créée l'objet mon_moteur de la façon suivante :

    Code : Tout sélectionner

    motor mon_moteur(broche_sens_A,broche_sens_B,proche_PWM);
    broche_sens_A et broche_sens_B définissent le sens de rotation du moteur. Si les deux sont à 0 ou à 1, le moteur est arrêté (bloqué). Si une des deux broches est à 1, le moteur peut tourner.
    broche_PWM est la broche permettant de régler la vitesse du moteur. Elle doit correspondre à une sortie PWM du moteur (broches 3,5,6,9 et 10 de l'Arduino Uno, la broche 11 étant utilisée par le module radio).
    Pour les modules à base de BTS7960, les entrées R_EN et L_EN sont reliées ensemble sur la broche PWM, tandis que les broches R_PWM et L_PWM sont reliées aux broches sens_A et sens_B.
  • On intialise le moteur avec la commande mon_moteur.begin(), dans la fonction setup().
  • Pour définir la vitesse et le sens de rotation, on utilise la commande mon_moteur.setPower(valeur), où valeurest la vitesse souhaitée entre -255 et 255.
  • Pour arrêter le moteur, on utilise la fonction mon_moteur.stop().
Dans l'exemple donné, on a défini deux moteurs : left_motor et right_motor, pour piloter les deux moteurs de déplacement.

La boucle principale (fonction loop()) du programme récepteur comporte deux blocs d'instruction
  • Le bloc commençant par while (nRF.available()) contient les instructions à exécuter lorsque l'on reçoit un message de la télécommande.
    C'est dans celui-ci que l'on pilote nos moteurs de déplacement, en utilisant les valeurs des axes du joystick. On peut rajouter à la suite le pilotage d'autres moteurs, de sorties tout ou rien, etc.
  • Le bloc commençant par if (millis() - last_joystick_time > 500) contient les instructions à exécuter lorsque l'on n'a plus reçu de message de la télécommande depuis un certain temps
    Il contient les instructions nécessaire à mettre le robot en sécurité : arrêt des moteurs, blocage des armes, etc.

Avatar de l’utilisateur
qiko68
Messages : 157
Inscription : 02 janv. 2015, 14:11
Localisation : Didenheim

Re: Télécommande à base d'Arduino et de nRF24L01+

Message par qiko68 » 18 févr. 2016, 23:39

merci Guillaume ;)
- Christophe -

Julien_Blaise
Messages : 37
Inscription : 12 déc. 2014, 12:15

Re: Télécommande à base d'Arduino et de nRF24L01+

Message par Julien_Blaise » 04 avr. 2016, 10:25

Hello !

le programme du récepteur modifié avec les 4 boutons sur les sorties en digital sur les ports a0 a1 a2 a3

recepteur_avec_4_sorties.ino
(7.35 Kio) Téléchargé 1485 fois

Avatar de l’utilisateur
qiko68
Messages : 157
Inscription : 02 janv. 2015, 14:11
Localisation : Didenheim

Re: Télécommande à base d'Arduino et de nRF24L01+

Message par qiko68 » 05 avr. 2016, 00:03

et voici la version modifié avec l'aide de Bertrand pour une utilisation avec une radio commande FlySky i6 et un récepteur 6canaux FS-ia6.

Mais je n'utilise que 2 canaux : la direction et le sens de marche (avant arrière)

mon robot n'utilisant pas d'armes commandées, je ne m'encombre pas de gérer ça dans le programme, mais j'ai quand même la possibilité de le faire évoluer

controle_robo_qiko.ino
commande arduino via récepteur 6 canaux de modélisme
(7.07 Kio) Téléchargé 1005 fois
- Christophe -

steering
Messages : 2
Inscription : 26 mai 2019, 01:06

Re: Télécommande à base d'Arduino et de nRF24L01+

Message par steering » 26 mai 2019, 01:24

Bonsoir
J'ai eu beaucoup de plaisir à construire le système arduino avec une télécommande RC (controle_robo-qiko.ino. J'en remercie l'auteur.
Mais j'ai un petit problème:
L'engin se comporte très bien dans les phases avant, arrière, avant gauche, avant droit, milieu droit et enfin milieu gauche.
Par contre, les phases arrière gauche et arrière droit sont inversées :
Stick de direction à bas gauche: l'engin devrait tourner en arrière gauche (comme il le fait pour l'avant gauche)
Stick de direction à bas droite : l'engin devrait tourner en arrière droit (comme il le fait pour l'avant droit)
Je n'arrive pas à trouver les valeurs qui me permettraient de modifier ce comportement. Pour constat et uniquement dans cette phase, les moteurs tournent bien dans le bon sens mais l'un possède une vitesse supérieure à l'autre, cela inverse la direction souhaitée.
Merci de votre aide à tous

Avatar de l’utilisateur
qiko68
Messages : 157
Inscription : 02 janv. 2015, 14:11
Localisation : Didenheim

Re: Télécommande à base d'Arduino et de nRF24L01+

Message par qiko68 » 26 mai 2019, 11:56

Bonjour,

Si tu utilise une radiocommande , regarde qu'il n'y ai pas dans sa configuration des couplage activés, cela arrive parfois, surtout si la radio est sur un mode avion ou drone
- Christophe -

steering
Messages : 2
Inscription : 26 mai 2019, 01:06

Re: Télécommande à base d'Arduino et de nRF24L01+

Message par steering » 26 mai 2019, 23:34

Bonsoir et merci pour l'aide.
J'ai donc pris en compte de votre remarque en travaillant cet après midi sur le sujet.
Ma radiocommande est de type FS-TH9x avec un récepteur IA6b. Les signaux transmis à l'uno sont PWM (canaux 1 et 2). Sur ma radio, j'ai donc créé un modèle vierge sans aucun mixage ou autre.
J'ai donc déclenché sur l'IDE arduino le moniteur série afin d'examiner les valeurs axis_x et axis_y du programme. Celles-ci varient 1000 à 2000 sans difficulté (canal 1-direction et canal2-sens). Le joystick dans le sens de l'axe des des x arrière gauche et arrière droit (mon problème précédemment exposé) ne montre pas d'erreurs d'interprétation.
Pourtant, avec le montage, visuellement le problème est simple :les moteurs tournent dans le bon sens mais le plus rapide devrait être le plus lent.
Voilà mes constatations de cet après midi !!!! et encore merci

Répondre