Vous êtes sur la page 1sur 18

Julien Lefrique - Vincent Marotta

Commande en temps réel


Noyau multitâches temps réel PICOS18
TR57 - A2008

13 janvier 2009

Université de Technologie de Belfort-Montbéliard


Département GESC
Sommaire
Introduction 3

1 Partie embarquée 3
1.1 Présentation du matériel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Présentation de PICOS18 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Découverte de PICOS18 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 Fichiers sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 Mise en œuvre de la liaison série . . . . . . . . . . . . . . . . . . . . . . . . 5
1.6 Mise en œuvre de l’ADC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.7 Mise en œuvre du module PWM . . . . . . . . . . . . . . . . . . . . . . . . 9
1.8 Structure de l’application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.9 Communication PC/microcontrôleur . . . . . . . . . . . . . . . . . . . . . . 10

2 Partie débarquée 13
2.1 Présentation de Visual C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Traitement des données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2.1 Réception sur la liaison série . . . . . . . . . . . . . . . . . . . . . . 13
2.2.2 Affichage dans un graphique . . . . . . . . . . . . . . . . . . . . . . . 14
2.3 Marche/arrêt du moteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4 Choix du mode de consigne . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.4.1 Consigne analogique . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.4.2 Consigne numérique . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

Conclusion 18

2
Introduction
Dans le cadre des travaux pratiques de l’unité de valeur TR57 — Commande en temps
réel, nous avons choisi de travailler sur le projet impliquant l’utilisation du noyau mutli-
tâche temps réel PICOS18.
Contrairement aux autres sujets de projet proposés, celui-ci ne portait pas sur la réa-
lisation d’une application particulière, mais plutôt sur l’utilisation d’un outil, en l’occur-
rence PICOS18. L’application finale n’était donc pas une finalité, et le but premier était
de comprendre et de maı̂triser les problématiques liées au temps réel en utilisant un outil
approprié.
Évidemment, pour que le projet n’en soit que plus intéressant, nous avons choisi d’uti-
liser le microcontrôleur afin de commander un moteur à courant continu. De plus, afin de
compléter la partie développement embarqué sur microcontrôleur, nous devions réaliser
une interfaçe de supervision et de commande sur PC. Celle-ci devait être réalisée avec
Visual C++, l’environnement de développement pour Windows conçu par Microsoft.
Ce rapport présentera donc les travaux menés durant le semestre aussi bien sur la
partie microcontrôleur que sur la partie interface PC.

1 Partie embarquée
1.1 Présentation du matériel
Afin de mener à bien notre projet, comme le schématise la figure 1, nous avions à notre
disposition, dans la salle de TP, le matériel suivant :
– Une carte de développement pour micrôcontoleur intégrant un PIC18F458.
– Un module MPLAB ICD2 qui est un débogueur temps réel et un programmateur
pour les micrôcontroleurs PIC.
– Une carte moteur nous permettant de disposer des connecteurs pour les sorties PWM
et pour les entrées de l’ADC.
– Une moteur à courant continu pour réaliser les tests.
– Un PC comportant les environnements de développement MPLAB et Visual C++.

Fig. 1 – Schématisation de notre poste de travail

3
Nous allons profiter de cette partie pour rappeler une petite astuce de configuration
qui permet d’éviter d’avoir à débrancher l’ICD2 de la carte PIC pour que le programme
démarre. Il faut donc se rendre dans les paramètres de l’ICD2 via le menu correspondant
dans MPLAB et se rendre dans l’onglet Program. Ensuite, comme le montre la figure 2, il
faut cocher la case Run after sucessful program.

Fig. 2 – Démarrage automatique du programme après la programmation

1.2 Présentation de PICOS18


Le cours présentant PICOS18, nous ne détaillerons pas cette partie. Nous rappelerons
juste que PICOS181 est un noyau temps réel basé sur OSEK/VDX qui est un standard
industriel ouvert créé par des constructeurs et équipementiers automobiles. Ce noyau,
distribué sous licence GPL2 , est destiné aux composants de la famille PIC18 de chez Mi-
crochip. Il permet donc de disposer d’un système d’exploitation pour gérer les applications
complexes et offrir un niveau d’abstraction facilitant la maintenabilité du code.

1.3 Découverte de PICOS18


Nous n’avions jamais utilisé le noyau PICOS18 avant le début du projet, nous avons
donc commencer par suivre le tutoriel disponible sur le site des développeurs du noyau.
Nous avons donc mis en œuvre une première tâche visant à faire clignoter une LED. Bien
que très basique, par analogie au traditionnel programme Hello world, c’est généralement
la première chose que l’on essai de faire lorsque l’on programme un système embarqué.
En effet, cela permet de prendre en main tous les outils nécessaires au développement. De
plus, ces systèmes minimalistes ne disposant généralement pas d’écran, la LED peut être
utile lors du déboguage.
L’utilisation d’un système multitâche temps réel n’ayant pas beaucoup de sens si l’on
ne dispose que d’une unique tâche, nous avons donc créé une seconde tâche réalisant une
horloge. Afin de générer les secondes, nous avons utilisé la fonction d’alarme disponible
dans PICOS18.

1.4 Fichiers sources


Pour fonctionner correctement, en plus des fichiers sources relatifs au noyau lui même,
chaque projet doit comporter les fichiers suivant :
define.h Spécifie toutes les définitions du projet.
main.c Initialisations du microcontrôleur et démarrage du noyau.
1
http://www.picos18.com
2
http://www.gnu.org/licenses/gpl.html

4
int.c Gestion des interruptions.
taskdesc.c Description des tâches et des alarmes.
tsk taskX Fonction associée à la tâche portant le numéro X.
L’ensemble de ces fichiers, ainsi que le fichier du linker nommé 18F458.lkr devront être
ajoutés au projet créé avec MPLAB.
Attention, suite à quelques problèmes, nous avons remarqué que les initialisations rela-
tives aux interruptions doivent être effectuées de préference après le démarrage du noyau,
dans une des tâches par exemple.

1.5 Mise en œuvre de la liaison série


Un autre avantage de PICOS18 est sa modularité. En effet, le site des développeurs
met à disposition de nombreux drivers permettant de faire fonctionner les périphériques
du microcontrôleur. Un driver est donc disponible pour la liaison série. Dans un premier
temps, nous l’avons fait fonctionner et l’avons testé. Il s’est avéré que celui-ci a été pensé
pour envoyer des chaines de caractères. Or, cela ne nous aurait pas été utile pour notre
application. Ajoutons à cela un code assez long et complexe, nous avons donc préféré
repartir à zéro et ne pas utiliser ce driver.
Nous avons commencé par réaliser la fonction initUART() permettant d’initialiser la
liaison série. La vitesse de transmission est fixée à 115200 bauds, il faut donc commencer
par calculer la valeur à placer dans le registre SPBRG en utilisant l’équation 1. La valeur
trouvée dans notre cas est 20,7 que nous arrondirons à 21.

F osc
BaudRate = (1)
16 ∗ (SP BRG + 1)

Listing 1 – Initialisation de la liaison série


1 void initUART(void)
2 {
3 /* Reset registres USART */
4 TXSTA = 0;
5 RCSTA = 0;
6
7 SPBRG = 21; /* 115200 bauds [40MHZ] */
8 TXSTAbits.BRGH = 1; /* High Speed */
9 TXSTAbits.SYNC = 0; /* Mode Asynchrone */
10 RCSTAbits.SPEN = 1; /* Activer port serie */
11 TXSTAbits.TXEN = 1; /* Activer transmission */
12 RCSTAbits.CREN = 1; /* Reception continue */
13 TRISCbits.TRISC7 = 1; /* RX en entree */
14 TRISCbits.TRISC6 = 0; /* TX en sortie */
15
16 PIR1bits.TXIF = 0; /* RAZ flag transmission */
17 PIR1bits.RCIF = 0; /* RAZ flag reception */
18 PIE1bits.RCIE = 1; /* Activer interruption de reception */
19 PIE1bits.TXIE = 0; /* Desactiver interruption de transmission */
20
21 INTCONbits.GIE=1; /* Activation globale des interruptions */
22 }

5
On peut maintenant tester que notre liaison série fonctionne en envoyant un caractère
avec la commande TXREG = data;. Avant d’envoyer un second caractère, il faudra s’assurer
que le premier a été transmis et bloquer l’exécution de la tâche avec la boucle while(
TXSTAbits.TRMT==0); dans le cas échéant. On aura au préalable connecté la carte PIC au
PC et lancé un terminal pour port série sur le PC.
Afin de tester la réception, nous avons placé le code du listing 2 dans la routine
InterruptVectorL du fichier int.c. Les données reçues sont placées dans un buffer circu-
laire et un évenement sera posté pour qu’elles soient traitées dans une autre tâche. Notons
au passage que le flag RCIF du registre PIR1 est remis à zéro automatiquement lors de la
lecture du caractère reçu.

Listing 2 – Réception par la liaison série


1 if (PIR1bits.RCIF && PIE1bits.RCIE) /* Test si caractère reçu */
2 {
3 if (RX_byteNumber<DATA_RX_SIZE)
4 {
5 dataRX[WR1++] = RCREG;
6 if (WR1>=DATA_RX_SIZE) WR1=0;
7 RX_byteNumber++;
8 SetEvent(TASK3_ID, EVENT_RS_RX); /* Poster un évenement */
9 }
10 }

La liaison série étant fonctionnelle, cela va nous permettre de dialoguer et d’échanger


des données avec l’exterieur3 et nous pourrons ainsi tester plus facilement les fonctions que
nous allons réaliser par la suite.

1.6 Mise en œuvre de l’ADC


Dans cette partie, nous allons tenter de faire fonctionner le convertisseur analogique/-
numérique. Afin de mettre en œuvre l’échantillonnage à fréquence fixe des valeurs, nous
avons utilisé le Timer 3 pour déclencher des conversions. La fonction initEch() du listing
3 va nous permettre de configurer un échantillonnage à 100 Hz. L’équation 2 montre le
calcul pour obtenir la valeur de CCPR1. A noter que le prescaler k est égal à 2.

F osc
4×k
CCP R1 = −1 (2)
F ech

Listing 3 – Configuration de l’échantillonnage


1 void initEch(void)
2 {
3 /* RAZ Timer 3 */
4 TMR3L=0;
5 TMR3H=0;
6
7 T3CON=0x59; /* Activer timer, k=2, horloge interne */
8 CCPR1H=49999>>8; /* Fech = 100 Hz */
9 CCPR1L=49999;
3
Dans ce cas, avec notre PC

6
10 CCP1CON=0x0B; /* Mode comparaison, flag CCPIF mis à 1 à chaque période*/
11 }

Comme le montre le listing 4, il faut maintenant configurer les conversions sur la voie
désirée. Ici, nous voulons convertir la voie 1. On remarque sur la datasheet (voir figure 3)
qu’il est impossible de configurer AN1 en analogique en conservant AN0 en numérique,
nous ne pourrons donc plus faire clignoter la LED de la première tâche puisque celle-ci est
connectée sur AN0.
Listing 4 – Configuration de l’ADC
1 void initADC(void)
2 {
3 /* Généralement on règle l’horloge de conversion de l’ADC à 2 * Tosc */
4 /* or pour un bon fonctionnment en TP on doit mettre 64 * Tosc */
5 ADCON0=0x89; /* Activer ADC, Voie 1 */
6 ADCON1=0xC0; /* Résultat justifié à droite, toutes les voies analogiques */
7 }

A noter qu’il ne faut pas oublier d’activer les interruptions liées à l’ADC, comme le
montre le listing 5 si l’on veut que cela fonctionne. Nous avons remarqué qu’il est préférable
des les activer (ou les réactiver) une fois le noyau lancé pour ne pas avoir de problèmes.
Listing 5 – Activation des interruptions liées à l’ADC
1 PIR1bits.ADIF=0; /* ADCint flag à 0 */
2 IPR1bits.ADIP=0; /* ADCint priority low */
3 PIE1bits.ADIE=1; /* Autorise ADCint */
4 PIR1bits.CCP1IF=0; /* CCP1 flag à 0 */
5 IPR1bits.CCP1IP=0; /* CCP1int priority low */
6 PIE1bits.CCP1IE=1; /* Autorise CCP1int*/
7
8 INTCONbits.GIE=1; /* Activation globale des interruptions */

Fig. 3 – Configuration des ports analogiques/numériques dans le registre ADCON1

Comme le montre la valeur assignée au registre CCP1CON, le module CCP1 est confi-
guré en mode comparaison. De plus, on remarque sur la figure 4 qu’il est explicitement

7
mentioné que la conversion sera lancée automatiquement si le module ADC est activé.
Or dans la pratique, nous avons remarqué que la conversion ne se lance pas. La solution
trouvée est donc de lancer une conversion manuellement (voir listing 6) dans la routine
InterruptVectorL du fichier int.c, lorsque le flag CCPIF est mis à 1. Bien sûr, cela nous
oblige à attendre la fin de la conversion en insérant une boucle while, ce n’est donc pas
optimisé. La valeur convertie sera récuperée dans une autre tâche.

Fig. 4 – Configuration du mode de CCP1 dans le registre CCP1CON

Listing 6 – Lancement de la conversion


1 if (PIR1bits.CCP1IF && PIE1bits.CCP1IE)
2 {
3 PIR1bits.CCP1IF = 0; /* RAZ flag */
4 ADCON0 = 0x8D; /* Lance la conversion (GO/DONE=1) */
5 while(ADCON0 & 0x04); /* Attendre fin conversion */
6 SetEvent(TASK2_ID, EVENT_FINCONV); /* Poster un évenement */
7 }

Le listing 7 montre une partie de la tâche récupérant le résultat de conversion. Ce


résultat est censé être codé sur 10 bits justifiés à droite. Or, après de nombreux tests en
modifiant la configuration, le résultat retourné est toujours sur 8 bits. En fait le registre
ADRESH contenant les bits de poids forts4 est toujours nul. Il en résulte une impossibilité
de dépasser la valeur 255. L’ADC pouvant théoriquement mesurer des tensions comprises
entre 0 et 5 V sur 10 bits, son fonctionnement sera donc normal jusqu’à 1,25 V. Au delà,
le résultat sera faussé. Nous ne pourrons donc pas mettre en œuvre un asservissement
correct.
Notons au passage que cette tâche envoi ensuite le résultat, précédé par un caractère
indiquant la nature de la mesure, par la liaison série. Cela nous permettra par la suite de
tracer les mesures sur le PC superviseur.

Listing 7 – Récuperation de la valeur convertie par l’ADC


1 int u;
2 while(1)
3 {
4 WaitEvent(EVENT_FINCONV); /* Attente fin conversion */
4
Qui sont donc les plus importants

8
5 ClearEvent(EVENT_FINCONV); /* Clear évenement */
6 u = (ADRESH<<8) + ADRESL; /* Récuperation valeure convertie */
7 TXREG = ’U’;
8 while(TXSTAbits.TRMT==0);
9 TXREG = u; /* Envoi 8 bits de poids faibles */
10 while(TXSTAbits.TRMT==0);
11 // TXREG = x>>8; /* Envoi 2 bits de poids fort */
12 // while(TXSTAbits.TRMT==0);
13 }

1.7 Mise en œuvre du module PWM


Le but de l’application étant de commander un moteur à courant continu, il faut donc
mettre en œuvre le module PWM. Nous allons donc commencer par la configuration avec
la fonction initPWM() presentée listing 8. La valeur de la période de la PWM est specifiée
dans le registre PR2. Elle est determinée avec l’équation 3 et vaut 249 lorsque k = 4.

F osc
4×k
P R2 = −1 (3)
F ech

Listing 8 – Initialisation du module PWM


1 void initPWM(void)
2 {
3 TRISDbits.TRISD0=0; /* Mettre broche RD0 en sortie pour I0PB1 */
4 LATDbits.LATD0=0; /* Desactiver pont */
5
6 TRISDbits.TRISD4=1; /* Broches en entrées */
7 TRISDbits.TRISD5=1; /* et PWM désactivées */
8 PR2=249; /* Valeur période PWM */
9 ECCPR1L=125>>2; /* 8 bits poid fort du rapport cyclique de la PWM */
10 ECCP1CON=0x9C; /* PWM Mode, P1A et P1B Active High, Mode demi pont,
11 /* 2 bits poid faible du rapport cycliquede la PWM (01) */
12 ECCP1DEL=0; /* Temps mort à 0 (geré par L298) */
13 T2CON=0x05; /* Activation du comptage avec k=4 */
14 TRISDbits.TRISD4=0; /* Broches en sorties */
15 TRISDbits.TRISD5=0; /* et PWM activées */
16 }

Le rapport cylcique de la PWM étant sur 10 bits (8 bits de poid fort dans ECCPR1L
et 2 bits de poids faibles dans ECCP1CONbits.EDC1B1 et ECCP1CONbits.EDC1B0), il faudrait
procéder comme le montre le listing 9 pour modifier sa valeur.
Or dans notre application, nous modifions la valeur du rapport cyclique en fonction de
la valeur retournée par l’ADC, qui elle, est sur 8 bits dans la pratique. Le plus judicieux et
donc d’assigner notre valeur ADC sur 8 bits aux 8 bits de poids fort du rapport cyclique
avec la commande ECCPR1L = u. Ainsi, le rapport cylcique peut être modifié sur toute sa
plage avec seulement une perte de précision.

Listing 9 – Modification du rapport cyclique de la PWM


1 /* 2 bits poid faible du rapport cyclique de la PWM */
2 ECCP1CONbits.EDC1B0 = u;

9
3 ECCP1CONbits.EDC1B1 = u>>1;
4
5 ECCPR1L = u>>2; /* 8 bits poid fort du rapport cyclique de la PWM */

Enfin, il est possible d’activer le pont en utilisant la commande LATDbits.LATD0=1 et de


le désactiver en utilisant LATDbits.LATD0=0. En fait cela va modifier la valeur de l’entrée
Enable B de la carte moteur.

1.8 Structure de l’application


Notre application finale est composée de 4 tâches distinctes. En fait, seul deux tâches
sont réellement utilisées. En effet, la première tâche, qui nous servait à faire clignoter la
LED pour nous indiquer le bon fonctionnement du programme est devenue inutile puisque
la sortie qui pilote la LED a été mise en mode analogique. La seconde tâche ayant pour but
de réaliser une horloge fonctionne toujours mais n’est pas nécessaire pour le fonctionnement
de notre application.
Concernant les tâches utiles à l’application, la troisième attend l’événement indiquant
la fin de conversion pour récupérer la valeur convertie, l’envoyer au PC superviseur, et si
nécéssaire, assigner la valeur au rapport cyclique de la PWM. Enfin, la dernière traite les
données présentes dans le buffer circulaire alimenté par la liaison série.
Le tableau 1 montre les priorités que nous avons assignées aux différentes tâches. On
remarque que la tâche récupérant les données de l’ADC est la plus prioritaire. Ce choix a
été fait afin de ne pas perdre de valeurs. De plus, la conversion se faisant à une fréquence
assez faible (100 Hz), on sait que l’on ne bloquera pas l’application en assignant une priorité
forte à cette tâche.
Ensuite, la tâche traitant les données reçues par la liaison série a une priorité un peu
plus faible. En fait, les données reçues sont stockées dans le buffer circulaire par la routine
d’interruption InterruptVectorL, les données ont donc peut de chance d’être perdues. Il
faudra juste s’assurer d’avoir un buffer circulaire de la bonne taille. Dans l’absolu, malgré
le fait que sa priorité soit plus faible, elle reste assez élevée, cela peut s’expliquer par le
fait que nous ayons uniquement deux tâches utiles à l’application.
Enfin, les deux autres tâches ne servant pas à l’application, elles ont des priorités encore
plus faibles.

Nom Priorité Fonction


TASK1 8 Clignotement de la LED
TASK2 5 Horloge
TASK3 10 Récupération données converties par l’ADC
TASK4 9 Traitement des données reçues par la liaison série

Tab. 1 – Priorités des tâches

1.9 Communication PC/microcontrôleur


Le but de ce projet étant également de faire communiquer le microcontrôleur et le
PC superviseur, nous avons mis en place un « mini protocole » faisant correspondre un

10
caractère ASCII à une valeur. La valeur n’excedant jamais 1 octet, la trame fera donc 2
octets au total. Le tableau 2 montre le protocole basique mis en place.
La figure 5 montre l’organigramme de la tâche de traitement des données du buffer
circulaire.
Le traitement des données est réalisé à l’aide de structures switch/case, ce qui rend le
tout assez simple et lisible comme on peut le voir sur le listing 10.

Caractère Données Type de donnée Sens


’U’ Tension mesurée par l’ADC 1 octet µC vers PC
’o’ Marche/Arrêt du moteur ’1’ démarrer le moteur PC vers µC
’0’ arrêter le moteur PC vers µC
’r’ Mode du rapport cyclique ’g’ consigne PWM par ADC PC vers µC
’v’ consigne PWM par RS232 PC vers µC
Valeur du rapport cyclique 1 octet

Tab. 2 – Protocole de communication entre le PC superviseur et le microcontrôleur

Listing 10 – Traitement des données présentes dans le buffer circulaire


1 WaitEvent(EVENT_RS_RX); /* Attente évenement réception caractère */
2 ClearEvent(EVENT_RS_RX); /* Clear évenement */
3 if (RX_byteNumber>0)
4 {
5 /* Echo sur liaison série */
6 TXREG = dataRX[RD1];
7 while(TXSTAbits.TRMT==0);
8 TXREG = dataRX[RD1+1];
9 while(TXSTAbits.TRMT==0);
10
11 switch (dataRX[RD1++])
12 {
13 case ’o’: /* Marche arret */
14 if (dataRX[RD1]==’1’) LATDbits.LATD0 = 1; /* Activer pont */
15 else if (dataRX[RD1]==’0’) LATDbits.LATD0 = 0; /* Désactiver pont */
16 break;
17 case ’r’: /* Rapport cyclique */
18 switch (dataRX[RD1])
19 {
20 case ’g’:
21 flag_dutyCycle = 0; /* Mode consigne par ADC */
22 break;
23 case ’v’:
24 flag_dutyCycle = 1; /* Mode consigne par RS232 */
25 break;
26 default: ECCPR1L = dataRX[RD1]; /* Modification du rapport cyclique */
27 }
28 break;
29 }
30 RD1++;
31 if (RD1>=DATA_RX_SIZE) RD1=0;
32 RX_byteNumber-=2;
33 }

11
Fig. 5 – Organigramme de la tâche de traitement des données du buffer circulaire

12
2 Partie débarquée
2.1 Présentation de Visual C++
Visual C++ est un environnement de développement intégré, conçu par Microsoft, pour
réaliser des applications en langage C ou C++ à destination du système d’exploitation
Microsoft Windows. Il fait partie de la suite de logiciels Visual Studio. Notons au passage
qu’une version du logiciel est disponible gratuitement pour les étudiants par le programme
MSDN Academic Alliance 5 .
L’utilisation des Windows Forms va nous permettre de disposer d’une palette d’outils
fournie et de fonctions diverses permettant de construire graphiquement la fenêtre de
l’application Windows sans avoir à écrire le code C++ correspondant.

2.2 Traitement des données


2.2.1 Réception sur la liaison série

La première fonction implémentée dans notre application est la réception de données


provenant de la liaison série. Après ajout de l’objet SerialPort gérant la liaison série
dans notre formulaire, il est plutôt simple de réceptionner les données avec la fonction du
listing 11. Evidemment, il ne faut pas oublier d’ouvrir le port en début d’application6 en
utilisant if (!serialPort1->IsOpen)serialPort1->Open(), mais également de le fermer en
fin d’application avec serialPort1->Close().
Notons qu’ici, seules les données précédées du caractère ’U’ seront stockées dans le
tableau TabSerie de dimension 1000. Les autres données reçues seront ignorées.

Listing 11 – Réception de données provenant de la liaison série


1 private: System::Void serialPort1_DataReceived(System::Object^ sender, System::IO
::Ports::SerialDataReceivedEventArgs^ e) {
2 int nbByte = serialPort1->BytesToRead; /* Nbr d’octets à lire */
3 while (nbByte>0)
4 {
5 if (cptTabSerie<nb_ech)
6 {
7 if (serialPort1->ReadByte() == ’U’) /* Lecture id */
8 {
9 /* La donnée est une tension */
10 TabSerie[cptTabSerie++]=serialPort1->ReadByte();
11 }
12 }
13 else
14 /* Tableau plein */
15 cptTabSerie=0;
16 nbByte--;
17 }
18 pictureBox1->Invalidate(); /* Rafraichissement pictureBox */
19 }

5
http://msdn.microsoft.com/fr-fr/academic/default.aspx
6
Dans le constructeur par exemple

13
2.2.2 Affichage dans un graphique

Au début du projet, nous affichions simplement les données du buffer dans un indicateur
de texte. Nous avons donc mis en place une solution plus conviviale pour visualiser les
données : l’affichage dans un graphique. Nous avons choisi d’afficher les données à la
manière d’un oscilloscope, c’est-à-dire avec un balayage de notre fenêtre.
Pour se faire, nous avons utilisé un contrôle PictureBox destiné à afficher une image.
Cette fonction est appelée automatiquement par Windows lorsque l’évènement de tracé
du contenu de l’élément est activé (lors de l’activation en premier plan de l’application
par exemple). Comme cela a été fait dans le listing 11, on peut forcer le déclenchement de
l’événement par l’instruction pictureBox1->Invalidate().
Dans le listing 12, on considère le tableau TraceSerie à l’échelle de la fenêtre PictureBox
qui sera completé à l’aide d’une boucle for. Notons ici que le graphique est prévu pour
recevoir des données allant de 0 à 255 puisque nous avons vu précédemment qu’elles étaient
codées sur 8 bits.
Listing 12 – Affichage des données dans un graphique
1 private: System::Void pictureBox1_Paint(System::Object^ sender, System::Windows::
Forms::PaintEventArgs^ e) {
2 Graphics^ g = e->Graphics; /* pointeur sur fen^etre graphique */
3 Pen^ pen; /* variable définissant le style de tracé */
4 double y0,dy;
5 if(pictureBox1->Width <5) return; /* détecte si fen^etre est trop
petite pour ^etre affichée */
6 if(pictureBox1->Height <5) return;
7 TraceSerie = gcnew array< PointF >(nb_ech); /* PointF : paire de
coordonnées x et y */
8 dy = 255; /* ymax-ymin */
9 y0 = 0; /* ymin */
10 for(UInt32 i=0;i<nb_ech;i++)
11 {
12 /* abscisse */
13 TraceSerie[i].X = pictureBox1->Width*(double)i/(double)nb_ech;
14 /* ordonnée */
15 TraceSerie[i].Y = (pictureBox1->Height-5)-(pictureBox1->Height
-5)*(TabSerie[i]-y0)/dy;
16 }
17 pen = gcnew Pen(Color::Blue,1.0f); /* choix style de tracé */
18 g->DrawLines(pen,TraceSerie); /* trace courbe */
19 }

2.3 Marche/arrêt du moteur


Une autre fonctionnalité importante dans notre application est le démarrage et l’arrêt
du moteur en utilisant le PC superviseur. En fait, lorsque le microcontrôleur recevra la
trame adéquate, il se contentera d’activer ou de désactiver le pont en modifiant la valeur
de RD0 (voir listing 10).
Il faut, après une action de l’utilisateur, que notre application de supervision envoi la
bonne trame (voir tableau 2). Nous avons choisi d’utiliser une CheckBox que l’utilisateur
pourra cocher ou décocher selon sa volonté. La trame a envoyer sera stockée dans un

14
tableau buf_serie puis sera envoyée par l’instruction serialPort1->Write(buf_serie,0,2)
. Il ne faudra toutefois pas oublier de préciser la longueur de notre trame en octet, soit 2
ici.
Listing 13 – Envoi de la commande marche/arrêt
1 private: System::Void marche_CheckedChanged(System::Object^ sender, System::
EventArgs^ e) {
2 array<Byte>^ buf_serie = gcnew array<Byte>(2);
3 if (marche->Checked)
4 {
5 /* Marche */
6 buf_serie[0]=’o’;
7 buf_serie[1]=’1’;
8 }
9 else
10 {
11 /* Arret */
12 buf_serie[0]=’o’;
13 buf_serie[1]=’0’;
14 }
15 serialPort1->Write(buf_serie,0,2);
16 }

2.4 Choix du mode de consigne


Enfin, la dernière fonction essentielle de notre application est le choix du mode de
consigne. Deux choix se présentent :
1. Consigne analogique fixée par un générateur externe
2. Consigne numérique fixée via le PC superviseur

2.4.1 Consigne analogique

Le mode analogique permet à l’utilisateur de fixer la consigne de rapport cylique en


utilisant une tension sur la voie 1 de l’ADC. Dans ce mode, présenté figure 6, l’application
de supervision est juste utilisée pour visualiser cette tension. Le mode de consigne est
choisi à l’aide de boutons radios. Comme le montre le listing 14, l’application doit juste
envoyer la trame correspondante lorsqu’un des boutons radios est selectionné.
Listing 14 – Consigne analogique par l’ADC
1 private: System::Void cons_ana_CheckedChanged(System::Object^ sender, System::
EventArgs^ e) {
2 array<Byte>^ buf_serie = gcnew array<Byte>(2);
3 if (cons_ana->Checked)
4 {
5 /* Mode consigne envoyée par ADC */
6 buf_serie[0]=’r’;
7 buf_serie[1]=’g’;
8 serialPort1->Write(buf_serie,0,2);
9 }
10 }

15
Fig. 6 – Capture d’écran de l’application en mode analogique

2.4.2 Consigne numérique

Le mode numérique permet à l’utilisateur de fixer la consigne de rapport cyclique en


utilisant l’application de supervision. Avant de saisir la consigne, il doit déjà spécifier, via
le bouton radio adéquat, qu’il veut utiliser ce mode de consigne. Le code, montré listing
15, est assez similaire à celui de la partie précédente.

Listing 15 – Consigne numérique par la liaison série


1 private: System::Void cons_num_CheckedChanged(System::Object^ sender, System::
EventArgs^ e) {
2 array<Byte>^ buf_serie = gcnew array<Byte>(2);
3 if (cons_num->Checked)
4 {
5 /* Mode consigne envoyée par RS232 */
6 buf_serie[0]=’r’;
7 buf_serie[1]=’v’;
8 serialPort1->Write(buf_serie,0,2);
9 }
10 }

Une fois le bon mode selectionné, plusieurs possibilitées s’offrent à l’utilisateur (voir
figure 7). Il peut d’abord utiliser la glissière à disposition, ou alors incrémenter la consigne
via le bouton poussoir, ou encore sélectionner une valeur numérique dans la boı̂te de texte.
Notons que toutes les valeurs sont liées et mises à jour à chaque changement sur l’une des
commandes. Enfin, l’utilisateur doit valider son choix afin que celui-ci soit envoyé sur la

16
liaison série (voir listing 17).

Fig. 7 – Capture d’écran de l’application en mode numérique

Listing 16 – Modification de la consigne numérique


1 private: System::Void val_num_ValueChanged(System::Object^ sender, System::
EventArgs^ e) {
2 /* Valeur de la glissière changée */
3 textBox->Text=String::Format("{0}", val_num->Value);
4 }
5 private: System::Void button_compteur_Click(System::Object^ sender, System::
EventArgs^ e) {
6 /* Valeur incrémenter par le bouton poussoir */
7 if (val_num->Value<255)
8 {
9 val_num->Value++;
10 textBox->Text=String::Format("{0}", val_num->Value);
11 }
12 }
13 private: System::Void textBox_Validated(System::Object^ sender, System::EventArgs
^ e) {
14 /* Valeur de la textBox changée */
15 try {val_num->Value=Convert::ToUInt16(textBox->Text);}
16 catch (System::OverflowException^){val_num->Value=0;}
17 catch (System::FormatException^){val_num->Value=0;}
18 catch (System::ArgumentException^){}
19 textBox->Text=String::Format("{0}", val_num->Value);
20 }

17
Listing 17 – Envoi de la consigne de rapport cyclique par la liaison série
1 private: System::Void valid_num_Click(System::Object^ sender, System::EventArgs^
e) {
2 array<Byte>^ buf_serie = gcnew array<Byte>(2);
3 if (cons_num->Checked)
4 {
5 buf_serie[0]=’r’;
6 buf_serie[1]=val_num->Value;
7 serialPort1->Write(buf_serie,0,2);
8 }
9 }

Conclusion
Ce projet nous a permis d’approfondir et de « toucher du doigt » les différentes no-
tions, relatives aux systèmes temps réel et au noyau PICOS18, étudiées en cours et en TD.
Il nous a permis d’acquérir une grande experience dans le domaine des systèmes embar-
qués et plus particulièrement dans la programmation de microcontroleurs. Ce point est
important pour nous, puisque le sujet de nos projets de fin d’étude implique l’utilisation
de microcontrôleurs.
Le but premier de ce projet étant de comprendre et des maı̂triser les problématiques
liées au temps réel en utilisant un outil approprié, l’application finale est assez libre et
donc moins importante que ce que nous avons appris durant le semestre.
L’utilisation de PICOS18 ne nous a pas posé beaucoup de problèmes, en effet, le noyau
est assez documenté et bien conçu. Cependant, nous avons rencontré de nombreux pro-
blèmes lors de la programmation. Nous pensons, en accord avec notre professeur, que la
documentation technique du microcontrôleur comporte des anomalies.
Nous avons également eu la possibilité de découvrir Visual C++, l’environnement de
développement intégré de Microsoft qui est largement utilisé dans les entreprises, et dont
l’utilisation nous a beaucoup rappelé la programmation avec Visual Basic.

18

Vous aimerez peut-être aussi