Changes between Version 2 and Version 3 of SysCom19


Ignore:
Timestamp:
Oct 9, 2019, 6:06:40 AM (5 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SysCom19

    v2 v3  
    1818- La photorésistance
    1919
    20 = Démarrage (rappel)
     20= Test du matériel
    2121
    2222Pour s'assurer que le module Arduino et la chaîne de compilation sont fonctionnels, vous pouvez  reprendre  l'exemple `blink``
     
    3333- La led doit clignoter sur le module
    3434
    35 = Exécution ''multi-tâches''
    36 
    37 == Principe
    38 
    39 Il est possible de programmer des applications multi-tâches coopératives dans l'environnement Arduino sans pour autant disposer des services d'un OS. Le principe a été volontairement simplifié à l'extrême. Ici, toute l'application sera dans un seul fichier.
     35= Principe d'exécution multi-tâches
     36
     37Il est possible de programmer des applications multi-tâches coopératives dans l'environnement Arduino sans pour autant disposer des services d'un OS. Le principe est volontairement simplifié à l'extrême. Ici, toute l'application sera dans un seul fichier.
    4038Nous n'allons pas utiliser la programmation objet pour ne pas complexifier, mais ce serait possible pour une application plus ambitieuse pour une meilleure maintenance.
    4139
     
    4543Les ISR (Interrupt Service Routine) qui sont des fonctions exécutées lors de la survenue d'interruption sont considérée comme des tâches.
    4644
    47 == Tâches
    48 
    49 Chaque tâche est représentée par
    50 * une fonction `loop_Tache()` qui code son comportement qui sera appelée dans la fonction `loop()`.
     45Chaque tâche est représentée deux fonctions `loop_Tache()` et `setup_Tache()`
     46* La fonction `loop_Tache()` code le comportement de la tâche. Elle es appelée dans la fonction `loop()`.
    5147  Dans cet exemple l'application a trois tâches : la tâche 1 est répliquée 2 fois et la tâche 2 n'a
    5248  {{{#!c
    5349  loop() {
    54     loop_tache1(args...); // 1er réplica de la tâche 1
    55     loop_tache1(args...); // 2nd réplica de la tache 1
    56     loop_tache2(args...); // unique réplica de la tâche 2
     50    loop_Tache1(args...); // 1er réplica de la tâche 1
     51    loop_Tache1(args...); // 2nd réplica de la tache 1
     52    loop_Tache2(args...); // unique réplica de la tâche 2
    5753  }
    5854  }}}
     
    6056  Les tâches n'ont pas le droit de conserver le processeur sinon cela crée un blocage du système.
    6157  Cela signifie qu'il est interdit de faire des boucles d'attente d'un événement.
    62  
    6358  La fonction loop() est en fait un ordonnanceur de tâche suivant un algorithme FIFO statique.
    6459
    6560  La fonction loop() ignore si la tâche est en mesure d'avancer. Les tâches sont toujours "prêtes".
    66   C'est chaque tâche qui doit vérifier si elle peut avancer, par exemple si les données
     61  C'est chaque tâche qui doit vérifier si elle peut avancer ou pas, par exemple si les données
    6762  attendues sont disponible ou si assez de temps c'est écoulé depuis la dernière instance d'exécution.
    6863  Si oui elle avance, sinon elle sort immédiatement et la tâche suivante dans la liste est exécutée.
    6964
    70 * une seconde fonction `setup_Tache()` qui initialise les ressources matérielles assignées de la tâche (périphériques) et l'état interne (voir plus loin).
     65* La seconde fonction `setup_Tache()` initialise les ressources matérielles assignées de la tâche (périphériques) et l'état interne (voir plus loin).
    7166  {{{#!c
    7267  setup() {
    73     setup_tache1(args...); // setup du 1er réplica de la tâche 1
    74     setup_tache1(args...); // setup du 2nd réplica de la tache 1
    75     setup_tache2(args...); // setup de l'unique réplica de la tâche 2
     68    setup_Tache1(args...); // setup du 1er réplica de la tâche 1
     69    setup_Tache1(args...); // setup du 2nd réplica de la tache 1
     70    setup_Tache2(args...); // setup de l'unique réplica de la tâche 2
    7671  }
    7772  }}}
     
    8479  - Elles peuvent aussi avoir des variables `static`. Ces variables ont une valeur
    8580    unique pour toutes les réplicas d'une tâche.
     81  - Ces variables définissent donc un état interne partagé par tous les réplicas.
    8682* Variables de contexte d'exécution :
    87   - Enfin chaque réplica de tâche dispose d'une structure contenant son état interne,
    88     lequel est conservé entre deux instance d'exécution de la tâche.
     83  - Enfin chaque réplica de tâche dispose d'une structure contenant son état interne propre,
     84    lequel est conservé entre deux instance d'exécution du réplica de tâche.
    8985    Le contexte d'exécution représenté par une variable globale du programme sous forme d'une structure.
    9086    Une structure différente est passée en argument de chaque réplica de tâche dans les arguments
     
    9995    }}}
    10096
    101 C'est la fonction `setup_Tache()`qui va devoir initialiser le contexte avec des arguments
     97C'est la fonction `setup_Tache()` qui initialise l'état interne des tâches
    10298{{{#!c
    10399void setup_Tache(struct Tache_st *ctx, args...) {
     100   stat type etat_tache;
     101   // Initialisation de l'état interne commun a tous les réplica
     102   etat_tache = etat_initial; //  reçu dans les args
    104103   // Initialisation du contexte}
    105    ctx->etat = etat_initial;  //  reçu dans les paramètres
     104   ctx->etat = etat_initial;  //  reçu dans les args
    106105   ...
    107106}
    108107}}}
    109108
    110 == Communication entre tâches
    111 
    112 Les tâches communiquent entre elles par des variables globales de l'application.
    113 Chaque tâche reçoit en argument les pointeurs vers les variables qu'elle utilise pour communiquer.
    114 Ces pointeurs sont nommés connecteurs (ou ports).
     109= Communications inter-tâches
     110
     111Lorsqu'on écrit un programme multi-tâches, les tâches communiquent entre elles. Pour ce faire, nous allons simplement créer variables globales et les donner en arguments aux tâches.
     112Chaque tâche reçoit donc en argument les pointeurs vers les variables qu'elle utilise pour communiquer.
     113Ces pointeurs sont nommés des ports.
    115114
    116115La structure forme de la fonction loop d'une tâche est donc :
    117116{{{#!c
    118 void loop_Tache(struct Tache_st *ctx, connectors....) {   
     117void loop_Tache(struct Tache_st *ctx, ports....) {   
    119118   // test de la condition d'exécution, si absent on SORT
    120119   if (evement_attendu_absent) return;
     
    124123}}}
    125124
     125Supposons que nous voulions que la tâche T1 envoie un message à la tâche T2. Nous allons utiliser une boite à lettre. Le code suivant explique le principe qui est basé sur une variable d'état à 2 valeur indiquant l'état de la boîte. La boîte peut être vide ou pleine.
     126l'écrivain T1 ne peut écrire que lorsque la boîte est vide. Lorsqu'elle est vide, il y écrit et il change l'état. Inversement, le lecteur attend qu'elle soit pleine. Lorsqu'elle est pleine, il la lit et change l'état.
     127
     128Il s'agit d'une communication sans perte. Si T1 ne testait pas l'état de la boîte, on pourrait avoir des pertes, c'est parfois nécessaire, si T2 n'a pas eu le temps d'utiliser la boîte mais que T1 a une nouvelle valeur, il peut écraser la valeur présente.
     129
     130{{{#!c
     131struct mailbox {           
     132  enum {EMPTY, FULL} state;                    // Création d'une boite vide
     133  int val;
     134} mb0 = {.state = EMPTY};
     135
     136struct T1_st {...} T1;                         // Création de 2 contextes d'exécution
     137struct T2_st {...} T2;
     138 
     139void setup_T1(struct T1_st *T1, args...) {...} // Déclaration des fonctions setup de tâche
     140void setup_T2(struct T2_st *T2, args...) {...}
     141
     142void loop_T1(struct T1_st *T1, struct mailbox *mb) {
     143  if (mb->state != EMPTY) return;              // attend que la mailbox soit vide
     144  mb->val = 42;
     145  mb->state = FULL;
     146}
     147
     148void loop_T2(struct T1_st *T1, struct mailbox *mb) {
     149  if (mb->state != FULL) return;               // attend que la mailbox soit pleine
     150  // usage de mb->val
     151  mb->state = EMPTY;
     152}
     153setup() {
     154  setup_T1(&T1, args...);
     155  setup_T2(&T2, args...);
     156}
     157
     158loop() {
     159  loop_T1(&T1, &mb0);
     160  loop_T2(&T2, &mb0);
     161}
     162}}}
     163
    126164== Gestion des tâches standard périodiques
    127165
    128 Pour les tâches périodiques (elles sont fréquentes), nous pouvons écrire une fonction qui exploite un timer interne du processeur qui s'incrémente chaque microseconde. Cette fonction nommée `waitFor(int timer, unsigned long period)` prend deux paramètres `timer` et `period`. Le premier un numéro de timer (il en faudra autant que de tâches périodiques). Le second est une période en microsecondes.
     166Pour les tâches périodiques (elles sont fréquentes), nous pouvons écrire une fonction qui exploite un timer interne du processeur qui s'incrémente chaque microseconde. Cette fonction nommée :
     167{{{#!c
     168waitFor(int timer, unsigned long period)
     169}}}
     170Elle prend deux paramètres `timer` et `period`. Le premier un numéro de timer (il en faudra autant que de tâches périodiques). Le second est une période en microsecondes.
    129171
    130172`wairFor()` peut être appelée aussi souvent que nécessaire, elle rend la valeur 1 une seule fois par période (second paramètre).
     
    252294
    253295
    254 = Communications inter-tâches
    255 
    256 Lorsqu'on écrit un programme multi-tâches, il est intéressant de les faire communiquer. Pour ce faire, nous allons simplement créer variables globales et les donner en arguments aux taches communicantes.
    257 
    258 Supposons que nous voulions que la tâche T1 envoie un message à la tâche T2. Nous allons utiliser une boite à lettre. Le code suivant explique le principe qui est basé sur une variable d'état à 2 valeur indiquant l'état de la boite. La boite peut être vide ou pleine.
    259 l'écrivain T1 ne peut écrire que lorsque la boite est vide. Lorsqu'elle est vide, il y écrit et il change l'état. Inversement, le lecteur attend qu'elle soit pleine. Lorsqu'elle est pleine, il la lit et change l'état.
    260 
    261 Il s'agit d'une communication sans perte. Si T1 ne testait pas l'état de la boite, on pourrait avoir des pertes, c'est parfois nécessaire, si T2 n'a pas eu le temps d'utiliser la boite mais que T1 a une nouvelle valeur, il peut écraser la valeur présente.
    262 {{{#!c
    263 struct mailbox {
    264   enum {EMPTY, FULL} state;
    265   int val;
    266 } mb0 = {.state = EMPTY};
    267 
    268 void loop_T1(&mb) {
    269   if (mb->state != EMPTY) return; // attend que la mailbox soit vide
    270   mb->val = 42;
    271   mb->state = FULL;
    272 }
    273 
    274 void loop_T2(&mb) {
    275   if (mb->state != FULL) return; // attend que la mailbox soit pleine
    276   // usage de mb->val
    277   mb->state = EMPTY;
    278 }
    279 }}}
    280 
    281296**Questions**
    282297