40 | | == Exécution ''multi-tâches'' == |
41 | | |
42 | | Avant de commencer à utiliser les modules nRF24L01+ nous allons voir comment il est possible de programmer des applications multi-tâches coopératives dans l'environnement Arduino sans pour autant alors les services d'un OS. Le code a été volontairement simplifié à l'extrème afin de bien comprendre le principe. |
43 | | |
44 | | Commençeons par exprimer les besoins. Dans une applications pour micro-contrôleur, il est nécessaire : |
45 | | - d'exécuter des tâches périodiquement ; |
46 | | - d'exécuter des tâches lorsque des événements surviennent ; |
47 | | - de mesurer le temps séparant deux événements ; |
48 | | - de synchroniser l'exécution de deux tâches (une tâche T1 s'exécute et produit des données qui sont récupérées par une tache T2) ; |
49 | | - etc. |
50 | | |
51 | | Chaque tâche est représentée par une fonction qui code son comportement. |
52 | | Dans l'environnement Arduino, la fonction loop() s'exécute en boucle, c'est elle qui va séquencer l'exécution des tâches. |
53 | | La fonction loop() demande donc l'exécution des tâches à tour de rôle. |
54 | | Les tâches n'ont pas le droit de conserver le processeur sinon celà crée un blocage du système. |
55 | | La structure générale d'une tâche est la suivante : |
56 | | |
57 | | {{{#!c |
58 | | void tache(arguments) { |
59 | | // test de la condition d'exécution |
60 | | if (evement_attendu_absent) return; |
61 | | // code de la tache |
62 | | .... |
63 | | } |
64 | | }}} |
65 | | |
66 | | Pour 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érioodiques). Le second est une période en microsecondes. |
67 | | |
68 | | `wairFor()` peut être appelée aussi souvent que nécessaire, elle rend 1 une seule fois par période (second paramètre). |
69 | | Si elle n'est pas appelée pendant longtemps alors elle rend le nombre de périodes qui se sont écoulées. |
70 | | |
71 | | Dans l'application suivante nous avons deux tâches périodiques `Led()` et `Mess()`. |
72 | | La première fait clignoter une led dont le numéro est passé en paramètre à 5Hz. |
73 | | La seconde affiche bonjour à une fois par seconde. |
74 | | {{{#!c |
75 | | // unsigned int waitFor(timer, period) |
76 | | // Timer pour taches périodique |
77 | | // arguments : |
78 | | // - timer : numéro de timer entre 0 et MAX_WAIT_FOR_TIMER-1 |
79 | | // - period : période souhaitée |
80 | | // retour : |
81 | | // - nombre de période écoulée depuis le dernier appel |
82 | | // |
83 | | #define MAX_WAIT_FOR_TIMER 16 |
84 | | unsigned int waitFor(int timer, unsigned long period){ |
85 | | static unsigned long waitForTimer[MAX_WAIT_FOR_TIMER]; |
86 | | unsigned long newTime = micros() / period; // numéro de la période modulo 2^32 |
87 | | int delta = newTime - waitForTimer[timer]; // delta entre la période courante et celle enregistrée |
88 | | if ( delta < 0 ) delta += 1 + (0xFFFFFFFF / period); // en cas de dépassement du nombre de périodes possibles sur 2^32 |
89 | | if ( delta ) waitForTimer[timer] = newTime; // enregistrement du nouveau numéro de période |
90 | | return delta; |
91 | | } |
92 | | |
93 | | void Led(int timer, long period, byte led) { |
94 | | static byte val = 0; // valeur à mettre sur la led |
95 | | if (!waitFor(timer,period)) return; // sort s'il y a moins d'une période écoulée |
96 | | digitalWrite(13,val); // ecriture |
97 | | val = 1 - val; // changement d'état |
98 | | } |
99 | | |
100 | | void Mess(int timer, long period, const char * mess) { |
101 | | if (!(waitFor(timer,period))) return; // sort s'il y a moins d'une période écoulée |
102 | | Serial.println(mess); // affichage du message |
103 | | } |
104 | | |
105 | | void setup() { |
106 | | pinMode(13,OUTPUT); // initialisation de la direction de la broche |
107 | | Serial.begin(115200); // initialisation du débit de la liaison série |
108 | | } |
109 | | |
110 | | void loop() { |
111 | | Led (0,100000,13); // Led est exécutée toutes les 100ms |
112 | | Mess(1,1000000,"bonjour"); // Mess est exécutée toutes les secondes |
113 | | } |
114 | | }}} |
115 | | |
116 | | Dans la pratique, les tâches des applications communiquent entre elles. Nous pouvons le faire ici en passant par des variables globales dont les adresses sont passées en paramètres des tâches. Ce dernier point est important, il faut absolument que les variables globales ne soient pas directement utilisées par les fonctions de tâches mais toujours passées explicitement en paramètre ceci afin de faciliter la mise au point ET permettre d'avoir plusieurs instances de la même tâche. |
117 | | |
118 | | Dans l'exemple qui suit, nous avons ajouter une tâche !GetKbd qui lit un message au clavier et le place dans un buffer nommé mess. |
119 | | C'est la tâche Mess qui fera l'affichage. |
120 | | Les deux tâches se synchronisent en utilisant une case mémoire nommée full dont la valeur peut prendre deux états: |
121 | | - 0 = le buffer ne contient pas de message, le buffer appartient à !GetKbd qui le remplit à chaque nouveau caractère |
122 | | - 1 = le buffer contient un message, le buffer appatient à Mess qui le lit et l'affiche |
123 | | |
124 | | La case full est mise à 1 par !GetKbd et mise à 0 par Mess. |
125 | | |
126 | | Il y a une autre communication entre !GetKbd et Led que je vous laisse analyser |
127 | | |
128 | | {{{#!c |
129 | | // unsigned int waitFor(timer, period) |
130 | | // Timer pour taches périodique |
131 | | // arguments : |
132 | | // - timer : numéro de timer entre 0 et MAX_WAIT_FOR_TIMER-1 |
133 | | // - period : période souhaitée |
134 | | // retour : |
135 | | // - nombre de période écoulée depuis le dernier appel |
136 | | // |
137 | | #define MAX_WAIT_FOR_TIMER 16 |
138 | | unsigned int waitFor(int timer, unsigned long period){ |
139 | | static unsigned long waitForTimer[MAX_WAIT_FOR_TIMER]; |
140 | | unsigned long newTime = micros() / period; |
141 | | int delta = newTime - waitForTimer[timer]; |
142 | | if ( delta < 0 ) delta += 1 + (0xFFFFFFFF / period); |
143 | | if ( delta ) waitForTimer[timer] = newTime; |
144 | | return delta; |
145 | | } |
146 | | |
147 | | // Variables globales pour la communication inter-taches |
148 | | char mess[32]; |
149 | | byte full; |
150 | | byte onled = 1; |
151 | | |
152 | | void Led (int timer, long period, byte led, byte *on) { |
153 | | static int val = 0; |
154 | | if (!waitFor(timer,period)) return; // sort s'il y a moins d'une période écoulée |
155 | | if (*on == 0) { |
156 | | val = 0; |
157 | | } |
158 | | digitalWrite(13,val); |
159 | | val = 1 - val; |
160 | | } |
161 | | |
162 | | void Mess (byte *full, char * mess) { |
163 | | if (! (*full) ) return; |
164 | | *full = 0; |
165 | | Serial.println(mess); |
166 | | } |
167 | | |
168 | | void setup () { |
169 | | pinMode(13,OUTPUT); |
170 | | Serial.begin(115200); |
171 | | } |
172 | | |
173 | | void GetKbd (byte *full, char * mess, byte len, byte *on) { |
174 | | static byte index = 0; |
175 | | if (!Serial.available()) return; |
176 | | char c = Serial.read(); |
177 | | mess[index++] = c; |
178 | | if (c == '\n') { |
179 | | mess[index-1] = 0; |
180 | | index = 0; |
181 | | *full = 1; |
182 | | *on = (strcmp(mess,"on")==0) ? 1 |
183 | | : (strcmp(mess,"off")==0) ? 0 |
184 | | : *on; |
185 | | |
186 | | } else if (index == len) { |
187 | | index--; |
188 | | } |
189 | | } |
190 | | |
191 | | void loop() { |
192 | | Led (0,100000,13, &onled); // Led est exécutée toutes les 100ms |
193 | | Mess (&full, mess); // mess est un buffer d'echange avec getkbd et full une bascule de synchro |
194 | | GetKbd (&full, mess, sizeof(mess), &onled); |
195 | | } |
196 | | }}} |