entete

Premiers programmes avec le Xbot

Nous allons voir quelques programmes de complexité croissante, apprendre au robot à aller droit, à obéir à ses capteurs. Vous devez avoir quelques connaissance de C/Arduino (if, while, for, fonctions). Pour un cours C temps réel dérivé du MOOC EPFL "Comprendre les Microprocesseurs", voir Coursera LC1..7

Comprenez que digitalWrite est une invention géniale d'Arduino pour cacher la structure interne des registres. Mais pour nous c'est le vélo d'enfant à 2 roues d'équilibrage. On peut aller partout aussi bien qu'un vélo, on risque moins de tomber, mais pour continuer, il faut un vrai vélo, et si on se passionne, il y a encore des options.

Notre objectif est d'apprendre à écrire des programmes lisible et compatible C, avec des définitions claires et des fonctions bien choisies et bien nommées. On verra que le #define, ignoré par Arduino, est un élément clé pour bien documenter.

Rappelons le câblage du Xbot et les noms des signaux .
Les pins 2 à 7 sont sur les bits 2 à 7 port D du processeur Avr368.
Les bits 2 et 3 sont en entrée pour lire les moustaches. Les bits 4 à 7 commandent les moteurs, avec les bits 5 et 6 qui permettent le PWM Arduino, ils sont donc utilisés comme signaux pour faire avancer.

Pour ce PORTD, les no de pins sont les même que les numéro de bits

LcPins

Comparons trois programmes qui font la même chose: éviter un obstacle en reculant et tournant. Vous pouvez faire du copie-collé ou trouver tous les programmes de cette doc ici

Programme 1 -  Arduino "pur" (mais avec des #define)

// EvitObstacleArduino.ino
//On recule et tourne s'il y a obstacle
#define RecG 4
#define AvG 5
#define AvD 6
#define RecD 7

#define MousD 3
#define MousG 2

void setup() // initialisation
{                
  pinMode(RecG,OUTPUT);
  pinMode(AvG,OUTPUT);
  pinMode(AvD,OUTPUT);
  pinMode(RecD,OUTPUT);
  pinMode(MousD,INPUT);
  pinMode(MousG,INPUT);
}

void loop() {
  digitalWrite(AvG,  HIGH);  // on avance
  digitalWrite(RecG, LOW);
  digitalWrite(AvD,  HIGH);
  digitalWrite(RecD, LOW);
  if (digitalRead (MousD) == 0) { //on recule et tourne
    digitalWrite(AvG,  LOW); // recule
    digitalWrite(RecG, HIGH);
    digitalWrite(AvD,  LOW);
    digitalWrite(RecD, HIGH);
    delay (300) ;
    digitalWrite(AvG,  LOW);  // tourne à gauche
    digitalWrite(RecG, HIGH);
    digitalWrite(AvD,  HIGH);
    digitalWrite(RecD, LOW);
    delay (200) ;
  }
  if (digitalRead (MousG) == 0) { //on recule  tourne
    digitalWrite(AvG,  LOW);
    digitalWrite(RecG, LOW);
    digitalWrite(AvD,  LOW);
    digitalWrite(RecD, HIGH);
    delay (300) ;
    digitalWrite(AvG,  HIGH);  // tourne à droite
    digitalWrite(RecG, LOW);
    digitalWrite(AvD,  LOW);
    digitalWrite(RecD, HIGH);
    delay (200) ;
  }
}

Le programme détaille ce qu'il faut faire.

Programme 2  - Compatible C

On ne gére plus des pins, mais les bits du port 8 bits, et on définit à l'avance les combinaisons de bits qui font avancer, tourner, etc. Il faut des opérations logiques pour ne pas peturber les bits 0 et 1 du PORTD.
C'est expliqué ici , mais c'est comme le digitalWrite, on peut utiliser sans bien compendre.

//EvitObstaclePorts.ino en C

#define RecG 4
#define AvG 5
#define AvD 6
#define RecD 7
#define Avance  PORTD |= 0b01100000; PORTD &= 0b01101111
#define Recule  PORTD |= 0b10010000; PORTD &= 0b10011111
#define TourneD PORTD |= 0b10100000; PORTD &= 0b10101111
#define TourneG PORTD |= 0b01010000; PORTD &= 0b01011111
#define Stop    PORTD &= ~0b11110000

#define MousD 3 // PORTD pin3)
#define MousG 2
#define ObsG  !(PIND&1<<3)  //!digitalRead(MousG)
#define ObsD  !(PIND&1<<2)  //!digitalRead(MousD)

 

void setup() // initialisation

  DDRD |=  0b11110000 ;
  DDRD &=  0b11110011 ;
}

void loop()  {
  Avance ;
  if (ObsD) { //on recule et tourne
    Recule;
    delay (300);
    TourneG;
    delay (200);
  }
  if (ObsG) {  //on recule et tourne
    Recule;
    delay (300);
    TourneD;
    delay (200);
  }
}

On voit que le programme est beaucoup plus clair

Programme 3 – avec des "include"

Les définitions sont "encapsulées" dans un fichier XBotDef.h que l'on charge avec le menu Arduino "sketch" "Add file", si le dossier qui contient le .ino ne contient pas déjà le .h. - une règle de cuisine de plus!

// EvitObstacleInclude.ino Arduino

#include "XBotDef.h"   //     --->

void setup()  {  // initialisation
 XBotSetup();
}

void loop()  {
  Avance();
  if (ObsD) { //on recule et tourne
    Recule() ;
    delay (300) ;
    TourneG() ;
    delay (200) ;
  }
  if (ObsG) {  //on recule et tourne
    Recule() ;
    delay (300) ;
    TourneD() ;
    delay (200) ;
  }
}

Fichier inclus 
//XBotDef.h Def moustaches et moteur en C ok
// le nom est parfois XBotCDef.pdf)
// voir XBotADef.h en Arduino et XBotAADef.h pour une variante
#include <Arduino.h>
#define bRecG 4 // bit 4 dans le PortD
#define bAvG 5
#define bAvD 6
#define bRecD 7
#define Avance() PORTD |= 0b01100000; PORTD &= 0b01101111
#define Recule() PORTD |= 0b10010000; PORTD &= 0b10011111
#define TourneD() PORTD |= 0b10100000; PORTD &= 0b10101111
#define TourneG() PORTD |= 0b01010000; PORTD &= 0b01011111
#define Stop() PORTD &= ~0b11110000

#define bMousD 3 // PORTD,3)
#define bMousG 2
#define ObsG !(PIND&1<<bMousG) //!digitalRead(MousG)
#define ObsD !(PIND&1<<bMousD) //!digitalRead(MousD)

void XBotSetup() {// initialisation
DDRD |= 0b11110000 ;
DDRD &= 0b11110011 ;
}

Les définitions, le set-up doivent être avant le programme. En quelques lignes, on voit maintanant qu'il y a une ressource Xbot que l'on initialise avec XbotSetup. Comment cela se fait dans le détail ne nous intéresse plus; on veut tester les obstacles et réagir.

L'IDE Arduino présente bien cette information. On peut éditer les fichier inclus. Le compilateur travaille sur les fichiers tampons montrés sur l'écran. Ne pas oublier de sauver fréquemment avec l'icône ▼ (ou mode automatique dans "Preferences".

incl

A noter que les fichiers inclus peuvent très bien être écrits avec les fonctions Arduino, c'est le cas du programme EvitObstacleIncludeA.ino sur le .zip. Remarquez que Avance(), etc y est alors une fonction. Un #define est possible, mais peu efficace dans ce cas. Le programme principal est identique, c'est tout l'avantage des fichiers inclus. Comparez la taille de ces 2 programmes!

Le programme principal ne doit pas dépendre du matériel et du type de compilateur. Notre programme principal ne sait rien du robot si ce n'est qu'il avanc, tourne et détecte des obstacles. Mais nous avons mal documenté en disant qu'il recule de 0.3 secondes. Ce n'est pas valable pour un autre robot! Il aurait fallu écrire DelRecule et déclarer au début du programmes, avec les déclarations matérielles

#define  DelRecule 300 // pour le Xbot qui est très rapide

C'est en écrivant des programmes plus riches, en changeant de robot, de compilateur, que l'on comprend bien cette nécessité d'une bonne structuration des programmes.

Recommendation: Toujours, toujours avant de modifier un programme qui marche, donner un nouveau nom au fichier, aussi explicite que possible. A début de chaque programme testé, décrire le comportement voulu, l'algorithme, la date, l'auteur.

Programme 5- Ne pas se coincer contre l'obstacle

On remarque que face à certains obstacle, le robot alterne à l'infini. Une amélioration est de changer la durée de la rotation. S'il tourne plus longtemps à droite qu'à gauche le robot va se décaler et a plus de chance de quitter l'obstacle.

Plus efficace, c'est de compter les obstacles et tous le 5 par exemple faire une pirouette.

Facile de déclarer un compteur et l'incrémenter dans chaque "if". Mais ou tester? Une solution est dans la boucle loop avec un if (programme complet sous EvitObstacleCompte.ino)
On pourrait aussi dire "tant que le compteur est inférieur à x"  on fait rien de spécial.


...
byte compteObst = 0 ;
#define  nbObst  5
#define  DelTouComptObst 800
void loop()  {
  Avance ;
  if (ObsD) { //on recule et tourne
     Recule ; // fonction EviterObsD();
     delay (300) ;
     TourneG ;
     delay (200) ;
     compteObst++;
  }
  if (ObsG) {  //on recule et tourne
     Recule ;
     delay (300) ;
     TourneD ;
     delay (100) ;
     compteObst++;
  }
  if ( compteObst > nbObst ) {
     compteObst = 0 ;
     TourneD ;
     delay  (DelTouComptObst) ;
  }  // end if
}  // end loop

// insérer ici ou après le programme  les fonctions  EviterObsD();EviterObsG();

byte compteObst = 0 ;
#define  nbObst  5
#define  DelTouComptObst 800
 
void loop()  {
  compteObst = 0 ;
  while (compteObst < nbObst) {
    Avance ;
   if (ObsD) {
      EviterObsG () ;
      compteObst++;
    }
    if (ObsG) {
      EviterObsD () ;
      compteObst++;
    }
  }    // end while
  TourneD ;
  delay  (DelTouComptObst) ;
}   // end loop

On remarque dans la 2e colonne que l'on appelle des fonctions (mises dans le fichier inclu) pour faciliter la lecture.du programme principal. Dans le programme, on veut éviter les obstacle. Comment? Reculer. C'est un autre niveau de réflexion qui se définit dans des fonctions - que faire pour reculer. Une fois les fonctions bien définies et adaptées, on les mets dans un #include.
Une fonction est une action, son nom commence par un verbe. Les fonctions commencent par une majuscules (sauf les fonctions Arduino comme delay) et les variables par une majuscules.
Attention, Arduino n'accèpte pas les minuscules accentuées dans les programmes et réagit stupidement!

void EviterObsD () { //on recule et tourne
       Recule ;
       delay (300) ;
       TourneG ;
       delay (200) ;
}

void EviterObsG () { //on recule et tourne
       Recule ;
       delay (300) ;
       TourneD ;
       delay (200) ;
}

A vous d'écrire le programme de test pour ces deux fonctions. Vous reprenez un programme, vous le renommez, vous ajoutez les fonctions ou dans le programme principal, ou dans le #include si c'est une fonction qui retrouvera souvent son utilité.

Le #include est parfois délicat à gérer, voir Utilisation des #include sous Arduino

Jouons avec des vitesses en tout-ou-rien avant de passer au PWM du prochain chapitre.

Programme 6 - Agir au hasard

Ce serait mieux de corriger le comportement au hasard. Arduino a la fonction random (min,max) qui génère des nombres entre min et max-1.
Dans le programme précédent, il suffit de changer dans le if  nbObst par random (min,max).
Renommez le fichier et essayez  (seulement si vous n'y arrivez pas: EvitObstacleRandom.ino)

Idées de programmes

Dessiner un polygone

Suivre un mur concave

Suivre un mur convexe

Contourner l'extrémité d'une paroi

Modifier la vitesse
Corriger pour une ligne droite
Tracer une spirale

Le Diduibot est prévu pour ajouter facilement sur le connecteur avant des capteurs de suivi de ligne, suivi de lumière, distance, et un affichage Oled et/ou 4 digits, sans fils de câblage peu fiables.
Voir Modules capteurs pour Xbot

jdn 170815