Changes between Initial Version and Version 1 of SysCom19


Ignore:
Timestamp:
Oct 9, 2019, 4:21:01 AM (6 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SysCom19

    v1 v1  
     1= Programmation Arduino
     2
     3= Objectifs de la séance
     4
     5Le but de la séance est d'écrire une application multitâches Arduino utilisant plusieurs périphériques.
     6
     7= Préambule
     8
     9Les documents nécessaires se trouvent :
     10- [https://github.com/adafruit/Adafruit_SSD1306 Repository API Ecran OLED]
     11- [https://github.com/adafruit/Adafruit-GFX-Library Repository API Graphique]
     12- [http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.ReferenceMaxi Langage Arduino]
     13
     14Lors de cette séance, nous allons programmer sur Arduino en utilisant :
     15- La LED présente sur le module.
     16- Le port série qui relie le module et le PC.
     17- L'écran OLED
     18- La photorésistance
     19
     20= Démarrage (rappel)
     21
     22Pour s'assurer que le module Arduino et la chaîne de compilation sont fonctionnels, vous pouvez  reprendre  l'exemple `blink``
     23- Brancher le module Arduino avec le câble USB
     24- lancer : `/opt/arduino-1.6.8/arduino &`
     25- Sélectionner : `Tools -> Boards -> Arduino Duemilanov...` (même si c'est une nano)
     26- Sélectionner : `Tools -> Processor -> ATmega328`
     27- Sélectionner : `Tools -> Ports -> /dev/COMMx` (le numéro du port est créé à la connexion)
     28- Charger le programme Blink : `File -> Exemples -> 0.1 Basics -> Blink`
     29- Vous pouvez changer la fréquence en modifiant les délais
     30- Cliquer sur l'icône en forme de V pour Compiler
     31- Cliquer sur l'icône en forme de -> pour uploader
     32- En bas de la fenêtre un message vous indique la réussite de la compilation et de l'upload.
     33- La led doit clignoter sur le module
     34
     35= Exécution ''multi-tâches''
     36
     37== Tâches standards
     38
     39Il 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 et nous n'allons pas utiliser la programmation objet pour ne pas complexifier, mais pour une application plus ambitieuse, ce serait souhaitable pour une meilleure maintenance.
     40
     41Une application est composée de plusieurs tâches. Chaque tâche peut être répliquée en plusieurs instances et chaque tâche est exécutée à tour de rôle en temps partagé.
     42* Une ''instance de tâche'' est une des copies d'une tâche
     43
     44Chaque tâche est représentée par
     45* une fonction `loop_Tache()` qui code son comportement qui sera appelée dans la fonction `loop()`.
     46  {{{#!c
     47  loop() {
     48    loop_tache1(args...);
     49    loop_tache1(args...);
     50    loop_tache2(args...);
     51  }
     52  }}}
     53* une seconde fonction `setup_Tache()` qui initialise les ressources de la tâche (périphériques) et l'état interne.
     54
     55Chaque tâche dispose de trois types de variables internes
     56* Variables locales temporaires :[[BR]]
     57  - Les fonctions `loop_Tache` et `setup_Tache` peuvent avoir des variables locales.
     58    L'état n'est pas conservé entre deux instances d'exécution.
     59* Variables d'état de tâche : [[BR]]
     60  - Elles peuvent aussi avoir des variables `static`. Ces variables ont une valeur
     61    unique pour toutes les instances de tâche.
     62* Variables de contexte d'exécution :
     63  - Enfin chaque instance de tâche dispose d'une structure contenant son état interne
     64    qui est conservé entre deux instance d'exécution.
     65    Le contexte d'exécution représenté par une variable globale du programme sous forme d'une structure.
     66    Cette structure est passée en argument de la tâche des fonctions `setup_Tache()` et `loop_Tache`.
     67  - La structure contexte ressemble à :
     68    {{{#!c
     69    struct Tache_st {
     70      unsigned int etat; 
     71      int config;       
     72    };
     73    struct Tache_st T1, T2;  // deux contextes pour deux tâches.
     74    }}}
     75
     76C'est la fonction `setup_Tache()`qui va devoir initialiser le contexte avec des arguments
     77{{{#!c
     78void setup_Tache(struct Tache_st *ctx, args...) {
     79   // Initialisation du contexte}
     80   ctx->etat = etat_initial;  //  reçu dans les paramètres
     81   ...
     82}
     83}}}
     84
     85La fonction `loop()` demande donc l'exécution des fonctions `loop_Tache()` à tour de rôle.
     86Les tâches n'ont pas le droit de conserver le processeur sinon cela crée un blocage du système.
     87Cela signifie qu'il est interdit de faire des boucles d'attente d'un événement.
     88`connectors` sont des pointeurs vers des variables globales utilisées pour la communications inter-tâches.
     89La structure générale d'une tâche est la suivante :
     90
     91{{{#!c
     92void loop_Tache(struct Tache_st *ctx, connectors....) {   
     93   // test de la condition d'exécution, si absent on SORT
     94   if (evement_attendu_absent) return;
     95   // code de la tache
     96   ....
     97}
     98}}}
     99
     100== Gestion des tâches standard périodiques
     101
     102Pour 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.
     103
     104`wairFor()` peut être appelée aussi souvent que nécessaire, elle rend la valeur 1 une seule fois par période (second paramètre).
     105Si elle n'est pas appelée pendant longtemps alors elle rend le nombre de périodes qui se sont écoulées.
     106Autrement dit, si dans une tâche vous écrivez `waitFor(12,100)` parce c'est le timer n°12 et que la période est de `100us` et si vous n'exécutez pas la tâche pendant `500us` alors au premier appel après ce délai de 500us `waitFor(12,100)` rendra 5.
     107
     108
     109== Exemple
     110
     111Dans l'application suivante nous avons deux tâches périodiques `Led` et `Mess`.
     112La première fait clignoter une led dont le numéro est passé en paramètre à 5Hz.
     113La seconde affiche bonjour à une fois par seconde.
     114
     115{{{#!c
     116// --------------------------------------------------------------------------------------------------------------------
     117// Multi-tâches cooperatives : solution basique
     118// --------------------------------------------------------------------------------------------------------------------
     119
     120// --------------------------------------------------------------------------------------------------------------------
     121// unsigned int waitFor(timer, period)
     122// Timer pour taches périodiques
     123// configuration :
     124//  - MAX_WAIT_FOR_TIMER : nombre maximum de timers utilisés
     125// arguments :
     126//  - timer  : numéro de timer entre 0 et MAX_WAIT_FOR_TIMER-1
     127//  - period : période souhaitée
     128// retour :
     129//  - nombre de période écoulée depuis le dernier appel
     130// --------------------------------------------------------------------------------------------------------------------
     131#define MAX_WAIT_FOR_TIMER 2
     132unsigned int waitFor(int timer, unsigned long period){
     133  static unsigned long waitForTimer[MAX_WAIT_FOR_TIMER];
     134  unsigned long newTime = micros() / period;              // numéro de la période modulo 2^32
     135  int delta = newTime - waitForTimer[timer];              // delta entre la période courante et celle enregistrée
     136  if ( delta < 0 ) delta += 1 + (0xFFFFFFFF / period);    // en cas de dépassement du nombre de périodes possibles sur 2^32
     137  if ( delta ) waitForTimer[timer] = newTime;             // enregistrement du nouveau numéro de période
     138  return delta;
     139}
     140
     141//--------- définition de la tache Led
     142
     143struct Led_st {
     144  int timer;                                              // numéro de timer utilisé par WaitFor
     145  unsigned long period;                                   // periode de clignotement
     146  int pin;                                                // numéro de la broche sur laquelle est la LED
     147  int etat;                                               // etat interne de la led
     148};
     149
     150void setup_Led( struct Led_st * ctx, int timer, unsigned long period, byte pin) {
     151  ctx->timer = timer;
     152  ctx->period = period;
     153  ctx->pin = pin;
     154  ctx->etat = 0;
     155  pinMode(pin,OUTPUT);
     156  digitalWrite(pin, ctx->etat);
     157}
     158
     159void loop_Led(struct Led_st * ctx) {
     160  if (!waitFor(ctx->timer, ctx->period)) return;          // sort s'il y a moins d'une période écoulée
     161  digitalWrite(ctx->pin,ctx->etat);                       // ecriture
     162  ctx->etat = 1 - ctx->etat;                              // changement d'état
     163}
     164
     165//--------- definition de la tache Mess
     166
     167struct Mess_st {
     168  int timer;                                              // numéro de timer utilisé par WaitFor
     169  unsigned long period;                                             // periode d'affichage
     170  char mess[20];
     171} Mess_t ;
     172
     173void setup_Mess(struct Mess_st * ctx, int timer, unsigned long period, const char * mess) {
     174  ctx->timer = timer;
     175  ctx->period = period;
     176  strcpy(ctx->mess, mess);
     177  Serial.begin(9600);                                     // initialisation du débit de la liaison série
     178}
     179
     180void loop_Mess(struct Mess_st *ctx) {
     181  if (!(waitFor(ctx->timer,ctx->period))) return;         // sort s'il y a moins d'une période écoulée
     182  Serial.println(ctx->mess);                              // affichage du message
     183}
     184
     185//--------- Déclaration des tâches
     186
     187struct Led_st Led1;
     188struct Mess_st Mess1;
     189
     190//--------- Setup et Loop
     191
     192void setup() {
     193  setup_Led(&Led1, 0, 100000, 13);                        // Led est exécutée toutes les 100ms
     194  setup_Mess(&Mess1, 1, 1000000, "bonjour");              // Mess est exécutée toutes les secondes
     195}
     196
     197void loop() {
     198  loop_Led(&Led1);                                       
     199  loop_Mess(&Mess1);
     200}
     201                                     
     202}}}
     203
     204**Questions**
     205- Que contient le tableau `waitForTimer[]`` ?
     206- Dans quel cas la fonction `waitFor()` peut rendre 2 ?
     207- Modifier le programme initial pour afficher "Salut" en plus de "bonjour" toutes les 1.5 secondes sans changer le comportement existant.
     208
     209= Utilisation de l'écran
     210
     211Nous allons utiliser un écran OLED connecté en I2C, 128x32 **ssd1306**
     212- La bibliothèque de l'écran se trouve en tapant la requête `ssd1306 arduino`[[BR]] à l'adresse   
     213  [https://github.com/adafruit/Adafruit_SSD1306].
     214  Vous devrez prendre également la bibliothèque GFX à l'adresse [https://github.com/adafruit/Adafruit-GFX-Library]
     215  qui est la bibliothèque graphique.   
     216- Vous pouvez exécuter l'exemple proposé dans la bibliothèque. Cette bibliothèque fonctionne pour plusieurs
     217  types modèles. Vous allez choisir le bon exemple : 128x32 I2C.
     218- Pour ajouter une bibliothèque Arduino, vous devez simplement télécharger le .zip et importer directement le
     219  .zip en sélectionnant le menu `Sketch -> include Library -> Add ZIP Library`
     220- Pour tester la librairie rendez-vous dans `File -> Exemples -> Adafruit SSD1306 -> ssd1306_128x32_i2c`.
     221  Il s'agit d'un programme qui teste les fonctionnalité de l'écran et de la bibliothèque graphique.
     222
     223**Questions**
     224
     225- Extraire de ce code, ce qui est nécessaire pour juste afficher un compteur qui s'incrémente toutes des 1 seconde sur l'écran OLED.
     226
     227
     228= Communications inter-tâches
     229
     230Lorsqu'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.
     231
     232Supposons 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.
     233l'é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.
     234
     235Il 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.
     236{{{#!c
     237struct mailbox {
     238  enum {EMPTY, FULL} state;
     239  int val;
     240} mb0 = {.state = EMPTY};
     241
     242void loop_T1(&mb) {
     243  if (mb->state != EMPTY) return; // attend que la mailbox soit vide
     244  mb->val = 42;
     245  mb->state = FULL;
     246}
     247
     248void loop_T2(&mb) {
     249  if (mb->state != FULL) return; // attend que la mailbox soit pleine
     250  // usage de mb->val
     251  mb->state = EMPTY;
     252}
     253}}}
     254
     255**Questions**
     256
     257- Ajouter une tâche qui lit toutes les 0,5 seconde le port analogique 15 (par `analogRead()`) sur lequel se
     258  trouve la photo-résistance et qui sort sa valeur dans une boite à lettre.
     259- Mofifier la tâche Led pour que la fréquence de clignotement soit inversement proportionnel à la lumière reçue
     260  (moins il y a de lumière plus elle clignote vite). La tâche Led devra donc se brancher sur la boite à lettre.
     261
     262= Gestion des interruptions
     263
     264Les périphériques peuvent lever des signaux d'interruption pour informer d'un événement sur un périphérique.
     265Avec Arduino, il est très simple d'attacher une routine d'interruption (ISR) à un signal d'interruption.
     266C'est la fonction `attachInterrupt(num, ISR, type)`. Pour l'Arduino nano `num` est égal à `0` ou `1`, ce qui correspond aux pins `2`et `3` qui sont des entrées de signaux d'interruptions. Il existe d'autres sources d'interruption comme le changement d'état d'une pins ou la réception d'une données depuis un bus par exemple `Serial.onReceive(ISR)`
     267
     268Dans notre contexte la fonction ISR sera comme une fonction loop_Tache.
     269
     270**Question**
     271
     272Ajouter une tâche qui arrête le clignotement de la LED si vous recevez un `s` depuis le clavier.
     273
     274{{{
     275#!comment
     276Pour le moment, ça ne marche pas.
     277
     278= Utilisation du détecteur de geste APDS-9960 (optionnel)
     279
     280le détecteur de geste APDS-9960 permet de détecter des mouvement de la main par exemple gauche -> droite, droite -> gauche.
     281C'est un composant I2C. Nous le verrons pour le micro-projet mais si vous voulez, vous pouvez le tester dès maintenant.
     282
     283Je vous propose d'ajouter une tâche qui arrête le clignotement de la LED si vous faite un déplacement droite -> gauche et qui le redémarre avec un déplacement gauche -> droite.
     284
     285Vous allez commencer par récupérer la library et le programme de test à partir du site : https://www.gotronic.fr/art-capteur-rgb-mouvement-apds9960-22744.htm
     286Puis intégrer une nouvelle tâche dans votre application.
     287}}}