Auteur Sujet: Tâches quasi parallèles et protothreads  (Lu 913 fois)

Marc-Henri

  • Jr. Member
  • **
  • Messages: 95
    • Voir le profil
    • Modélisme ferroviaire & électronique
Tâches quasi parallèles et protothreads
« le: mars 14, 2017, 02:50:43 pm »
Bonjour à tous,

Lorsqu'un logiciel doit gérer plusieurs activités en parallèle, il s'avère intéressant de le structurer en tâches donnant l'illusion de se dérouler en parallèle. Plusieurs approches sont possibles et j'aimerais partager l'une d'elles, tout à fait applicable aux microcontrôleurs et à l'Arduino.

Un thread, traduction du mot "fil" ou une tâche est une suite d'instructions. Sur un système à processeur unique, les threads donnent l'illusion de se dérouler en parallèle. En réalité, lorsqu'un thread rend la main, un autre thread continue son exécution. Lorsque le premier thread est à nouveau activé, il reprend l'exécution des instructions où il avait rendu la main.

Un protothread est un thread ultra simplifié, mécanisme dont on trouve plusieurs réalisations. L'une d'elles, décrite dans http://dunkels.com/adam/pt/ est facile à mettre en oeuvre. Je l'ai décrite sur mon blog à cette adresse https://savignyexpress.wordpress.com/2014/01/17/gare-du-reseau-dun-ami-programmation/.

Ma première utilisation des protothreads est la commande des 2 PN de mon réseau N. 3 protothreads assurent simultanément:
  • la commande des servos qui actionnent les 2 barrières de l'un des PN.
  • le clignotement des signaux du même PN.
  • le clignotement des signaux d'un 2ème PN, lui sans barrières.

Voir le programme C ci-joint pour Attiny2313.

C'est certainement facilement adaptable à l'Arduino. Il n'y a aucune librairie à installer, tout est réalisé sous forme de macros C contenues dans des fichiers *.h. Les protothreads exploitent l'instruction switch du langage C, les étiquettes permettant de reprendre le protothread où il a rendu la main.

Il faut toutefois mentionner quelques restrictions:
  • l'instruction switch étant à la base du mécanisme, il ne faut plus l'utiliser dans le programme et remplacer par des if.
  • il s'agit de multi-tâches coopératif non pré-emptif. Dans certains cas on peut même se passer d'interruptions.
  • les protothreads sont bien adaptés à des tâches très séquentielles. Dans l'exemple du PN: attendre le train, baisser les barrières, attendre, lever les barrières. Si le déroulement est beaucoup plus complexe, une machine à états finis est plus adaptée. Cette dernière peut tout à fait être encapsulée dans un protothread !

Meilleures salutations à tous.
« Modifié: mars 14, 2017, 03:51:54 pm par Marc-Henri »

DDEFF

  • Sr. Member
  • ****
  • Messages: 445
    • Voir le profil
Re : Tâches quasi parallèles et protothreads
« Réponse #1 le: mars 14, 2017, 03:49:46 pm »
Merci Marc Henri,

J'aime bien l'idée. ;)

Je ne jure pas  que je vais m'en servir demain matin , mais je garde ça dans un coin de ma tête. Je suis sûr que c'est utile.
On maîtrise tout, puis qu'on fait tout soi-même. Et ça, j'aime bien.

Amicalement
Denis


Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1246
  • 100% Arduino et N
    • Voir le profil
Re : Tâches quasi parallèles et protothreads
« Réponse #2 le: mars 14, 2017, 10:28:45 pm »
« Par ma foi ! il y a plus de quarante ans que je dis de la prose sans que j'en susse rien, et je vous suis le plus obligé du monde de m'avoir appris cela. »

Bonsoir Marc-Henri,

Comme le dirai Monsieur Jourdain, je viens de découvrir que je fais du thread sans le savoir ! Si, en fait et je donne un exemple :

Dans un programme Arduino, il y a le Setup() qui est éxécuté une seule fois en premier, puis la Loop() qui est éxécutée ensuite de façon répétitive. C'est exactement comme un while(1) { ………}. Le compilateur l'ajoute subrepticement sans qu'on le voit !

Quand je programme mon gestionnaire de réseau, je fais donc éxécuter dans la Loop(), toute une série de tâches qui ne vont occuper le processeur qu'une petite fraction du temps en le libérant le plus vite possible, pour que la suivante prenne la main, et ainsi de suite.

Ces tâches sont (dans mon gestionnaire, mais tout le monde reconnaitra des cas qu'il rencontre aussi) :

  • Récupération des messages CAN si le flag a été monté par l'interruption. Il faut lire tous les messages CAN reçus sinon l'interruption n'aura plus lieu ensuite. Les messages sont stockés temporairement dans un tampon circulaire.
  • Traitement d'UN SEUL message CAN reçu et stocké dans le tampon circulaire (une occupation ou une libération, ou une demande d'itinéraire, de changement d'aiguille ou de mouvement de train.
  • Lecture du port série s'il y a des caractères reçus et une action est lancée seulement si une commande complète est reçue (sinon elle est stockée temporairement). Cela sert principalement au Debug pour valider ou non certains affichages ou passer des commandes de simulation
  • Lecture des boutons d'un écran tactile : un seul à la fois donne lieu à l'exécution d'une commande
  • Toute une série de tâches périodiques se suivent ensuite en testant la valeur de millis() par rapport à des tops d'actions comme le rafraichissement d'affichages, des clignotements de leds, les calculs de durée d'execution (combien de tours de Loop() par seconde pour mesurer la vitesse du programme), des time-out, des pas de simulation (envoyer par exemple une commande lue dans une carte SD toutes les 2 secondes), etc..
  • On peut en imaginer d'autres ...

J'ai décrit là 5 types de threads qui sont, en fait, programmés comme des sous-programme (une entrée et une sortie) et qui ne s'interdisent aucune contrainte de programmation.

En particulier le switch / case est totalement permis et fort heureusement car il faut bien agir en fonction de la commande reçue et une suite de if ne serait pas agréable. Les Macros ne sont pas obligatoires non plus.

Ces tâches correspondent bien à un gestionnaire de réseau, mais mon Arduino Mega qui commande des aiguilles est fait de la même façon, avec les tâches liées aux messages, mais aussi les commandes des moteurs d'aiguilles qui sont décalées dans le temps d'au moins une 1/2 seconde pour éviter les appels de courant simultanés (j'ai fait sauter un fusible avent cela !)

La seule contrainte est de bien respecter le non-blocage du processeur pendant l'éxécution d'une tâche (pas de delay(), des while() bien contrôlés, des tests sur toutes les limites) et que chaque tâche puisse reprendre la main sans perdre d'événement d'où quelques tests de Debug pour mesurer les temps d'exécution. D'une manière générale, je n'ai jamais eu de surprise (hors bug évidemment).

Voilà comment on fait du thread sans le savoir !

Amicalement


Marc-Henri

  • Jr. Member
  • **
  • Messages: 95
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Tâches quasi parallèles et protothreads
« Réponse #3 le: mars 14, 2017, 11:23:58 pm »
Bonsoir Denis et Dominique,

Merci beaucoup pour vos encouragements et vos messages.

Merci à Dominique d'avoir mentionné qu'il ne faut pas bloquer le processeur et rendre la main au plus vite pour qu'une autre tâche puisse tourner. Implantées sous forme de sous-programmes, chaque tâche doit mémoriser l'état ainsi que d'autres variables entre 2 appels consécutifs. Une approche de type machine à états finis est la mieux adaptée. Le protothread tel que décrit est adéquat pour des activités essentiellement séquentielles. Il est par exemple possible de quitter une boucle pour la reprendre où on l'avait laissée à l'appel suivant: très pratique pour baisser et lever les barrières du PN.

Ma première application ferroviaire d'un microcontrôleur est la gestion du TCO de mon réseau (https://savignyexpress.wordpress.com/2016/10/12/mon-reseau-panneau-de-controle-tco/). Le programme comporte 2 tâches: une tâche principale destinée à gérer les états du système selon les poussoirs pressés, une seconde tâche destinée à commander les aiguilles en 2 groupes pour éviter la surcharge de l'alimentation. Ces 2 tâches sont des machines à états appelées l'une après l'autre dans le programme principal.

Les 2 approches peuvent cohabiter dans la boucle principale d'un programme: while dans mon cas ou dans la fonction loop en Arduino.

Meilleures salutations de Suisse romande.
« Modifié: mars 14, 2017, 11:26:03 pm par Marc-Henri »