LOCODUINO

Transcription d’un programme simple en programmation objet

.
Par : Dominique, Guillaume, Jean-Luc

DIFFICULTÉ :

Pourquoi parler de codage avant d’avoir spécifié les besoins d’un réseau ?

Quand nous concevons un réseau, en tout cas pour ma part, mon souhait est de diversifier les tâches. En effet, coder, poser les voies, faire les bâtiments, tout cela dans un but de non monotonie.
Bref, comment donc coder sans avoir défini entièrement le réseau ?
Nous avons tout de même une idée de ce que l’on veut faire : quelques animations lumineuses, faire bouger le train, etc...

Dans cette optique, Thierry nous apporte la solution : les objets. Nous vous invitons à lire les articles de la série : Le monde des objets (1).

Néanmoins, cette façon de coder n’apporte pas que cet avantage. Nous allons en voir d’autres au cours de cet exemple de traduction d’un programme.

Seul programme pour une carte Arduino

La plupart des programmes d’animations lumineuses que nous trouvons sont seuls sur la carte Arduino. C’est-à-dire que seule cette animation pourra être téléversée et en ajouter une dans le code bouleversera la cinématique, et l’animation sera parasitée. Cela est dû à la présence de la commande delay() qui met en pause la carte Arduino ne permettant pas l’exécution d’un autre code.
Il faut donc traduire ce code avec un autre moyen de gérer le temps : la commande millis(). Petit rappel : Comment gérer le temps dans un programme ?.

Exemple de programme

Prenons l’animation soudure à l’arc, décrite dans l’article éponyme : Simulateur de soudure à arc. Nous allons commencer simple en y allant petit à petit. Nous nous attacherons à réutiliser les acquis que nous aurons vu pour les autres animations.
Traduisons tout d’abord ce programme avec la commande millis().
Il nous faut établir le diagramme d’enchaînement des commandes, l’animation se décompose comme ceci :
Cycle de 10 à 20 flashs de 10 à 100 ms avec un laps de temps entre flashs de 10 à 30 ms et entre chaque cycle, une pause de 1,5 à 7s.
L’aléatoire viendra de la commande random() comme décrit dans cet article : Comment gérer l’aléatoire ?.

Traduction

Traduisons le programme. Pour cela, il va nous falloir utiliser des instructions conditionnelles telles que les if et else, des variables "compteur"...
Il n’est pas admissible en langage C d’utiliser la balise goto, même si le langage en fournit la possibilité, parce que cela introduit du code non linéaire difficilement lisible et maintenable. Une bonne maîtrise de base est donc nécessaire mais est facilement surmontable avec les if quand nous posons bien le problème sur une simple feuille. Eh oui, car le bon outil d’un codeur est paradoxalement un bloc-notes.
Un petit dessin vaudra mieux qu’un grand discours, vous ne verrez pas mon talent mais simplement un début de carte fait sur Framindmap :

Petite explication du dessin : nous partons du if attente entre 2 cycles. Si non au résultat, les flashs se font. Si oui au résultat, les flashs s’arrêtent et nous testons le temps d’attente. Si oui, on reprend les flashs ; si non, nous attendons encore.

Ce dessin ne représente pas tout le cheminement mais en montre au moins une partie. Il faut bien penser que la fonction loop() est une boucle infinie. A chaque fin, nous revenons automatiquement au point de départ de la fonction. En dessinant l’arbre décisionnel, le code vient tout seul.

Tester aussi des conditions implique un apport de certaines variables qui changent quand une autre condition est remplie, qui, elles, ne sont pas sur le dessin.

Mais le meilleur dessin est le vôtre, essayez.

Maintenant que nous avons dessiné notre plan, le code vient de lui-même :

const byte pin=13;
boolean boucle=0;
byte dureeflash;
unsigned long temps=0;
byte compteur=0;
byte tempsflash;
byte compte;
long tempspause;

void setup() {
  // put your setup code here, to run once:
  pinMode(pin, OUTPUT);
  randomSeed(analogRead(0));
  tempsflash=random(10,31);
  compte=random(10,21);
  tempspause=random(1500,7001);
}

void loop() {
  // put your main code here, to run repeatedly:
  if((compteur>compte)&&(boucle==0))
  {
    if((millis()-temps)>tempspause)
    {
      temps=millis();
      compte=random(10,21);
      tempspause=random(1500,7001);
      compteur=0;
    }
  }
  else
  {
    switch(boucle)
    {
      case 0:
      if((millis()-temps)>tempsflash)
      {
        boucle=1;
        digitalWrite(pin, HIGH);
        temps=millis();
        dureeflash=random(10,101);
      }
      break;
      case 1:
      if((millis()-temps)>dureeflash)
      {
        boucle=0;
        digitalWrite(pin, LOW);
        temps=millis();
        tempsflash=random(10,31);
        compteur++;
      }
      break;
    }  
  }
}

Petite note : quand nous comparons un random avec un if dans une boucle infinie, l’effet pseudo-aléatoire est anéanti puisque chaque appel va donner un nombre différent. Il faut stocker le nombre aléatoire dans une variable pour avoir tout de même l’effet aléatoire. En effet, il faut exécuter random un minimum de fois.

Avantages de ce code et des objets

Nous notons donc que maintenant il est facile d’ajouter une animation à ce code. Prenons, par exemple, sur une même carte Arduino, deux simulateurs à l’arc sur deux broches différentes. Il est facile avec le programme précédent de combiner en un seul programme les deux codes (même code reproduit avec d’autres variables). Cependant cela reste une opération fastidieuse. En effet, il faut copier-coller au bon endroit du programme :

  • déclaration des variables
  • void setup()
  • void loop()

L’apport des objets va nous simplifier le code. Nous avons dans notre exemple théorique deux animations identiques. Pourquoi ne pas transcrire cette animation en classe ?
Nous aurons notre classe, et dans la partie effective du code une déclaration d’objets par animation et le lancement de chaque animation dans le loop.

Cela peut paraître compliqué mais l’étape précédente était plus dure. Venons-en au fait.

Construction de la classe

Comme dit au début de cet article, nous allons donc nous inspirer des articles sue les objets et nous complexifierons dans un autre article pour nous faciliter ensuite les choses.

Premièrement, les parties privées et publiques de la classe : finalement ce qui est suffisant est qu’il nous faut modifier la broche de sortie de la DEL mais aussi pouvoir lancer ou arrêter l’animation, tout le reste est privé. Pour coller au maximum à d’autres bibliothèques tel que liquidcrystal, nous utiliserons notre constructeur perso pour créer notre objet avec la broche de sortie mais aussi une fonction begin pour initialiser (mettre le code que nous avons mis dans le setup).

Déclarons la classe :

class SoudureArc
{
  private:
    byte pin;
    boolean boucle=0;
    byte dureeflash;
    unsigned long temps=0;
    byte compteur=0;
    byte tempsflash;
    byte compte;
    long tempspause;    
  public:
    void off();
    SoudureArc(byte apin);
    void begin(); 
    void on();
};

Spécifions ensuite les méthodes que l’on a ébauché avec la classe :

void SoudureArc::off()
{
  digitalWrite(pin, LOW);
}

SoudureArc::SoudureArc(byte apin)
{
  pin=apin;
}

void SoudureArc::begin()
{
  pinMode(pin, OUTPUT);
  randomSeed(analogRead(0));
  tempsflash=random(10,31);
  compte=random(10,21);
  tempspause=random(1500,7001);
  off();
}

void SoudureArc::on()
{
  if((compteur>compte)&&(boucle==0))
  {
    if((millis()-temps)>tempspause)
    {
      temps=millis();
      compte=random(10,21);
      tempspause=random(1500,7001);
      compteur=0;
   }
  }
  else
  {
    switch(boucle)
    {
      case 0:
      if((millis()-temps)>tempsflash)
      {
        boucle=1;
        digitalWrite(pin, HIGH);
        temps=millis();
        dureeflash=random(10,101);
      }
      break;
      case 1:
      if((millis()-temps)>dureeflash)
      {
        boucle=0;
        off();
        temps=millis();
        tempsflash=random(10,31);
        compteur++;
      }
    }  
  }
}

Tel quel, ce code ne fonctionnera pas sur un Arduino. Il nous faut déclarer l’objet avec sa broche, initialiser dans le setup et lancer l’animation dans le loop. A la suite des deux bouts de codes précédents, voici donc la partie principale :

SoudureArc soudure(13);

void setup()
{
  soudure.begin();
}

void loop()
{
  soudure.on();
}

Et pour mettre sur une carte Arduino deux animations Soudure à l’arc :

SoudureArc soudure1(13);
SoudureArc soudure2(12);

void setup()
{
  soudure1.begin();
  soudure2.begin();
}

void loop()
{
  soudure1.on();
  soudure2.on();
}

à la suite de la description de la classe et de ses méthodes.

Et vous avez deux animations sans complexifier le code. Nous voyons donc par cet exemple simple l’avantage de la programmation objet : pouvoir coder par avance et maintenir un code propre et net dans le sketch principal.

Et avantage non négligeable, le programme est plus léger.

9 Messages

  • Transcription d’un programme simple en programmation objet 17 octobre 2015 09:26, par Zebulon91

    Bravo ... belle approche ;) !

    Répondre

  • Bravo pour cette article !

    J’avoue toutefois n’avoir pas compris le sens de la petite note ?

    ...quand nous comparons un random avec un if dans une boucle infinie, l’effet pseudo-aléatoire est anéanti...

    Il me paraît tout à fait logique de stocker le résultat de l’appel à random dans les variables telles que tempspause, tempsflash car ces temps ne doivent pas changer jusqu’à ce qu’ils se soient écoulés. Ensuite seulement on réévalue de nouvelles valeurs aléatoires pour ces temps.

    Meilleures salutations.
    Marc-Henri

    Répondre

    • Logique oui et non.
      Dans la logique pour éviter de stocker et de prendre de la place avec des variables de temps, on pourrait simplement mettre un if(time>random()) (version abrégée), mais la l’effet disparaît d’où cette petite note. Et donc ne pas comparer un random à un autre truc dans if ou toute autre condition.

      Au contraire un delay peut être rempli par random. Ca le fait qu’une fois, c’est plus simple et moins de lignes à écrire.

      Répondre

  • Merci Guillaume pour ces précisions.

    Je pense que nous partageons le même point de vue. Si on n’utilise qu’une fois le résultat du random, on peut l’utiliser directement sans le sauver dans une variable. En revanche, si on a besoin plusieurs fois du même résultat d’un random, dans le cas d’un if appelé à chaque itération, une variable est nécessaire.

    Meilleures salutations.
    Marc-Henri

    Répondre

  • Excellent article !

    Ce qui est frappant avec les objets, c’est qu’on peut découper un programme "fourre-tout" en morceaux parfaitement distincts, comme des briques.

    Chaque brique-fonction s’occupe d’un problème qui peut être très simple, mais qui peut aussi être plus complexe.
    Quand on rédige une "brique", on ne s’occupe que du problème lié à cette "brique". Cet aspect, qui va de soi quand on sait programmer en objet, n’est pas du tout évident au départ.

    Et on n’écrit chaque brique qu’une seule fois.

    C’est un énorme gain en lisibilité (et donc en déplantage si nécessaire).
    En particulier, le "programme principal" fond littéralement.

    La difficulté se répartit dans les différents objets, bien rangés.

    Répondre

  • Se passer de la methode begin 31 janvier 2018 10:21, par Franck Demongin

    Découvrant Arduino et la prog en C++, vos articles sont une mine d’infos. Merci.
    Une question concernant l’organisation du code dans celui ci : est-ce qu’on ne peut pas se passer de la méthode begin ?
    Le constructeur pourrait faire le même boulot...

    Hummm, je regardais en parallèle le contenu de la méthode init (wiring.c) appelée en premier dans le main. Sans comprendre grand chose à son contenu, je me doute que son taf est important !
    Je crois que je vais rentrer sagement dans le rang et initialiser mes objets dans setup...

    Répondre

    • Se passer de la methode begin 31 janvier 2018 12:48, par Jean-Luc

      Bonjour,

      On peut faire des choses dans le constructeur mais pas tout. En effet, si il s’agit d’un objet global, le constructeur est appelé bien avant l’exécution de main (qui fait setup() puis itérativement loop()). Or, à cet instant, beaucoup de choses ne sont pas initialisées et, si le constructeur repose sur une de ces choses, ça ne fonctionnera pas. D’où le begin qui est exécuté dans setup et donc après que tout dans l’Arduino ait été initialisé.

      Répondre

  • Transcription d’un programme simple en programmation objet 19 avril 2018 23:23, par BoBillier Eric

    Bonjour. Merci pour cet article. Cependant , le lien indiqué pour passer à l’article 114 sur la la construction d’un objet Led, semble défectueux.
    Eric

    Répondre

Réagissez à « Transcription d’un programme simple en programmation objet »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Programmation »

Les derniers articles

Les articles les plus lus