Développement Temps Réel
Dans la grande majorité des cas, le développement d'une application temps réel avec ADwin nécessite deux étapes.
La première étape consiste à développer les processus temps réel qui seront exécutés par le processeur temps réel du système ADwin et/ou par l'un de ses coprocesseurs TiCo. Ces développements s'effectuent à l'aide de l'environnement de développement intégré ADBASIC. (Note : ADBASIC est à la fois le nom du langage de programmation des process temps réel et le nom de l'environnement de développement). Une autre option pour développer ces process temps réel consiste à utiliser Matlab Simulink et l'outil ADsim.
La seconde étape est optionnelle et consiste à développer un programme de supervision des process temps réel fonctionnant sur la plateforme ADwin. Cette application fonctionne sur le PC hôte. Elle offre en général une interface graphique permettant de suivre le fonctionnement des process temps réel, de leur envoyer des consignes, de stocker des résultats, etc... Bref, elle implémente l'interface utilisateur.
Pour développer cette interface utilisateur, vous pouvez choisir n'importe quel langage ou logiciel. La bibliothèque logicielle ADwin couvre la très grande majorité des options possibles. De plus, que vous développiez en C/C++, en Laview, avec Kallisté, etc... Cette bibliothèque reste identique. Seule son implémentation est adaptée au langage ou au logiciel hôte. En C/C++, vous disposez d'un fichier d'entête et d'un librairie dynamique. Sous Labview vous disposez d'une classe de VIs.
Dans certains cas, lorsque l'interaction entre un utilisateur et les process temps réel est simple et limitée, cette seconde étape peut être omise. En effet, avec les systèmes ADwin sont livrés toutes une suite d'outils logiciels vous permettant d'interagir avec les process temps réel sans aucune programmation : Les outils ADtools, ADdesk, ADlog sont prévus à cet effet. D'ailleurs, même si vous souhaitez développer votre propre application de supervision, ces outils vous permettront de mettre au point et de déboguer vos process temps réel avant même de développer la partie de supervision. Leur utilisation est donc recommandée, car en cas de problèmes, ils vous permettent d'identifier la source de ces problèmes : Les process temps réel ou l'application de supervision.
Le schéma suivant présente les tâches à effectuer et les options à votre disposition :
Dans ce chapitre, nous explorons les bases du langage ADBASIC qui est la solution la plus largement utilisée pour développer les process temps réel. Connaître ADBASIC implique qu'il n'y a pas d'apprentissage pour TiCoBasic étant donné qu'il s'agit du même langage. Seule la cible d'exécution est différente.
- Notions de base ADBASIC
- Variables ADBASIC
- Structures des programmes ADBASIC
- Exemples ADBASIC
Notions de base ADBASIC
Un process ADBASIC est généralement constitué de 3 sections nommées INIT, EVENT et FINISH.
Section INIT : Cette section est exécutée une fois et une seule au démarrage du Process, et cela quelle que soit la méthode utilisée pour démarrer ce process. Cette section permet d’initialiser l’ensemble des variables et ressources utilisées par le Process. Par exemple, si le process déclare une variable nommée X, celle-ci pourra être initialisée à une valeur quelconque dans cette section. Cette section est également souvent utilisée pour initialiser des sorties analogiques, numériques, des bus numériques. Lorsque le process temps réel est dépendant d'une vitesse réglée par une horloge interne, c'est également souvent dans cette section que cette fréquence est ajustée via une variable spéciale nommée "ProcessDelay". Il existe également une section nommée LOWINIT qui peut être utilisée conjointement à la section INIT. Une section LOWINIT peut exécuter les mêmes tâches que la section INIT, mais elle le fait en mode basse priorité. Elle peut donc être interrompue par n'importe quel process de haute priorité. Elle est souvent utilisée pour des initialisations longues à effectuer.
Section EVENT : Cette section comporte toutes les instructions devant être exécutées lors du traitement d’un évènement temps réel. C’est donc le cœur du process. La source de l’évènement sera soit un timer (dont la fréquence de récurrence de génération de l’évènement est réglée via la variable nommée "ProcessDelay"), soit un déclenchement matériel via une borne « Event ». Le choix entre ces deux modes se faisant lors des réglages de paramètres du process dans l’EDI ADBASIC (Commande Options/Process). Il faut noter que la variable "ProcessDelay" peut être modifiée à tout instant. En d'autres termes, un process peut lui-même modifier sa vitesse de déclenchement.
Section FINISH : Cette section est exécutée une fois et une seule lors de l’arrêt du process et cela quelle que soit la méthode d’arrêt utilisée. Elle permet par exemple de ramener des sorties de tout type à un niveau de sécurité de la machine sous contrôle.
En dehors de ces 3 sections, un programme ADBASIC peut contenir des lignes permettant de définir des constantes, variables, inclure des librairies complémentaires, définir des routines ou des macros, etc...
Le schéma suivant présente la structure globale d'un process temps ADBASIC :
Variables ADBASIC
Le langage ADBASIC supporte des variables de types entiers, réels simples et double précision, chaînes de caractères. Elles peuvent être organisées en structures de scalaires, tableaux à 1 ou 2 dimensions, ou FIFO. Toutes les variables doivent être déclarées. Par défaut, les variables sont locales au process dans lequel elles sont déclarées et définies. Elles sont donc inaccessibles aux autres process. Ci-dessous des exemples de déclarations de variables locales :
Dim value1, value2 As Float
Dim value4[100] As Float
Dim free,used,value5 As Long
Dim Data[1003] As Long As FIFO
Afin de simplifier l'échange d'informations entre les divers process et le programme de supervision fonctionnant sur le PC hôte,
le langage ADBASIC prédéfinie tout un ensemble de variables globales connues de tous les process et de toutes les applications
fonctionnant sur le PC hôte. Le système définie les variables globales suivantes :
- 79 variables scalaires globales de type entier nommées PAR_1 à PAR_80.
- 79 variables scalaires globales de type réel nommées FPAR_1 à FPAR_80.
- 200 tableaux (pointeurs) dont le type, la forme et la taille sont définies par l'utilisateur, nommées DATA_1 à DATA_200.
Bien évidemment, il n'est pas pratique d'utiliser des noms de variables comme PAR_XX, FPAR_XX ou DATA_XX dans un programme car leur nom
n'est pas représentatif de leur utilisation ou de leur contenu. Il est donc possible de redéfinir leurs noms de manière locale au moyen
du mot clé ""#Define". A titre d'exemple, les lignes de codes suivantes définissent un tableau global nommé DATA_1 puis lui assignent
un nom local : "ACQUISITION" :
#define ACQUISITION DATA_1
Le nom "ACQUISITION" est local au process dans lequel il est définie. La même variable DATA_1 peut donc être nommée différemment dans un autre process. Dans le cas des scalaires, la même approche est utilisée. A la différence qu'il n'est pas nécessaire de les déclarer :
#define GAIN FPAR_1
#define OFFSET FPAR_2
#Define LOOP PAR_1
La même technique est utilisable pour définir des constantes :
Les variables globales sont protégées. Un process ne peut pas la lire ou l'écrire au même moment qu'un autre process. Cela permet de garantir qu'une variable est à jour lors de son accès par un process
Les variables globales sont aussi connues du logiciel de supervision fonctionnant sur le PC hôte. La bibliothèque d'interface ADwin contient des fonctions permettant leur lecture ou leur écriture. A titre d'exemple, en langage C/C++, on dispose des fonctions suivantes (liste non exhaustive) :
void Set_FPar(short Index, float Value, short DeviceNo, long *ErrorNo);
long Get_Par(short Index, short DeviceNo, long *ErrorNo);
float Get_FPar (short Index, short DeviceNo, long *ErrorNo);
void GetData_Long (short DataNo, long Data[],long Startindex, long Count, short DeviceNo , long *ErrorNo);
void GetData_Float (short DataNo, float Data[], long Startindex, long Count, short DeviceNo , long *ErrorNo);
void SetData_Long (short DataNo, long Data[], long Startindex, long Count, short DeviceNo , long *ErrorNo);
void SetData_Float (short DataNo, float Data[], long Startindex, long Count, short DeviceNo , long *ErrorNo);
A titre d'exemple, si le programme de supervision souhaite fixer le contenu de la variable PAR_1 à 10 du châssis ADwin numéroté 1, il exécutera le code suivant :
Set_Par(1, 10, 1, &err);
Cette technique d'échange des données entre la supervision et les process temps réel présente des avantages majeurs :
- La communication entre le PC hôte et les process temps réel est totalement prise en charge par la bibliothèque ADwin. Il n'est pas
nécessaire à l'utilisateur de prendre en charge les protocoles d'échanges de données.
- Les variables globales restent protégées en lecture/écriture.
Il existe des variables globales définies par le système et qui permettent de contrôler ou régler les divers process temps réel. La plus importante se nomme "ProcessDelay" et permet de régler la fréquence d'exécution d'un process
Structures des programmes ADBASIC
ADBASIC est un langage complet. Il intègre toutes les notions nécessaires à la mise en place d'algorithmes et à une structuration
du code efficace. Il met à disposition les structures de code suivantes :
- Boucles FOR... NEXT
- Choix IF... THEN... ELSE... ENDIF
- Choix SELECTCASE... CASE... ENDSELECT
Il est possible des définir des fonctions et/ou des macros. Ces fonctions et/ou macros peuvent être précompilées et importer dans le code via
la commande de préprocesseur "IMPORT". Enfin, vous pouvez créer des librairies complètes de fonctions et/ou macros via la commande
de préprocesseur "#INCLUDE". C'est d'ailleurs via cette commande "#INCLUDE" que les fonctions spécifiques dédiées aux divers types de
systèmes ADwin (Light 16, Gold II, X-A20, Pro II) sont insérées dans le code de l'utilisateur. Si un programme est dédié au système
ADwin-Pro II la première ligne de code sera :
Il est également possible d'inclure des librairies statiques développées en langage C. Cette fonctionnalité étant potentiellement importante pour des utilisateurs disposant de librairies C adaptées à leurs besoins.
Exemples ADBASIC
Nous donnons ci-après quelques exemples simples de programmes ADBASIC.
- Evaluation en ligne de points de mesure
- Echanges de données via les tableaux globaux DATA
- Optimisation de l'acquisition de données
- Génération d'une sinusoïde et de son signal carré de synchronisation
Evaluation en ligne de points de mesure :
Ce programme développé pour un système ADwin-Pro II équipé du processeur temps réel CPU T11 acquiert 1000 points de mesure. A chaque acquisition, il évalue si la nouvelle acquisition est supérieure ou inférieure aux valeurs minimales et maximales relevées lors des mesures précédentes.
'The program is searching for the maximum
'and minimum value out of 1000 samples from ADC #1 'and writes the result to PAR_1 (min) and PAR_2 (max). 'After 1000 samples a flag (= PAR_10) is set. 'PAR_1 = minimum value 'PAR_2 = maximum value #Include ADwinPro_All.Inc #Define limit 65535 #Define module 1 #Define input 1 Dim i1, iw, max, min As Long Init: i1 = 1 max = 0 min = limit Par_10 = 0 Processdelay = 0.001 * 3E8 Event: iw = P2_ADC(module, input) If (iw > max) Then max = iw If (iw < min) Then min = iw Inc i1 If (i1 > 1000) Then i1 = 1 Par_1 = min Par_2 = max max = 0 min = 65535 Par_10 = 1 Endif |
'Include file 'max. 16 bit ADC value 'module number 'input number 'reset sample counter 'initial maximum value 'initial minimum value 'init End-Flag 'cycle-time of 1ms 'get sample 'new maximum sample? 'new minimum sample? 'increment index '1000 samples done? 'reset index 'write minimum value 'write maximum value 'reset minimum value 'reset maximum value 'set End-Flag |
De ce simple exemple, nous pouvons noter 3 informations :
- ADBASIC n'impose pas la casse des lettres. Ainsi le mot clé "IF" peut également s'écrire "If" ou "if". De la même manière la variable globale "PAR_1"
peut s'écrire "Par_1", "par_1", "pAR_1", etc...
- Les variables globales PAR_1, PAR_2 sont utilisées pour stocker les valeurs minimales et maximales mesurées. La variable globale PAR_10 passe à la valeur 1
lorsque les 1000 points de mesure ont été effectués. Ces 3 variables étant globales, la supervision, ou tout autre process temps réel peut les lire et/ou les écrire.
Ainsi une supervision pourrait lire PAR_10. Lorsque celle-ci passe à 1, elle peut alors relever le contenu de PAR_1 et PAR_2 puis ramener le contenu de la variable
PAR_10 à 0. La supervision est alors synchronisée au process temps réel.
- La ligne de code suivante permet de régler la fréquence à laquelle la section EVENT est exécutée :
La variable "ProcessDelay" est exprimée en nombre de cycles d'horloge du processeur temps réel. Sur un processeur CPU T11, cette horloge est fixée à 300 MHz (3E8). Pour régler la valeur de "ProcessDelay" à la bonne valeur de délai entre chaque cycle "Event", il suffit de multiplier la valeur 300 MHZ par la durée en secondes entre chaque cycle "Event". Dans cet exemple, on multiplie 300 MHZ par 0,001 secondes, soit 1 milliseconde. La section "Event" sera donc exécutée toutes les millisecondes, soit à une fréquence de 1KHz.
Echanges de données via les tableaux globaux DATA :
Ce programme développé pour un système ADwin-Pro II équipé du processeur temps réel CPU T11 acquiert 1000 points de mesure dans un tableau global puis informe la supervision au moyen de la variable PAR_10 que les données sont disponibles.
'The program Pro_DMO3 samples ADC #1 1000 times,
'then stops and informs the PC to fetch the samples. 'Data is transferred using a standard DATA array. 'DATA_1 = series of samples 'Par_10 = End-Flag #Include ADwinPro_All.Inc Dim Data_1[1000] As Long Dim index As Long Init: Par_10 = 0 index = 0 Processdelay = 40000 Event: index = index + 1 If (index > 1000) Then Par_10 = 1 End EndIf Data_1[index] = P2_ADC(1, 1) |
'Include file 'reset array pointer 'cycle-time of 1ms (T9) 'increment array pointer '1000 samples done? 'set End-Flag 'terminate process 'acquire sample and save in array |
De ce simple exemple, nous pouvons noter 2 informations :
- Un process temps réel ADBASIC peut "s'autoterminer" via le mot clé "End".
- La supervision peut lire la variable PAR_10. Lorsque celle-ci passe à 1, cela indique que l'acquisition est terminée et qu'elle peut alors lire le tableau DATA_1
pour récupérer les données acquises.
Optimisation de l'acquisition de données :
Il existe plusieurs techniques d'acquisition de données sur les systèmes ADwin. Les techniques utilisables dépendent de plusieurs facteurs à prendre en compte :
- Quel est le système utilisé ? ADwin-light-16, ADwin-Gold II, ADwin-X-A20, ADwin-Pro II : Chacun de ces systèmes possède des spécificités. Il existe donc des fichiers
de définition à insérer dans le code via la commande de préprocesseur "#Include" implémentant les méthodes propres à chaque version.
- Dans le cas d'un système ADwin-Pro II, la carte d'acquisition utilisée est-elle multiplexée ? Synchrone, avec un convertisseur sur chaque voie ? Possède-t-elle une horloge locale ?
Est-elle équipée de mémoire locale ? Est-elle équipée du coprocesseur TicO ? etc...
Explorer toutes ces méthodes dépasse le cadre de ce petit tutoriel. Toutes ces techniques sont décrites dans le manuel ADBASIC. Cependant, il est toutefois intéressant de se pencher
sur un des mots clés principaux exécutant des acquisitions de données : "P2_ADC".
Dans les 2 exemples précédents, nous avons utilisé ce mot clé pour faire des acquisitions. Que fait-il exactement ?
Il reçoit en paramètre le numéro de la voie à mesurer. A l'exécution il effectue donc les opérations suivantes :
- Sélection de la voie sur le multiplexeur de la carte d'acquisition.
- Attente de la fin du multiplexage et du temps d'établissement de l'amplificateur analogique.
- Déclenchement de la conversion.
- Attente de la fin de conversion (EOC).
- Lecture du convertisseur ADC et stockage de la valeur mesurée dans une variable.
ADBASIC permet de détailler toutes ces opérations :
- Sélection de la voie sur le multiplexeur de la carte d'acquisition : Mot clé P2_SET_MUX
- Attente de la fin du multiplexage et du temps d'établissement de l'amplificateur analogique : Mot clé SLEEP
- Déclenchement de la conversion : Mot clé START_CONV
- Attente de la fin de conversion (EOC) : Mot clé WAIT_EOC
- Lecture du convertisseur ADC et stockage de la valeur mesurée dans une variable : Mot clé P2_READ_ADC24
Ainsi le programme suivant qui mesure la voie 1 de la carte 1 :
Init:
Processdelay = 0.001 * 3E8
Event:
Par_1 = P2_ADC(1, 1)
FPar_1 = 20.0 * (Par_1 / 262144) - 10.0 'Conversion en tension
Peut être remplacé par :
Init:
Processdelay = 0.001 * 3E8
Event:
P2_SET_MUX(1, 1)
P2_SLEEP(300)
P2_START_CONV(1)
P2_WAIT_EOC(1)
PAR_1 = P2_READ_ADC24(1)
FPAR_1 = 20.0 * (SHIFT_RIGHT(PAR_1, 6) / 262144) - 10.0
Dans cet exemple, on note 2 points intéressants :
- La valeur 300 passée à la commande "SLEEP" est exprimée en nombre de cycle d'horloge. Sur un CPU T11 dont l'horloge est à 300 MHZ chaque cycle équivaut donc à 3,33 ns.
Le délai d'attente est donc de 300 X 3,3 ns = 1 µs. Cette valeur est bien sur dépendante des caractéristiques de la carte utilisée.
- La valeur retournée par la commande "P2_READ_ADC24" est codée sur 24 bits. Cela est du au fait que les cartes ADC des systèmes ADwin-Pro II sont équipées
de convertisseurs 18 bits. Il faut donc éliminer les 6 bits de poids faible inutiles par la commande "SHIFT_RIGHT".
Les deux programmes précédents auront strictement le même comportement. Cependant, la seconde méthode recèle des avantages intéressants.
Nous pourrions modifier le programme comme suit :
Init:
Processdelay = 0.001 * 3E8
P2_SET_MUX(1, 1)
P2_SLEEP(300)
P2_START_CONV(1)
Event:
P2_WAIT_EOC(1)
PAR_1 = P2_READ_ADC24(1)
FPAR_1 = 20.0 * (SHIFT_RIGHT(PAR_1, 6) / 262144) - 10.0
P2_START_CONV(1)
Dans cet exemple, le multiplexeur et le temps d'établissement de celui-ci sont inclus dans la section "INIT". En fin de section "INIT" on déclenche une première
conversion par P2_START_CONV. Au début de la section "EVENT" on attend une fin de conversion, puis on lit et on linéarise en tension. En fin de section "EVENT" on redéclenche
une conversion qui sera attendue lors du début de la prochaine boucle pour être traitée.
L'avantage de cette technique est que la conversion se fait pendant le temps séparant deux exécutions de la section "EVENT", donc ce temps de conversion n'influe plus sur le temps
d'occupation du processeur temps réel. Le P2_WAIT_EOC est alors presque (et même souvent) immédiat.
Dans ces exemples, nous avons fixé la période de la section "EVENT" à 1 ms. Etant donné que le temps de conversion est très petit par rapport à ce délai de 1 ms, on constatera peu
de modification du temps d'occupation du processeur qui restera très faible. En revanche, si la période de la section "EVENT" est fixée à 10 µs, alors on pourra diviser le temps
d'occupation du processeur par un facteur d'au moins 5. D'ou l'intérêt de cette méthode d'acquisition point par point.
Génération d'une sinusoide et de son signal carré de synchronisation :
Les sorties analogiques d'un système ADwin-Pro II peuvent être gérées de deux manières. Soit on utilise le mot clé "P2_DAC" qui permet d'écrire une valeur sur une voie de sortie analogique. Soit, on utilise le mot clé "P2_WRITE_DAC" qui permet de charger le registre de sortie d'une sortie analogique, puis le mot clé "P2_START_DAC" pour générer la valeur chargée dans le registre de sortie. Cette deuxième méthode permet donc de synchroniser la mise à jour de plusieurs voies de sorties analogiques. Cet exemple utilise cette méthode.
#DEFINE AOUT_BOARD_ADDR 6
Dim index AS Long
Dim DATA_1[360] AS Float 'Array for sine values
Dim DATA_2[360] AS Long 'Array for sine raw values
Dim DATA_3[360] AS Long 'Array for syn raw values
Dim pi AS Float
INIT:
PROCESSDELAY = 0.001 * 3E8
pi = 3.14159
For index = 1 To 360
DATA_1[index] = Sin(((index - 1) * 2 * pi/360))* 10.0
DATA_2[index] = (10.0 - DATA_1[index]) * (65536 / -20) + 65536
IF(DATA_2[index] > 32767) THEN
DATA_3[index] = 65535
ELSE
DATA_3[index] = 32768
ENDIF
NEXT index
index = 1 'Initialize the count index
EVENT:
P2_WRITE_DAC(AOUT_BOARD_ADDR, 1, DATA_2[index]) 'load the sinewave point'
P2_WRITE_DAC(AOUT_BOARD_ADDR, 2, DATA_3[index]) 'load the synchro square wave'
P2_START_DAC(AOUT_BOARD_ADDR)
INC index 'Increase the count index
Rem From 360 degrees onward, restart at 0
If (index > 360) Then index = 1
FINISH:
P2_WRITE_DAC(AOUT_BOARD_ADDR, 1, 32767) 'Set channel 0 to 0V'
P2_WRITE_DAC(AOUT_BOARD_ADDR, 2, 32767) 'Set channel 1 to 0V'
P2_START_DAC(AOUT_BOARD_ADDR)
Dans cet exemple, la section "INIT" permet de créeer les deux tableaux contenant les points constituant la sinusoïde et son sugnal carré de synchronisation. La section FINISH est utilisée pour ramener les 2 sorties analogiques à 0V de manière à ne pas laisser un signal résiduel en fin d'application.