| |
Quelques trucs et astuces SIOC
Sommaire
Introduction et
programme SIOC
Je ne vais pas ici faire une
introduction à ce langage. Si vous ne le connaissez pas, je vous renvoie pour l'excellent et
incontournable site de Claude Kieffer, www.simucockpit.fr, et particulièrement à la page d'initiation à
SIOC. Je désire simplement
exposer les points un peu obscurs ou les solutions que j'ai trouvées à certains
problèmes.
C'est moi (Pierre, dit Kelt760) qui ai écrit notre programme SIOC : j'en ai conçu la plus grande partie, je me
suis inspiré d'exemples glanés ici ou là pour d'autres. Mais je ne suis pas un
programmeur confirmé, et je suis preneur de tous commentaires, suggestions
d'amélioration et de simplification (via la page Contacts).
Dans tous les exemples ci-dessous, je conserverai les numéros de variables,
d'entrées et de sortie de notre programme SIOC ; à vous de les adapter à votre
câblage et à vos variables déjà existantes.
Vous pouvez afficher le programme ou le
télécharger entièrement (mais n'oubliez pas qu'il
toujours en développement, pas entièrement commenté, qu'il contient certainement
des bugs, et qu'on peut sûrement faire plus simple à de nombreux endroits. Donc
utilisez-le "à vos risques et périls" !).
Voici un aspect de SIOC qui peut paraître
obscur aux débutants, même après la lecture de l'initiation de Simucockpit
(pardon Claude !).
Je veux parler du fait qu'un programme SIOC n'a pas véritablement de début ni
de fin ; il n'est pas exécuté linéairement, une ligne après l'autre, comme on
pourrait le penser.
Il s'agit d'un langage fonctionnant à base
d'événements. On définit des variables ; chaque variable peut
être
- liée à une entrée (un interrupteur, par exemple),
- liée à une sortie (une led...),
- liée à un "offset" de FSUIPC, c'est à dire un état ou une commande de FS,
- liée à à d'autres éléments, tels que IOCP,
- n'être liée à rien du tout,
- ou encore déclarée "subrutine" (c'est-à-dire sous-routine).
Tout cela est très bien décrit dans le tuto de Simucockpit.
Chaque variable peut (mais ce n'est pas obligé) contenir des commandes, comme
la modification d'une autre variable, de sa propre valeur, de calculs sur la
valeur d'une variable, ou encore l'appel d'une sous-routine.
Le point important, c'est que le contenu d'une
variable n'est exécuté que si la valeur de cette variable est modifiée.
La seule exception est la sous-routine, dont le contenu est exécutée
lorsque la variable est appelée par la commande CALL. C'est la modification
d'une variable qui constitue un événement.
Tant qu'une variable n'est pas modifiée, les commandes qu'elle contient ne
sont jamais exécutées. En revanche dès qu'un événement survient et modifie une
variable, le code de celle-ci est exécuté. Ce code peut alors modifier d'autres
variables ou appeler une ou des sous-routines, ce qui produit autant
de nouveaux événements. Et bien sûr le code de chaque variable ainsi
modifiée sera alors aussi exécuté.
Le tout est de bien structurer la cascade d'événements pour déclencher ce
qu'il faut quand il le faut.
Haut de page
Vous avez donc compris qu'un programme SIOC n'a ni début, ni fin. En réalité,
pour être tout à fait exact, il possède quand même une sorte de début : c'est ce
qu'on appelle l'initialisation, ce qui se passe quand SIOC démarre ou est
relancé.
Le cas particulier de la variable 0000
D'abord, il existe une variable particulière, la variable numérotée 0. En
effet, c'est la seule variable dont le contenu (qu'on appelle aussi ses
instructions ou son script) est exécuté au démarrage de SIOC. C'est
donc l'endroit idéal pour effectuer toute sortes d'actions de départ, sans
oublier que toute variable modifiée au cours de cette exécution verra son script
exécuté aussi. Il est donc très simple de déclencher une cascade d'actions au
démarrage de SIOC (voir la section sur l'utilisation des
sous-routines). Par exemple, voici le code actuel de notre variable 0000 :
Var 0000, name init, Value 0 // initialisation
{
&fs_bat = &sw_batt
&fs_park_brake = &sw_park_brake
CALL &init_flaps
CALL &blink // lance le timer pour cligontement voyant
CALL &affich_Leds
}
Les trois instructions CALL appellent des
sous-routines, dont nous expliquerons le principe dans la section
Utiliser une sous-routine.
Les deux premières commandes assurent que la batterie et le frein de parking
prendront l'état dans lequel se trouvent leurs inters sur le panneau ;
c'est utile, car on ne peut pas être sûr de leur état au démarrage de SIOC (on
peut avoir basculé un inter entre deux vols), mais qu'en même temps seul un
changement d'état des inters déclenche l'exécution de leurs scripts. Forcer
la batterie de FS au départ de SIOC assure donc un plus grand réalisme.
En poussant le raisonnement à fond, il faudrait faire de même pour tous les
autres inters du panneau. En réalité, nous avons fait autrement, car le
démarrage n'est pas le seul cas où il faut assurer que la cohérence entre FS et
le panneau : même avec un panneau physique, rien n'empêche de cliquer les inters
de l'écran. Voir la solution adoptée plus bas dans la section
Assurer la cohérence entre le panneau physique et FS.
Les deux premières commandes de la variable &init
sont donc finalement inutiles...
Remarque : FS envoie tous ses
offsets au lancement (ou réinitialisation) d'un vol. Donc, si SIOC est lancé
avant, les scripts de toutes les variables liées à FSUIPC seront exécutés. En
revanche, si SIOC est lancé après le vol de FS, seul le script de la variable 0
est exécuté (et ceux que cette variable déclenche).
La commande Value
Il existe une autre façon de conner une valeur initiale à toute variable, à
l'aide de la commande Value, que l'on rajoute à la fin de la déclaration de la
variable :
Var 0001, name ma_variable, value 10
Quand on lance SIOC, &ma_variable prend la
valeur 10. Il y a cependant deux choses à savoir. Au lancement de SIOC :
- si la variable est modifiée par la variable 0000, cette dernière prend
le pas sur la commande Value (qui n'a alors guère de sens). Dans ce cas, le
script de la variable est exécuté, comme indiqué ci-dessus.
- sinon, la variable prend la valeur indiquée, mais son script n'est pas
exécuté.
Haut de page
En SIOC, les variables sont numérotées. On peut choisir leurs numéros
librement, mais il est préférable, pour s'y retrouver dans un grand programme,
de choisir un schéma de numérotation, et de laisser de grands "trous" entre les
blocs de numéros pour pouvoir facilement rajouter des variables par la suite en
cas de modification du programme.
Cependant, SIOC donne aussi la possibilité de nommer les variables. Bien sûr,
cela demande aussi un peu de discipline, car le nombre de caractères est limité,
les espaces interdits. Il faut utiliser des noms cohérents et parlants, pour
qu'ils décrivent d'eux-mêmes à quoi sert la variable.
Un nom de variable SIOC peut comporter 14 caractères, sans espace, par
exemple fs_batt. Pour référencer une variable dans le reste du programme,
il faut faire précéder son nom du signe &. ConfigSioc le fait automatiquement,
mais si vous programmez dans un logiciel de texte (voir ci-dessous), c'est une
erreur de syntaxe fréquente que de l'oublier.
Le gain apporté par les noms est très important : le programme reste
lisible, même longtemps après son écriture. Sans noms, il sera presque
incompréhensible par quelqu'un d'autre que son auteur. Avec des noms, il devient
facile à explorer. Et surtout, la maintenance est grandement facilitée. C'est
pourquoi j'ai nommé (pratiquement) toutes mes variables.
Dernier avantage : ConfigSioc donne la possibilité de combiner plusieurs
programmes (en txt) en un seul ; dans ce cas, il renumérote les variables en cas
de conflit de numéros entre les parties, mais il conserve les noms.
Haut de page
ConfigSioc est très pratique quand on débute, car il interdit les erreurs de
syntaxe, et propose toutes les options dans des menus locaux déroulants.
Malheureusement, il ne permet pas de copier/coller facilement des morceaux de
programme déjà faits. Pour ma part, je préfère écrire mon programme dans un
fichier texte, puis l'importer dans ConfigSioc. Cela demande un peu de rigueur
pour éviter les erreurs de syntaxe, mais cela permet d'aller beaucoup plus vite.
Nouvelle méthode
- J'indique dans le fichier de configuration sioc.ini le nom du fichier
texte de mon programme en cours d'écriture, par exemple monsioc.txt.
- Je lance Sioc. Ce dernier compile automatiquement le fichier texte et en
fait un fichier .ssi, qu'il enregistre et exécute aussitôt (pas
d'avertissement d'écrasement du fichier précédent).
- Si une erreur survient à la compilation, Sioc le signale. Il suffit de
la corriger dans le fichier monsioc.txt, et de relancer Sioc.
- Pour faire une nouvelle version et garder l'ancienne, je modifie le nom
de monsioc.txt, par exemple en monsiocv2.txt, et je le mentionne dans
sioc.ini.
Difficile de faire plus simple !
Ancienne méthode
Voici comment je procèdais précédemment :
- Dans Sioc.ini, j'indique à SIOC le nom du fichier ssi que je vais écrire
(en cas de modification d'une version déjà fonctionnelle, je lui donne un
autre nom, pour être sûr de conserver celle-ci). Pour l'exemple, appelons-le
monsioc.ssi.
- Je lance SIOC (si le fichier monsioc.ssi n'existe pas encore, il
indiquera une erreur, pas grave).
- Je vais dans Notepad ou un autre éditeur de texte, je commence à écrire
le programme (pas trop de lignes en une seule fois, pour limiter les fautes
de frappe et de syntaxe).
- Je sauve ce fichier, sous le nom de (par exemple) monscioc.txt.
- Je l'importe dans ConfigSioc (commande File -> Import Text) ; s'il y a
des erreurs de compilation, je corrige et réenregistre monsioc.txt,
puis je l'importe à nouveau.
- Quand le fichier est importé, je le sauve sous le nom monsioc.ssi.
- J'active la fenêtre de SIOC, je clique Reload (recharger).
- Je teste, avec FS et/ou IOCPConsole. Dans IOCPConsole, ne pas oublier de
cliquer CONNECT, car il se déconnecte à chaque RELOAD de SIOC.
- Et je continue en reprenant à l'étape 3, pour compléter ou corriger le
programme jusqu'à obtenir le résultat voulu.
Attention, à chaque importation dans Configsioc,
ce dernier produit un fichier sans nom ; il faut donc l'enregistrer chaque fois
sous monsioc.ssi, en écrasant la version précédente (penser à
sauvegarder sous un autre nom chaque version intermédiaire qui fonctionne
correctement pour pouvoir facilement revenir en arrière en cas d'erreur par la
suite).
Avec un peu d'habitude, le procédé est très rapide. Si on part d'un programme
existant, il suffit de l'exporter en txt (commande File -> Export Text dans ConfigSioc).
Haut de page
Simplifier l'écriture avec
NotePad++
NotePad++ est un éditeur de texte gratuit, spécialement conçu pour faciliter
la programmation. En effet, il est capable d'appliquer différentes couleurs aux
divers éléments : variables et leurs attributs, fonctions, opérateurs, valeurs,
etc. Et comme il est entièrement configurable, il peut être adapté à SIOC.
Vous pouvez le trouver
ici. Si vous le
désirez, vous pouvez également télécharger
le fichier de configuration que j'utilise pour programmer en SIOC. Pour
l'utiliser, il suffit de le décompresser, puis de le placer dans le dossier
C:\Documents and Settings\NomUtilisateur\Application
Data\Notepad++. Si vous n'avez pas encore personnalisé de langage dans
NotePad++, vous pouvez écraser le fichier existant s'il existe. En revanche, si
vous utilisez déjà NotePad++ avec d'autres langages, il faut copier le contenu
de notre fichier dans l'existant.
Voici à quoi cela ressemble :

Cool, non ?
Haut de page
Tant que le programme SIOC du cockpit n'est pas trop long, ou qu'une seule
personne travaille dessus, on peut le conserver "en un seul morceau".
En ravanche, quand il commence à grossir, ou si on désire travailler à
plusieurs (par exemple l'un s'occupe des radios, l'autre du PA, un troisième du
trim), il devient beaucoup plus simple de scinder le programme en plusieurs
morceaux ou modules. On utilisera pour cela la très intéressante fonction
Group
de Config_sioc.exe, ou un fichier
.LST directement dans SIOC. Voici les détails
(testés avec la version 3.7 de sioc).
Ecriture des modules
On écrit les modules normalement, indépendamment les uns des autres, avec
comme simple règles :
- Ne vous préoccupez pas des numéros de variable : le compilateur les
renumérotera lors de la réunion des modules.
- Si vous voulez imposer un numéro de variable (et donc empêcher que cette
variable soit renumérotée à la compilation, utiliser le mot réservé
Static après le nom de la variable. Exemple
:
Var 003, name out_init_mc_1, Static, Link IOCARD_OUT, Output 49 // out re-initialise Master 1
- Ne pas donner le même nom à des variables
différentes dans les divers modules (instruction
name). C'est la contrainte la plus forte,
surtout si l'on travaille à plusieurs. Mais cela peut facilement être
surmonté en utilisant des conventions de nommage ou des préfixes dans les
noms.
- Vous pouvez réutiliser, modifier, appeler toute variable définit dans un
autre module, comme si elle était définie dans le module en cours.
Toutefois, cela imposera lors de la compilation, que tous les modules
contenant la définition de toutes les variables utilisées soient inclus dans
liste des fichiers à compiler. La conséquence de cela est qu'il est
difficile de tester un module seul, s'il fait appel à des variables
"externes".
- Les sous-routines peuvent être mises dans n'importe quel module, qui
devra être inclus dans liste des fichiers à compiler. On peut envisager
plusieurs manières de faire, mais il semble logique de regrouper les
sous-routines générales (c'est-à-dire appelées par plusieurs variables de
différents modules) dans un même module, ce qui simplifiera leur
maintenance.. En revanche, les sous-routines "locales" (appelées une
seulement pour une tâche unique et précise) peuvent rester dans leur module,
car la lecture en sera simplifée.
- Chaque fichier reçoit l'extension .txt.
- Il est plus simple de mettre ces modules dans le dossier de SIOC.
Réunion des modules
Pour réunir les modules et obtenir le fichier .ssi
exécutable, il y a deux méthodes. L'une utilise les commandes
Group et Run
de Config_sioc.exe, l'autre un fichier .LST
(liste) directement dans sioc.ini.
Avec Config_sioc.exe
Voici la procédure pas à pas :
- Si vous désirez travailler dans un dossier ne contenant que vos modules,
vous devez y recopier Config_sioc.exe et tous
ses fichiers (Config_sioc_spa.lng,
Config_sioc_eng.lng, Config_sioc_eng.cat, Config_sioc_spa.cat,
Config_sioc_spa.lng, SIOCGEN.HLP et SIOCGEN.GID). Sinon, mettez les
modules dans le dossier de SIOC (plus simple).
- Lancez Config_sioc.exe.
- Si les menus sont en espagnol, demandez English
dans le menu Language.
- Déroulez le menu Group et choisissez
Files. Une petite fenêtre s'ouvre, sans
aucun bouton ni contrôle.
- La première fois, si elle n'est pas vide, supprimez tout le texte
qu'elle contient.
- Tapez le nom de chaque module, avec son extension .txt, chacun sur une
nouvelle ligne.

- Fermez la fenêtre. Bien qu'il n'y ait aucune confirmation, cela
enregistre la liste des fichiers au sein de
Config_sioc.exe (comme vous pouvez le voir en rouvrant cette
fenêtre).
- Déroulez le menu Group et choisissez
cette fois Run. Si une fenêtre affiche
File not found, cela veut dire que l'un au
moins des fichiers de la liste n'a pas été trouvé (faute de frappe...).
- La compilation s'effectue. Si tout se passe bien, une fenêtre avec
indique OK en vert. Sinon, elle affiche STOP en rouge. Il vous faut alors
localiser l'erreur (ce qui n'est pas toujours facile, car le numéro de ligne
indiqué correspond au nouveau fichier qui n'est pas encore créé !). L'erreur
la plus fréquente est variable already exists,
une variable est définie deux fois. Bien sûr, dans la mesure du possible, il
vaut mieux avoir testé les modules individuellement avant, pour limiter les
erreurs.

Erreur de compilation
Compilation réussie
- Dans les deux cas (erreur ou non), la fenêtre de
Config_sioc s'affiche. Si une erreur est survenue, corrigez-la et
recommencez à l'étape 8, sans enregistrer le fichier
noname.ssi qui est forcément incomplet.
- Quand la compilation s'est bien passée, enregistrez le fichier
noname.ssi sous le nom de votre choix
(par exemple monavion.ssi)
de préférence dans
le dossier de SIOC.
- Ouvrez le fichier sioc.ini et indiquez le
nom du fichiers .ssi obtenu (ici
monavion.ssi) dans la ligne CONFIG_FILE=.
Enregistrez ensuite sioc.ini .
CONFIG_FILE=.\monavion.ssi
Voilà. Reste plus qu'à lancer Sioc.exe pour
faire fonctionner le programme.
Si vous modifiez un module, repartez à l'étape 8. Si vous en ajoutez un,
repartez à l'étape 4.
Directement dans sioc.ini
Autre possibilité, sans doute plus simple :
- Rassemblez tous les modules dans le dossier de SIOC (cette fois je crois
que c'est impératif).
- Créez un fichier de texte, par exemple avec NotePad++, et ecrivez le nom
de chacun des fichiers de modules (chacun sur une nouvelle ligne).
- Enregistrez ce fichier dans le dossier de SIOC, en lui donnant
l'extension .LST (pour liste). Par exemple,
monavion.lst.
- Ouvrez le fichier sioc.ini et indiquez le
nom du fichiers .LST que vous venez de créer
(ici monavion.lst) dans la ligne
CONFIG_FILE=. Enregistrez ensuite sioc.ini .
- Lancez Sioc.exe. Il effectue
automatiquement la compilation, créé et enregistre le fichier .ssi
correspondant (ici monavion.ssi).
A priori, c'est donc plus facile, mais vous avez moins de contrôle sur les
fichiers : à chaque modification d'un module, il suffit de relancer SIOC, mais
la version précédente de monavion.ssi est
écrasée.
Assurer la cohérence entre le panneau physique et
FS
Quand on désire programmer par exemple un interrupteur, il faut évidemment
placer l'action de cet inter dans sa propre variable. Par exemple, pour l'inter
de batterie, il faut deux variables ici &fs_bat, la
batterie (qu'il faut commander) et &sw_batt
(l'inter de commande) :
Var 0002, name fs_bat, Link FSUIPC_INOUT, Offset $3102, Length 1 // batterie FS
Var 0003, name sw_batt, Link IOCARD_SW, Input 1 // Inter_batterie
Tel quel, rien ne marche ; il faut basculer la batterie de FS selon l'état de
l'inter :
Var 0002, name fs_bat, Link FSUIPC_INOUT, Offset $3102, Length 1 // batterie FS
Var 0003, name sw_batt, Link IOCARD_SW, Input 1 // Inter_batterie
{
&fs_bat = &sw_batt
}
Là, c'est mieux : quand on bascule l'inter, sa variable change (de 0 à 1 ou
l'inverse), son code est exécuté et la batterie de FS est modifiée en
conséquence.
Mais il reste un problème : si on clique sur l'inter batterie sur l'écran de
FS, la batterie réagit, et l'inter physique n'est plus "d'accord" avec celui de
l'écran. Pour corriger ça, il suffit de remettre la commande dans la variable de
la batterie :
Var 0002, name fs_bat, Link FSUIPC_INOUT, Offset $3102, Length 1 // batterie FS
{
&fs_bat = &sw_batt
}
Var 0003, name sw_batt, Link IOCARD_SW, Input 1 // Inter_batterie
{
&fs_bat = &sw_batt
}
Maintenant, si on clique l'inter à l'écran, ou si la batterie de FS est
modifiée par une action quelconque (la réinitialisation du vol - vous ne vous
crashez jamais, vous ? -, le changement d'avion ou autre), sa variable est
modifiée, donc son code est exécuté, et elle revient toujours dans la même
position que l'inter physique.
En généralisant cette méthode à tous les inters et commandes, on assure que
FS sera toujours d'accord avec le cockpit. Bien sûr, cela augmente le nombre de
lignes du programme, comme ici pour le commutateur rotatif d'une magnéto :
Var 0501, name fs_Magneto1, Link FSUIPC_INOUT, Offset $0892, Length 2 // Magentos moteur 1 forcées selon panneau
{
IF &mot1_off = 1
{
&fs_Magneto1 = 0
}
ELSE
{
IF &mot1_R = 1
{
&fs_Magneto1 = 1
}
ELSE
{
IF &mot1_L = 1
{
&fs_Magneto1 = 2
}
ELSE
{
IF &mot1_B = 1
{
&fs_Magneto1 = 3
}
ELSE
{
IF &mot1_S = 1
{
&fs_Magneto1 = 4
}
}
}
}
}
}
Var 0502, name mot1_off, Link IOCARD_SW, Input 10 // Inter magnétos moteur 1
{
IF &mot1_off = 1
{
&fs_Magneto1 = 0
}
}
Var 0503, name mot1_R, Link IOCARD_SW, Input 11
{
IF &mot1_R = 1
{
&fs_Magneto1 = 1
}
}
Var 0504, name mot1_L, Link IOCARD_SW, Input 13
{
IF &mot1_L = 1
{
&fs_Magneto1 = 2
}
}
Var 0505, name mot1_B, Link IOCARD_SW, Input 12
{
IF &mot1_B = 1
{
&fs_Magneto1 = 3
}
}
Var 0506, name mot1_S, Link IOCARD_SW, Input 17
{
IF &mot1_S = 1
{
&fs_Magneto1 = 4
}
}
Mais quel plaisir de ne plus avoir à se soucier de rien : le panneau
commandera toujours FS.
A ce propos, Pierre01 a trouvé comment modifier un contacteur 12 positions
avec un ressort pour simuler la position Start de la clef.
Tout ce qui vient d'être dit fonctionne parfaitement, à la condition que
l'inter soit associé à un offset de FS. Mais il peut arriver que ce ne soit pas
le cas : par exemple, on voudra simuler une fonction qu n'existe pas dans FS,
comme l'extinction d'une radio, ou une prise de park, qui assurera du courant au
sol aussi longtemps qu'on voudra sans vider la batterie.
Ces fonctions sont faciles à prgrammer en SIOC, mais on recontre un gros
"hic". Il existe à l'heure où nous écrivons un bug dans la MasterCard
d'Opencockpits, qui l'empêche de lire correctement l'état de certaines entrées
au démarrage (ou redémarrage) de SIOC.
Cela a pour conséquence que le programme n'initialise pas correctement ces
entrées, car elles ne subissent aucun changement d'état au démarrage de SIOC.
Dès qu'on a manipulé UNE fois UNE DES ENTREES du même groupe de la Master, tout
revient dans l'ordre. L'ennui est qu'il ne suffit pas de les manipuler dans le
programme SIOC (ce qui pourrait se faire dans la variable 000), il faut
physiquement basculer une entrée.
Nous aons trouvé plusieurs solutions . La première consiste à consacrer une
entrée par groupe (soit 8 par MasterCard) à un bouton poussoir (utile par
ailleurs ou non). Un relai connecté à l'interrupteur de batterie permettrait
"d'appuyer" une fois sur chacun de ces poussoirs, par autant de circuitsd
indépendants. Cela forcerait la Master à lire toutes les entrées et donc à
initialiser correctement les inters programmés.
Cette solution est un peu lourde et perd 8 entrées par Master (mais on peut
utiliser des poussoirs utilisés par ailleurs, comme ceux de test, par exemple).
L'autre solution est constituée d'une (petite) modification sur la MasterCard
(ajout d'un fil au dos) et de deux lignes de programmation. Vous en trouverez la
description complète à la page Amélioration de
la MasterCard.

Haut de page
Eviter les IF dans les conditions
simples
Voici une astuce qui permet de simplifier l'écriture des
conditions,(instruction IF) dans le cas où il n'y a pas besoin de ELSE. Merci à
Stevelep, alias Bob, qui a donné ce truc sur le très actif forum
français des constructeurs de cockpits. Je cite :
Petit truc SIOC pour éviter les IF THEN ELSE
Var 1388, Link IOCARD_SW, Input 96 // Sélect G Aux
{
IF V1388 = 1
{
V1385 = 5
}
devient:
Var 1388, Link IOCARD_SW, Input 96 // Sélect G Aux
{
V1385 = V1388 * 5
}
Autre exemple :
Var 1389, Link IOCARD_SW, Input 97 // Sélect G Croisé
{
IF V1389 = 1
{
V1385 = 2
}
}
devient:
Var 1389, Link IOCARD_SW, Input 97 // Sélect G Croisé
{
V1385 = V1389 * 2
}
Tout simple, mais il fallait y penser : comme la variable testée vaut 0 ou 1,
on évite le IF en la multipliant par la valeur à donner à la variable résultat.
Haut de page
Entrées des
connecteurs J3 et J4 de la MasterCARD IOCARD
vues depuis l'avant et l'arrière
Section complétée et déplacée vers la page
MasterCard.
Les routines (ou sous-routines, c'est la même chose) font souvent peur aux
débutants ; elles sont pourtant essentielles dans quasiment tous les programmes,
quel que soit le langage. Et SIOC ne fait pas exception.
Pourquoi une sous-routine ? Certaines tâches doivent être exécutées dans de nombreux cas.
Plutôt que de la réécrire chaque fois, le principe de la sous-routine consiste à
ne l'écrire qu'une seule fois, puis de l'appeler aussi souvent que cela sera
nécessaire. Elle permet aussi souvent de simplifier l'écriture et de rendre le
programme plus lisible.
Mais un exemple vaut mieux qu'un long discours. Prenons une led :
Var 000&, name Ma_Led, Link IOCARD_OUT, Output 77
Supposons que nous voulions l'allumer quand une certaine variable, disons
&cond1_led est à 1, et l'éteindre sinon. Il faut
allumer ou éteindre la led dans le script de la variable
&cond1_led :
Var 0002, name cond1_Led, value 0
{
&Ma_led = &cond1_led
}
Au départ, &cond1_led vaut 0 et la led est
éteinte. Si ensuite &cond1_led passe de 0 à 1 ou
inversement, la led s'allume ou s'éteint. Jusque là, tout va bien (j'ai
volontairement évité le IF, voir la section Eviter les IF dans les conditions simples).
Mais supposons maintenant que la led dépende aussi d'une seconde condition
&cond2_led. On peut écrire de la même façon :
Var 0003, name cond2_Led, value 0
{
&Ma_led = &cond2_led
}
Si on en reste là, dès que l'une des deux variables
&cond1_led ou &cond2_led passe à 1, la led
s'allume. Et elle s'éteint dès que l'une des deux repasse à 0. Mais ce n'est pas
forcément ce que l'on désire. On peut vouloir la laisser allumée si l'une des
deux conditions est à 1 (opération OU -OR en anglais- entre les conditions), ou
au contraire ne l'allumer que si les deux conditions sont à 1(opération ET -AND
en anglais).
Sans sous_routine, on est obligé de programmer
tout cela dans les scripts des deux variables
&cond1_led et &cond1_led
puisque l'action doit avoir lieu quand n'importe laquelle des condition est
modifiée. Et sans se tromper dans le sens des conditions...
La sous-routine va tout simplifier. On va lui faire calculer l'allumage de la
led selon la valeur des 2 conditions. Supposons que nous voulions que les 2
conditions soient à 1 pour que la led s'allume. On écrira :
Var 0002, name cond1_Led, value 0
{
CALL &calc_led
}
Var 0003, name cond2_Led, value 0
{
CALL &calc_led
}
Var 0004, name calc_Led, Link subrutine
{
&Ma_led = &cond1_led * &cond2_led
}
On a modifié le script des deux variables conditions, afin qu'elles appellent
la sous-routine (ce que chacune fera si elle est modifiée, d'après le principe
de base de SIOC). On utilise pour cela la fonction CALL, qui se contente de
forcer l'exécution du code de la variable sous-routine, comme si cette dernière
avait été modifiée. Notez d'ailleurs qu'ici, on n'utilise pas la valeur de la
variable sous-routine elle même (&calc_led). Mais
rien n'empêcherait de le faire, comme avec toute autre variable, en affectation
comme en lecture. On verra dans la section Passer un
paramètre à une sous-routine qu'on peut appeler la routine en lui donnant
une valeur, ce qui est très puissant.
Dans la sous-routine, j'évite encore les IF - parce que les conditions valent
toujours 0 ou 1 par hypothèse -, mais ce n'est pas du tout obligé.
Les avantages de la sous-routine sont multiples :
- les variables de condition sont extrêmement simples, et de plus
identiques, ce qui est logique puisqu'elles sont symétriques ;
- le programme est bien lisible ;
- plus fort : on peut modifier facilement la sous-routine. Si, par
exemple, on s'est trompé dans la logique et que la led doive maintenant
s'allumer si l'une OU l'autre condition vaut 1, il suffit de modifier la
sous-routine :
Var 0004, name calc_Led, Link subrutine
{
&Ma_led = &cond1_led + &cond2_led // la led s'allume pour une valeur supérieure à 0, donc si l'une des 2 condition au moins vaut 1
}
- encore plus fort : si une troisième condition venait à s'ajouter, il
suffirait de modifier &calc_led pour tenir
compte de cette troisième condition et d'écrire cette variable condition :
Var 0005, name cond3_Led, value 0
{
CALL &calc_led
}
Vous voyez, il n'y a rien de plus simple !
Mais bien sûr, rien n'empêche une sous-routine d'en appeler une autre, et
ainsi de suite... C'est ce qui donne toute sa puissance à SIOC, mais ça peut se
compliquer rapidement. C'est pourquoi il est utile de faire un
organigramme (c'est-à-dire un dessin de la logique) du programme, ou du
moins de la fonction que l'on désire implémenter, avant de commencer à l'écrire.
Notre programme SIOC comporte de nombreuses
sous-routines (mais n'oubliez pas qu'il n'est pas finalisé).
Haut de page
Faire clignoter un voyant (une led) est un problème courant. Voici une façon
de faire ; elle est inspirée de l'excellent site (en anglais)de Nico Kaan (http://www.lekseecon.nl/).
Var 0002, name Ma_Led, Link IOCARD_OUT, Output 77 // Led à faire clignoter
Var 0000, value 0 // initialisation ; variable à modifier pour lancer le clignotement
{
&Ma_Led = 0
&Clignot = 10 // valeur initiale
&Clignot = TIMER 0, -1, 50
// 0 = valeur finale, -1 = incrément (ou decrément), 50 = intervalle ( * 10 msec)
// On compte donc ici de 10 à 0 par pas de 1, toutes les 0.5 secondes
// On pourrait compter des valeurs croissantes avec un incrément positif.
}
Var 0001, name Clignot
{
L0 = MOD &Clignot, 2
&Ma_Led = L0 // allume ou éteint la led
}
Explication : la variable 0000 sert à lancer le
clignotement (en lui assignant une valeur ; on peut aussi en faire une
sous-routine, voir ci-dessous). Elle éteint la led, initialise la variable
&Clignot au nombre de clignotements voulus (ici
10), lance le timer, qui va décompter (incrément = -1) jusqu'à 0, toutes
les 0,5 secondes.
Cela modifie chaque fois la valeur de &Clignot ;
son code est donc exécuté toutes les 0,5 secondes : la fonction MOD renvoie le
reste de la division par l'argument. Si l'argument est 2, comme ici, le reste
sera alternativement 0 ou 1.
Il reste à affecter cette valeur 0 ou 1 à la led, selon ce reste.
Je modifié le code ci-dessus pour faire clignoter le voyant Gear up
(alarme du train) quand il le faut. Ici le problème est que son déclenchement
dépend de plusieurs conditions (train rentré, volets full, au moins un moteur
gaz réduits), c'est pourquoi je l'ai implémenté dans une sous-routine.
Au départ, j'ai appelé cette sous-routine dans chacune des variables
représentant ces conditions ; mais comme chacune modification de ces variables
relançait le timer, j'obtenais un clignotement irrégulier. De plus je ne savais
pas pendant combien de temps faire clignoter le voyant.
J'ai résolu le problème en appelant la sous-routine dans la variable
000 d'initialisation de SIOC et en mettant la
valeur finale à 9999. De cette façon, le timer est lancé ou relancé en même
temps que SIOC, et ne s'arrête que s'il atteignait la valeur 9999, soit au bout
d'1h25 environ (généralement, je suis posé avant !). Pas d'inquiétude, cela n'a pas
d'influence sur les FPS !
La routine &calc_voy_gear (non montrée ici), est
la résultante des conditions (voir le programme) ;
je m'en sers dans la variable blinking pour faire
clignoter le voyant quand il le faut. Et cette fois, comme le timer est
constant, plus de problème d'irrégularité. Si le vole peut dépasser 1h25, on
peut relancer le timer dans une routine quelconque, en s'assurant toutefois de
ne faire que si le voyant n'est pas en marche, pour éviter toute irrégularité
dans son clignotement.
Notez pour finir que je n'allume pas la led directement, mais via une
variable intermédiaire &temp_led_gear ; celle-ci
commandera la led au travers de la routine &Affich_Leds
(non montrée ici), qui prend en compte les conditions de courant et d'appui du
bouton Test.
Var 0000, name init, Value 0 // initialisation
{
CALL &blink // lance le timer pour clignotement voyant
}
//
// clignotement voyant Gear up
//
Var 0072, name blink, Link SUBRUTINE // timer
{
&blink_count = 0
&blink_count = TIMER 9999 ,1 ,50
// La valeur finale maximale du timer est 9999, ce qui donne environ 1h 25 de clignotement.
// Rien n'empêche de relancer le timer s'il arrive à expiration trop tôt.
}
Var 0073, name blink_count, Value 0 // met blinking à 1 une fois sur deux, génè
{
&blinking = MOD &blink_count ,2
}
Var 0074, name blinking
{
IF &calc_voy_gear = 1
{
&temp_led_gear = &blinking
}
ELSE
{
&temp_led_gear = 0
}
}
On peut écrire toute la dernière variable comme ceci (en utilisant l'astuce
citée dans la section Eviter les IF dans les conditions simples) :
Var 0074, name blinking
{
&temp_led_gear = &calc_voy_gear * &blinking
}
C'est plus court, plus élégant, mais un peu moins lisible.
Haut de page
Passer un paramètre à une
sous-routine
Pour compléter ce qui a été dit plus haut sur les sous-routine, il faut
savoir qu'on peut passer un paramètre à une sous-routine avec la commande CALL :
la valeur indiquée après le CALL sera donnée à la variable sous-routine
elle-même lors de l'appel. Cela veut dire que si on écrit (pour reprendre
l'exemple ci-dessus) :
Var 0000, name init, Value 0 // initialisation
{
CALL &blink 1000
}
la variable &blink (qui doit être
Link SUBRUTINE), prendra la valeur
1000 avant d'être exécutée.
On peut ensuite utiliser cette valeur à l'intérieur même de la routine, ici
par exemple pour déterminer la valeur finale du timer, donc le temps de
clignotement :
Var 0072, name blink, Link SUBRUTINE // timer
{
&blink_count = TIMER &blink ,1 ,50
}
Cela veut dire que l'on peut utiliser la même routine de clignotement pour
des temps différents : il suffit de lui passer la valeur correspondante. Ce
principe peut évidemment être généralisé à toutes les sous-routines, qui
deviennent ainsi plus générales.
Haut de page
Asservir les voyants
à la présence de courant et au bouton Test
Quoi de moins réaliste que des afficheurs ou des voyants (leds) qui
s'allument dès la mise sous tension du cockpit, sans tenir compte de la présence
de courant dans l'avion ?
En fait, il est assez facile d'asservir tous ces éléments à la présence de
courant. L'astuce consiste à ne pas commander la led directement, mais au
travers d'une variable intermédiaire, que j'ai préféré appeler "temporaire" (le
préfixe temp ne risque pas de faire
confusion avec les inter... rupteurs).
Donc, pour chaque élément (led, afficheur), on définit
deux variables.
- L'une est directement liée à l'élément, par exemple pour la led volets
bas :
Var 0404, name led_flaps_down, Link IOCARD_OUT, Output 16 // Led volets bas
La première déclenche réellement l'allumage de la led ; la seconde figure
dans le calcul qui détermine si elle doit être allumée ou non. Ici, cela donne
(j'ai rajouté les deux autres leds, transit (&temp_flap_t)
et volets APR (&temp_flap_APR):
Var 0430, name calc_led_flaps, Link SUBRUTINE // gère les leds des volets
{
IF &fs_pos_flaps = 0
{
&temp_flap_APR = 0
&temp_flap_B = 0
&temp_flap_T = 0
}
ELSE
{
IF &fs_pos_flaps = 16383
{
&temp_flap_APR = 0
&temp_flap_B = 1
&temp_flap_T = 0
}
ELSE
{
IF &fs_pos_flaps = 8191
{
&temp_flap_APR = 1
&temp_flap_B = 0
&temp_flap_T = 0
}
ELSE
{
&temp_flap_APR = 0
&temp_flap_B = 0
&temp_flap_T = 1
}
}
}
}
Selon la position des volets dans FS (&fs_pos_flap),
on allume la led bas, APR ou transit (si les volets ne sont ni à 0, ni APR, ni
Bas, c'est qu'ils sont en mouvement, et on allume Transit). Notez que ce calcul
est fait dans une sous-routine, ce qui permet de l'appeler non seulement quand
l'inter de volets est actionné, mais aussi quand la position des volets change
dans FS (clic à l'écran), ou encore à l'initialisation (comme les volets sont
commandés par un inter 3 positions,la position APR donne 0 sur les 2 entrées).
Ensuite, on une autre routine va se charger d'allumer effectivement les leds,
selon la présence de courant (variable &courant
à 1),
Var 0340, name affich_Leds, Link SUBRUTINE // allume ou éteint les leds selon courant
{
IF &courant = 0 // pas de courant
{
&led_flaps_APH = 0
&led_flaps_down = 0
&led_trans_flap = 0
}
ELSE
{
&led_flaps_APH = &temp_flap_APR
&led_flaps_down = &temp_flap_B
&led_trans_flap = &temp_flap_T
}
}
Il est également facile de rajouter l'action du bouton poussoir AnnunTest,
qui doit tout allumer, s'il y a du courant, pendant qu'on le presse :
Var 0340, name affich_Leds, Link SUBRUTINE // allume ou éteint les leds selon courant
{
IF &courant = 0 // pas de courant
{
&led_flaps_APH = 0
&led_flaps_down = 0
&led_trans_flap = 0
}
ELSE
{
&led_flaps_APH = &temp_flap_APR
&led_flaps_down = &temp_flap_B
&led_trans_flap = &temp_flap_T
IF &sw_test = 1 // bouton Test pressé, ne fonctionne que si courant et à 1
{
&led_flaps_APH = 1 // tous les voyants sont allumés
&led_flaps_down = 1
&led_trans_flap = 1
}
ELSE // Bouton Test relâché, on remet les voyants à leur état calculé avant le test
{
&led_flaps_APH = &temp_flap_APR
&led_flaps_down = &temp_flap_B
&led_trans_flap = &temp_flap_T
}
}
}
Quand on relâche ce bouton, les leds sont remises aux valeurs temporaires,
c'est-à-dire à ce qu'elles étaient avant l'appui du bouton.
Si vous regardez le programme entier, vous verrez que ce principe est étendu
à toutes les leds. Les afficheurs des radios et transpondeurs ont des conditions
supplémentaires : inter Avionics sur ON pour les deux, bouton ON encore en plus
pour le transpondeur. Je n'ai pas implémenté de bouton ON OFF pour la radio,
mais il serait très simple de le faire.
Haut de page
Utiliser les afficheurs
7-segments avec SIOC
Pour allumer des chiffres sur un afficheur avec SIOC, il faut définir des
variables comme ceci :
Var 2103, name aff_c1ac_1, Link IOCARD_DISPLAY, Digit 9, Numbers 1
Var 2104, name aff_c1ac, Link IOCARD_DISPLAY, Digit 5, Numbers 4
Le digit indique le numéro de l'afficheur (sachant que le premier est
numéroté 0), et Numbers indique le nombre de chiffres du nombre à afficher.
En plus des chiffres, on peut utiliser les valeurs suivantes :
- Valeur - 999999 = éteint
l'afficheur
- Valeur - 999998 = affiche le
signe "-"
- Valeur - 999997 = affiche "6"
- Valeur - 999996 = affiche "t"
- Valeur - 999995 = affiche "d"
- Valeur - 999994 = affiche "_"
Points importants (merci à Bob, voir
ce sujet sur le forum
Air-cockpit)
"Quand on envoie un nombre à un afficheur, il est
très important que le nombre de chiffres
de la variable corresponde au nombre de chiffres de l'afficheur (on peut
utiliser la fonction LIMIT pour s'en assurer).
Exemple : de 0-999 pour un afficheur à 3 chiffres.
Si, à cause d'une faute dans le code, on envoie un nombre à 5 chiffres sur un
afficheur à 3 chiffres, on va provoquer des bugs, apparemment parce qu'on va
décaler une base de registre dans l'exécuteur de SIOC et perturber toutes les
autres variables, ce qui a pour cause d'allumer ou d'éteindre des LED de façon
aléatoire.
Ce n'est pas vraiment un bug de SIOC mais une absence de sécurité sur les
variable display si on dépasse leur longueur.
On peut aussi provoquer cet effet en envoyant une variable négative (la VS
p.ex.) si on oublie de déclarer son Type=1 qui la force à prendre un valeur
négative. La variable va alors prendre une valeur dans les 65000 et donc
dépassera le nombre de chiffres de l'afficheur".
Haut de page |