RoMeo BLE v1.0

Présentation

Cette carte « RoMeo BLE v1.0 », compatible Arduino, contient :

  • un micro-contrôleur ATmega328P
  • un contrôle des moteurs (L298)
  • un récepteur Bluetooth 4.0 BLE (Bluetooth Low Energy)

Dans l’IDE, il faut sélectionner carte Arduino Uno. Elle se branche par port micro-USB.

Page wiki du constructeur : DFrobot RoMeo BLE (SKU:DFR0305)

Pour contrôler les 2 moteurs des roues, la RomeoBLE utilise 4 broches sur sa carte (vitesse maxi = 68 cm/s) :

  • 2 digitales pour donner le sens de rotation (la direction) : horaire ou anti-horaire :
    • pin 4 = moteur 1 (sens de rotation)
    • pin 7 = moteur 2 (sens de rotation)
  • 2 PWM (donc digitales) pour indiquer la vitesse :
    • pin 5 = moteur 1 (vitesse)
    • pin 6 = moteur 2 (vitesse)

Ces broches sont branchées sur les connecteurs moteurs en interne (le branchement est invisible de l’extérieur). Cependant, il faut savoir qu’elles ne peuvent pas être utilisées pour un autre périphérique si on utilise les moteurs.

Rôle de l’interrupteur MOTOR POWER SWITCH :

  • ON : le micro-contrôleur est alimenté par l’alimentation des moteurs (M_VIN).
  • OFF : le micro-contrôleur n’est pas alimenté par M_VIN … donc il est alimentée par le port micro-USB.

Alimentation : Un câble USB apporte un courant de 5V (intensité maximale 500 mA), ce courant est insuffisant pour faire tourner des moteurs.

Programme Moteur

  1. Mettre les batteries (sinon les moteurs ne fonctionnent pas).
  2. Surélever le robot pour que ses roues tournent dans le vide pendant les tests.
  3. Brancher le câble mini USB.
  4. Ouvrir l’IDE Arduino (régler le port COM et le type de carte = Uno 0_panneau_attention).
  5. Copier-Coller le code ci-dessous. Enregistrer.
  6. Téléverser le programme.
  7. Ouvrir le « moniteur Série »
  8. Régler la fréquence sur 115200 bauds (la même fréquence que le programme : Serial.begin(115200); )
  9. Taper par exemple le caractère « a » pour démarrer les moteur (avancer), puis « s » pour les stopper (en laissant le cable miniUSB branché).
// --- les variables : déclaration et initialisation
int E1 = 5;    //M1 : vitesse du moteur M1
int E2 = 6;    //M2 : vitesse du moteur M2
int M1 = 4;    //M1 Direction : horaire/anti-horaire
int M2 = 7;    //M2 Direction : horaire/anti-horaire
 
// --- le Programme : setup() et loop()
void setup() { 
   pinMode(E1, OUTPUT); 
   pinMode(E2, OUTPUT); 
   pinMode(M1, OUTPUT); 
   pinMode(M2, OUTPUT);  
   Serial.begin(115200);   //Frequence de la liaison 'Moniteur Serie'
   Serial.println("Run keyboard control");
}
 
void loop() {
  //si on recoit des données (des caractères)
  if (Serial.available()) { //la liaison série se fait par les pin 0 et 1 (RX TX)
    //on récupère le caractère tapé au clavier (or -1 if no data is available)
    char val = Serial.read(); 
    if (val != -1) {
      //étude des cas possibles
      Serial.println(val);
      switch(val) {
        case 'a'://Move Forward
          advance (155,155); //move forward (max speed=255)
          break;
        case 'b'://Move Backward
          back_off (155,155); //move back (max speed=255)
          break;
        case 'l'://Turn Left
          turn_L (100,100); //gauche, vitesse lente=100
          break;
        case 'r'://Turn Right
          turn_R (100,100); // droite, vitesse lente=100
          break;
        case 's': stopped();  break;
        case 'h': Serial.println("Hello");  break;
      }
    }
    else stopped();
  }
}
 
// --- Les fonctions -----
void stopped() {
  Serial.println("STOP");
  digitalWrite(E1,0);
  digitalWrite(E2,0);
}
 
void advance(char a,char b) {
  Serial.println("AVANCE");
  analogWrite (E1,a);
  digitalWrite(M1,HIGH);
  analogWrite (E2,b);
  digitalWrite(M2,HIGH);
}
 
void back_off (char a,char b) {
  Serial.println("ARRIERE");
  analogWrite (E1,a);
  digitalWrite(M1,LOW);
  analogWrite (E2,b);
  digitalWrite(M2,LOW);
}
 
void turn_L (char a,char b) {
  Serial.println("LEFT");
  analogWrite (E1,a);
  digitalWrite(M1,LOW);
  analogWrite (E2,b);
  digitalWrite(M2,HIGH);
}
 
void turn_R (char a,char b) {
  Serial.println("RIGHT");
  analogWrite (E1,a);
  digitalWrite(M1,HIGH);
  analogWrite (E2,b);
  digitalWrite(M2,LOW);
}

Bluetooth

 Éteindre le Bluetooth du téléphone avant de téléverser le programme dans la carte car le Bluetooth utilise la même liaison série que le câble USB donc si les 2 liaisons cohabitent, ça bug !

De plus, la liaison Bluetooth utilise les pin 0 et 1, donc on ne peut pas brancher d’autres modules sur ces broches.

iOS (GoBLE)

  • Installer l’application GoBLE d’iTunes : https://itunes.apple.com/app/goble-bluetooth-4.0-controller/id950937437
  • wiki de l’application : https://www.dfrobot.com/wiki/index.php/Bluetooth_APP_Control
  • Dans l’IDE d’Arduino, il faut télécharger et installer les 3 librairies :
    1. Ouvrir  https://github.com/CainZ/GoBle  puis  Clone or Download  puis Download ZIP.
    2. Décompresser le fichier ZIP obtenu (Clic droit > ‘extraire l’archive’).
    3. Ouvrir le logiciel Arduino : Croquis > Inclure une Bibliothèque > Ajouter la bibliothèque ZIP :

      • Choisir le dossier DFMobile [ mais ne pas entrer dans le répertoire !!! … ]
      • Puis recommencer pour le dossier GoBLE et encore pour le dossier Metro.
  • Copier/coller le programme :
    1. GoBLE_Test (dossier  GoBle-master\GoBLE_Test\GoBLE_Test.ino) : ce programme permet de visualiser sur le moniteur série les commandes envoyées par l’application du téléphone (le robot n’avance pas).
      • Éteindre le Bluetooth du téléphone avant de téléverser le programme dans la carte.
      • Téléverser le programme.
      • Ouvrir le Moniteur Série  et le régler sur 115200 bauds.
      • Sur le téléphone : ouvrir le Bluetooth et se connecter à la carte BLUNO.
      • Appuyer sur les boutons et/ou le joystick.
    2. GOBBLE APP : idem, mais cette fois, le robot avance.
      Les commentaires seront utiles pour débugger avec le Moniteur Série.

      joystickX0 à 255.    joystickY : 0 à 255.
      buttonState[i] : PRESSED ou RELEASED (ou bien LOW ou HIGH)

      #include <GoBLE.h>
      #include <Metro.h>
      #include <DFMobile.h>
      
      DFMobile Robot (7,6,4,5);
      int LeftWheelSpeed;
      int RightWheelSpeed;
      int joystickX, joystickY;
      int buttonState[7];
      int ledPin = 13;
      
      void setup (){
        Robot.Direction (LOW,LOW); // (left direction,right direction); 
        pinMode(ledPin,OUTPUT);
        Goble.begin();
      //   Serial.begin(115200);
      }
      
      void loop (){
        if ( Goble.available() ) { //si l'iPad envoie une commande Bluetooth
          joystickX = Goble.readJoystickX();  
          joystickY = Goble.readJoystickY();
      //    Serial.print("joystick : X = ");
      //    Serial.print(joystickX);
      //    Serial.print(" || Y = ");
      //    Serial.println(joystickY);
      
          buttonState[SWITCH_UP]     = Goble.readSwitchUp();
          buttonState[SWITCH_LEFT]   = Goble.readSwitchLeft();
          buttonState[SWITCH_RIGHT]  = Goble.readSwitchRight();
          buttonState[SWITCH_DOWN]   = Goble.readSwitchDown();
          buttonState[SWITCH_SELECT] = Goble.readSwitchSelect();
          buttonState[SWITCH_START]  = Goble.readSwitchStart();
      
          //enable to map the value from (1~255) to (-255~255)
          int SpeedX=2*joystickX-256;
          int SpeedY=2*joystickY-256;
          Serial.print("Speed: ");
          Serial.print(SpeedX);
          Serial.print("  ");
          Serial.println(SpeedY);
      
          if (SpeedX>200 || SpeedX<-200){           //when joystick X is pushed up
            LeftWheelSpeed=SpeedX;                 
            RightWheelSpeed=SpeedX;
            Robot.Speed (LeftWheelSpeed,RightWheelSpeed);
          }         
          else if (SpeedY>200 || SpeedY<-200){           
            LeftWheelSpeed=SpeedY-80;               //when joystick Y is pushed up
            RightWheelSpeed=-SpeedY-80;
            Robot.Speed(LeftWheelSpeed,RightWheelSpeed);
          }
          else if (SpeedX==0 && SpeedY==0){
            Robot.Speed(0,0);
          }
          
          if (buttonState[1] == PRESSED){
              digitalWrite(ledPin,HIGH);  
          }
          if (buttonState[1] == RELEASED){
              digitalWrite(ledPin,LOW);
          }
        } //-- Fin de : if ( Goble.available() )
        
      } // Fin de loop()

Exemple 1 : tirer un missile avec le bouton 1 : 

#include <Metro.h>
#include "GoBLE.h"
int joystickX, joystickY;
int buttonState[7];
int MISSILE_PIN = 12;

void setup(){
  initMissile();
  Goble.begin();
  Serial.begin(115200);
}

void loop() {  
  if( Goble.available() ) {
    joystickX = Goble.readJoystickX();
    joystickY = Goble.readJoystickY();
    buttonState[SWITCH_UP]     = Goble.readSwitchUp();
    buttonState[SWITCH_DOWN]   = Goble.readSwitchDown();
    buttonState[SWITCH_LEFT]   = Goble.readSwitchLeft();
    buttonState[SWITCH_RIGHT]  = Goble.readSwitchRight();
    buttonState[SWITCH_SELECT] = Goble.readSwitchSelect();
    buttonState[SWITCH_START]  = Goble.readSwitchStart();

    if (buttonState[1] == PRESSED) { tir(); }
  }
}

void initMissile() {
  pinMode(MISSILE_PIN, OUTPUT);
  digitalWrite(MISSILE_PIN, LOW);
}

void tir() {
  digitalWrite(MISSILE_PIN, HIGH);
  delay(120); // 120 ms = 1 fléchette
  digitalWrite(MISSILE_PIN, LOW);
}

exemple 2 : diriger un servomoteur avec les boutons 3 et 4 :   

#include <Metro.h>
#include "GoBLE.h"
#include <Servo.h>

int joystickX, joystickY;
int buttonState[7];
int angle = 90;  // angle du servo : 0°-180°
int SERVO_PIN = 3; // pin du servo
Servo monServo;

void setup(){
  Goble.begin();
  Serial.begin(115200);
  monServo.attach(SERVO_PIN);
  monServo.write(angle);
}

void loop() {  
  if( Goble.available() ) {
    joystickX = Goble.readJoystickX();
    joystickY = Goble.readJoystickY();
    buttonState[SWITCH_UP]     = Goble.readSwitchUp();
    buttonState[SWITCH_DOWN]   = Goble.readSwitchDown();
    buttonState[SWITCH_LEFT]   = Goble.readSwitchLeft();
    buttonState[SWITCH_RIGHT]  = Goble.readSwitchRight();
    buttonState[SWITCH_SELECT] = Goble.readSwitchSelect();
    buttonState[SWITCH_START]  = Goble.readSwitchStart();

    if (buttonState[SWITCH_LEFT] == PRESSED &&
        buttonState[SWITCH_RIGHT] == RELEASED) {
          angle = angle + 5;
          if (angle > 180) angle = 180;
          Serial.println("servo LEFT ++");
     } 
     else if (buttonState[SWITCH_RIGHT] == PRESSED &&
             buttonState[SWITCH_LEFT] == RELEASED) {
          angle = angle - 5;
          if (angle < 0) angle = 0;
          Serial.println("servo RIGHT --");
    }

    Serial.println(angle);
    monServo.write(angle);
    Serial.println("-------------");
  }
}

Android (Bluno Remote)

Prendre l’application Androïd Bluno Remote.

RoMeo V2 (=> Leonardo)

Ar_ro_romeoIntroduction

Cette carte « RoMeo V2.2 [R3] », compatible Arduino, contient :

  • un micro-contrôleur ATmega32u4
  • un contrôle des moteurs

Dans l’IDE, il faut sélectionner carte Arduino Leonardo 0_panneau_attention. Elle se branche par port micro-USB. Elle remplace Arduino Uno + carte contrôle moteur.

Page wiki du constructeur : DFrobot Romeo V2.2 [R3] (SKU:DFR0225)

Pour contrôler les 2 moteurs des roues, la Romeo utilise 4 broches sur sa carte :

  • 2 digitales pour donner le sens de rotation (la direction) : horaire ou anti-horaire :
    • pin 4 = moteur 1 (sens de rotation)
    • pin 7 = moteur 2 (sens de rotation)
  • 2 PWM (donc digitales) pour indiquer la vitesse :
    • pin 5 = moteur 1 (vitesse)
    • pin 6 = moteur 2 (vitesse)

Ces broches sont branchées sur les connecteurs moteurs en interne (le branchement est invisible de l’extérieur). Cependant, il faut savoir qu’elles ne peuvent pas être utilisées pour un autre périphérique si on utilise les moteurs, c’est à dire si l’interrupteur moteur est sur ON.

Alimentation : Un câble USB apporte un courant de 5V (intensité maximale 500 mA), ce courant est insuffisant pour faire tourner des moteurs.

Programme : simple et élémentaire

Ar_Ro-plateforme2

  1. Mettre les batteries (sinon les moteurs ne fonctionnent pas).
  2. Surélever le robot pour que ses roues tournent dans le vide pendant les tests.
  3. Brancher le câble mini USB.
  4. Ouvrir l’IDE Arduino (régler le port COM et le type de carte = Leonardo 0_panneau_attention).
  5. On va préparer 2 programmes (donc 2 fenêtres) :
    • 1er programme : faire avancer et faire tourner le robot.
    • 2ème programme : un programme vide qui sert à vider la mémoire de la carte et donc à arrêter le 1er programme !
  6. Copier-Coller les 2 codes ci-dessous. Enregistrer.
  7. Téléverser le 1er programme … et le robot devrait démarrer.
  8. Téléverser le 2ème programme … et le robot devrait s’arrêter.

1er code : le programme simple :

// --- les variables : déclaration et initialisation
int E1 = 5;    //M1 : vitesse du moteur M1
int E2 = 6;    //M2 : vitesse du moteur M2
int M1 = 4;    //M1 Direction : horaire/anti-horaire
int M2 = 7;    //M2 Direction : horaire/anti-horaire
 
// --- le Programme : setup() et loop()
void setup() { 
   pinMode(E1, OUTPUT); 
   pinMode(E2, OUTPUT); 
   pinMode(M1, OUTPUT); 
   pinMode(M2, OUTPUT);  
} 
 
void loop() {
  avance();
  delay(2000);
  gauche();
  delay(500);
}

// --- Les fonctions -----
void avance()  { // AVANCE
  analogWrite (E1,100);      //PWM Speed Control : vitesse 100
  digitalWrite(M1,HIGH);    
  analogWrite (E2,100);    
  digitalWrite(M2,HIGH);
}  
 
void gauche() { // Tourne à GAUCHE
  analogWrite (E1,70);      //PWM Speed Control : vitesse 70
  digitalWrite(M1,LOW);    
  analogWrite (E2,70);    
  digitalWrite(M2,HIGH);
}

2ème code : le programme vide :

void setup() {} 
void loop() {}

Programme : plus évolué

Voici un programme (qui provient du site officiel) qui répond aux commandes du clavier lorsque celles-ci sont envoyées par la fenêtre « Serial » (avec le câble usb branché évidemment) : le même programme que RoMeoBLE (ci-dessus) avec l’IDE réglé sur Leonardo.

Capteur Ultra-Son  HC SR04Ar_UltraSon

Ce capteur mesure la distance jusqu’au premier obstacle.

Le branchement est simple :

  • Vcc : branché à « +5V »
  • Trig : un pin digital (PWM ou pas)
  • Echo : un pin digital (idem)
  • GND : relié à la masse.

Ar_SensorPingOperationLe principe est simple : Lorsque l’arduino donne une impulsion HIGH sur le pin Trig pendant 10 micro-secondes, cela envoie un ultra-son (par le module T, à gauche) … puis le module R (à droite) attend son retour et donne une impulsion HIGH sur le pin Echo d’une durée proportionnelle à la longueur du trajet ou 0 s’il n’y a aucun obstacle. Ensuite, il faut attendre au moins 60 millisecondes avant de refaire une nouvelle mesure (pour éviter les interférences avec les précédentes).

ar_moniteurSerieBaudLe code suivant affiche la distance sur l’ordinateur (en ouvrant la fenêtre ‘liaison série’ Ar_Pr_Btn_serial de l’IDE) par l’intermédiaire du câble USB (entre l’ordi et la carte Arduino) émulé en communication série sur la fréquence 9600 bit/s (l’unité bit/s est aussi appelé baud). Il faut donc régler le moniteur série Ar_Pr_Btn_serial de l’IDE sur la fréquence 9600 aussi !

Il y a 2 librairies possibles : Arduino (qui a l’inconvénient d’être « monotâche ») et NewPing (multitâche).

Davantage de documentation :
https://f-leb.developpez.com/tutoriels/arduino/univers_arduino/part2/

1ère librairie : Arduino (trop limitée)

Son avantage est qu’il n’y a rien à installer (c’est la librairie d’origine) mais son inconvénient est qu’elle est monotâche, c’est à dire pendant tout le temps qu’on effectue une mesure, le reste du code est en pause. Par conséquent, si vous voulez faire des mesures qui interagissent avec le mouvement du robot, passez à la librairie suivante (qui est multitâche : le code continue de fonctionner pendant les mesures).

/* Utilisation du capteur Ultrason HC-SR04 (librairie Arduino) */
// --- les variables : déclaration et initialisation
int trig = 9; // émetteur (digital pin 9) (mode OUTPUT)
int echo = 8; // récepteur (digital pin 8) (mode INPUT)
long lecture_echo, cm; 

void setup() {
  pinMode(trig, OUTPUT); 
  digitalWrite(trig, LOW); 
  pinMode(echo, INPUT); 
  Serial.begin(9600); //démarre la liaison "série" avec l'IDE sur 9600 baud
}

void loop() {
  digitalWrite(trig, HIGH); 
  delayMicroseconds(10); 
  digitalWrite(trig, LOW); 
  lecture_echo = pulseIn(echo, HIGH); 
  cm = lecture_echo / 58; 
  Serial.print("Distance : "); 
  Serial.println(cm); 
  delay(1000); 
}

2ème librairie : NewPing (très polyvalente)

  1. Télécharger  NewPing_v1.9.zip  sur  https://bitbucket.org/teckel12/arduino-new-ping/wiki/Home
  2. Puis il faut l’importer dans l’IDE Arduino :  Croquis > inclure une bibliothèque > Ajouter la bibliothèque ZIP > NewPing_v1.9.zip

Fonctionnement : NewPing utilise la bibliothèque du Timer2 (ou Timer4 pour Teensy/Leonardo avec ATmega32U4) pour interrompre l’appel à la fonction echoCheck() toutes les 24 microsecondes. C’est aussi cette fonction echoCheck() qui vérifie si le ping est revenu (en statut ‘complété’) et les instructions à faire en conséquence. L’avantage est que le scketch peut faire 2 tâches en même temps, comme tester la présence d’obstacle et télécommander le robot.

Voici un code exemple : Le plus important est de remarquer qu’il n’y a pas de delay() ainsi le programme ne s’arrête pas !! En revanche il y a une horloge, et une mesure se déclenche lorsqu’il est l’heure de le faire.

‘unsigned int’ et ‘unsigned long’ :

  • byte prend des valeurs entières entre 0 et 255 (codé sur 8 bits) [il n’a pas de signe].
  • int prend des valeurs entières entre -32 768 et 32 767 (codé sur 16 bits) [de -2^15 à (2^15) – 1].
  • unsigned int peut varier de 0 à 65 535 [soit 2^16 – 1] (codé sur 16 bits aussi).
  • long prend des valeurs entières entre  -2 147 483 648 et 2 147 483 647 (codé sur 32 bits).
  • unsigned long peut varier de 0 à 4 294 967 295 [soit 2^32 – 1] (codé sur 32 bits aussi).
/* Capteur Ultrason HC-SR04 (librairie NewPing : multitache) */
#include <NewPing.h>
#define TRIGGER_PIN 7 //pin émetteur de l'Ultra-Son (mode OUTPUT)
#define ECHO_PIN 6    //pin récepteur de l'Ultra-Son (mode INPUT)
#define MAX_DISTANCE 200 // distance maximale (en cm) à "pinger". Portée max=400-500cm.
#define PING_INTERVAL 50 // tps d'attente entre 2 pings. 50 ms correspond à 20 ping/s.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);//initialise NewPing (pins et portée)
unsigned int distance;   //distance mesurée
unsigned long pingTimer; //l'heure du prochain ping


void setup() { 
  Serial.begin(9600); //démarre la liaison "série" avec l'IDE sur 9600 baud
  pingTimer = millis() + 100; // millis() = tps écoulé depuis le début du pgm (en ms).
}

void loop() {
  // mettre du code ici (comme lire une commande 'avancer', 'tournerAdroite' ..)

  // si le temps est écoulé depuis le dernier "ping", on fait à nouveau un "ping" :
  if (millis() > pingTimer) {
        pingTimer = pingTimer + PING_INTERVAL;  // l'heure du prochain "ping"        
        sonar.ping_timer(echoCheck); //envoie un "ping" et exécute la fct echoCheck()
   }

  // mettre du code (comme faire avancer le robot ...)
}


void echoCheck() { // sous-programme d'interruption (une fonction)
  if (sonar.check_timer()) { // si le ping est complété et dans la portée maximale  
  
    //On calcule la distance mesurée, et on l'affiche dans le moniteur série
    distance = sonar.ping_result/US_ROUNDTRIP_CM; //convertit la durée du signal en cm
    Serial.print(distance); //affichage (facultatif)
    Serial.println(" cm.");
    
    //Puis on interprète le résultat : Vérification du danger (20cm)
    if ((distance > 0) && (distance < 20)) { 
       Serial.print(" - ZONE DE DANGER !!!!"); //affichage ds moniteur série (facultatif)
       // ajouter du code, comme arrêter le robot : stopped(); delay(400); 
    }
  }
}

2 capteurs & mesure médiane


L’idée est de faire un cycle de 5 mesures (en alternant les capteurs) puis de ne garder que la médiane de ces 5 mesures (pour écarter les résultats farfelus) et aussi on enlève les mesures nulles.

#include <NewPing.h>

#define SONAR_NUM     2 // Number of sensors.
#define ITERATIONS    5 // nb d'itérations pour le calcul de la distance médiane
#define MAX_DISTANCE 200 // Maximum distance (in cm) to ping.
#define PING_INTERVAL 50 // Minimum = 29ms (to avoid cross-sensor echo).
#define TRIGGER_PIN_b 12 //pin émetteur de l'Ultra-Son (mode OUTPUT)
#define ECHO_PIN_b 13    //pin récepteur de l'Ultra-Son (mode INPUT)
#define TRIGGER_PIN_j 11 //pin émetteur de l'Ultra-Son (mode OUTPUT)
#define ECHO_PIN_j 10    //pin récepteur de l'Ultra-Son (mode INPUT)

unsigned long pingTimer[SONAR_NUM]; // tableau 1D des heures de ping (capteurs)
unsigned int cm[SONAR_NUM][ITERATIONS]; // tableau 2D des mesures [capteur, iteration]
uint8_t currentSensor = 0;          // pour savoir quel capteur est actif
uint8_t currentIteration = 0;       // pour savoir quelle itération est active

NewPing sonar[SONAR_NUM] = {  // tableau de capteurs (avec pin trig, echo, max_dist)
  NewPing(TRIGGER_PIN_b, ECHO_PIN_b, MAX_DISTANCE),
  NewPing(TRIGGER_PIN_j, ECHO_PIN_j, MAX_DISTANCE)
};

void setup() {
  Serial.begin(115200);
  pingTimer[0] = millis() + 100;          // millis() = tps écoulé depuis le début du pgm (en ms)
  for (uint8_t i = 1; i < SONAR_NUM; i++) // Définit l'heure du prochain ping de chaque capteur
    pingTimer[i] = pingTimer[i - 1] + PING_INTERVAL;
}

void loop() {
  for (uint8_t i = 0; i < SONAR_NUM; i++) { // Boucle sur les capteurs
    if (millis() >= pingTimer[i]) {// S'il est l'heure de lancer un ping pour le capteur n°i
      pingTimer[i] = pingTimer[i] + PING_INTERVAL * SONAR_NUM; // heure du ping d'après
      if (i == 0 && currentSensor == SONAR_NUM-1) oneSensorCycle();//Cycle complet, utiliser mesures
      sonar[currentSensor].timer_stop(); // Arrête l'ancien timer avant de lancer un nouveau ping
      currentSensor = i;                 // Capteur en activité
      cm[currentSensor][currentIteration] = 0;// Au cas où il n'y aurait pas de ping réponse
      sonar[currentSensor].ping_timer(echoCheck); // Lance le ping, appelle la fct echoCheck()
    }
  }
  // ici  ... mettre du code qui n'analyse pas les pings (commande des moteurs ...)
}

void echoCheck() { // sous-programme d'interruption (une fonction)
  if (sonar[currentSensor].check_timer())  // si le ping est complété et dans la portée maximale
    cm[currentSensor][currentIteration] = sonar[currentSensor].ping_result / US_ROUNDTRIP_CM;
}

void oneSensorCycle() { // Cycle des capteurs fini : ici ... faire quelque chose des résultats
  // == CALCUL DE LA MEDIANE de chaque capteur (pour écarter les mesures bizarres)
  for (uint8_t i = 0; i < SONAR_NUM; i++) {  // boucle sur les capteurs : capteur n°i
    // -- TRI PAR INSERTION DE cm[i][] (mesures du capteur n°i) ds sens décroissant
    unsigned int temp[ITERATIONS]; // tableau des résultats ordonné
    uint8_t nbIterations = ITERATIONS; // nb de mesures non nulles
    for (uint8_t j = 0 ; j < ITERATIONS ; j++) {
      temp[j] = 0;
      if (cm[i][j] != 0) {
        uint8_t k = j;
        while ( k > 0 && temp[k - 1] < cm[i][j]) {//on pousse à droite les temp[] inférieurs à cm[i][j]
          temp[k] = temp[k - 1];
          k--;
        }
        temp[k] = cm[i][j];
      } else nbIterations--;
    } //fin de boucle sur j : les mesures cm[i][j]

    //facultatif : affiche la médiane et le tableau temp[] (ordonné ds sens décroissant)
    Serial.print(temp[nbIterations/2]); // médiane
    Serial.print(" [");
    for (uint8_t k = 0 ; k < nbIterations ; k++) { //tableau des résultats (sens décroissant)
      Serial.print(temp[k]);
      Serial.print(", ");
    }
    Serial.print("] | ");
  } //fin de boucle sur i : les capteurs

  Serial.println();
  currentIteration++;
  if (currentIteration > ITERATIONS) currentIteration = 0;
}

Servomoteur

Bien respecter le câblage au risque de griller le servo :

  • Marron = GND (masse)
  • rouge = VCC
  • Jaune = broche Digital PWM*

Problème récurrent avec les servo : c’est la consommation électrique : perte de la communication avec la carte Arduino ou redémarrage intempestif (la surconsommation déclenche le fusible électronique des cartes Arduino et a pour effet de les faire redémarrer). La seule solution est d’utiliser une alimentation (une pile de 9V) dédiée aux servomoteurs.

On utilise la librairie Servo.h disponible dans l’installation par défaut d’Arduino.

  • #include <Servo.h>
  • Variables :
    Servo monServo;
    int servoPin = 9;
  • Dans le setup() : monServo.attach(servoPin);
  • Envoyer un ordre : 2 possibilités :
    ♦ soit un angle : monServo.write(angle);
    ♦ soit un temps d’impulsion en µs : monServo.writeMicroseconds(temps);

 Cette librairie Servo.h rend inutilisables les broches D9 et D10 de la carte Arduino UNO en PWM (càd avec analogWrite() même s’il n’y a aucun servo de branché dessus), mais ces broches restent utilisables comme de simples entrées/sorties numériques.

=> Une impulsion est envoyée à intervalle régulier et c’est la durée de cette impulsion qui détermine l’angle.

Caractéristiques du TowerPro SG-5010

Amplitude : de 0 à 180° : de  540 µs (angle=0°) à  2400 µs (angle=180°).
Vitesse : 0.19 sec/60°
Couple de force : 5 kg.cm  [ couple = masse x distance (depuis l’axe de rotation) ]
Nb de dents autour de l’axe : 25

Trouver l’amplitude en µs

Voici un petit programme simple, dans lequel on saisit les caractères ‘a’ ou ‘d’ dans le moniteur série pour augmenter/diminuer l’angle du servo.
PS : Ne surtout pas forcer un servomoteur => revenir en arrière, lorsque le corps du servo tremble ou fait un bruit aigu.

Conclusion : TowerPro SG-5010 s’avère avoir une fourchette de 540µs (angle=0°) à 2400µs (angle=180°)  [ et pour ma part, en dehors de cette fourchette, il ne se passe rien : le servo ne bouge plus].

#include <Servo.h>
int temps = 1500; // entre 540µs(angle=0°) et 2400µs(angle=180°)
int servoPin = 9; // pin du servo
Servo monServo;

void setup() {
  //initialise le Moniteur Série
  Serial.begin(9600);
  Serial.println("Bjr, taper 'a' ou 'd' (pour augmenter/diminuer)");

  //met le servo (attaché à sa broche) en position initiale
  monServo.attach(servoPin);  
  monServo.writeMicroseconds(temps);
}

void loop() {
  if ( Serial.available() )  {
    //on lit le caractère tapé
    char commande = Serial.read();

    //si ce caractère est 'a' ou 'd'
    if (commande == 'a') {
      temps = temps + 10;
    } else if (commande == 'd') {
      temps = temps - 10;
    }

    //on envoie la consigne du servo
    monServo.writeMicroseconds(temps);

    //on écrit la nouvelle valeur à l'écran
    Serial.println(temps, DEC);
  }
}

Trouver l’amplitude d’angle

#include <Servo.h>
int angle = 0;   // angle du servo : 0°-180°
int servoPin = 9; // pin du servo
Servo monServo;

void setup() {
  //initialise le Moniteur Série
  Serial.begin(9600);
  Serial.println("Bjr, taper 'a' ou 'd' (pour augmenter/diminuer)");

  // met le servo en position initiale (attaché à sa broche)
  monServo.attach(servoPin);  
  monServo.write(angle);
}

void loop() {
  if ( Serial.available() )  {
    //On lit le caractère tapé
    char commande = Serial.read();

    //si ce caractère est 'a' ou 'd'
    if (commande == 'a') {
      angle = angle + 10;
    } else if (commande == 'd') {
      angle = angle - 10;
    }

    //on envoie la consigne du servo
    monServo.write(angle);

    //on écrit la nouvelle valeur à l'écran
    Serial.println(angle);
  }
}

Mapper la fonction attach() : Si on connait la fourchette des durées d’impulsion, on peut les transmettre à la librairie en ajoutant 2 paramètres optionnels (« min » et « max » en microsecondes, par défaut 544 et 2400) à la fonction attach() :   monServo.attach(servoPin, 540, 2400);

Module RGB

La led reproduit la somme de 3 composantes lumineuses RGB par : 3 broches PWM (ex : n° 9, 10, 11) … chacune allant de 0 à 255.
Mais les commandes sont toutes inversées (du fait du montage avec des résistances « pull up » reliées au courant 5V) :

LOW = 0 = pour allumer une composante de couleur (en commande inversée) !!
HIGH = 255 = pour éteindre la composante lumineuse (en commande inversée) !!
analogWrite(redpin,0); analogWrite(greenpin,0); analogWrite(bluepin,0); donne une lumière blanche.
analogWrite(redpin,255); analogWrite(greenpin,255); analogWrite(bluepin,255); donne une lumière éteinte.

int redpin = 11;
int bluepin = 10;
int greenpin = 9;

void setup () {
  pinMode (redpin, OUTPUT);
  pinMode (bluepin, OUTPUT);
  pinMode (greenpin, OUTPUT);
  Serial.begin (9600); //facultatif : ouvre une liaison 'moniteur Série'
}

void loop () {
  for (int i = 255; i > 0; i--)  {
    analogWrite (redpin, i);
    analogWrite (bluepin, 255-i);
    analogWrite (greenpin, 128-i);
    delay (10);
    Serial.println (i, DEC); //affiche i sur le 'moniteur Série'
  }
  for (int i = 0; i <255; i++)  {
    analogWrite (redpin, i);
    analogWrite (bluepin, 255-i);
    analogWrite (greenpin, 128-i);
    delay (10);
    Serial.println (i, DEC); //affiche la valeur de i à l'écran
  }
}

Autre exemple avec GoBLE: On convertit les coordonnées du joystick en composantes RGB (en passant par les coordonnées polaires).

#include <Metro.h>
#include <DFMobile.h>
#include <GoBLE.h>
#include <QueueArray.h>
#define RED_PIN 11
#define BLUE_PIN 10
#define GREEN_PIN 9
DFMobile Robot (7,6,4,5);
int buttonState[7];
int joystickX, joystickY, LeftWheelSpeed, RightWheelSpeed;

void setup (){
  Robot.Direction (LOW,LOW); // (left direction,right direction);
  pinMode(RED_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  Goble.begin();
  Serial.begin(115200); // facultatif : moniteur série
}

void loop (){
  if(Goble.available()){ // si l'appli du téléphone envoie une commande :
    // ============ LECTURE de joystickX et joystickY ===========
    joystickX = Goble.readJoystickX();  //joystickX = [1,255]
    joystickY = Goble.readJoystickY();  //joystickY = [1,255]

    // ============= VITESSE DES MOTEURS ========================
    int SpeedX=2*joystickX-256; // SpeedX = [-255,255]
    int SpeedY=2*joystickY-256; // SpeedY = [-255,255]
    if (SpeedX>200 || SpeedX<-200) {  //when joystick X is pushed up
      LeftWheelSpeed=SpeedX;
      RightWheelSpeed=SpeedX;
      Robot.Speed (LeftWheelSpeed,RightWheelSpeed);
    } else if (SpeedY>200 || SpeedY<-200) {
      LeftWheelSpeed=SpeedY-80;       //when joystick Y is pushed up
      RightWheelSpeed=-SpeedY-80;
      Robot.Speed(LeftWheelSpeed,RightWheelSpeed);
    } else if (SpeedX==0 && SpeedY==0) {
      Robot.Speed(0,0);
    }

    // ============= LAMPE RGB ========================
    // Convertir joystickX=[1,255] joystickY=[1,255] ==> R=[0,255] G=[0,255] B=[0,255]
    // NB : lorsqu'on relâche/arrête le joystick, on a (joystickX,joystickY)=(128,128)
    // 1 -- on met le repère au centre du joystick -> x = [-127,127] et y = [-127,127]
    int x = joystickX - 128;
    int y = joystickY - 128;      //NB : lorsqu'on relâche le joystick (x,y)=(0,0)

    // 2 -- calcul les coordonnées polaires : rayon, phi (phi est un angle en radian)
    float rayon = sqrt(x * x + y * y);
    //Mais on ne cherche l'angle que si (x,y) != (0,0) càd si rayon non nul
    float phi = 0;
    if (rayon > 0) phi = atan2(y,x); // phi = [....................]

    // 3 -- Calcul de HSV : h = [0,360] (la teinte)   s = [0,1]   v = [0,1]
    float h = ...... // h est grosso-modo la conversion en degré de phi, ainsi h = [0,360]
    float s = v = 1;  // luminosité maximum

    // 4 -- Calcul de RGB : r = [0,1]   g = [0,1]   b = [0,1]
    float f, p, q, t, r, g, b;
    if ( s == 0 ) { // achromatique (gris)
      r = g = b = v;
    } else {
      int i = floor( h/60 ); // i = 0 à 5 = partie entière de h/60 (secteur de 60°)
      f = h/60 - i;          // f = partie décimale de h/60
      p = v * ( 1 - s );
      q = v * ( 1 - s * f );
      t = v * ( 1 - s * ( 1 - f ) );
      switch ( i ) {
        case 0: r = v; g = t; b = p; break;
        case 1: r = q; g = v; b = p; break;
        case 2: r = p; g = v; b = t; break;
        case 3: r = p; g = q; b = v; break;
        case 4: r = t; g = p; b = v; break;
        default: r = v; g = p; b = q; break;   // case i=5
      }
    }
    if (rayon == 0) r = g = b = 0; // si joystick relâché alors lampe éteinte

    /// A ENLEVER : affichage des valeurs de r,g,b sur le moniteur série
    Serial.print(r); Serial.print(" "); Serial.print(g); Serial.print(" "); Serial.println(b);

    // 5 -- allume la lampe (en commandes inversées) r=[0,1] g=[0,1] b=[0,1] ==> [0,255]
    analogWrite (RED_PIN  , 255 - r*255);
    analogWrite (BLUE_PIN , 255 - b*255);
    analogWrite (GREEN_PIN, 255 - g*255);
  }
}

RGB LED Strip WS2812

Il faut télécharger / installer une librairie : Adafruit NeoPixel  ou  FastLED : clic sur ‘Clone or Download’ (en vert) > Download ZIP.
Puis dans l’IDE Arduino : Sketch > Import Library… > Add Library… > sélectionner le fichier ZIP téléchargé.
Puis après l’installation, fermer et ré-ouvrir l’IDE Arduino, pour que les exemples de la librairie soient visibles.

#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NB_LED 8
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NB_LED, PIN, NEO_GRB + NEO_KHZ800);
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)

void setup() {
  strip.begin();
  strip.show(); // Initialise toute les led à 'off'
  strip.setBrightness(80);
}

void loop() {
  for (int i = 0; i < NB_LED; i++ ) { // boucle sur chaque led
    strip.setPixelColor(i, 223, 223, 223); //(n° de led, Rouge, Vert, Bleu)
  }
  strip.show(); // on affiche
  strip.setBrightness(50); // entre 0 et 100
  delay(1000);
}

MP3 shield

  •   il faut enlever la carte MP3 lorsqu’on téléverse le code Arduino.
  • La clé USB ou carte SD doit être déjà connecté à la carte MP3 avant de lancer le programme (on ne peut pas l’insérer à chaud).
  • La musique peut être stockée sur la clé USB, ou une carte SD, ou la mémoire interne de la carte (une mémoire SPI-FLASH de 64Mo).
  • La clé USB ou carte SD (ou SDHC) doit être formatée en FAT 16 ou FAT 32.
  • Enregistrer les noms de musique avec des préfixes de 0000 à 9999 comme 0000-My_music.mp3
  • Sont supportés les MP3 (16K à 320Kbps) et WAV (8K à 44.1KHz) … mais le SPI-FLASH (de 64Mo) ne prend que les MP3.

Il y a 2 façons de lancer la musique :

  • par programmation : Software [par l’interface Série UART sur les 2 pins :  pin 7 (pour RX )  ,  pin 8 (pour Tx)]. Pour utiliser d’autres pins, il suffit de ne pas enficher la carte et de changer les n° pins dans la librairie MP3.ccp.
  • par les boutons de la carte : Hardware [par Hardware Serial sur les 2 pins : pin 0 (pour RX ) et pin 1 pour TX].

Installer la librairie et modifier les n° pins (si nécessaire) :

  • Télécharger la librairie Mp3shield  : clic sur ‘Clone or Download’ (en vert) > Download ZIP.
  • Facultatif : Si on veut utiliser d’autres pins (que pin 7pin 8) on va modifier la librairie :
    1. décompresser le ZIP, entrer dans le dossier \Mp3Shield-master
    2. ouvrir le fichier MP3.cpp dans un éditeur comme notepad++ (ou Apple : Sublime Text, Bluefish, BBEdit , TextWrangler, gedit)
    3. modifier la ligne 18 : SoftwareSerial COM_SOFT(7, 8); et remplacer les n° 7 et 8 par ceux souhaités.
  • Puis dans l’IDE Arduino : Sketch > Import Library… > Add Library… > sélectionner le dossier \Mp3Shield-master.

Puis on peut prendre l’exemple SoftwareSerial, c’est-à-dire :

#include <SoftwareSerial.h>
#include <MP3.h>
MP3 mp3; //déclare un objet mp3 de la classe MP3

void setup() {
  mp3.begin(MP3_SOFTWARE_SERIAL); // en software serial
  mp3.volume(0x1F); //volume maximum (32 niveaux : de 00 à 1F)
  mp3.set_mode(MP3::CYCLE); //mode CYCLE (sinon : SINGLE, REPEAT, RANDOM)
  mp3.play_usb_disk(0x0001); //sur clé USB, '0001'= 1ère musique
  // carte SD : mp3.play_sd(0x0002);  // '0002' = la 2e musique
  // SPI FLASH (mémoire flash) : mp3.play_spi_flash(0x0001)
}

void loop() {
  // ajouter du code ici
}

Pour davantage d’informations : le PDF.

Encodeur de roue SEN0038

roue dentée = 10 dents = 20 changements = 20.5 cm parcourue (la circonférence de la roue)

vitesse maxi = 68 cm/s = 180 tr/min

INT0 et INT1 : digital pin 2 et 3 (trop de perturbations)

Le code utilise les interruptions matérielles sur les pin digitales 2 et 3 … mais les moteurs perturbent énormément les interruptions en provoquant des pics et chutes de tensions dans les pins 2 et 3 (interprétées à tort comme venant des encodeurs) donc je déconseille la méthode qui va suivre … mais on peut la lire tout de même !

  • INT0 : interruption externe n°0 sur la broche digitale 2
  • INT1 : interruption externe n°1 sur la broche digitale 3

Lorsqu’on utilise une interruption, tout le programme se met en « pause » et la fonction appelée dans l’interruption prend le relais (les fonctions de temps delay(), millis() seront également stoppées). Puis lorsque la fonction d’interruption est achevée, le programme principal reprend son cours.
Une interruption externe correspond à un changement de la tension électrique sur la pin. Il existe 4 types :

  • LOW : le pin est à un état bas
  • RISING : le pin passe d’un état bas à haut
  • FALLING : le pin passe d’un état haut à bas
  • CHANGE : le pin change d’état

attachInterrupt(digitalPinToInterrupt(2),fct,FALLING);   Cette instruction démarre « l’écoute » des interruptions de la pin 2 (digitalPinToInterrupt(2) vaut 0, le n°de l’interruption de la pin 2).  Elle appellera la fonction fct() lors d’un passage du pin 2 à l’état FALLING. Le nom de la fonction est noté sans les parenthèses. La fonction est appelée ISR (Interrupt Service Routine).

  • L’ISR doit être la plus courte / rapide possible.
  • Les changements doivent être stockés dans des variables volatile puis l’analyse et les conséquences se feront dans loop().
  • L’ISR ne peut pas contenir de delay(), d’écriture sur moniteur série Serial.println(), on ne peut pas passer d’argument (du coup, on est un peu obligé d’écrire plusieurs fois la même fonction).
  • static est utilisé pour qu’une variable locale (uniquement connue à l’intérieur de cette fonction) dont la valeur est conservée en mémoire lors de chaque appel à la fonction. Mais son initialisation n’est faite qu’une seule fois (lors de la 1ère exécution de la fonction) puis le programme retient sa valeur d’une exécution à l’autre. C’est plus facile à gérer qu’une variable globale lorsqu’on le code est sur plusieurs onglets.
  • volatile est utilisé pour les variables partagées entre une interruption et le programme principal car volatile force le compilateur à aller chercher la variable en mémoire à chaque fois qu’elle est utilisée.

Un tel programme peut difficilement mêler d’autres fonctionnalités car elles seront continuellement interrompues par les interruptions des encodeurs.

detachInterrupt(digitalPinToInterrupt(2)) permet de déconnecter l’ISR de la broche.

On peut ajouter un dispositif anti-rebond au cas où un changement d’état provoque des rebonds de FALLING (au lieu d’en créer un seul). C’est surtout fréquent avec les dispositifs mécaniques (et moins fréquent avec l’optique) et les ISR sont très réactifs, capables de capturer des événements qui se succèdent toutes les 10µs. Les rebonds sont plus lents (une centaine de µs), on peut fixer un seuil de 5ms (ou 33ms au regard de la vitesse hors charge du moteur 180 tr/min).

PCINT1 : analog pin A0 – A5 (???)

C’est le même principe que la méthode précédente (INT0 et INT1) sauf qu’on utilise d’autres broches, elle est nommée  PCINT1.

L’interruption externe PCINT1 offre moins de possibilités que les précédentes (INT0 et INT1) et elle est moins prioritaire. De plus elle est partagée par les 6 broches A0-A5. Par conséquent, l’ISR sera très souvent déclenchée (à changement de tension sur une des 6 broches).

La même ISR est exécutée pour toutes les broches A0-A5, donc on utilise la librairie PinChangeInterrupt pour savoir quelle broche a réagi et ainsi on peut définir autant de fonctions qu’il y a de broches. Mais ceci implique un temps d’exécution plus long !

  • Ne pas utiliser Serial ou delay() dans les fonctions d’interruption.
  •  Cette librairie est incompatible avec la librairie SoftSerial.
  • On peut aussi utiliser les blocs (8-13, A0-A5, 0-7) notés (PCINT0, PCINT1, PCINT3).
  • 3 types d’interruption : RISING, FALLING, CHANGE.

Pour installer la librairie : Croquis > inclure une bibliothèque > gérer les bibliothèques > taper PinChangeInterrupt dans la barre de recherche > clic dessus puis installer.

  • attachPCINT(digitalPinToPCINT(A0), fct, FALLING); attache la fonction d’interruption [ou en version non abrégée : attachPinChangeInterrupt(...) et digitalPinToPinChangeInterrupt(..) ]
  • detachPCINT(digitalPinToPCINT(A0));  pour arrêter l’interruption [ou detachPinChangeInterrupt(...)]
  • disablePCINT(digitalPCINT(A0)); pour mettre en pause temporaire l’interruption.
  • enablePCINT(digitalPCINT(A0)); pour reprendre l’interruption (qui était en pause temporaire).
#include "PinChangeInterrupt.h" //incompatible avec SoftSerial
#define PIN_ENCODEUR_M1  A0
#define PIN_M1_VITESSE   5
#define PIN_M1_SENS      4 
volatile long compteurDents = 0;
void setup() {
  for (int i=4 ; i<=5 ; i++) pinMode(i, OUTPUT);//moteur M1 (pin 4 & 5)
  pinMode(PIN_ENCODEUR_M1, INPUT_PULLUP); //INPUT avec PULL-UP
  attachPCINT(digitalPinToPCINT(PIN_ENCODEUR_M1),fctEncodeurM1, FALLING);  
  Serial.begin(19200);  // moniteur série
  Serial.println(F("Départ"));
  analogWrite(PIN_M1_VITESSE, 100); //démarre moteur M1
  digitalWrite(PIN_M1_SENS, HIGH);
}
void loop() {
  delay(1000);
  Serial.println(compteurDents);
  if (compteurDents >= 100) { //arrête après 100 dents
    Serial.println(F("Detache l'interruption."));
    detachPCINT(digitalPinToPCINT(PIN_ENCODEUR_M1));
    analogWrite(PIN_M1_VITESSE, 0);//stop moteur=1-2 tour supplémentaire
  }
}
void fctEncodeurM1() { compteurDents ++; }

méthode filtrant les perturbations

Comme les moteurs perturbent beaucoup les encodeurs, en infligeant des variations de tensions (appelées « bruit »), alors on peut programmer un filtre basé sur un Timer (chronomètre) et cette fois on n’utilise pas du tout les interruptions matérielles :

  • Toutes les 200 micro-secondes, on cherche l’état de l’encodeur par une ISR de la librairie TimerOne.
  • Si l’état a changé alors il incrémente un compteur. Si l’état n’a pas changé, alors le compteur est décrémenté (ou reste à 0).
  • Lorsque le compteur dépasse le seuil prédéfini (ici c’est 30), alors c’est l’encodeur qui est incrémenté, le nouvel état est acté et on remet le compteur à 0.

Conclusion : on exige 30 vérifications de changements d’état avant d’incrémenter l’encodeur (la garantie d’éliminer les mauvaises mesures). Avec un seuil fixé à 30 succès et un intervalle de 200μs, alors il faut 6000μs = 6ms pour atteindre le seuil (l’incrémentation de l’encodeur). C’est bon, on est large car théoriquement, même en vitesse maxi, chaque état dure 16ms.

 Cette librairie TimerOne.h rend inutilisables les broches D9 et D10 de la carte Arduino UNO en PWM (càd avec analogWrite() même s’il n’y a aucun encodeur sur D9 ou D10), mais ces broches restent utilisables comme de simples entrées/sorties numériques .. ou bien utiliser la fonction Timer1.pwm(9,95*1023/255); pour remplacer analogWrite(9,95); sur D9 (en fait Timer.pwm() est basé sur l’intervalle [0,1023] tandis que analogWrite() est sur [0,255]).

Calculer une distance

#include <TimerOne.h> //!\TimerOne rend le PWM inutilisable sur pin 9 et10
#define PIN_M1_VITESSE   5 // pin E1
#define PIN_M1_SENS      4 // pin M1
#define PIN_ENCODEUR_M1  2
#define FILTRE_SEUIL     30
volatile int filtreCompteur1 = 0;
volatile bool encoder1EtatAncien = 0;
volatile unsigned long encodeur1Nb = 0;
unsigned long encodeur1Nb_ancien = 0;

void setup() {
  for (int i=4 ; i<=5 ; i++) pinMode(i, OUTPUT); // Moteur M1 (pin4&5) 
  Serial.begin(9600);
  while (!Serial) // attend la connection série
    ;
  pinMode(PIN_ENCODEUR_M1, INPUT_PULLUP); //encodeur avec PULL-UP
  Timer1.initialize(200); // interruption Timer1 toutes les 0.2ms
  Timer1.attachInterrupt(ISR_Timer1_Encodeur1); // la fct ISR
  analogWrite(PIN_M1_VITESSE, 100); //démarre le moteur M1
  digitalWrite(PIN_M1_SENS, HIGH);
}

void loop() { // affiche la distance tous les 10 chgts d'état
  unsigned long encodeur1NbCopie;  // une copie de encodeur1Nb
  noInterrupts(); // On arrête temporairmt (juste le tps d'une copie)
  encodeur1NbCopie = encodeur1Nb; //pour lire une variable qui bouge pas
  interrupts(); // Et c'est avec la copie qu'on fait les calculs ds loop()
  Serial.print("encodeur1 = ");  Serial.println(encodeur1NbCopie);
  delay(100);

  //traitmt : écrire la distance tous les 10 chgmts d'état de l'enc1
  if (encodeur1NbCopie >= encodeur1Nb_ancien + 10 ) {
    double distance1 = ((double) encodeur1NbCopie) * 20.5 / 20.0;
    Serial.print("distance1 = "); Serial.println(distance1,2);
    delay(100);  //distance1 est notée avec 2 décimales
    encodeur1Nb_ancien = encodeur1NbCopie;
  }
  //traitmt : stop le robot après 100 chgmts d'état de l'encodeur 1
  if (encodeur1NbCopie >= 100) { //
    analogWrite(PIN_M1_VITESSE, 0); //stop moteur M1
    Timer1.detachInterrupt(); //stop l'interruption
    exit(0); //arrête le sketch arduino
  }
}

void ISR_Timer1_Encodeur1() {
  bool etatActuel = digitalRead(PIN_ENCODEUR_M1);
  if (etatActuel != encoder1EtatAncien) {
    filtreCompteur1++;
    if (filtreCompteur1 > FILTRE_SEUIL) {
      encoder1EtatAncien = etatActuel;
      encodeur1Nb++;
      filtreCompteur1 = 0;
    }
  }
  if (etatActuel == encoder1EtatAncien && filtreCompteur1 > 0)
    filtreCompteur1 --;
}

Réguler les vitesses de M1 et M2

Si c’est un tableau de variables qu’on souhaite récupérer, alors il faut enlever complètement l’interruption sur toute la séquence utilisant ce tableau récupéré. Du coup, je n’ai pas mis de tableau dans l’ISR.

#include <TimerOne.h> //!\TimerOne rend le PWM inutilisable sur pin 9 et10
#define PIN_M1_VITESSE   5  //pin E1
#define PIN_M1_SENS      4  //pin M1
#define PIN_M2_VITESSE   6  //pin E2
#define PIN_M2_SENS      7  //pin M2
#define PIN_ENCODEUR_M1  2
#define PIN_ENCODEUR_M2  3
#define FILTRE_SEUIL     30
volatile unsigned long encodeur1Nb = 0;
volatile unsigned long encodeur2Nb = 0;
unsigned long enc1NbCopie = 0;
unsigned long enc2NbCopie = 0;
byte vitesse[2] = {100, 100};
#define M1   0      // index de M1 ds le tableau vitesse[]
#define M2   1      // index de M2

void setup() {
  for (int i=4 ; i<=7 ; i++) pinMode(i, OUTPUT); // Moteurs M1 & M2
  Serial.begin(9600);
  while (!Serial) // attend la connection série
    ;
  pinMode(PIN_ENCODEUR_M1, INPUT_PULLUP); //encodeur 1 avec PULL-UP
  pinMode(PIN_ENCODEUR_M2, INPUT_PULLUP); //encodeur 2 avec PULL-UP
  Timer1.initialize(200); // interruption Timer1 toutes les 0.2ms
  Timer1.attachInterrupt(ISR_Timer1_Encodeurs); // la fct ISR
}

void loop() { //pdt 100 chgmts d'état, régulation de la vitesse 
  if ( (enc1NbCopie + enc2NbCopie )/2 < 100 ) {
    analogWrite(PIN_M1_VITESSE, vitesse[M1]);
    digitalWrite(PIN_M1_SENS, HIGH);
    analogWrite(PIN_M2_VITESSE, vitesse[M2]);
    digitalWrite(PIN_M2_SENS, HIGH);
    noInterrupts();
    enc1NbCopie = encodeur1Nb;
    enc2NbCopie = encodeur2Nb;
    interrupts();
    Serial.print("encodeur1 = ");  Serial.print(enc1NbCopie);
    Serial.print("  vitesse1 = ");  Serial.println( vitesse[M1]);
    Serial.print("encodeur2 = ");  Serial.print(enc2NbCopie);
    Serial.print("  vitesse2 = ");  Serial.println( vitesse[M2]);
    delay(100);
    //régule la vitesse M1-M2 selon l'écart entre encodeur1 et 2
    long ecartM1moinsM2 = enc1NbCopie - enc2NbCopie;
    if (ecartM1moinsM2 < -2) { //si M1 a du retard
      ralentirMoteur (M2,M1);
      Serial.println("ralentir M2");
    } else if (ecartM1moinsM2 > 2) { //si M2 en retard
      ralentirMoteur (M1, M2);
      Serial.println("ralentir M1");
    }
  } else { //stop le robot après 100 chgmts d'état de l'encodeur
    analogWrite(PIN_M1_VITESSE, 0); //=> Stop moteurs
    analogWrite(PIN_M2_VITESSE, 0);
    Timer1.detachInterrupt(); //stop l'interruption
    exit(0); //arrête le sketch arduino
  }
}

void ISR_Timer1_Encodeurs() {
  static bool ancienEtatEnc1 = 0;
  static bool ancienEtatEnc2 = 0;
  static int filtre1Compteur = 0;
  static int filtre2Compteur = 0;
  bool etat1 = digitalRead(PIN_ENCODEUR_M1);
  bool etat2 = digitalRead(PIN_ENCODEUR_M2);
  ////////// Encodeur 1 ////////////////////////
  if (etat1 != ancienEtatEnc1) {
    filtre1Compteur++;
    if (filtre1Compteur > FILTRE_SEUIL) {
      ancienEtatEnc1 = etat1;
      encodeur1Nb++;
      filtre1Compteur = 0;
    }
  }
  if (etat1 == ancienEtatEnc1 && filtre1Compteur > 0)
    filtre1Compteur--;
  /////////// Encodeur 2 ////////////////////////
  if (etat2 != ancienEtatEnc2) {
    filtre2Compteur++;
    if (filtre2Compteur > FILTRE_SEUIL) {
      ancienEtatEnc2 = etat2;
      encodeur2Nb++;
      filtre2Compteur = 0;
    }
  }
  if (etat2 == ancienEtatEnc2 && filtre2Compteur > 0)
    filtre2Compteur--;
}

void ralentirMoteur (byte Mrapide, byte Mlent) {
  vitesse[Mrapide]--; //rallentit la roue rapide
  if (vitesse[Mrapide] < 100) { //mais pas en-dessous du minimum = 100
    vitesse[Mrapide] = 100;  // sinon on accélère le moteur lent
    vitesse[Mlent]++;
  }
}

 

Lance Missile

Ar_LanceMissile

Seuls 2 fils sont nécessaires : le jaune et le blanc (qui sont interchangeables), connectés à la carte Arduino :

  • un pin digital (PWM ou pas) : jaune ou blanc.
  • GND (l’autre fil) :   blanc ou jaune.

Pour lancer une fléchette :  il suffit de donner une impulsion HIGH sur le pin digital pendant 120 millisecondes, puis on remet le signal LOW.

Pour lancer une rafale de fléchettes : il suffit de maintenir le signal HIGH pendant au moins 150 millisecondes … puis remettre le signal LOW pour arrêter.

/**
 *  LANCE - MISSILE :
 *   - fil blanc = GND
 *   - fil jaune = digital pin (ou vice-versa)
 *   
 *   1 fléchette = HIGH pendant 120 ms
 *   tir en rafale = HIGH plus longtemps ( au moins 150ms )
 */

int MISSILE_PIN = 12;

void setup() {
  initMissile();
  tir();  // 1 tir
  delay(8000);
  rafale(); // 4 tirs en rafale
}

void loop() {
}

void initMissile() {
  pinMode(MISSILE_PIN, OUTPUT);
  digitalWrite(MISSILE_PIN, LOW);
}
void tir() {
  digitalWrite(MISSILE_PIN, HIGH);
  delay(120); // 120 ms = 1 fléchette
  digitalWrite(MISSILE_PIN, LOW);
}

void rafale() {
  digitalWrite(MISSILE_PIN, HIGH);
  delay(400);  //  400 ms = 4-5 fléchettes
  digitalWrite(MISSILE_PIN, LOW);
}

Bluetooth JY-MCU v1.06

Le module Bluetooth JY-MCU communique avec la carte Arduino en mode série (càd RS 232). Il y a 2 méthodes (2 librairies et 2 types de branchements)  …

Avec la librairie interne d’Arduino

Ar_bluetoothRectoMais avec de gros inconvénients :

  1. Je n’ai pas réussi à faire fonctionner le module Bluetooth en l’état (il faudrait tester un montage avec 2 résistances « pull-up »).
  2. Il faut débrancher le module Bluetooth JY-MCU (de RX et TX) dès qu’on branche le câble USB … donc lorsqu’on téléverse le programme vers l’Arduino (car la connexion utilise la même liaison série RX-TX).
  3. On ne peut pas utiliser la console de l’IDE et le moniteur série pour déboguer … car il faudrait utiliser le câble USB !!!

Le branchement ‘librairie Arduino’ est :

  • VCC –> +5V
  • GND –> GND
  • TXD –> RX (pin 0)
  • RXD –> TX (pin 1)

Avec la librairie Softserial

Elle est très simple à utiliser (c’est juste un import en début de sketch) et elle permet d’utiliser le câble USB à volonté. Mais il faut effectuer ce type de branchement :

  • VCC –> +5V
  • GND –> GND
  • TXD –> Port digital (ex : pin 10)
  • RXD –> Port digital (ex : pin 11)

Ar_bluetoothVersoQuelques précisions

Nom et mot de passe : le module bluetooth JY-MCU se nomme HC-06 et son mot de passe est 1234 . Son port série est configuré à 9600 Bauds, 8 Bits données, pas de parité, 1 Stop bit :  (9600,N,8,1).

Couplé / Pas couplé (paired / not paired) : Pour savoir si le module bluetooth JY-MCU est couplé à un autre dispositif bluetooth (PC ou téléphone Android) il suffit d’observer sa led rouge :

  • led rouge clignotante => module non pairé.
  • led rouge fixe => module pairé.

Changer la vitesse, nom, mot de passe : On peut changer les paramètres avec des « commandes AT » mais uniquement quand le module n’est pas couplé (donc led clignotante). Attention à ne pas dépasser la vitesse maximale d’Arduino 115200bps sous peine de devoir faire un reset hardware du module !!!

Tuto avec la librairie ‘SoftSerial

  • VCC –> +5V
  • GND –> GND
  • TXD –> Port digital (ex : pin 10)
  • RXD –> Port digital (ex : pin 11)

1ère étape : Téléverser le programme.

Le programme suivant consiste à allumer la led de la carte Arduino lorsque la liaison série transmet le caractère ‘H’ (c’est le programme le plus simple possible) :

/*
* Programme (le plus simple) :
*   - la commande est 1 caractère 'command'
*   - la librairie SoftwareSerial 
* 
* Branchement :  RxD (HC-06) <-> pin 9  (Arduino)
*                TxD (HC-06) <-> pin 10 (Arduino)
*/

char command = ' '; //commande envoyée par BT (1 caractère)
#define LEDpin 13

//Librairie Software Serial Port
#include <SoftwareSerial.h>
#define RxD 9
#define TxD 10
SoftwareSerial btSerial(TxD, RxD); // TxD <-> Rx  et  RxD <-> Tx

void setup() {
  pinMode(LEDpin, OUTPUT);
     
  // Ouvre une communication Série avec le HC-06
  btSerial.begin(9600);
}

void loop() {
  // Lecture de la commande envoyée par le HC-06 (si disponible)
  if (btSerial.available()) {
    command = (char)btSerial.read();

    // On interprète la commande : allume la led ou pas
    if ( command == 'H')  { // si 'H' est recu  
      digitalWrite(LEDpin, HIGH);  // on allume la LED
    } else {
      digitalWrite(LEDpin, LOW);   // sinon on l'éteint
    }
  }
  delay(100); //on attend un peu avant une nouvelle lecture
}

Option 1 : Si on préfère que la commande soit une chaîne de caractère [ String command = ""; ] alors il suffit de concaténer tous les caractères (tant qu’on reçoit des caractères) donc :

  if (btSerial.available()) {
    while (btSerial.available()) {
      //Tant que des caractères sont envoyés, on les concatène
      command += (char)btSerial.read();
    }

    // Puis interprèter la commende ici ...... : allume la led ou pas

    // Puis vider la commande 
    command = "";
  }

Option 2 : Si on souhaite faire du débogage, on peut ajouter une 2e communication Série : le moniteur Série … qui a aussi la possibilité d’envoyer des commandes en passant par le bluetooth.

void setup() {
  // Ouvre une autre communication Série (le Moniteur Série) :
  Serial.begin(9600);
}

void loop() {
   // Commencer par lire de la commande envoyée par le Bluetooth (ici)  ...

   // On affiche "command" dans le moniteur Série
    Serial.println(command);

   // On interprète la commande (ici) ...

   // une commande du Moniteur Série est transférée vers le BT (qui l'interprétera)
   if (Serial.available()) {
     delay(10); // délai nécessaire pour que ça fonctionne !
     btSerial.write(Serial.read());
  }
}

2ème étape : Mise en place du module Bluetooth : l’esclave.

  • Brancher le module Bluetooth JY-MCU.
  • Alimenter la carte Arduino par batterie (ou câble USB).
  • La led rouge du module Bluetooth devrait clignoter en attente de « pairing ».

3ème étape : Mise en place du PC ou téléphone Bluetooth : le maître.

Sur un ordinateur :

      1. Pour le Dongle Bluetooth CDR 4.0 (Chipset: CSR8510) : Utiliser le mini Disc pour installer les pilotes.
      2. Ouvrir la connexion Bluetooth, clic droit sur l’icone de la barre des tâches et ajouter le périphérique.
        Ar_BluetoothPC_1     Ar_BluetoothPC_3
      3. Entrer le mot de passe 1234 :
        Ar_BluetoothPC_4     Ar_BluetoothPC_5
      4. Le couplage est fait, mais la led rouge du module continue de clignoter (car il faut rajouter une communication ‘port série’) :
        Ar_BluetoothPC_6       Ar_BluetoothPC_8
      5. Ajouter un port COM sortant (le PC est le maître) et bien noter son numéro.
        Ar_BluetoothPC_7      Ar_BluetoothPC_9
      6. Télécharger le petit logiciel PuTTY gratuit (bien connu des administrateurs de serveur distant)
        Ar_BluetoothPC_10
      7. Le couplage se fait avec PuTTY COM23 et la led rouge du module Bluetooth devient fixe (enfin ! ). Taper dans cette console le texte, rien ne s’affiche mais les caractères sont envoyés immédiatement (pas besoin d’appuyer sur Entrée).
        Ar_BluetoothPC_11

Sur un téléphone Android : Sur Google Play, télécharger une application de communication Bluetooth. En voici quelques unes (testées et approuvées 🙂 ) :

Le fonctionnement est simple :

  • Ouvrir le bluetooth du téléphone, puis ouvrir l’application.
  • Dans l’application, il y a un bouton « connect » pour coupler le téléphone avec le module bluetooth (et la led rouge devient fixe).
  • Taper le texte puis ‘Send’.

LCD Nokia 5110

C’est un petit écran LCD Nokia 5110 (avec un contrôleur Philips PCD8544) de résolution de 84 x 48 px. Le branchement est le suivant (il peut utiliser n’importe quelle broche digitale) :Ar_nokia5110branchmt

      • pin digitale 7 : RST   (Reset LCD)
      • pin digitale 6 : CE     (Sélecteur LCD chip)
      • pin digitale 5 : DC     (Sélecteur Data/Command)
      • pin digitale 4 : DIN   (sortie Série Data)
      • pin digitale 3 : CLK   (sortie Série Clock)
      • 3.3V : VCC
      • GND : GND

Rétroéclairage : Connecter alors la broche BL (écran bleu) est à 3,3 V pour activer le rétroéclairage. (Pour l’écran rouge, c’est le contraire, il faut connecter LIGHT à GND.)

Le code

Il y a 2 solutions selon l’usage souhaité : une bibliothèque complète ou le code minimale (pour un texte simple) :

En important les librairies : Adafruit_GFX et Adafruit_PCD8544.

Pour importer une librairie : aller dans le menu : Croquis > inclure une bibliothèque > Gérer les bibliothèques et taper son nom, puis « Installer ».

Pour utiliser une librairie dans un sketch : aller dans le menu : Croquis > inclure une bibliothèque puis dérouler la liste des librairies disponibles et cliquer sur celle souhaitée. Une instruction #include <Adafruit_PCD8544.h> devrait s’ajouter au sketch.

Importer l’exemple pcdtest : aller dans le menu : Fichier > exemple > [ Exemples depuis les bibliothèques] Adafuit PCD8544… > pcdtest.
Puis changer ces 2 instructions :

      • Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);  pour initialiser des pins selon le branchement ci-dessus [ au lieu de (7, 6, 5, 4, 3) tout au début de l’exemple].
      • display.setContrast(60);  au lieu de display.setContrast(50).

Ecrire un texte :   display.print("Hello");

Code minimal

Ce code est suffisant pour écrire un texte simple en caractère ASCII (pas de librairie, gain de mémoire) :

#define LCD_RST 7
#define LCD_CE 6
#define LCD_DC 5
#define LCD_DIN 4
#define LCD_CLOCK 3

#define LCD_C  LOW
#define LCD_D  HIGH
#define LCD_X  84
#define LCD_Y  48

static const byte ASCII[][5] =
{
 {0x00, 0x00, 0x00, 0x00, 0x00} // 20  
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j 
,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ←
,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f →
};

void LcdCharacter(char character) {
  LcdWrite(LCD_D, 0x00);
  for (int index = 0; index < 5; index++)  {
    LcdWrite(LCD_D, ASCII[character - 0x20][index]);
  }
  LcdWrite(LCD_D, 0x00);
}

void LcdClear() {
  for (int index = 0; index < LCD_X * LCD_Y / 8; index++)  {
    LcdWrite(LCD_D, 0x00);
  }
}

void LcdInitialise() {
  pinMode(LCD_CE, OUTPUT);
  pinMode(LCD_RST, OUTPUT);
  pinMode(LCD_DC, OUTPUT);
  pinMode(LCD_DIN, OUTPUT);
  pinMode(LCD_CLOCK, OUTPUT);
  digitalWrite(LCD_RST, LOW);
  digitalWrite(LCD_RST, HIGH);
  LcdWrite(LCD_C, 0x21 );  // LCD Extended Commands.
  LcdWrite(LCD_C, 0xB1 );  // Set LCD Vop (Contrast). 
  LcdWrite(LCD_C, 0x04 );  // Set Temp coefficent. //0x04
  LcdWrite(LCD_C, 0x14 );  // LCD bias mode 1:48. //0x13
  LcdWrite(LCD_C, 0x0C );  // LCD in normal mode.
  LcdWrite(LCD_C, 0x20 );
  LcdWrite(LCD_C, 0x0C );
}

void LcdString(char *characters) {
  while (*characters) {
    LcdCharacter(*characters++);
  }
}

void LcdWrite(byte dc, byte data) {
  digitalWrite(LCD_DC, dc);
  digitalWrite(LCD_CE, LOW);
  shiftOut(LCD_DIN, LCD_CLOCK, MSBFIRST, data);
  digitalWrite(LCD_CE, HIGH);
}

void setup(){
  LcdInitialise();
  LcdClear();
  LcdString("Hello World!");
}

void loop() {
}

 

Arduino Uno + Ardumoto

La carte Arduino Uno

La carte Arduino Uno contient le micro-contrôleur (le cerveau du robot). Elle est alimentée par du 6-15V (par sa broche Vin -ou- l’entrée jack -ou- le port USB).

La carte Ardumoto L298P (shield)

Ar_Pr_avecMotorShieldLa shield Ardumoto contrôle des moteurs, par l’intermédiaire de 4 broches de l’Arduino :

  • 2 digitales pour donner le sens de rotation (la direction) : horaire ou anti-horaire :
    • pin 12 = moteur A (sens de rotation)
    • pin 13 = moteur B (sens de rotation)
  • 2 PWM (donc digitales) pour indiquer la vitesse :
    • pin 3 = moteur A (vitesse)
    • pin 11 = moteur B (vitesse)
  • GND ou « – » : pour relier les masses entre elles (Arduino Uno et Ardumoto).
  • 5V : idem, pour relier les impulsions 5V.
  • Vin Motor +/- : une batterie (5 x 1,2V) pour alimenter les moteurs des roues (qui sont réglementés à 3-6v).

Comment alimenter l’Ardumoto ?

Ce n’est pas la sortie 5V de l’Arduino qui alimente l’Ardumoto (et donc les moteurs) mais c’est une entrée Vin. Il y a 2 types d’alimentation :

  • une seule alimentation (qui alimentera les 2 cartes) : Connecter les broches Vin (Arduino et Ardumoto) et placer la batterie : sur le jack de l’Arduino -ou- le bornier Vin de l’Ardumoto -ou- sur une pin Vin (Arduino ou Ardumoto).
  • deux alimentations (une pour chaque cartes) : Attention, dans ce cas il faut absolument déconnecter la broche Vin entre Arduino et Ardumoto … et donc tordre cette broche Vin. Effectivement,  lorsque les cartes sont emboitées, la broche Vin alimente les cartes entre elles et donc les 2 alimentations alimentent le même circuit (risque de surtension) !!!

C’est cette 2ème solution que j’ai choisi … en ajoutant un interrupteur général qui coupe les 2 cartes.

Ar_schema_AlimArduinoArdumoto

Programme simple

  1. Mettre les batteries (pile 9V pour l’Arduino + bloc 5×1,2V pour les moteurs).
  2. Placer l’interrupteur général du robot sur OFF.
  3. Brancher le jack (de la pile 9V) sur la carte Arduino
  4. Brancher le jack (de la carte Ardumoto) sur le chassis (juste à côté de l’interrupteur).
  5. Brancher le câble USB entre l’Arduino et l’ordinateur.
  6. Ouvrir l’IDE Arduino (régler le port COM et le type de carte = Uno 0_panneau_attention).
  7. Copier-Coller le code du programme (voir ci-dessous).
  8. Téléverser le programme Ar_Pr_Btn_upload.
  9. Débrancher le câble USB sur la carte Arduino.
  10. Placer le robot sur le sol, puis mettre l’interrupteur sur ON. Le robot devrait démarrer.

1er code :  programme très très simple :

// --- les variables : déclaration et initialisation
int pwm_a = 3;   //vitesse moteur A (PWM)
int pwm_b = 11;  //vitesse moteur B (PWM)
int dir_a = 12;  //direction du moteur A : horaire/anti-horaire
int dir_b = 13;  //direction du moteur B : horaire/anti-horaire

// --- le Programme : setup() et loop()
void setup() {
  pinMode(pwm_a, OUTPUT);  //On met les 4 broches en mode "sortie"
  pinMode(pwm_b, OUTPUT);
  pinMode(dir_a, OUTPUT);
  pinMode(dir_b, OUTPUT);
}

void loop() {
  forward();
  delay(500);
  turnLeft();
  delay(250);
}

// --- Les fonctions -----
void turnLeft() {
  digitalWrite(dir_a, HIGH); 
  digitalWrite(dir_b, HIGH);  
  analogWrite(pwm_a, 100);    // vitesse ralentie
  analogWrite(pwm_b, 100);
}

void forward() { //avance à pleine vitesse
  digitalWrite(dir_a, HIGH);
  digitalWrite(dir_b, LOW);
  analogWrite(pwm_a, 255); // vitesse maximale
  analogWrite(pwm_b, 255);
}

 

2ème code : le même .. en plus précis (avec un coefficient correcteur de vitesse sur le moteur B) et des paramètres dans les fonctions :

/** Pour Ardumoto 
* COEF_B = coefficient de réduction de vitesse du moteur B 
*          car le moteur A tourne moins vite que le B !!!
**/

// --- les constantes : HORAIRE et ANTI-HORAIRE (sens du moteur)
//à modifier selon les branchements des moteurs (1=HIGH  et 0=LOW)
#define HOR  LOW
#define AHOR HIGH
#define COEF_B 0.89

// --- les variables : déclaration et initialisation
int pwm_a = 3;   //vitesse moteur A [digital pin 3 (PWM)]
int pwm_b = 11;  //vitesse moteur B [digital pin 11 (PWM)]
int dir_a = 12;  //sens de rotation du moteur A [digital pin 12]
int dir_b = 13;  //sens de rotation du moteur B [digital pin 13]
 
// --- le Programme : setup() et loop()
void setup() {
  initMoteur();
}
 
void loop() {
  avance(250,2000); //tout droit à une vitesse 58%=150/255 (pendant 2000ms)
  gauche(80,950);   //quart de tour gauche
}

// --- les fonctions
void initMoteur() {
  pinMode(pwm_a, OUTPUT); //on met les 4 broches en mode "sortie"
  pinMode(pwm_b, OUTPUT);
  pinMode(dir_a, OUTPUT);
  pinMode(dir_b, OUTPUT);
}

void avance(byte vitesse, int duree) { //vitesse entre 0 et 255
  digitalWrite(dir_a, AHOR); // sens de rotation
  digitalWrite(dir_b, HOR); 
  analogWrite(pwm_a, vitesse); // vitesse
  analogWrite(pwm_b, vitesse*COEF_B);
  delay(duree);
}

void gauche(byte vitesse, int duree) { // préférer une vitesse lente
  avance(0,1000);  // d'abord, on se met à l'arrêt
  digitalWrite(dir_a, HOR); // on tourne
  digitalWrite(dir_b, HOR);  
  analogWrite(pwm_a, vitesse);    
  analogWrite(pwm_b, vitesse*COEF_B);
  delay(duree);
  avance(0,1000); // puis, on se remet à l'arrêt
}

3ème code : le programme vide (pour vider la mémoire de l’Arduino) :

void setup() {} 
void loop() {}