Aller au contenu


Photo
- - - - -

Multitâches avec arduino


  • Veuillez vous connecter pour répondre
16 réponses à ce sujet

#1 Path

Path

    Made By Humans

  • Modérateur
  • PipPipPipPipPip
  • 2 504 messages
  • Gender:Male
  • Location:Paris

Posté 13 août 2017 - 04:15

C'est une petite réflexion sur le multitâche dans arduino. C'est mon quart d'heure pensée profonde de l'été. C'est de l'apologie assumée du non-bloquant. Je mettais mes idées en ordre avant de me remettre sur Emile.

 

Le concept selon wikipedia :
"La simultanéité apparente est le résultat de l’alternance rapide d’exécution des processus présents en mémoire. Le passage de l’exécution d’un processus à un autre est appelé commutation de contexte. Ces commutations peuvent être initiées par les programmes eux-mêmes (multitâche coopératif) ou par le système d’exploitation lors d’événements externes (multitâche préemptif)."

 

Cool !! Dans la procédure loop(), quand on appelle différentes librairies les unes après les autres, on fait du multitâches peut-être sans le savoir. Le mot "apparente" dans la définition est importante. Notre arduino favori ne fera qu'une seule chose à la fois. Mais tellement vite que cela semble être fait simultanément.

 

Piloter le driver de moteurs, compter les ticks des roues codeuses, afficher un message, commander et lire un capteur, envoyer et recevoir sur un port série, exécuter la logique de décision du robot ... On ne met pas un arduino pour chacune de ces fonctions mais on peut vouloir dédier un arduino pour la lecture des ticks des roues codeuses. Car on le fait avec des interruptions. A chacune des interruptions, Arduino sauvegarde l'état des registres du programme principale, exécute la procédure de l'interruption, rétabli les registres et reprend le programme principale. On voit bien qu'il va être difficile pour l'arduino de capter un 2e ticks qui arriverait pendant une interruption. C'est pour cela que les interruptions doivent être très courtes. C'est une manière de faire du multitâches avec arduino en mode préemptif. C'est très pratique mais on voit apparaître une limite. A partir d'une certaine fréquence d'interruption, l'arduino ne pourra plus les exécuter toutes. Avant d'atteindre sa limite, il ne fera plus que traiter ces interruptions. On peut choisir d'ignorer les interruptions pendant un temps dans le programme principale mais pas plus. C'est pour cela qu'on dédie un arduino, pour maximiser le nombre de ces lectures. Malgrés la limite, c'est le meilleur moyen de lire des évènements déclenchés par un composant du robot extérieur à l'arduino.

 

De là à dire qu'il faut 2 arduino dans un robot, j'en suis pas loin. C'est un moyen radicale de faire du multitâche.

 

L'autre manière de faire du multitâches, c'est le mode coopératif. Il ne règle pas le problème qu'on vient de voir. On est toujours dans les limites de fonctionnement d'un micro contrôleur. Il permet de faire du multi tâches en le gérant soit même dans le programme. On le fait quand on est pas dépendant d'évènements extérieur et quand on a pas de contraintes de mesure de temps. Il consiste à faire des sous-programmes avec des fonctions ou des classes. À réserver des variables à chaque sous programmes. Et à appeler ces sous programmes dans la procédure loop(). C'est le même concept que l'interruption quand arduino sauvegarde les registres et les restaure après l'interruption. L'arduino a besoin de remplacer ses variables (ou registres) pour laisser la place aux données de l'interruption. Quand on le fait soit-même dans un programme, on ne va pas remplacer, on va avoir plusieurs variables, autant qu'il en faut pour chacun des sous programmes.

 

Pour illustrer, je vais expliquer pourquoi il faut se passer de la procédure delay(). Et comment ? En faisant du multitâche coopératif. Admettons qu'on veuille piloter la vitesse de rotation d'un servo moteur. Sans pilotage particulier, le servo ira à la position demandée aussi vite qu'il peut. Là, on veut qu'il aille à 10° par secondes. On est dans loop(). On a une variable qui contient la position courante. On commence à 0°, on place le servo à 0°, on attend 1 seconde avec delay(), on avance de 10° et ainsi de suite. Le résultat est un peu saccadé. On se dit qu'on va faire mieux en avançant de 1° à la fois avec des attentes de 100 ms. On est bien à la vitesse voulu et c'est pas saccadé. Génial !! On a réussi. Maintenant, on va placer un 2e servo. À la suite du programme, dans loop(), je vais piloter mon 2e moteur avec le même principe. Je déplace le moteur 1 de 1°, j'attend 100ms, je déplace le moteur 2 de 1°, j'attend 100ms et ainsi de suite. Ça fonctionne mais la vitesse est divisée par 2. Le temps d'attente est maintenant de 200ms. Je réfléchi 5 min et je décide de diviser par 2 les valeurs dans le delay(). Tout fonctionne bien.

 

Pour mon humanoïde avec 13 dof, je vais diviser par 13 pour conserver ma vitesse ... J'arrête là ce raisonnement. Tous les moteurs ne fonctionnent pas en même temps et pas tous à la même vitesse. Je n'ai pas envie de calculer chaque valeur de delay() pour mes 13 servo. La solution consiste à ne pas attendre, a ne pas utiliser de fonction bloquante et laisser la boucle loop() s'exécuter aussi vite qu'elle peut. Indépendamment de la période de calcul des servo. Pour utiliser le temps d'attente du delay() et pour laisser le temps aux autres calculs. Il n'y a pas que la vitesse des moteurs à calculer. Alors comment on fait ? Le principe : je connais la position courante et la vitesse que doit avoir chacun des moteurs. A chaque passage dans loop(), je regarde l'heure qu'il est (toujours en ms). Et pour chaque moteur, je calcul la position où il doit être pour cette heure de passage dans loop(). On ne maîtrise pas le délai entre 2 passages dans loop() mais on le mesure et on en déduit la position.

 

Pour y arriver, on va dire que le calcul de la position d'un servo est un sous programme. Chaque servo doit avoir ses propres variables : vitesse, heure de départ, position de départ et position d'arrivée. Faire une classe Servo est tout indiqué. On va utiliser la fonction map().

La durée du parcours est donnée par la formule : vitesse = abs((position d'arrivée - position de départ)) / durée du parcours.

L'heure d'arrivée est donnée par l'heure de départ + durée du parcours.

Et chaque moteur est piloté par la fonction suivante.

long position courante = map(heure qu'il est, heure de départ, heure d'arrivée, position de départ, position d'arrivée);

 

Pour boucler la boucle, il y a le cas du capteur ultrason HC-SR04. Pour avoir une lecture de la distance, la fonction pulseIn va mesurer le temps qu'un pin du module ultrason reste à l'état haut. L'enjeu consiste à détecter avec précision les changements d'état. PulseIn est une instruction bloquante. Le temps d'attente est dépendant de la distance. Pendant ce temps, on voudrait que l'arduino fasse autre chose. Dans ce but, on pourrait avoir envie de mesurer le temps entre le front montant et le front descendant avec un code multitâches coopératif. Dans la boucle, je lis l'état du capteur. s'il passe à haut, je note l'heure (en ms). Je continue de lire à chaque passage de la boucle. S'il repasse à bas, je note aussi l'heure. La différence de temps entre ces 2 horaires maximise la largeur de l'impulsion. C'est possible mais on perd en précision. La précision serait dépendante de la longueur de la boucle. C'est à dire la quantité d'instruction qu'on a placé dans le procédure loop(). Même si la fonction pulseIn() ressemble à la procédure delay() par on caractère bloquant, on peut être tenté de l'utiliser. Parce que c'est simple à faire. Et si on veut en optimiser le résultat, on peut même ignorer les interruptions pendant qu'elle attend ... Cela fonctionne très bien, mais là on coupe toute possibilité de multitâches !! Et si on a des roues codeuses à intercepter, soit on arrête le robot pendant qu'on utilise le capteur, soit on prend un 2e arduino. La bonne solution est de passer par les interruptions pour intercepter les changements d'état du capteur. Le seul inconvénient est qu'il faudra un arduino avec plus de 2 pin d'interruption si on souhaite les codeuses en même temps. 



#2 zavatha

zavatha

    Habitué

  • Membres
  • PipPip
  • 233 messages
  • Gender:Male

Posté 13 août 2017 - 05:41

Billet très intéressant !
Je le savais déjà mais maintenant j'en ais la certitude : j'ai codé mon module arduino comme un cochon ☺

Je devais bosser la propreté de mon code durant ces vacances mais une grippe A un peu bousculé mes plans... Ceci dit tu viens de me faire économiser un temps précieux avec cette analyse je sais déjà en partie ou placer mes optimisations !
Merci donc et chapeau bas Monsieur 🙌

@+
Zav

#3 Leon

Leon

    Membre passionné

  • Membres
  • PipPipPipPipPip
  • 1 289 messages
  • Gender:Male

Posté 13 août 2017 - 05:51

Salut Path.

 

Oui, comprendre les contraintes du multitâche temps réel, c'est important.

Mais il n'y a pas 1 seule méthode pour y arriver.

 

Attention, je ne connais rien en Arduino, mais voici quelques éléments supplémentaires. C'est issu de mes expériences diverses.

 

* OK globalement pour ton affirmation sur les fonctions "bloquantes"

 

* Tu dis que les capacités en interruption d'un arduino Mega sont limitées. C'est issu de ton expérience? Si c'est le cas, il faut bien voir qu'un Atméga, c'est un microcontrôleur 8 bits très peu performant, et largement dépassé techniquement. Pour le même prix, on peut trouver des microcontrôleurs 32 bits avec plusieurs dizaines de ko de RAM, et tournant à ~100MHz. J'utilise principalement les ARM Cortex M3 / M4. Pour les suites de développement simples, équivalente à Arduino, j'utilise MBED : https://developer.mbed.org/

Un tel microcontrôleur 32 bits est infiniment plus performant qu'un Atméga Asthmatique, et il est beaucoup plus riche en périphériques, bus haut débit interne, DMA, gestion des interruptions, souplesse de configuration des broches.

 

* multiplier les microcontrôleurs similaires, c'est rarement nécessaire, et c'est une source d'emmerdes incroyable. OK pour faire cohabiter un R-Pi et un Arduino, l'un pour des fonctions "haut niveau" (serveur web, streaming vidéo, traitement d'image, algo de cartographie), et l'autre pour les aspects "temps réel / bas niveau". Mais faire cohabiter 2 arduinos, c'est rarement judicieux, car on peut très souvent trouver une solution "logicielle" si c'est un simple problème de cohabitation de tâches de priorité différentes. La seule application légitime que je vois pour utiliser plusieurs microcontrôleurs équivalents, c'est pour "multiplexer" des fils, comme on dit dans l'électronique automobile : quand des capteurs/actionneurs sont nombreux et éloignés géographiquement, on a parfois intérêt à faire transiter une liaison "série" plutôt que des dizaines de fils.

 

* On peut faire du "multi-tâches temps réel" de plusieurs manières différentes

    - avec un OS temps réel coopératif (je n'ai plus en mémoire, il faut que je retrouve)

    - avec un OS temps réel préemptif (type RTOS)

    - avec un simple séquenceur fait maison : une boucle principale, qui appelle successivement, et à intervalles réguliers, mais à des périodes différentes et/ou en fonctions d'événements, les différentes "tâches". C'est comme l'OS coopératif, mais en beaucoup plus simple

    - avec des interruptions, en plus du séquenceur. Interrputions déclenchées sur timer ou sur événement externe (IO, périphérique)

 

* perso, j'ai testé les 3 dernières solutions, et ce que j'en retiens:

    - Attention avec les OS temps réel préemptifs. C'est pratique, ça simplifie la vie, surtout pour coder des systèmes très complexes, mais... Si on veut faire des tâches très rapides et précises à la milliseconde près ou moins, c'est complexe. Le basculement de tâches bouffe énormément de performances, et ça devient inapproprié avec des tâches très récurrentes (moins de 1ms).

    - Au final, un tel OS n'est pas forcément indispensable pour nos applications simples.

    - Perso, tous mes derniers développements sont un mixte de : séquenceur fait maison + interruptions.

 

* Interruptions (=INT). Plusieurs choses à dire:

    - Contrairement à ce que tu dis, il est tout à fait possible d'écouter les autres sources d'interruption pendant une interruption. A ma connaissance tous les processeurs ne réagissent pas de la même façon quand une INT est en cours et qu'une autre INT arrive. Soit il faut désactiver temporairement les INT pendant une INT puis les réactiver après. Soit ça n'est pas nécessaire, et les autres IRQ sont mises en attente. Mais dans certains cas, avant de sortir de l'INT, il est possible de vérifier, par soft, qu'aucun autre événement à traiter n'est arriver.

    - La règle qui consiste à imposer de rester un temps le plus court possible pendant l'INT, et à ne surtout pas appeler de fonctions bloquantes, est une règle simpliste. Elle est valable dans la majorité des cas... Mais on peut s'en affranchir! Pour un développement en cours, je gère énormément de choses "temps réel" avec des INT, car j'ai besoin de temps de réactions hyper rapide, et j'ai besoin d'interrompre immédiatement (à quelques microsecondes près) les tâches moins prioritaire, et je dois faire beaucoup de choses dans ces INT. Et j'appelle des fonctions "légèrement bloquantes" (= qui durent des centaines de microsecondes) dans les INT! Au final, j'arrive dans le pire des cas à 20% du temps passé en INT. C'est énorme, mais ça marche! Il faut bien comprendre les contraintes, le principe. Et bien évidemment tester pour comprendre comment ça se comporte. L'analyseur logique est l'outil indispensable pour débugger ça!

 

Leon.


BOB4, mon drone hélicoptère autonome d'intérieur http://heli.bot.free.fr/
BOB3, mon robot autonome d'intérieur avec WiFi + Foxboard Linux http://ze.bot.free.fr/
BOB5, robot bipède simulé, puis tentative de réalisation (fail)


#4 Path

Path

    Made By Humans

  • Modérateur
  • PipPipPipPipPip
  • 2 504 messages
  • Gender:Male
  • Location:Paris

Posté 13 août 2017 - 06:57

Merci Zav' :) Moi aussi ça m'a aidé de poser tout ça par écrit.
 
Léon, merci pour ce complément. Pour ce qui est d'utiliser l'arduino aux limites. J'ai pas trouvé de littérature. C'est mes déductions. Ça fait pas 2 ans que je m'amuse avec l'arduino. Un jour je passerai à mieux :)

#5 gerardosamara

gerardosamara

    Membre passionné

  • Membres
  • PipPipPip
  • 374 messages
  • Gender:Male
  • Location:Costa Rica & Bretagne

Posté 13 août 2017 - 11:30

Un exemple de  "pseudo multi-taches" sur Arduino ( en anglais)

 

http://forum.arduino.cc/index.php?topic=223286.0


Pura vida

 

Ma chaine youtube  https://www.youtube....EQ5MTR3A/videos

Tutoriel MIT Inventor2  https://www.robot-ma...e-robot-mobile/


#6 Path

Path

    Made By Humans

  • Modérateur
  • PipPipPipPipPip
  • 2 504 messages
  • Gender:Male
  • Location:Paris

Posté 14 août 2017 - 06:49

Oui :) On le voit dans le code de Robin2, on peut même faire de l'anti rebond pour un bouton dans un code multi tâche coopératif.

Merci Gerardo.

Et à mon humble avis, c'est un code multitâches coopératif. Le mec n'utilise pas de code bloquant et il passe d'un sous programme à l'autre aussi rapidement que possible.



#7 cocothebo

cocothebo

    Membre passionné

  • Membres
  • PipPipPip
  • 341 messages
  • Gender:Male

Posté 14 août 2017 - 08:19

Salut,

 

Juste un petit rappel de vocabulaire, quand on parle de temps réel dans un OS, cela ne veut pas dire que ça va vite etc, cela veut juste dire que l'on sait (pratiquement) précisément la durée que prendra la temps de traitement d'un événement (changement de contexte inclus). 

 

Pour un exemple d'OS multi tache coopératif, il y avait windows jusque 95 (exclu qui était hybride), le gros problème de la version coopérative, c'est que si un programme part en boucle infinie ou ne rend pas la main, tout le reste est bloqué.

Par contre l'implémentation est très simple, un gros programme arduinon pourrait être vu comme un multitache coopératif, chaque méthode appellée dans "loop()" serait alors une tâche, qui si elle ne rend pas la main bloque les autres.

 

Et je suis plutôt d'acord avec ce que dit Leon, un OS est pas toujours nécessaire pour nos petits bricolage, même si sur des robots "évolués" c'est sur qu'un Pi est un confort plus qu'appréciable.



#8 Telson

Telson

    Membre chevronné

  • Membres
  • PipPipPipPip
  • 975 messages
  • Gender:Male
  • Location:Punaauai - Tahiti

Posté 14 août 2017 - 10:24

Je me suis également posé la question du multitâches, des interruptions  sur arduino et j'avais trouvé une solution qui me paraissait assez sympa : l'utilisation de Timer0 :
 
https://learn.adafru...rnal-interrupts
 
https://learn.adafru...o-part-2/timers
 
ce n'est pas du pur multitâche mais bon .......
 
 
La carte Arduino Due n'utilise telle pas un microcontrolleur Atmel ATSAM3X8E ARM Cortex-M3 : https://store.arduino.cc/arduino-due

Ainsi que le PcDuino : http://www.mon-club-elec.fr/pmwiki_mon_club_elec/pmwiki.php?n=MAIN.PCDUINO



#9 Bobox

Bobox

    Habitué

  • Membres
  • PipPip
  • 157 messages
  • Gender:Male
  • Location:Montigny le Bretonneux

Posté 14 août 2017 - 10:47

Il peut y avoir un  interet à avoir des microcontrolleurs multiple dans une même application, et pas seulement pour des problèmes de localisation.

C'est pour séparer les problèmes, comme en language objet, quand tu commences a faire des classes séparés. Le micro-controlleur dédié à l'analyse des encodeurs, au calcul de vitesse et évaluation de position tu peux décider de le séparer comme ça plus besoin de reccompiler, et les modifications sur les autres microcontroleur ne peuvent pas baver sur celui-là.

D'ailleurs la majorité des gros capteurs ( par exemple le BNO055 ), viennent avec leur microcontrolleur dédié, pour masquer le détail de la conversion du signal analogique des capteurs en des données digitales un peu plus haut niveau.

 

Après, dans nos réalisation personelles où l'on concoit pas des cartes, mais plutôt assemble des composants, les micro-controlleurs sont 'gros', car ils viennent avec beaucoup de feature inutile une fois mis en place. ( programmeteur par exemple, port USB etc..).

Ceci étant, un arduino nano, c'est assze petit, et parfois séparer certains problèmes matériel en deux pourrait faciliter le développement.



#10 maximusk

maximusk

    Membre passionné

  • Membres
  • PipPipPip
  • 304 messages
  • Gender:Male
  • Location:Le Mans

Posté 14 août 2017 - 11:09

La carte Arduino Due n'utilise telle pas un microcontrolleur Atmel ATSAM3X8E ARM Cortex-M3 : https://store.arduino.cc/arduino-due

Ainsi que le PcDuino : http://www.mon-club-elec.fr/pmwiki_mon_club_elec/pmwiki.php?n=MAIN.PCDUINO

 

L'arduino due utilise bien un microcontrôleur ARM Cortex M3.

Et il y a aussi l'arduino M0, M0 pro, MKR Zero, MKR1000, MKR Fox 1200, Zero, Tian qui tournent sur du Cortex M0, ou Cortex M0+.

Sans compter les autres cartes type Teensy (freescale Kinetis), Nucleo (STM32), et encore d'autres moins connues.

 

Par contre le PCduino c'est différent, à part la connectique arduino, il n'a pas grand chose à voir puisque c'est un Cortex A7, pour faire tourner un OS, on est donc plus proche de Raspberry (sauf que c'est du SoC allwinner, que j'aurais clairement tendance à déconseiller a ceux qui n'ont pas des connaissances avancées en Linux embarqué, parce que le support logiciel est moisi, on est à des années lumières de Raspbian).



#11 Telson

Telson

    Membre chevronné

  • Membres
  • PipPipPipPip
  • 975 messages
  • Gender:Male
  • Location:Punaauai - Tahiti

Posté 14 août 2017 - 11:40

ok, merci pour ces compléments d'informations.

#12 Oliver17

Oliver17

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 2 758 messages
  • Gender:Male

Posté 15 décembre 2017 - 11:59

Et hop, on ressort ce post ^^

 

Tiens Path, je suis tombé sur ça, et ça rejoins je trouve tes propos si je dis pas de bêtises :P

 

http://www.vorobotics.com/wiki/index.php?title=Adafruit-multi-tasking-the-arduino-part-1


signature_01.png -->

 

Mon Tipeee
 


#13 Path

Path

    Made By Humans

  • Modérateur
  • PipPipPipPipPip
  • 2 504 messages
  • Gender:Male
  • Location:Paris

Posté 15 décembre 2017 - 01:33

J'ai pas lu tout l'article mais cest exactement le sujet.

#14 Oliver17

Oliver17

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 2 758 messages
  • Gender:Male

Posté 15 décembre 2017 - 05:20

J'essaye de voir un peu pour faire pareil, dans l'ensemble je comprend, c'est déjà rassurant ^^

 

Sinon ça va se finir sur le Discord lol ;)


signature_01.png -->

 

Mon Tipeee
 


#15 Melmet

Melmet

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 1 521 messages
  • Gender:Male
  • Location:Saint-Sozy (46200)

Posté 18 décembre 2017 - 01:41

Je savait bien que j'avais vu un truc de bien aussi la dessus. :

https://forum.arduin...?topic=470879.0


Ne demande jamais à un robot de faire ce que tu peux faire toi même. :nono: :laugh1:


#16 arobasseb

arobasseb

    Membre chevronné

  • Administrateur
  • PipPipPipPip
  • 737 messages
  • Gender:Male
  • Location:BORDEAUX (33)

Posté 18 décembre 2017 - 06:53

J'avais mis un exemple avec une machine à état qui permettait à deux servos de bouger d'un angle différent sur le même temps, on pourrai faire aussi l'inverse. http://www.robot-maker.com/forum/topic/11614-robot-bipede-amhnemus/page-6#entry87519

#17 Melmet

Melmet

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 1 521 messages
  • Gender:Male
  • Location:Saint-Sozy (46200)

Posté 18 décembre 2017 - 07:24

En effet :) 


Ne demande jamais à un robot de faire ce que tu peux faire toi même. :nono: :laugh1:





0 utilisateur(s) li(sen)t ce sujet

0 members, 0 guests, 0 anonymous users