0

Le multi-tâche

Aujourd’hui, un mot sur la structure général de nos programmes pour robot (coté Asservissement et intelligence, pour le robot métalique).

En général, le principe d’un logiciel pour robot est construit sur la base d’une machine à état sur le modèle :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void Main()

{
// Initialisation du système
Ecran.Init();
Capteur.Init();
// Etc.

while (true)
{
switch (etatActuel)
{
case etat.AttenteJack:
// traitement
break;
case etat.Demarrage:
// traitement
break;
// Etc.
}
}
}

Les limites de ce système est qu’il se bloque à chaque étape du traitement. De plus, on est là sur un fonctionnement très séquentiel alors que certains traitements peuvent être fait en parallèle (affichage sur un écran, récupération de l’état des capteurs, calcul des objectifs du robot…)

Notre approche est un peu différente : nous avons mis en place (déjà l’an dernier avec succès) un système multi-tâche. L’équipe Pobot a déjà fait un post à ce sujet, voilà notre approche en C#.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void Main()

{
// Initialisation du système
Ecran.Init();
Capteurs.Init();
// Etc.

while (true)
{
ComPort.Process();
Capteurs.Process();
Ecran.Process();
Objectif.Process();
// Etc.
}
}

Comme on peut le voir, chaque élément du robot (Capteur, communication, etc.) est appelé systématiquement à chaque boucle. Ainsi, chacun des traitements doit évaluer l’état du robot (capteurs, temps, position…) et entraîner ou non une nouvelle action.

Il faut minimiser au maximum le « Process » de chaque élément afin de ne pas bloquer sur une étape. Par exemple, on ne lira/enverra qu’un seul octet sur le port COM par boucle laissant ainsi la possibilité de continuer les vérifications des capteurs ou l’affichage d’information sur l’écran.

Grace à ce mécanise, tous les traitements se font en simultanés. On traite un octet reçu, puis on affiche un caractère à l’écran, puis on récupère l’état des capteurs, on regarde s’il faut choisir un nouvel objectif et on refait tout ça à nouveau. Pour finalement afficher une phrase à l’écran et récupérer un message de 10 octets.

 

Un bon exemple d’application est l’écran LCD. Nous utilisons un écran commandé en série, mais c’est également vrai pour les autres types d’écran LCD : un temps d’attente (pour le traitement coté écran) doit être observé  entre l’envoi de chaque instruction. Plutôt que de faire des Thread.Sleep(5) qui bloqueraient le programme principale, nous profitons de ce délai pour faire d’autres traitements.

Voici la fonction appelée lorsqu’on souhaite afficher un string à l’écran, on créé une Queue (first in, first out) de caractères qui seront Dequeués pour envoi :

1
2
3
4
5
6
7
static Queue buffer = new Queue();

public static void Print(string data)
{
foreach (char c in data)
buffer.Enqueue((byte)c);
}

Et le « Process » appelé dans la fonction principale :

1
2
3
4
5
6
7
8
9
10
11
static DateTime dateNextChar = DateTime.Now;

public static void Process()
{
if (buffer.Count > 0 && DateTime.Now > dateNextChar)
{
// process du char et modification de la date de prochain char
serial.SendByte((byte)buffer.Dequeue(), false);
dateNextChar = DateTime.Now.AddMilliseconds(5);
}
}

L’envoie de chaque caractère est ainsi espacé de 5 millisecondes sans que le système ne se bloque.