Changes between Initial Version and Version 1 of IOC_T04


Ignore:
Timestamp:
Mar 26, 2021, 7:51:58 AM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC_T04

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