Changes between Initial Version and Version 1 of IOC20_T04


Ignore:
Timestamp:
Feb 18, 2020, 8:50:31 PM (5 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC20_T04

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