Ecrire un composant de HAL peut se révéler être une tâche ennuyeuse, la plupart de cette tâche consiste à appeler des fonctions rtapi_ et hal_ et à contrôler les erreurs associées à ces fonctions. comp va écrire tout ce code pour vous, automatiquement.
Compiler un composant de HAL est également beaucoup plus simple en utilisant comp, que le composant fasse partie de l'arborescence d'emc2, ou qu'il en soit extérieur.
Par exemple, cette portion des blocks ddt fait environ 80 lignes de code. Le composant équivalent est vraiment très court quand il est écrit en utilisant le préprocesseur comp:
component ddt "Calcule la dérivée de la fonction d'entrée";
pin in float in;
pin out float out;
variable float old;
function _;
license "GPL";
;;
float tmp = in;
out = (tmp - old) / fperiod;
old = tmp;
et il peut être compilé et installé très facilement en plaçant simplement ddt.comp dans src/hal/components puis en lançant make, ou en le plaçant quelque part sur le système et en lançant comp --install ddt.comp
Pour un singleton, une seule instance est créée quand le composant est chargé.
Pour un non-singleton, le paramètre 'count' du module détermine combien d'instances seront créées.
Un fichier .comp commence par un certain nombre de déclarations, puis par un ;; seul sur sa propre ligne. Il se terminé par le code C implémentant les fonctions du module.
Déclarations d'include:
Les parenthèses indiquent un item optionnel. Une barre verticale indique une alternative. Les mots en CAPITALES indiquent une variable texte, comme ci-dessous:
Lorsqu'ils sont utilisés pour créer un identifiant de HAL, tous les caractères soulignés sont remplacés par des tirets, tous les points et les virgules de fin, sont supprimés, ainsi ce_nom_ est remplacé par ce-nom et si le nom _, alors le point final est enlevé aussi, ainsi function _ donne un nom de fonction HAL tel que component.<num> au lieu de component.<num>.
S'il est présent, le préfixe hal_ est enlevé du début d'un nom de composant pour la création des pins des paramètres et des fonctions.
Dans l'identifiant de HAL pour une pin ou un paramètre, # indique un membre de tableau, il doit être utilisé conjointement avec une déclaration [SIZE]. Les hash marks sont remplacées par des nombres de 0-barrés équivalents aux nombres de caractères #.
Quand ils sont utilisés pour créer des identifiants C, les changements de caractères suivants sont appliqués au HALNAME:
Un _ final est maintenu, de sorte que les identifiants de HAL, qui autrement seraient en conflit avec les noms ou mots clé réservés (par exemple: min), puissent être utilisés.
HALNAME | Identifiant C | Identifiant HAL |
x_y_z | x_y_z | x-y-z |
x-y.z | x_y_z | x-y.z |
x_y_z_ | x_y_z_ | x-y-z |
x.##.y | x_y(MM) | x.MM.z |
x.## | x(MM) | x.MM |
param rw bit zot=TRUE
"""L'effet de ce paramètre, également connu comme "the orb of zot",
requiert au moins deux paragraphes d'explications.
J'espère que ces paragraphes vous ont permis de comprendre "zot"
plus profondément.""";
La chaine documentation est en format groff -man. Pour plus d'informations sur ce format de markup, voyez groff_man(7). Souvenez vous que comp interpréte backslash comme Echap dans les chaines, ainsi par exemple pour passer le mot example en font italique, ecrivez \\fIexample\\fB.
Ne crée pas le paramètre numéro de module et crée toujours une seule instance. Avec singleton, les items sont nommés composant-name.item-name et sans singleton, les items des différentes instances sont nommés composant-name.<num>.item-name.
Normalement, le paramètre count par défaut est 0. Si spécifié, count remplace la valeur par défaut.
Normalement, le numéro des instances à créer est specifié dans le paramètre count du module, si count_function est spécifié, la valeur retournée par la fonction int get_count(void) est utilisée à la place de la valeur par défaut et le paramètre count du module n'est pas défini.
Normalement, les fonctions rtapi_app_main et rtapi_app_exit sont définies automatiquement. Avec option rtapi_app no, elles ne le seront pas et doivent être fournies dans le code C.
Quand vous implémentez votre propre rtapi_app_main, appellez la fonction int export(char *prefix, long extra_arg) pour enregistrer les pins, paramètres et fonctions pour préfixer.
If specified, each instance of the component will have an associated data block of type (which can be a simple type like float or the name of a type created with typedef).
In new components, variable should be used instead.
Si spécifié, appelle la fonction définie par EXTRA_SETUP pour chaque instance. Si la définition automatique rtapi_app_main est utilisée, extra_arg est le numéro de cette instance.
Si spécifié, appelle la fonction définie par EXTRA_CLEANUP depuis la fonction définie automatiquement rtapi_app_exit, ou une erreur est détectée dans la fonction automatiquement définie rtapi_app_main.
Si spécifié, ce fichier décrit un composant d'espace utilisateur, plutôt que le réel. Un composant d'espace utilisateur peut ne pas avoir de fonction définie par la directive de fonction. Au lieu de cela, après que toutes les instances soient construites, la fonction C user_mainloop() est appelée. Dès la fin de cette fonction, le composant se termine. En règle générale, user_mainloop() va utiliser FOR_ALL_INSTS() pour effectuer la mise à jour pour chaque action, puis attendre un court instant. Une autre action commune dans user_mainloop() peut être d'appeler le gestionnaire de boucles d'événements d'une interface graphique.
Si spécifiée, la fonction userinit(argc,argv) est appelée avant rtapi_app_main() (et cela avant l'appel de hal_init()). Cette fonction peut traiter les arguments de la ligne de commande ou exécuter d'autres actions. Son type de retour est void; elle peut appeler exit() et si elle le veut, se terminer sans créer de composant HAL (par exemple, parce que les arguments de la ligne de commande sont invalides).
Si aucune option VALUE n'est spécifiée, alors c'est équivalent à spécifier la valeur… yes. Le résultat consécutif à l'assignation d'une valeur inappropriée à une option est indéterminé. Le résultat consécutif à n'utiliser aucune autre option est indéfini.
Déclare la variable par-instance NAME de type CTYPE, optionnellement comme un tableau de SIZE items et optionnellement avec une valeur default. Les items sans default sont initialisés all-bits-zero. CTYPE est un simple mot de type C, comme float, u32, s32, etc.
Les variables d'un tableau sont mises entre crochets.
Les commentaires de style C++ une ligne (// …) et de style C multi-lignes (/* … */) sont supportés tous les deux dans la section déclaration.
Bien que HAL permette à une pin, un paramètre et une fonction d'avoir le même nom, comp ne le permet pas.
En se basant sur les déclarations des items de section, comp crée une structure C appellée structure d'état. Cependant, au lieu de faire référence aux membres de cette structure (par exemple: *(inst->name)), il leur sera généralement fait référence en utilisant les macros ci-dessous. Certains détails de la structure d'état et de ces macros peuvent différer d'une version de comp à la suivante.
Quand pin_name ou parameter_name sont des tableaux, la macro est de la forme pin_name(idx) ou param_name(idx) dans laquelle idx est l'index dans le tableau de pins. Quand le tableau est de taille variable, il est seulement légal de faire référence aux items par leurs condsize.
Quand un item est conditionnel, il est seulement légal de faire référence à cet item quand ses conditions sont évaluées à des valeurs différentes de zéro.
Si un composant a seulement une fonction et que la chaine FUNCTION n'apparaît nulle part après ;;, alors la portion après ;; est considérée comme étant le corps d'un composant simple fonction.
Si un composant a n'importe combien de pins ou de paramètres avec un if condition ou [maxsize : condsize], il est appelé un composant avec personnalité. La personnalité de chaque instance est spécifiée quand le module est chargé. La Personnalité peut être utilisée pour créer les pins seulement quand c'est nécessaire. Par exemple, la personnalité peut être utilisée dans un composant logique, pour donner un nombre variable de broches d'entrée à chaque porte logique et permettre la sélection de n'importe quelle fonction de logique booléenne de base and, or et xor.
Placer le fichier .comp dans le répertoire emc2/src/hal/components et lancer/relancer make. Les fichiers Comp sont automatiquement détectés par le système de compilation.
Si un fichier .comp est un pilote de périphérique, il peut être placé dans emc2/src/hal/components et il y sera construit excepté si emc2 est configuré en mode simulation.
comp peut traiter, compiler et installer un composant temps réel en une seule étape, en plaçant rtexample.ko dans le répertoire du module temps réel d'emc2:
comp --install rtexample.comp
Ou il peut aussi être traité et compilé en une seule étape en laissant example.ko (ou example.so pour la simulation) dans le répertoire courant:
comp --compile rtexample.comp
Ou il peut simplement être traité en laissant example.c dans le répertoire courant:
comp rtexample.comp
comp peut aussi compiler et installer un composant écrit en C, en utilisant les options --install et --compile comme ci-dessous:
comp --install rtexample2.c
La documentation au format man peut être créée à partir des informations de la section declaration:
comp --document rtexample.comp
La manpage résultante, exemple.9 peut être lue avec:
man ./exemple.9
ou copiée à un emplacement standard pour une page de manuel.
comp peut traiter, compiler et installer un document de l'espace utilisateur:
comp usrexample.comp
comp --compile usrexample.comp
comp --install usrexample.comp
comp --document usrexample.comp
Cela fonctionne seulement pour les fichiers .comp mais pas pour les fichiers .c.
Ce composant fonctionne comme dans 'blocks', y compris la valeur par défaut à 1.0. La déclaration function _ crée les fonctions nommées 'constant.0', etc.
component constant;
pin out float out;
param r float value = 1.0;
function _;
;;
FUNCTION(_) { out = value; }
Ce composant calcule le sinus et le cosinus d'un angle entré en radians. Il a différentes possibilités comme les sorties 'sinus' et 'cosinus' de siggen, parceque l'entrée est un angle au lieu d'être librement basé sur un paramètre 'frequency'.
Les pins sont déclarées avec les noms sin_ et cos_ dans le code source pour que ça n'interfère pas avec les fonctions sin() et cos(). Les pins de HAL sont toujours appelées sincos.<num>.sin.
component sincos;
pin out float sin_;
pin out float cos_;
pin in float theta;
function _;
;;
#include <rtapi_math.h>
FUNCTION(_) { sin_ = sin(theta); cos_ = cos(theta); }
Ce composant est un pilote pour une carte imaginaire appelée out8, qui a 8 pins de sortie digitales qui sont traitées comme une simple valeur sur 8 bits. Il peut y avoir un nombre quelconque de ces cartes dans le système et elles peuvent avoir des adresses variées. La pin est appelée out_ parceque out est un identifiant utilisé dans <asm/io.h>. Il illustre l'utilisation de EXTRA_SETUP et de EXTRA_CLEANUP pour sa requête de région d'entrées/sorties et libère cette région en cas d'erreur ou quand le module est déchargé.
component out8; pin out unsigned out_ "Output value; only low 8 bits are used"; param r unsigned ioaddr; function _; option count_function; option extra_setup; option extra_cleanup; option constructable no; license "GPL"; ;; #include <asm/io.h> #define MAX 8 int io[MAX] = {0,}; RTAPI_MP_ARRAY_INT(io, MAX, "I/O addresses of out8 boards"); int get_count(void) { int i = 0; for(i=0; i<MAX && io[i]; i++) { /* Nothing */ } return i; } EXTRA_SETUP() { if(!rtapi_request_region(io[extra_arg], 1, "out8")) { // set this I/O port to 0 so that EXTRA_CLEANUP does not release the IO // ports that were never requested. io[extra_arg] = 0; return -EBUSY; } ioaddr = io[extra_arg]; return 0; } EXTRA_CLEANUP() { int i; for(i=0; i < MAX && io[i]; i++) { rtapi_release_region(io[i], 1); } } FUNCTION(_) { outb(out_, ioaddr); }
component hal_loop;
pin out float example;
Ce fragment de composant illustre l'utilisation du préfixe hal_ dans un nom de composant. loop est le nom d'un module standard du kernel Linux, donc un composant loop ne pourrait pas être chargé si le module loop de Linux est également présent.
Quand il le charge, halcmd montre un composant appelé hal_loop. Cependant, les pins affichées par halcmd sont loop.0.example et non hal-loop.0.example.
Ce composant temps réel illustre l'utilisation d'un tableau de taille fixe:
component arraydemo "Registre à décalage 4-bits";
pin in bit in;
pin out bit out-# [4];
function _ nofp;
;;
int i;
for(i=3; i>0; i--) out(i) = out(i-1);
out(0) = in;
Ce composant de l'espace utilisateur modifie la valeur de ses pins de sortie vers une nouvelle valeur aléatoire dans l'étendue [0,1) à chaque ms.
component rand; option userspace; pin out float out; license "GPL"; ;; #include <unistd.h> void user_mainloop(void) { while(1) { usleep(1000); FOR_ALL_INSTS() out = drand48(); } }
Ce composant temps réel montre l'utilisation de la personnalité pour créer un tableau de taille variable et des pins optionnelles.
component logic "EMC2 HAL component providing experimental logic functions"; pin in bit in-##[16 : personality & 0xff]; pin out bit and if personality & 0x100; pin out bit or if personality & 0x200; pin out bit xor if personality & 0x400; function _ nofp; description """ Experimental general `logic function' component. Can perform `and', `or' and `xor' of up to 16 inputs. Determine the proper value for `personality' by adding: .IP \\(bu 4 The number of input pins, usually from 2 to 16 .IP \\(bu 256 (0x100) if the `and' output is desired .IP \\(bu 512 (0x200) if the `or' output is desired .IP \\(bu 1024 (0x400) if the `xor' (exclusive or) output is desired"""; license "GPL"; ;; FUNCTION(_) { int i, a=1, o=0, x=0; for(i=0; i < (personality & 0xff); i++) { if(in(i)) { o = 1; x = !x; } else { a = 0; } } if(personality & 0x100) and = a; if(personality & 0x200) or = o; if(personality & 0x400) xor = x; }
Une ligne de chargement typique pourrait être:
loadrt logic count=3 personality=0x102,0x305,0x503
qui créerait les pins suivantes: