Changes between Initial Version and Version 1 of SujetTP6-2018


Ignore:
Timestamp:
Apr 3, 2018, 9:16:26 PM (7 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SujetTP6-2018

    v1 v1  
     1{{{#!protected
     2= Programmation Arduino & communication NRF24L01
     3
     4== Objectifs de la séance
     5
     6Cette séance de TME est assez chargée puisqu'elle va avoir 4 objectifs
     71. Découverte de l'arduino et premier programme
     82. Programmation multitâches par composants
     93. Utilisation de l'écosystème Arduino pour la programmation des périphériques
     104. Communication point-à-point NRF24L01
     11
     12== Remarques ==
     13
     14Vous devez utiliser la version d'arduino qui se trouve :
     15{{{#!bash
     16> /opt/arduino-1.6.8/arduino
     17}}}
     18
     19Les documents nécessaires se trouvent :
     20- [https://github.com/adafruit/Adafruit_SSD1306 Repository API Ecran OLED]
     21- [https://github.com/adafruit/Adafruit-GFX-Library Repository API Graphique]
     22- [http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.ReferenceMaxi Langage Arduino]
     23
     24== Objectif ==
     25
     26Le but de cette séance est de faire de la programmation sur Arduino en utilisant 3 entrées-sorties :
     27- La LED présente sur le module.
     28- L'écran OLED
     29- Le port série qui relie le module et le PC.
     30
     31Nous voulons aussi expérimenter la programmation "multi-tâches". Les guillemets expriment le fait qu'il s'agit plus d'une structure du programme que d'une vraie programmation multi-tâches dans la mesure où il n'y a pas de système d'exploitation. Toutefois, les "tâches" seront programmées de sorte à être indépendantes de leur contexte d'utilisation et elles seront "ordonnancées" par la fonction loop().
     32
     33== Exécution ''multi-tâches'' ==
     34
     35Nous allons donc voir comment il est possible de programmer des applications multi-tâches coopératives dans l'environnement Arduino sans pour autant disposee des services d'un OS. Le code a été volontairement simplifié à l'extrême afin de bien comprendre le principe. ICI, toute l'application sera dans un seul fichier et nous n'allons pas utiliser la programmation objet pour ne pas complexifier (mais nous le ferons plus tard).
     36
     37Chaque tâche est représentée par une fonction qui code son comportement.
     38Dans l'environnement Arduino, la fonction loop() s'exécute en boucle, c'est elle qui va séquencer l'exécution des tâches.
     39La fonction loop() demande donc l'exécution des tâches à tour de rôle.
     40Les tâches n'ont pas le droit de conserver le processeur sinon cela crée un blocage du système.
     41La structure générale d'une tâche est la suivante :
     42
     43{{{#!c
     44void Tache(arguments) {
     45   // test de la condition d'exécution
     46   if (evement_attendu_absent) return;
     47   // code de la tache
     48   ....
     49}
     50}}}
     51
     52Pour les tâches périodiques, 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.
     53
     54`wairFor()` peut être appelée aussi souvent que nécessaire, elle rend 1 une seule fois par période (second paramètre).
     55Si elle n'est pas appelée pendant longtemps alors elle rend le nombre de périodes qui se sont écoulées.
     56
     57Dans l'application suivante nous avons deux tâches périodiques `Led()` et `Mess()`.
     58La première fait clignoter une led dont le numéro est passé en paramètre à 5Hz.
     59La seconde affiche bonjour à une fois par seconde.
     60{{{#!c
     61// --------------------------------------------------------------------------------------------------------------------
     62// Multi-tâches cooperatives : solution basique
     63// --------------------------------------------------------------------------------------------------------------------
     64
     65// --------------------------------------------------------------------------------------------------------------------
     66// unsigned int waitFor(timer, period)
     67// Timer pour taches périodiques
     68// configuration :
     69//  - MAX_WAIT_FOR_TIMER : nombre maximum de timers utilisés
     70// arguments :
     71//  - timer  : numéro de timer entre 0 et MAX_WAIT_FOR_TIMER-1
     72//  - period : période souhaitée
     73// retour :
     74//  - nombre de période écoulée depuis le dernier appel
     75// --------------------------------------------------------------------------------------------------------------------
     76#define MAX_WAIT_FOR_TIMER 2
     77unsigned int waitFor(int timer, unsigned long period){
     78  static unsigned long waitForTimer[MAX_WAIT_FOR_TIMER];
     79  unsigned long newTime = micros() / period;              // numéro de la période modulo 2^32
     80  int delta = newTime - waitForTimer[timer];              // delta entre la période courante et celle enregistrée
     81  if ( delta < 0 ) delta += 1 + (0xFFFFFFFF / period);    // en cas de dépassement du nombre de périodes possibles sur 2^32
     82  if ( delta ) waitForTimer[timer] = newTime;             // enregistrement du nouveau numéro de période
     83  return delta;
     84}
     85
     86void Led(int timer, long period, byte led) {
     87  static byte val = 0;                                    // valeur à mettre sur la led
     88  if (!waitFor(timer,period)) return;                     // sort s'il y a moins d'une période écoulée
     89  digitalWrite(led,val);                                  // ecriture
     90  val = 1 - val;                                          // changement d'état
     91}
     92
     93void Mess(int timer, long period, const char * mess) {
     94  if (!(waitFor(timer,period))) return;                   // sort s'il y a moins d'une période écoulée
     95  Serial.println(mess);                                   // affichage du message
     96}
     97
     98void setup() {
     99  pinMode(13,OUTPUT);                                     // initialisation de la direction de la broche
     100  Serial.begin(115200);                                   // initialisation du débit de la liaison série
     101}
     102
     103void loop() {
     104  Led (0,100000,13);                                      // Led est exécutée toutes les 100ms
     105  Mess(1,1000000,"bonjour");                              // Mess est exécutée toutes les secondes
     106}
     107}}}
     108
     109== Utilisation de l'écran ==
     110
     111Nous allons utiliser un écran OLED connecté en I2C, 128x32 **ssd1306**
     112- La bibliothèque de l'écran se trouve en tapant la requête `ssd1306 arduino`[[BR]] à l'adresse   
     113  [https://github.com/adafruit/Adafruit_SSD1306].
     114  Vous devrez prendre également la bibliothèque GFX à l'adresse [https://github.com/adafruit/Adafruit-GFX-Library]
     115  qui est la bibliothèque graphique.   
     116- Vous pouvez exécuter l'exemple proposé dans la bibliothèque. Cette bibliothèque fonctionne pour plusieurs
     117  types modèles. Vous allez choisir le bon exemple : 128x32 I2C.
     118
     119**Questions**
     120- Que contient le tableau `waitForTimer[]`` ?
     121- Dans quel cas la fonction `waitFor() peut rendre 2 ?
     122- Quel est le numéro de l'abonné I2C de l'écran ?
     123- Modifier le programme initial pour afficher "bonjour" sur l'Oled toutes les 2 secondes sans changer le comportement existant.
     124
     125== Communications inter-tâches ==
     126
     127Lorsqu'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.
     128
     129Supposons 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.
     130l'é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.
     131
     132Il 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.
     133{{{#!c
     134struct mailbox {
     135  enum {EMPTY, FULL} state;
     136  int val;
     137} mb0 = {.state = EMPTY};
     138
     139void T1(&mb) {
     140  if (mb->state != EMPTY) return; // attend que la mailbox soit vide
     141  mb->val = 42;
     142  mb->state = FULL;
     143}
     144
     145void T2(&mb) {
     146  if (mb->state != FULL) return; // attend que la mailbox soit pleine
     147  // usage de mb->val
     148  mb->state = EMPTY;
     149}
     150}}}
     151
     152**Questions**
     153
     154- Vous allez reprendre le programme ci-dessus qui fait clignoter la LED et afficher un message périodique sur l'écran Oled et le transformer de sorte à modifier son comportement par des commandes envoyé par l'utilisateur depuis le clavier du PC.
     155
     156- Le programme est en attente d'un commande pour contrôler l'état de la LED et le message affiché sur l'écran Oled. Les commandes pourraient être :
     157  - A : met la lED en clignotement.
     158  - E : éteint la LED.
     159  - P message : change le message affiché sur l'écran.
     160
     161  Le langage est volontairement simple pour que l'interprétation de la commande soit simple.
     162  Commencez par les commandes de la LED
     163
     164  Quand cela marche ajouter la commande de message.
     165
     166- Pour la tâche qui lit le clavier vous avez deux possibilités. Un tâche "normale" qui est ordonnancée par la fonction loop() ou une tâche dont l'exécution est déclenchée par l'arrivée d'un caractère. En effet, l'arrivée d'un caractère déclenche une interruption, il est possible de programmer une routine d'interruption ISR (Interrupt Service Routine). Faites une tâche "normale" et tenter de faire une tâche ISR.
     167
     168
     169
     170
     171
     172
     173
     174
     175
     176
     177== Récupération de la bibliothèque du NRF24L01 ==
     178
     179Si nous voulons continuer à cross compiler, il faut installer la librairie qui va permettre de contrôler le module NRF24L01. Il existe plusieurs librairies. Celle choisie à le mérite d'être disponible dans l'environnement !RaspberryPi et !Arduino. C'est-à-dire que lorsque vous aurez compris comment l'utiliser avec la !RaspberryPi, le passage sur Arduino sera facile.
     180
     181 * Aller sur le site https://github.com/tmrh20/RF24
     182 * Récupérer le .zip de la branche master (bouton `clone and download -> Download ZIP`)
     183{{{#!bash
     184$ unzip RF24-master.zip
     185$ cd RF24-master
     186$ mkdir $HOME/rf24
     187$ ./configure --prefix=$HOME/rf24 --soc=BCM2835 --c_compiler=bcm2708hardfp-gcc --cxx_compiler=bcm2708hardfp-g++ --driver=SPIDEV --ldconfig=''
     188$ make
     189$ make install
     190}}}
     191 * Vérification que la library est installée.
     192{{{#!bash
     193$ ls $HOME/rf24
     194  include  lib
     195}}}
     196
     197Cette même bibliothèque a été installée sur les cartes !RaspberryPi car la bibliothèque est dynamique et non pas statique, donc il faut la bibliothèque sur la !RaspberryPi.
     198
     199== Documents de référence du module NRF24L01 ==
     200
     201 * [http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P Site Nordic nRF24L01Plus]
     202 * [https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf Spéicification nRF24L01plus]
     203 * [https://github.com/TMRh20/RF24 Repository API TMRh20/RF24]
     204
     205== Communication entre le capteur et la base ==
     206
     207La documentation de la bibliothèque est [http://tmrh20.github.io/RF24/classRF24.html ici] dont voici un résumé :
     208
     209* `     RF24 (uint8_t _cepin, uint8_t _cspin)`[[BR]]
     210  Configuration du module radio et du SPI, reçoit les numéros de broche cepin (radio) cspin (SPI Slave Select)
     211* `bool         begin (void)`  Démarrage du module radio
     212* `void         startListening (void)`
     213* `void         stopListening (void)`
     214* `bool         available (void)`]
     215* `void         read (void *buf, uint8_t len)`
     216* `bool         write (const void *buf, uint8_t len)`
     217* `void         openWritingPipe (const uint8_t *address)`
     218* `void         openReadingPipe (uint8_t number, const uint8_t *address)`
     219
     220- **sensor** (sur l'Arduino)
     221{{{#!c
     222#include <SPI.h>
     223#include "RF24.h"
     224#include "printf.h"
     225
     226RF24 radio(9,10); // radio(CE,CS)
     227
     228byte addresses[][6] = {"0Node"};
     229
     230void setup() {
     231  Serial.begin(115200);
     232  printf_begin();
     233  radio.begin();
     234  radio.setPALevel(RF24_PA_LOW);
     235  radio.openWritingPipe(addresses[0]);
     236  radio.printDetails();
     237  delay(1000);
     238}
     239
     240
     241void loop() {
     242  Serial.println(F("Now sending !"));
     243
     244  unsigned long start_time = millis();                             // Take the time, and send it.  This will block until complete
     245  if (!radio.write( &start_time, sizeof(unsigned long) )){
     246     Serial.println(F("failed!"));
     247  }
     248 
     249  delay(1000);
     250}
     251}}}
     252
     253
     254- **baseSensor** (sur la raspberry pi)
     255{{{#!c
     256#include <cstdlib>
     257#include <iostream>
     258#include <sstream>
     259#include <string>
     260#include <unistd.h>
     261#include <RF24/RF24.h>
     262
     263typedef uint8_t byte;
     264
     265using namespace std;
     266
     267RF24 radio(15,0);
     268
     269byte addresses[][6] = {"0Node","1Node","2Node","3Node","4Node","5Node"};
     270
     271void setup() {
     272  radio.begin();
     273  radio.setRetries(15,15);
     274  radio.setPALevel(RF24_PA_LOW);
     275  radio.openReadingPipe(1,addresses[0]);
     276  radio.printDetails();
     277  radio.startListening();
     278}
     279
     280void loop() {
     281  unsigned long got_time;
     282
     283  if( radio.available()){
     284     radio.read( &got_time, sizeof(unsigned long) );             // Get the payload
     285     cout << got_time << endl;
     286   }
     287}
     288
     289int main(int argc, char** argv){
     290    setup();
     291    while (1) loop();
     292    return 0;
     293}
     294}}}
     295
     296- Makefile sur la raspberry pi
     297{{{#!make
     298RPI?=20
     299SRC=src
     300APP=NRF24L01_base
     301DST=lacas/nrf
     302
     303CROSSDIR        = /users/enseig/franck/peri
     304CROSS_COMPILE   = $(CROSSDIR)/arm-bcm2708hardfp-linux-gnueabi/bin/bcm2708hardfp-
     305
     306INC=$(HOME)/rf24/include
     307LIB=$(HOME)/rf24/lib
     308CFLAGS=-Wall -Wfatal-errors -O2 -I$(INC)
     309LDFLAGS=-L$(LIB) -lrf24
     310
     311all:  $(APP).x
     312
     313$(APP).x: $(APP).cpp
     314    $(CROSS_COMPILE)g++ -o $@ -I$(INC) $<  -O2 $(LDFLAGS)
     315
     316upload:
     317    scp -P50$(RPI) $(APP).x pi@peri:$(DST)
     318
     319clean:
     320    rm -f *.o *.x *~
     321}}}
     322
     323== Travail demandé ==
     324
     325 * Le but initial est de lire la valeur envoyée par l'Arduino et de l'afficher sur le terminal de la raspberry. Il y a donc au moins 2 noeuds, un émetteur (l'arduino) et un récepteur (la raspberry).
     326 * Ensuite, l'idée est de faire une communication entre deux raspberry pi. Il va falloir que vous lisiez la documentation du NRF pour comprendre comment numéroter les noeuds. Une raspberry allume la led de sa voisine.
     327 * Comme, il n'y a pas assez de raspberry, nous allons ajouter des arduinos.
     328 * [http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.ReferenceMaxi Langage Arduino]
     329
     330== Programmation des Arduinos ==
     331
     332Vous devez utiliser la dernière version d'arduino qui se trouve `/opt/arduino-1.6.8/arduino`.
     333Vous commencerez par compiler le programme `blink` qui fait clignoter la led 13 présente sur l'arduino.
     334Pour pouvoir compiler un programme sur le NRF24, il faut ajouter la bibliothèque.
     335
     336**L'usage des bibliothèques Arduino**
     337
     338* Ce qu'il y a de bien dans l'écosystème Arduino, c'est la volonté de faire simple.
     339  En effet, pour presque tous les "périphériques" existants il existe un et même
     340  souvent plusieurs bibliothèques de fonctions écrites par des "amateurs" souvent très doués.
     341  En plus, les sources sont ouvertes, et il est donc possible d'adapter ces codes pour des
     342  besoins spécifiques.
     343
     344* Les bibliothèques sont trouvées, en général, en tapant sur un moteur de recherche, la requête
     345  "nom-du-module Arduino". Les projets sont souvent sur github. Pour faire court,
     346   * Vous téléchargez la bibliothèque (un RF24-master.zip)
     347   * Vous ajoutez la bibliothèque dans l'environnement Arduino (import Library)
     348   * Vous lancer l'IDE Arduino et dans le menu '''file/exemples''' vous avez un exemple (souvent plusieurs) de la nouvelle bibliothèque.
     349   * Vous en choisissez un, vous le chargez, vous le compilez, vous l'uploadez, vous le testez :-)
     350
     351L'idée sera d'allumer la led de l'arduino depuis la raspberry pi ou l'inverse.
     352
     353}}}