Infoforall

7 - MAKEBLOCK / Le robot Ultimate avec bras articulé

Nous allons passer sur un nouveau robot de la marque MakeBlock : le modèle Ultimate qui permet de faire ceci par exemple :

Image du robot Ultimate en mode Robotic Arm Tank

1 - Présentation : les encodeurs et la carte MegaPi

Commençons par présenter les trois nouveaux composants de ce modèle.

1 - Les codeurs

Première différence : les moteurs à courant continu sont remplacés par des encodeurs.

Encodeur
Encodeur

Vu de l'extérieur, on ne voit pas grosse différence.

Les moteurs à courant continu (DC Motor) du modèle précédent sont simples à contrôler : plus on met de tension électrique (la grandeur qui se mesure en volt), plus le moteur tourne vite. Cette commande de tension se fait par l'utilisation d'un nombre compris entre 0 et 255.C'est simple, mais nous n'avons pas de possibilité de contrôler exactement le nombre de tours qu'a fait le moteur. On met de la tension et on espère qu'il tourne comme il devrait... Or, une commande de 2500 ms en montée ou en descente ne donnera la même distance parcourue. C'est génant.

DC Motor
Moteur à courant continu (CC en français, DC en anglais)

Comme on peut le voir ce type de moteur ne possède que deux bornes : il suffit de lui appliquer une tension.

Heureusement, il existe un autre type de dispositif : l'encodeur. Un moteur de ce type va pouvoir fournir une indication précise sur le nombre de tours qu'il a fait. Ainsi, on peut le commander pour faire 100 tours en descente et le commander pour qu'il refasse 100 tours précisement en montée. Il reviendra donc exactement à sa position initiale.

Encodeur
Encodeur

On peut voir sur l'image qu'il y a plus que deux bornes : il faut commander le moteur et on peut lire de combien de degrés le moteur à tourner.

En réalité, le robot Ultimata en version tank avec pince possède deux types de moteur de ce type :

  • Les deux moteurs destinés à faire tourner les roues sont des codeurs 9V pour 185 tours par minute
  • Le moteur destiné à faire bouger la plate-forme de la pince est un codeur 9V pour 86 tours par minute

2 - La carte MegaPi

Il existe également une grosse différence au niveau de la carte.

Le robot précédent utilisait une carte Orion basée sur la UNO-328. La carte Orion fournit de base 8 prises RJ-45 et 2 prises permettant de contrôler directement des moteurs à courant continu.

carte Orion

Le robot Ultimate est lui fourni avec une carte MegaPi basée sur la carte Arduino MEGA2560.

MegaPi
MegaPi

Sur la photo, on peut voir en haut à gauche, les 4 interfaces qui permettent de contrôler des moteurs à courant continu, des moteurs pas à pas (stepper en anglais) ou des codeurs.

Pour les codeurs, on a placé, sur ces 4 interfaces, 4 Drivers capables de gérer chacun deux moteurs à courant continu ou un Codeur.

MegaPi
Makeblock Megapi Encoder/DC Motor Driver

La maquette possède également un Shield permettant de gérer 4 entrées/sorties avec les RJ45 typiques des robots Makeblock :

MegaPi Shield for RJ45
MegaPi Shield for RJ45

Au final, la carte ressemble alors à ceci (avec le Shield Bluetooth en plus en haut à droite):

Carte avec les Shields
Carte avec les Shields

On placera les 3 codeurs sur les premieres bornes blanches et le moteur pas à pas de la pince sur deux des bornes vertes du dernier module de commande des moteurs.

Cablage de la pince
Connexion typique de la pince d'après la notice de l'Ultimate

3 - La pince

Il s'agit d'un modèle simple à utiliser, alimenté par un moteur à courant continu mais muni d'un dispositif de protection pour ne pas pouvoir l'endommager ni à l'ouverture complète ni à la fermeture complète.

Robot Gripper
Robot Gripper

En gros, dans un sens on la ferme et dans l'autre sens de rotation, on l'ouvre.

Voyons d'ailleurs maintenant comment commander les 4 moteurs de la façon la plus facile possible : comme des moteurs à courant continu.

2 - Commande simple des moteurs

Dans un premier temps, nous allons vérifier que les moteurs fonctionnent bien en les commandant comme de simples moteurs à courant continu. Cela nous permettra de vérifier la connectique.

En effet, on peut contrôler le codeur pour qu'il se comporte comme un simple moteur à courant continu. Les vrais moteurs à courant continu n'ont besoin que de deux cables qu'on peut placer sur les bornes vertes. Ainsi sur l'interface n°1, on nommera ces bornes PORT1A et PORT1B puisqu'on a 4 fils. Ici, nous avons donc cabler 3 codeurs sur les ports 1, 2 et 3. A l'interne, on n'utilisera par contre que deux des entrées : on pourra les commander comme si on avait branché un moteur à courant continu sur les bornes vertes.

01° Ouvrir Arduino. Aller dans le menu OUTILS - TYPE DE CARTE et choisir Arduino/Genuino Mega or Mega2560.

Choix de la carte

02° Ouvrir Arduino. Aller dans le menu OUTILS - TYPE DE CARTE et choisir Arduino/Genuino Mega or Mega2560.

Pour trouver un programme compatible pour la gestion des moteurs à courant continu, le mieux est encore d'aller chercher dans les exemples : FICHIER - EXEMPLES - MAKEBLOCK DRIVE. A partir de là, il faudra chercher les fichiers MeMegaPi. Ici, je fournis un dérivé du MeMegaPiDMotor.

03° Utiliser le programme d'exemple fourni ci-dessous. Attention, je considère que vous avez placé votre pince sur les deux premières broches de l'interface n°4 :.

Cablage de la pince
Connexion typique de la pince d'après la notice de l'Ultimate

#include "MeMegaPi.h"

#include <SoftwareSerial.h>


MeMegaPiDCMotor motorPince(PORT4B);


uint8_t motorSpeed = 100;


void ouverture_totale()

{

    motorPince.run(-100);

    delay(6000);

    motorPince.stop();

}


void setup()

{

    ouverture_totale();

}


void loop()

{

    motorPince.run(motorSpeed);

    delay(1000);

    motorPince.run(-motorSpeed);

    delay(1000);

    motorPince.stop();

    delay(5000);

}

Normalement, vous devriez constater que la pince tente initialement de s'ouvrir à fond. Ensuite, elle se sert et se remet en place pendant 2s puis reste immobile pendant 5s.

Attention :Les parties suivantes vous demande de commander des pièces qui sont moins protégées que la pince. Pensez à utiliser l'interrupteur si vous constatez que l'échelle peut forcer sur la carte ou monter trop haut.

04° Faire des tests de façon à trouver l'initialisation à utiliser pour commander la plate-forme (parmi les choix-ci dessous). Voyez également si on la fait monter avec une vitesse positive ou négative.

MeMegaPiDCMotor motorEchelle(PORT3B);

MeMegaPiDCMotor motorEchelle(PORT3A);

05° Faire des tests de façon à trouver l'initialisation à utiliser pour commander le moteur droit puis le moteur gauche.

MeMegaPiDCMotor motorDroit(PORT1B);

MeMegaPiDCMotor motorDroit(PORT1A);

MeMegaPiDCMotor motorDroit(PORT2B);

MeMegaPiDCMotor motorDroit(PORT2A);

3 - Contrôle des Codeurs

Passons aux codeurs. D'abord quelle est la différence entre un moteur DC et un codeur DC ?

Un moteur DC tourne lorsqu'on lui impose une tension. Simple. Il tourne. De combien de tours ? Là, c'est impossible de le savoir précisément.

Un codeur DC tourne lorsqu'on lui impose une tension. Mais il possède des bornes de sortie qui renvoient des signaux. Par exemple, le codeur pourrait envoyer 100 signaux lorsque son axe de rotation fait un tour complet. Ils permettent donc de savoir de combien de degrés le moteur à tourner.

On veut tourner de 1/4 de tours (90°) en sachant qu'on possède un codeur qui envoie 100 signaux par tour ? C'est simple : il vous suffit de le faire fonctionner jusqu'à avoir obtenu 25 signaux de sortie (un quart de 100). Simple et efficace.

Observez le signal rouge parvenant à traverser le disque dans le système de codage ci-dessous :

Si vous comptez, vous pourrez voir qu'il y a 16 trous qui permettant le passage du signal rouge (nous verrons plus tard à quoi servent les autres). Du coup, 16 détections d'apparition du lumière rouge veut dire qu'on a fait un tour complet, 360°.

Pour voir comment contrôler ces fameux codeurs, le mieux est encore d'aller voir le fichier test : il se nomme MegaPiOnBoardStepperTest.

06° Aller chercher le fichier dans les exemples : EXEMPLES - MAKEBLOCKDRIVE - MeMegaPiProMotor -- MegaPiOnBoardStepperTest.

#include "MeMegaPiPro.h"

#include <SoftwareSerial.h>


MeEncoderOnBoard Encoder_1(SLOT1);

MeEncoderOnBoard Encoder_2(SLOT2);


void isr_process_encoder1(void)

{

    if (digitalRead(Encoder_1.getPortB()) == 0)

    {

        Encoder_1.pulsePosMinus();

    }

    else

    {

        Encoder_1.pulsePosPlus();

    }

}


void isr_process_encoder2(void)

{

    if (digitalRead(Encoder_2.getPortB()) == 0)

    {

        Encoder_2.pulsePosMinus();

    }

    else

    {

        Encoder_2.pulsePosPlus();


    }

}


void setup()

{

    attachInterrupt(Encoder_1.getIntNum(), isr_process_encoder1, RISING);

    attachInterrupt(Encoder_2.getIntNum(), isr_process_encoder2, RISING);

    Serial.begin(115200);


    //set pwm 1khz

    TCCR1A = _BV(WGM10);//PIN12

    TCCR1B = _BV(CS11) | _BV(CS10) | _BV(WGM12);


    TCCR2A = _BV(WGM21) | _BV(WGM20);//PIN8

    TCCR2B = _BV(CS22);


    TCCR3A = _BV(WGM30);//PIN9

    TCCR3B = _BV(CS31) | _BV(CS30) | _BV(WGM32);


    TCCR4A = _BV(WGM40);//PIN5

    TCCR4B = _BV(CS41) | _BV(CS40) | _BV(WGM42);


    Encoder_1.setPulse(7);

    Encoder_2.setPulse(7);

    Encoder_1.setRatio(26.9);

    Encoder_2.setRatio(26.9);


    Encoder_1.setPosPid(1.8,0,0.5);

    Encoder_2.setPosPid(1.8,0,0.5);

    Encoder_1.setSpeedPid(0.18,0,0);

    Encoder_2.setSpeedPid(0.18,0,0);

}


void loop()

{

    if (Serial.available())

    {

        char a = Serial.read();

        switch(a)

        {

            case '0':

                Encoder_1.moveTo(0,300);

                Encoder_2.moveTo(0,300);

                break;

            case '1':

                Encoder_1.moveTo(360,300);

                Encoder_2.moveTo(-360,300);

                break;

            case '2':

                Encoder_1.moveTo(1800,300);

                Encoder_2.moveTo(-1800,300);

                break;

            case '3':

                Encoder_1.moveTo(3600,50);

                Encoder_2.moveTo(-3600,300);

                break;

            case '4':

                Encoder_1.moveTo(-360,50);

                Encoder_2.moveTo(360);

                break;

            case '5':

                Encoder_1.moveTo(-1800,50);

                Encoder_2.moveTo(1800,50);

                break;

            case '6':

                Encoder_1.moveTo(-3600,50);

                Encoder_2.moveTo(3600,50);

                break;

            default:

                break;

        }

    }

    Encoder_1.loop();

    Encoder_2.loop();

    Serial.print("Spped 1:");

    Serial.print(Encoder_1.getCurrentSpeed());

    Serial.print(" ,Spped 2:");

    Serial.print(" ,CurPos 1:");

    Serial.print(Encoder_1.getCurPos());

    Serial.print(" ,CurPos 2:");

    Serial.println(Encoder_2.getCurPos());

}

07° Lancer le programme et la console série. Placer le robot pour qu'il ne puisse pas tomber en avancant ou en reculant..

08° Taper les commandes de 0 à 6 via la liaison série pour voir ce qu'elles provoquent. Attention : pensez à modifier la vitesse de communication de votre console pour que cela corresponde à votre programme : regardez la méthode begin de la liaison série dans la fonction setup.

Tentons maintenant de comprendre comment fonctionne ce programme...

#include "MeMegaPiPro.h"

#include <SoftwareSerial.h>


MeEncoderOnBoard Encoder_1(SLOT1);

MeEncoderOnBoard Encoder_2(SLOT2);


Ici, rien de bien nouveau : on permet l'accès aux codes contenus dans les bibliothèques MeMegaPiPro (gestion de la carte MegaPi) et SoftwareSerial (gestion de la liaison Série).

On voit par contre l'apparition d'un nouveau constructeur : MeEncoderOnBoard. Il permet la création d'un objet permettant de faire l'interface entre le programme et le codeur.

Ainsi, maintenant, nous avons ceci pour contrôler les moteurs en tant que codeurs :

MeEncoderOnBoard Encoder_1(SLOT1);

Alors qu'avant nous les contrôlions comme de simples moteurs à courant continu à l'aide d'un autre constructeur :

MeMegaPiDCMotor motorDroit(PORT1B);

Il est temps d'aller voir la fonction setup puisque c'est la première à s'activer :

void setup()

{

    attachInterrupt(Encoder_1.getIntNum(), isr_process_encoder1, RISING);

    attachInterrupt(Encoder_2.getIntNum(), isr_process_encoder2, RISING);

    Serial.begin(115200);


    //set pwm 1khz

    TCCR1A = _BV(WGM10);//PIN12

    TCCR1B = _BV(CS11) | _BV(CS10) | _BV(WGM12);


    TCCR2A = _BV(WGM21) | _BV(WGM20);//PIN8

    TCCR2B = _BV(CS22);


    TCCR3A = _BV(WGM30);//PIN9

    TCCR3B = _BV(CS31) | _BV(CS30) | _BV(WGM32);


    TCCR4A = _BV(WGM40);//PIN5

    TCCR4B = _BV(CS41) | _BV(CS40) | _BV(WGM42);


    Encoder_1.setPulse(7);

    Encoder_2.setPulse(7);

    Encoder_1.setRatio(26.9);

    Encoder_2.setRatio(26.9);


    Encoder_1.setPosPid(1.8,0,0.5);

    Encoder_2.setPosPid(1.8,0,0.5);

    Encoder_1.setSpeedPid(0.18,0,0);

    Encoder_2.setSpeedPid(0.18,0,0);

}

Et là, c'est le drame : pas une ligne qui contienne quelque chose que nous ayons déjà vu.

4 - Surveillance grace à attachInterrupt

Nous allons ici nous focaliser sur les deux premières lignes :

void setup()

{

    attachInterrupt(Encoder_1.getIntNum(), isr_process_encoder1, RISING);

    attachInterrupt(Encoder_2.getIntNum(), isr_process_encoder2, RISING);

09° Réflexe à avoir quand on ne sait pas : Faire une recherche Internet du type "Arduino + attachInterrupt". Vous devriez réussir à trouver plein de documentations. Tenter d'en comprendre des bouts. C'est surement en anglais, surtout si vous allez à la source de la documentation : DOCUMENTATION SUR ARDUINO.CC

Résumons : la fonction attachInterrupt est une fonction intégrée directement dans le langage. Comme print par exemple. Du coup, dans les codes ici présents, je ne les indique pas en rouge. Juste en noir.

Cette fonction attachInterrupt est un gestionnaire d'événements spécialisé dans la surveillance des broches de votre Arduino. En gros, cette fonction va provoquer la surveillance d'une des broches et va déclencher une fonction lorsque les données recues sur cette broche sont modifiées.

La fonction attend les arguments suivants dans l'ordre :

  1. Le numéro de la broche à surveiller. Attention, on demande de la fournir via la fonction intégrée digitalPinToInterrupt avec un argument correspondant à votre numéro de broche. Si vous voulez le faire à la main, aller lire le bas de la page fournie. Mais soyez certain de téléverser votre code dans la bonne carte !
    • Sur la Uno, on peut surveiller les broches 2 et 3
    • Sur la MegaPi, on peut surveiller les broches 2,3,18,19,20,21.
  2. La fonction à appeler une fois que l'événement est détecté. Attention, cette fonction ne peut pas avoir de paramètres et ne doit rien renvoyer. Il s'agit donc d'une simple routine (procédure ne renvoyant aucune donnée). Du coup, il faudra utiliser des variables globales si vous voulez transférer des informations. Utilisez alors plutôt des variables de type volatile.
  3. L'événement à surveiller sur la broche. A choisir parmi :
    • LOW : on surveille si la broche passe est à l'état BAS uniquement.
    • CHANGE : on surveille si la broche change d'état (LOW vers HIGH ou HIGH vers LOW)
    • RISING : on surveille uniquement les passages LOW vers HIGH. On nomme ceci un front montant.
    • FALLING : on surveillance uniquement les passages HIGH vers LOW. On nomme ceci un front descendant.
    • Certaines cartes savent aussi surveiller HIGH : les moments où la borne est à l'état HAUT.

10° Que fait la fonction suivante ?

attachInterrupt( digitalPinToInterrupt(3), blink, CHANGE );

...CORRECTION...

On surveille l'événement suivant : la broche 3 change d'état.

En cas de détection, on lance la fonction blink.

11° Que fait le code suivant ?

const byte pinCapteur = 2;

attachInterrupt( digitalPinToInterrupt(pinCapteur), blink, RISING );

...CORRECTION...

On surveille l'événement suivant : la broche 2 passe de l'état LOW à l'état HIGH.

En cas de détection, on lance la fonction blink.

Retournons à notre Robot Makeblock :

    attachInterrupt(Encoder_1.getIntNum(), isr_process_encoder1, RISING);

    attachInterrupt(Encoder_2.getIntNum(), isr_process_encoder2, RISING);

Alors, c'est quoi ces deux lignes ? En réalité, c'est simple : nous ne savons absolument pas sur quelle broche de la MEGA PI est relié le signal de notre codeur 1 par exemple. Heureusement, les concepteurs de Makeblock ont créé une méthode getIntNum qu'on peut appliquer aux objets fabriqués à partir du constructeur MeEncoderOnBoard. Par exemple, quand on le déclare, on tape simplement :

MeEncoderOnBoard Encoder_1(SLOT1);

On sait donc que la variable Encoder_1 mène à une interface qui permet de gérer un codeur qui doit être connecté au SLOT1 de notre carte MEGAPI. Mais aucun noméro de broche. Pour la connaitre, il faut aller lire les schémas OpenSource de la MegaPi ou simplement utiliser Encoder_1.getIntNum ? Vous préférez quoi ?

Si vraiment vous êtes curieux, vous pouvez utiliser la liaison série pour afficher la valeur de la broche. Mais ca ne restera valable que sur votre configuration actuelle :

Serial.print( attachInterrupt(Encoder_2.getIntNum() );

12° Alors, que fait le code suivant ?

    attachInterrupt(Encoder_1.getIntNum(), isr_process_encoder1, RISING);

    attachInterrupt(Encoder_2.getIntNum(), isr_process_encoder2, RISING);

...CORRECTION...

On surveille les fronts montants (RISING) de la broche de sortie du codeur correspondant à Encoder_1. On lancera alors à chaque fois la fonction isr_process_encoder1.

On surveille les fronts montants (RISING) de la broche de sortie du codeur correspondant à Encoder_2. On lancera alors à chaque fois la fonction isr_process_encoder2.

5 - isr_process_encoder 1 et 2

Nous progressons : lorsque les codeurs envoient un signal indiquant qu'ils ont tourné, on active les fonctions isr_process_encoder1 et isr_process_encoder2.

Allons voir ces fonctions :

void isr_process_encoder1(void)

{

    if (digitalRead(Encoder_1.getPortB()) == 0)

    {

        Encoder_1.pulsePosMinus();

    }

    else

    {

        Encoder_1.pulsePosPlus();

    }

}


void isr_process_encoder2(void)

{

    if (digitalRead(Encoder_2.getPortB()) == 0)

    {

        Encoder_2.pulsePosMinus();

    }

    else

    {

        Encoder_2.pulsePosPlus();


    }

}

A finir

Plus d'info :

http://learn.makeblock.com/en/Makeblock-library-for-Arduino/class_me_encoder_on_board.html

Caractéristiques des codeurs : 185 rpm, rapport de réduction 1/46