Contrôleur de servomoteurs FPGA sur plate-forme Armadeus : partie logicielle

Magazine
Marque
Open Silicium
Numéro
3
Mois de parution
juillet 2011
Spécialité(s)


Résumé

Dans la première partie, nous avons conçu un nouveau périphérique basé sur un FPGA : le contrôleur matériel de servomoteurs. Il est l’équivalent d’un circuit électronique capable d’une concurrence vraie tout en ayant la souplesse d’une conception logicielle.Comme tout périphérique, il nécessite un peu de logiciel pour être utilisable sous un système d’exploitation tel que Linux. Tout d’abord, nous aborderons le pilote de périphérique ou driver. Il est au sein du noyau Linux et assure la communication entre le matériel et l’espace utilisateur. Ensuite, nous verrons le serveur de commandes. Il reçoit les ordres d’un client distant et les exécute. Pour clore le sujet loin des arcanes du mode noyau, un exemple de client graphique Qt est présenté, qui permet de faire bouger les servomoteurs avec une souris.


Body

1. Le pilote Linux du contrôleur de servos

La gestion du matériel étant l’une des fonctions régaliennes du noyau Linux, l’accès au contrôleur de servos FPGA est réalisé via un pilote de périphérique implanté dans un module noyau. Une application en mode utilisateur, comme le serveur de commandes que nous verrons plus loin, communiquera avec ce pilote plutôt que de dialoguer directement avec le matériel. Je ne reviendrai pas sur la nécessité d’adopter une telle architecture tant elle est acceptée et utilisée aujourd’hui.

1.1 L’environnement de compilation croisée et Linux Kbuild

La compilation du code source pour le système cible ARM9 nécessite une chaîne de compilation croisée pour créer les fichiers exécutables ARM9 sur le système hôte qui est le plus souvent du type x86. Le SDK Armadeus est basé sur Buildroot [34], GCC [35], GNU Binutils [36] et la uClibc [37]. Buildroot permet d’ordonnancer la construction d’un système embarqué complet. La chaîne GCC et Binutils assurent les compilations croisées. La uClibc apporte la bibliothèque C standard pour le code en mode utilisateur. D’autres logiciels libres sont bien sûr utilisés, mais ces quatre éléments constituent le cœur du SDK, que nous appellerons toolchain. L’installation de la toolchain est décrite en [15]. Lors de l’installation initiale, Buildroot lance la compilation de tous ces outils, ce qui explique le temps pris par cette première étape. À l’issue, la toolchain est installée dans le répertoire $ARMADEUS_BASE_DIR/buildroot/build_armvXX/staging_dir/usr/bin$ARMADEUS_BASE_DIR est le répertoire de base du SDK Armadeus et XX vaut 4t pour une APF9328 et 5te pour une APF27. Toutes les compilations à destination du système cible devront utiliser les outils présents dans ce répertoire.

Ce chemin sera simplifié dans les prochaines versions du SDK Armadeus qui utiliseront une version plus récente de Buildroot. Il est contenu dans la variable $ARMADEUS_TOOLCHAIN_PATH définie en sourçant armadeus_env.sh. armadeus_env.sh est obtenu en lançant make shell_env dans le répertoire $ARMADEUS_BASE_DIR.

Cette remarque s’applique aussi à la compilation du noyau Linux. Mais le SDK Armadeus et Buildroot nous facilitent le travail en masquant toute cette complexité et en plaçant les variables requises par Kbuild. Kbuild est le système de compilation du noyau Linux. Il est assez complexe mais permet de simplifier grandement l’écriture des fichiers de configuration Kconfig et des fichiers Makefile nécessaires à la compilation d’un module noyau Linux.

Le pilote est distribué sous la forme d’un patch qui crée le répertoire $ARMADEUS_BASE_DIR/target/linux/modules/fpga/armadeusservodriver. Celui-ci contient les fichiers suivants :

  • Kconfig : fichier de configuration du module noyau ;
  • Makefile : fichier Makefile permettant la compilation du module ;
  • servo.h : fichier d’en-tête du pilote ;
  • servo.c : fichier source du pilote.

Le répertoire $ARMADEUS_BASE_DIR/target/linux/modules contient tous les pilotes spécifiques aux systèmes Armadeus. Il est intégré à l’arborescence du noyau Linux via un lien symbolique drivers/armadeus qui pointe vers lui.

Le fichier Kconfig ajoute une entrée sous Device Drivers ---> Armadeus specific drivers ---> FPGA Drivers dans l’outil de configuration de Linux obtenu habituellement par make menuconfig. Le fichier Kconfig du répertoire parent l’inclut par :

source "drivers/armadeus/fpga/armadeusservodriver/Kconfig"

En voici son contenu :

config ARMADEUS_SERVO_DRIVER

tristate "Armadeus Servo driver"

depends on ARMADEUS_FPGA_DRIVERS

---help---

This driver allow you to use the servo FPGA firware

config ARMADEUS_SERVO_DRIVER définit une variable CONFIG_ARMADEUS_SERVO_DRIVER accessible dans tous les fichiers Makefile et par le préprocesseur C. Elle sera stockée dans le fichier de configuration .config de Linux. tristate "Armadeus Servo driver" indique que cette variable pourra prendre une valeur parmi trois et affiche le texte "Armadeus Servo driver" dans le menu. Enfin, ---help--- contient le texte le l’aide contextuelle de l’outil de configuration.

Le fichier Makefile pourrait être réduit à :

obj-$(CONFIG_ARMADEUS_SERVO_DRIVER) += servo.o

Kbuild nous rend là un grand service : une seule ligne pour définir la compilation croisée du module. CONFIG_ARMADEUS_SERVO_DRIVER pourra prendre trois valeurs :

  • y : Le code correspondant sera compilé et lié au sein du noyau.
  • m : Un module sera créé dans ce cas.
  • non définie : Le code est ignoré.

Dans notre cas, un module sera créé, donc CONFIG_ARMADEUS_SERVO_DRIVER=m. Make étend cette variable et la ligne devient obj-m += servo.o. obj-m est une directive Kbuild qui signifie qu’un module servo doit être compilé à partir d’un fichier servo.c. Comme pour le fichier Kconfig, ce fichier Makefile doit être référencé par celui du répertoire parent par :

obj-$(CONFIG_ARMADEUS_FPGA_DRIVERS) += dev_tools/ others/ POD/ wishbone_example/ armadeusservodriver/

Lorsque les directives obj-y ou obj-m contiennent des répertoires dans leurs membres droits, Kbuild explorera ces répertoires à la recherche de fichiers Makefile. Ainsi, grâce au toolchain et à Kbuild, une simple dizaine de lignes suffisent à définir la configuration et la compilation du pilote du contrôleur de servos. Maintenant que le module peut être compilé facilement, passons à son contenu : du code noyau.

1.2 Architecture et description du pilote

Le pilote assure la liaison bidirectionnelle entre l’application en mode utilisateur et le matériel. Du côté matériel, les fonctions readw() et writew() sont utilisées pour communiquer avec le contrôleur FPGA. Par ailleurs, le système de fichiers virtuel sysfs permet à une application en mode utilisateur d’effectuer des accès au pilote en lecture et en écriture via certains fichiers dans /sys créés par le pilote.

Du point de vue du pilote, le contrôleur de servos FPGA est constitué d’un périphérique de type contrôleur principal et de 32 périphériques indépendants de type servo. Le périphérique principal prend en charge les propriétés globales que sont l’identifiant magique, le numéro de version du firmware, la remise à zéro et l’activation globale des 32 servos. Les périphériques servos gèrent les propriétés de chaque servo individuellement, qui sont :

  • la position de consigne ;
  • la position courante ;
  • le décalage de position ;
  • la butée logicielle basse ;
  • la butée logicielle haute ;
  • l’état d’activation ;
  • la vitesse des mouvements.

Chaque propriété du contrôleur et des servos prend la forme d’un fichier dans /sys, dont les droits d’accès dépendent de la sémantique de la propriété. Le numéro de version du firmware est par exemple en lecture seule, alors la position de consigne est en lecture et écriture. L’application en mode utilisateur lit dans ces fichiers pour consulter l’état courant du contrôleur et des servos. Elle y écrit pour donner un ordre au contrôleur. Par exemple, l’écriture de '2000' dans le fichier correspondant à la position de consigne du servo 0 placera celui-ci à mi-course. Ce principe permet à n’importe quel langage de pouvoir utiliser le pilote même en shell, comme nous le verrons plus loin. Il applique aussi l’une des grandes maximes UNIX, « Tout est fichier ». Ces accès aux fichiers sont faits, par nature, de manière totalement asynchrone et les fonctions de callback du fichier sysfs (c’est un attribut dans la terminologie du noyau Linux) stockent ou retournent les valeurs souhaitées. Un timer, maintenu par le code du périphérique principal, s’occupe de lire ces valeurs et de les transmettre au contrôleur FPGA toutes les 20 ms.

L’architecture générale du pilote étant posée, passons maintenant à une séance de dissection du code. La description n’est pas faite exhaustivement, elle serait trop longue et pesante. Seul le périphérique contrôleur est décrit et la description pourra être transposée aux périphériques servos dont la structure est très similaire. Le lecteur curieux pourra consulter en détail les fichiers sources servo.c et servo.h. La partie la plus complexe du pilote est située dans son initialisation. En effet, passé celle-ci, le fonctionnement du pilote consiste en l’exécution des fonctions de lecture et d’écriture des attributs ainsi que l’exécution périodique de la fonction du timer. Comme je l’ai déjà mentionné, ce type de conception ne consomme quasiment pas de ressource processeur car la complexité du traitement est déportée dans le matériel.

Plusieurs êtres du monde fascinant du noyau Linux vont être découverts. Le premier est la classe de périphérique incarnée par la structure class définie dans linux/device.h. Elle permet de définir d’un point de vue fonctionnel et de haut niveau un type particulier de périphérique. Le pilote crée donc deux classes, l’une, servo_controller_class, pour le contrôleur principal, l’autre, servo_class, pour les servos. Voici la mise en œuvre pour la première, d’abord sa déclaration :

#define SERVO_CONTROLLER_DRIVER_NAME “servo_contoller”
static struct class servo_controller_class = {
.name = SERVO_CONTROLLER_DRIVER_NAME,
.dev_release = servo_controller_release,
.owner = THIS_MODULE,
};

Le membre name permet de nommer la classe et son répertoire dans /sys/class. servo_controller_release est une fonction appelée lors de la suppression de la classe. Le propriétaire owner est le module courant retourné par la macro THIS_MODULE. Vient ensuite sa création :

if (unlikely((result = class_register(&servo_controller_class)))) {
PERR(“%s: couldn’t register class, exiting\n”, SERVO_CONTROLLER_
DRIVER_NAME);
goto error_class_register;
}

La réussite de cet appel provoque la création du répertoire /sys/class/servo_contoller qui contiendra un sous-répertoire par périphérique de cette classe enregistré auprès du système. Lors du déchargement du module, la classe doit être supprimée par :

class_unregister( &servo_controller_class );

L’utilisation de goto est fustigée en programmation procédurale et cela à juste titre. Cependant, dans certains cas bien précis, son utilisation permet d’obtenir un code plus clair, plus court et plus maintenable. Les fonctions d’initialisation de modules en sont l’exemple typique. Celles-ci sont, en général, une succession d’allocations de structures de données dont la suivante dépend de la réussite de la précédente. En cas d’erreur, toutes les structures déjà allouées doivent être désallouées dans l’ordre inverse. L’utilisation de blocs if … else imbriqués rend le code extrêmement disgracieux à cause des niveaux d’indentation (8 dans notre cas), mais aussi parce que le code de libération des structures allouées doit être répété à chaque niveau. En utilisant goto et des étiquettes judicieusement placées, chaque erreur est gérée par un simple saut et la désallocation est linéaire. La fonction servo_init() en est un exemple concret.

Vient ensuite l’être platform_device. C’est une structure qui représente un périphérique physique connecté directement au bus système du processeur. Dans le cas des bus USB et PCI, le sous-système correspondant énumère les périphériques connectés au bus et crée les structures représentant les périphériques physiques. Ces bus modernes et complexes disposent en effet de mécanismes d’auto-description. Mais dans notre cas, il n’existe pas de moyen générique de détecter et d’énumérer les périphériques connectés au bus système. Ce bus est appelé « platform bus » dans la documentation Linux [38]. Il correspond au cas de l’ancien bus ISA, où une connaissance précise du matériel est nécessaire pour le détecter tout en comportant des risques de confusion. Comme Linux ne le fait pas pour nous, la première étape est la création de ce platform_device :

if(unlikely((servo_controller_platform_device=platform_device_
alloc(SERVO_CONTROLLER_DRIVER_NAME, 0)) == NULL)){
PERR(“platform_device_alloc %s.0 failed.\n”,SERVO_CONTROLLER_
DRIVER_NAME);
result = -ENOMEM;

goto error_platform_device_alloc;
}
if (unlikely((result = platform_device_add(servo_controller_platform_device)))) {
PERR(“platform_device_register %s.0 failed.\n”,SERVO_CONTROLLER_DRIVER_NAME);
goto error_platform_device_register;
}

L’utilisation du nom SERVO_CONTROLLER_DRIVER_NAME permettra que ce platform_device soit reconnu par la fonction platform_driver_probe() dont la description suit. 32 platform_device sont aussi créés pour les périphériques servos. Voici maintenant l’être platform_driver :

static struct platform_driver servo_controller_driver = {
.remove = servo_controller_driver_remove,
.suspend = servo_controller_driver_suspend,
.resume = servo_controller_driver_resume,
.driver = {
.name = SERVO_CONTROLLER_DRIVER_NAME,
.owner = THIS_MODULE,
},
};
[...]
if (unlikely((result = platform_driver_probe( &servo_controller_driver,servo_controller_driver_probe )))) {
PERR(“platform_driver_probe %s failed.\n”, SERVO_CONTROLLER_DRIVER_NAME);
goto error_platform_driver_probe;
}

Cette structure décrit un pilote. Elle comporte un membre driver qui est une structure plus générique device_driver. Cette dernière contient, entre autres, le nom des périphériques gérés. Elle contient aussi des pointeurs de fonctions qui seront appelées dans certains cas. Ainsi, la fonction du membre remove sera appelée lors du retrait d’un périphérique géré par ce pilote, suspend et resume lors de la mise en veille et de la reprise du système. Le membre probe n’est pas renseigné ici pour des raisons présentées plus bas. La fonction pointée par probe est appelée lors de l’ajout d’un périphérique lorsque celui-ci a le même nom que celui du pilote renseigné dans driver.name. Cette fonction a une importance capitale car c’est elle qui vérifie la présence physique du périphérique, l’initialise et lui alloue une structure de données propre. Ce mode de fonctionnement est parfait pour des périphériques qui peuvent être ajoutés et retirés dynamiquement. Mais notre contrôleur de servos est toujours connecté et la documentation du noyau Linux préconise dans ce cas d’appeler la fonction platform_driver_probe() plutôt que la fonction platform_driver_register(). platform_driver_probe() enregistre le pilote servo_controller_driver auprès du système en indiquant via le pointeur de fonction servo_controller_driver_probe la fonction équivalente au membre probe. Elle est déclarée avec l’attribut __init, qui signifie que la mémoire occupée par le code de la fonction pourra être libérée à l’issue de l’initialisation du module. Cela produit une optimisation de la mémoire consommée, mais impose que la détection des périphériques ne soit faite qu’au moment de l’initialisation du module et pas après.

static int _ _init servo_controller_driver_probe(struct platform_device *pdev){
[...]
}

Cette fonction est lancée automatiquement par l’appel de platform_driver_probe() une fois par périphérique portant le même nom que le pilote déclaré. Exactement le même principe est utilisé pour les 32 périphériques servos, et la fonction équivalente servo_driver_probe() est appelée 32 fois. La première tâche à réaliser est la vérification de la présence du contrôleur de servos FPGA sur le bus système du processeur. C’est là l’utilité du registre regMagicIDAddr du contrôleur présent à la première adresse de la zone mémoire allouée au FPGA :

[...]
#define SERVO_MAGIC_ID 0x7207
#define SERVO_BASE_ADDR ARMADEUS_FPGA_BASE_ADDR_VIRT
#define SERVO_MAGIC_ID_ADDR SERVO_BASE_ADDR
[...]
magic_id = readw(SERVO_MAGIC_ID_ADDR);
if (magic_id == SERVO_MAGIC_ID) {
PINFO(“Magic ID OK %#X\n”,magic_id);
} else {
PERR(“Bad Magic ID %#X, exit...\n”,magic_id);
return -ENXIO;
}
[...]

Cette technique de détection primitive et peu fiable est cependant l’une des seules à notre disposition. En effet, un autre concepteur aurait pu avoir l’idée de stocker la valeur arbitraire 0x7207 à l’adresse SERVO_MAGIC_ID_ADDR ! Peu probable, mais possible. La fonction readw() permet la lecture des registres 16 bits du contrôleur. Contrairement à U-Boot, qui utilise un adressage physique, l’espace mémoire pointé par ARMADEUS_FPGA_BASE_ADDR_VIRT est virtuel, comme la plupart des adresses manipulées dans le noyau Linux. La correspondance entre les adresses physiques et les adresses virtuelles est mise en place par le code d’initialisation de la carte Armadeus.

[...]
if (unlikely((mem = request_mem_region(ARMADEUS_FPGA_BASE_ADDR_PHYS,FPGA_CSx_SIZE,SERVO_CONTROLLER_DRIVER_NAME)) == NULL)) {
PERR(“couldn’t register I/O memory\n”);
result = -ENOMEM;
goto error_request_mem_region;
}
if (unlikely((spd = kmalloc(sizeof(struct servo_contoller_device), GFP_KERNEL)) == NULL)) {
PERR(“couldn’t allocate memory for servo_contoller_device\n”);
result = -ENOMEM;
goto error_malloc;
}

memset(&spd->class_dev, 0, sizeof(struct device));
spin_lock_init(&spd->servo_spinlock);
init_timer(&spd->servo_timer);
[...]

Vient ensuite la réservation de l’espace mémoire d’entrée/sortie, l’allocation mémoire de la structure de données privées du périphérique et l’initialisation d’un spinlock et d’un timer. Le spinlock est utilisé pour assurer la cohérence de l’écriture des attributs. Le timer sert à exécuter périodiquement la fonction qui transfère les valeurs des attributs vers le contrôleur.

La structure de données privées spd contient une structure struct device class_dev. Elle est utilisée pour déclarer au système un périphérique logique de haut niveau (contrairement au périphérique de bas niveau platform_device). Sa création est faite comme suit :

[...]
spd->class_dev.class = &servo_controller_class;
spd->class_dev.parent = &pdev->dev;
snprintf(spd->class_dev.bus_id, BUS_ID_SIZE, “%s%i”,SERVO_CONTROLLER_
DRIVER_NAME, pdev->id);
result = device_register(&spd->class_dev);
if (unlikely(result)) {
PERR(“%s class registering failed\n”, SERVO_CONTROLLER_DRIVER_
NAME);
goto error_class_device_register;
}
[...]

Le nouveau périphérique logique est affecté à la classe servo_controller_class déclarée précédemment. L’organisation des périphériques étant hiérarchique, son parent est le membre dev de type device du platform_device pdev passé en argument de la fonction servo_controller_driver_probe() pour lequel elle a été appelée. device_register() effectue l’enregistrement auprès du système. Le résultat est la création d’un répertoire /sys/class/servo_contoller/servo_contoller0/ dans le répertoire réservé à la classe servo_controller_class. spd->class_dev.bus_id donne le nom du répertoire. Il contiendra des fichiers qui sont des attributs du périphérique et qui vont permettre la communication avec l’espace utilisateur. Voici le détail de la création de l’un de ces fichiers :

[...]
static struct device_attribute attr_servo_contoller_enable = {
.attr = { .name = “enable”, .mode = 0644, .owner = THIS_MODULE },
.show = servo_contoller_show_enable,
.store = servo_contoller_store_enable,
};
[...]
result |= device_create_file( &spd->class_dev, &attr_servo_contoller_
enable );
if (unlikely(result)) {
PERR(“%s class_device_create_file attr_servo_contoller_enable
failed\n”, SERVO_CONTROLLER_DRIVER_NAME);
goto error_class_device_create_file;
}
[...]

La structure device_attribute permet de déclarer un attribut de périphérique. attr.name définit le nom du fichier qui sera créé et attr.mode le mode l’accès au fichier. attr.mode utilise le même format que la commande chmod. 0644 donnera donc un accès en lecture/écriture pour le propriétaire qui est root et un accès en lecture seule pour le groupe et les autres. show et store sont des pointeurs de fonctions sur les fonctions appelées respectivement lors d’un accès en lecture et en écriture. device_create_file() provoque la création du fichier /sys/class/servo_contoller/servo_contoller0/enable. Une lecture de celui-ci exécutera la fonction :

[...]
#define to_servo_contoller_device(d) container_of(d, struct servo_
contoller_device, class_dev)
[...]
static ssize_t servo_contoller_show_enable(struct device *dev, struct
device_attribute *attr, char *buf)
{
int i;
u32 value;
struct servo_contoller_device *spd = to_servo_contoller_
device(dev);
value=spd->desired_servos_enable;
for (i=31; i>=0; i--){
if ((value & 0x00000001)==1){
buf[i]=’1’;
} else {
buf[i]=’0’;
}
value=value>>1;
}
return 32;
}
[...]

La fonction reçoit en argument le device dev auquel appartient le fichier, l’attribut device_attribute et un tampon buf dans lequel écrire le résultat. La macro to_servo_contoller_device permet d’obtenir la structure de données privées du périphérique. Ensuite, le membre u32 desired_servos_enable est converti en ASCII et stocké dans buf. C’est le contenu de ce tampon qui est retourné au processus en mode utilisateur qui a fait l’appel read() sur le fichier. Et pour une écriture :

[...]
static ssize_t servo_contoller_store_enable(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct servo_contoller_device *spd = to_servo_contoller_
device(dev);
spin_lock(&spd->servo_spinlock);
spd->desired_servos_enable = (u32)simple_strtol(buf, NULL, 2);
spin_unlock(&spd->servo_spinlock);
return size;
}
[...]

La fonction prend un argument en plus, qui est la taille size des données écrites par le processus en mode utilisateur. Ensuite, elle prend possession du spinlock pour éviter toute concurrence, convertit la valeur à écrire d’ASCII en binaire, la stocke dans les données privées du périphérique et relâche le spinlock. Ces deux fonctions traitent la lecture et la mise à jour du fichier /sys/class/servo_contoller/servo_contoller0/enable qui gère l’état d’activation des 32 servos et correspond aux registres regServosValidationLSBAddr et regServosValidationMSBAddr du contrôleur de servos. Tous les autres fichiers gérant l’accès au contrôleur sont traités suivant le même canevas.

Le périphérique contrôleur est maintenant opérationnel. Les mêmes opérations sont réalisées sur les 32 périphériques servos. La fonction d’initialisation servo_init() du module se termine par le démarrage du timer :

[...]
#define SERVO_WAIT_TICKS (2*HZ)/100
[...]
spd->servo_timer.data=0;
spd->servo_timer.function=servo_timer_function;
spd->servo_timer.expires=jiffies+SERVO_WAIT_TICKS;
add_timer(&spd->servo_timer);
[...]

servo_timer_function() est la fonction exécutée lors de l’expiration du timer après SERVO_WAIT_TICKS ticks système qui correspond à 20 ms. Elle lit les données privées du périphérique contrôleur et des 32 périphériques servos et les écrit dans le contrôleur FPGA après avoir pris possession du spinlock. Elle se termine en redémarrant le timer. Elle gère aussi la vitesse des mouvements des servos. Celle-ci est une propriété de chaque périphérique servo. Si elle vaut 0, les modifications de position sont répercutées directement et la vitesse de déplacement du servo est au maximal. Dans le cas contraire, la modification appliquée à la position courante du servo sera de cette valeur à chaque lancement de la fonction, c’est-à-dire toutes les 20 ms jusqu’à atteindre la position souhaitée. Par exemple, si la position initiale est 1000, que la position souhaitée est 2000 et que la vitesse vaut 5, la position souhaitée sera atteinte après : ((2000-1000)/5)*0,02 = 4 secondes. Ainsi s’achève la description du code du pilote, passons maintenant à la pratique.

1.3 Mise en œuvre du pilote sur la carte APF27

Le code source du pilote est distribué sous la forme d’un patch qui s’applique à la version 3.3 du SDK Armadeus :

$ cd $ARMADEUS_BASE_DIR
$ patch -p1 < ~/sandbox/fpga-servo-controller-00.00.01/armadeusservodriver/armadeusservodriver.patch
patching file target/linux/modules/fpga/armadeusservodriver/Kconfig
patching file target/linux/modules/fpga/armadeusservodriver/Makefile
patching file target/linux/modules/fpga/armadeusservodriver/servo.c
patching file target/linux/modules/fpga/armadeusservodriver/servo.h
patching file target/linux/modules/fpga/Kconfig
patching file target/linux/modules/fpga/Makefile

La variable $ARMADEUS_BASE_DIR contient le chemin du répertoire de base du SDK Armadeus 3.3. Ensuite, le support du pilote doit être activé dans la configuration du noyau Linux :

$ make linux26-menuconfig

Déplacez-vous dans le menu Device Drivers ---> Armadeus specific drivers ---> FPGA Drivers ---> et sélectionnez < > Armadeus Servo driver (NEW) comme module (M), puis sauvegardez la configuration du noyau Linux. Lancez ensuite une recompilation du noyau :

$ make linux26-clean

$ make

Puis, sur le système cible, sous U-Boot, flashez le nouveau noyau Linux et le nouveau rootfs :

BIOS> run update_kernel
FEC ETHERNET: Link is up - 100/Full
TFTP from server 192.168.0.27; our IP address is 192.168.0.10
Filename ‘apf27-linux.bin’.
Load address: 0xa0000000
Loading: #################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
########################################
done
Bytes transferred = 2197320 (218748 hex)
NAND erase: device 0 offset 0x180000, size 0x500000
OK
NAND write: device 0 offset 0x180000, size 0x218748
2197320 bytes written: OK
Flashing of kernel succeed
BIOS> run update_rootfs
FEC ETHERNET: Link is up - 100/Full
TFTP from server 192.168.0.27; our IP address is 192.168.0.10
Filename ‘apf27-rootfs.arm.jffs2’.
Load address: 0xa0000000
Loading: #################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
##############
done
Bytes transferred = 4063232 (3e0000 hex)
NAND erase: device 0 offset 0x680000, size 0xf980000
OK
NAND write: device 0 offset 0x680000, size 0x3e0000
4063232 bytes written: OK
Flashing of rootfs succeed

Si ces manipulations et celles du chapitre 3.2 se sont correctement déroulées, le pilote est prêt à être utilisé après un redémarrage du système. Commençons par son chargement ; dans un shell sur le système cible :

# modprobe servo

Armadeus FPGA R/C servo driver: Version 0.2

Armadeus FPGA R/C servo driver: Magic ID OK 0X7207

Armadeus FPGA R/C servo driver: Firmware version 0X4

Armadeus FPGA R/C servo driver: 32 R/C servo(s) managed by the FPGA firmware

Ces messages sont issus de la console système et indiquent le bon chargement du module. Voici les fichiers dans /sys créés par le pilote pour le périphérique contrôleur principal :

# ls -l /sys/class/servo_contoller/servo_contoller0/

wxrwxrwx 1 root root 0 Jan 1 01:11 device -> ../../../devices/platform/servo_contoller.0

-rw-r--r-- 1 root root 4096 Jan 1 01:11 enable

-r--r--r-- 1 root root 4096 Jan 1 01:11 firmware_version

-r--r--r-- 1 root root 4096 Jan 1 01:11 nbr_servos

--w------- 1 root root 4096 Jan 1 01:11 reset

lrwxrwxrwx 1 root root 0 Jan 1 01:11 subsystem -> ../../servo_contoller

-rw-r--r-- 1 root root 4096 Jan 1 01:11 uevent

Nous pouvons remarquer que certains fichiers sont en lecture seule, comme firmware_version et nbr_servos, dont la signification est directe. reset est en écriture seule, car ce fichier correspond à la commande de remise à zéro matérielle. Celle-ci sera exécutée de la manière suivante :

# cd /sys/class/servo_contoller/servo_contoller0/

# echo '1' > reset

Et c’est là l’intérêt du système de fichiers /sys : un simple shell permet de tout contrôler. enable est en lecture/écriture. En lecture, il renvoie l’état d’activation des 32 servos.

# cat enable

00000000000000000000000000000000

Ce qui signifie que les 32 servos sont en roue libre. Voici la commande pour activer les premier et dernier servos :

# echo '10000000000000000000000000000001' > reset

Chaque périphérique servo dispose de son propre répertoire :

# ls /sys/class/servo

servo0 servo12 servo16 servo2 servo23 servo27 servo30 servo6

servo1 servo13 servo17 servo20 servo24 servo28 servo31 servo7

servo10 servo14 servo18 servo21 servo25 servo29 servo4 servo8

servo11 servo15 servo19 servo22 servo26 servo3 servo5 servo9

Voici le détail de l’un d’entre eux :

# ls -l /sys/class/servo/servo0/

-r--r--r-- 1 root root 4096 Jan 1 01:14 current_position

-rw-r--r-- 1 root root 4096 Jan 1 01:14 desired_position

lrwxrwxrwx 1 root root 0 Jan 1 01:14 device -> ../../../devices/platform/servo.0

-rw-r--r-- 1 root root 4096 Jan 1 01:14 enable

-rw-r--r-- 1 root root 4096 Jan 1 01:14 lower_boundary

-rw-r--r-- 1 root root 4096 Jan 1 01:14 offset

-rw-r--r-- 1 root root 4096 Jan 1 01:14 speed_step

lrwxrwxrwx 1 root root 0 Jan 1 01:14 subsystem -> ../../servo

-rw-r--r-- 1 root root 4096 Jan 1 01:14 uevent

-rw-r--r-- 1 root root 4096 Jan 1 01:14 upper_boundary

Certains fichiers méritent une description plus précise :

  • current_position : En lecture seule, la position actuelle du servo.
  • desired_position : En lecture/écriture, la position de consigne.
  • enable : En lecture/écriture, l’état d’activation du servo.
  • lower_boundary : En lecture/écriture. Ce paramètre n’a encore été que brièvement abordé. Chaque modèle de servo a un débattement spécifique. Positionner le servo hors des limites de ce débattement provoque une surcharge car il lutte pour atteindre une position inatteignable. La solution consiste à mettre en place des butées logicielles réglables individuellement. lower_boundary correspond à la butée basse : le servo ne prendra jamais une position inférieure à celle-ci, même si une telle position est écrite dans la position de consigne via desired_position.
  • upper_boundary : Similaire à lower_boundary pour la butée haute.
  • offset : En lecture/écriture. Comme le débattement, la position absolue d’un servo en fonction de la largeur de l’impulsion de commande varie suivant les modèles. offset permet de compenser ces différences et d’utiliser des positions absolues sans se soucier des tolérances de chaque servo.
  • speed_step : En lecture/écriture. Contrôle la vitesse des changements de position, comme évoqué plus haut.

Maintenant, faisons bouger les servos. Ceci active le servo 1 et vérifie son état :

# cd /sys/class/servo/servo1

# echo '1' > enable

# cat enable

1

Pour consulter la position de consigne du servo 1 et le placer dans la position extrême anti-horaire :

# cat desired_position

2048

# echo '0' > desired_position

# cat desired_position

0

2. Le serveur de commandes du système

À présent, nous sommes en mesure de commander les servos depuis le mode utilisateur. Mais il serait beaucoup plus pratique de le faire à distance en utilisant un jeu de commandes bien défini sans ce soucier des arcanes de /sys. C’est le rôle du serveur de commandes implanté sous la forme d’un deamon en mode utilisateur. Il est à l’écoute d’un port TCP sur lequel un client envoie les commandes. Il les interprète, effectue les opérations de lecture et d’écriture dans /sys et renvoie le statut de l’exécution de la commande au client.

2.1 Architecture et description du serveur de commandes

L’architecture du serveur est simple. Après une séquence d’initialisation, le serveur se place en attente de la connexion d’un client unique. Lorsqu’une connexion est établie, une boucle de traitement des commandes s’exécute jusqu’à sa fermeture. L’initialisation consiste en la lecture du fichier de configuration .MotionServerrc dont voici un extrait :

[Module]
ModuleFilePath /lib/modules/2.6.29.6/kernel/drivers/armadeus/fpga/armadeusservodriver/servo.ko
ModuleLoad 1
[Default]
ServoPosition 0 2047
ServoOffset 0 0
ServoMin 0 300
ServoMax 0 3500
ServoEnable 0 1

La section [Module] concerne le module noyau du pilote du contrôleur. ModuleFilePath indique le chemin du module et ModuleLoad, s’il vaut 1, stipule si le module doit être chargé au démarrage du serveur. La section [Default] définit les paramètres par défaut des servos appliqués par le serveur à son démarrage :

  • ServoPosition N Val : La position de consigne du servo N vaudra Val.
  • ServoOffset N Val : Le décalage du servo N vaudra Val.
  • ServoMin N Val : La butée logicielle inférieure du servo N vaudra Val.
  • ServoMax N Val : La butée logicielle supérieure du servo N vaudra Val.
  • ServoEnable N [0|1] : Le servo N sera activé pour la valeur 1 et en roue libre pour 0.

Ces cinq directives pourront être répétées pour chaque servo sans obligation. Le serveur accepte un certain nombre de commandes qui sont simplement envoyées par le client sous la forme de chaîne de caractères ASCII. Le tableau suivant les récapitule, servo indiquant le numéro du servo adressé :

Commande

Description

SET_POSITION ( servo , position )

Fixe la position de consigne entre 0 et 4095.

SET_ENABLE ( servo , status)

Active le servo pour status=1. Place le servo en roue libre pour status=0.

SET_OFFSET ( servo , offset )

Fixe le décalage du servo entre -4095 et 4095.

SET_MIN ( servo , min )

Fixe la butée logicielle inférieure entre 0 et 4095.

SET_MAX ( servo , max )

Fixe la butée logicielle supérieure entre 0 et 4095.

GET_POSITION ( servo )

Retourne la position du servo.

GET_ENABLE ( servo )

Retourne l’état d’activation du servo.

GET_OFFSET ( servo )

Retourne le décalage du servo.

GET_MIN ( servo )

Retourne la butée logicielle inférieure.

GET_MAX ( servo )

Retourne la butée logicielle supérieure.

WRITE_CONFIG_FILE ( path )

Ecrit la configuration courante au format .MotionServerrc dans un fichier dont le chemin est contenu dans path.

SLEEP ( seconds )

Attend le nombre de secondes indiquées avant de traiter la commande suivante.

Le code C du serveur n’appelle pas beaucoup de commentaires, si ce n’est le traitement du fichier de configuration et du langage de commande des servos. Ils sont traités avec des analyseurs lexicaux et syntaxiques Lex et Yacc. C’est l’occasion de revenir sur ces deux grands outils qui sont un peu éclipsés par les grammaires universelles XML. Nous parlerons bien sûr de leurs implantations libres Flex et Bison. Flex permet de créer des analyseurs lexicaux et de tokeniser un fichier ou une chaîne de caractères avant de passer cette liste de tokens à analyseur syntaxique créé par Bison. Ils génèrent des analyseurs performants et surtout robustes alors qu’un code équivalent écrit manuellement comporterait son cortège de bugs et de cas non traités. Ils utilisent une syntaxe qui leur est propre et fournissent en sortie du code C utilisable directement. Pour finir l’éloge du couple Flex et Bison, dès qu’une analyse lexicale et syntaxique doit être faite par un programme en langage C, le recours à ces deux outils devrait être un automatisme tant la facilité et la souplesse de mise en œuvre sont grandes.

Passons maintenant aux analyseurs du langage de commande. Comme deux analyseurs lexicaux et deux analyseurs syntaxiques sont utilisés dans le serveur, Flex et Bison doivent être invoqués avec des paramètres complémentaires. Par défaut, ils préfixent les fichiers et les noms de symboles générés avec y ou yy, ce qui provoque des conflits en cas d’usage multiple.

lex -P command command.l

yacc -b command -p command -d command.y

Le fichier command.l contient l’analyseur lexical du langage de commande. L’option -P indique à Flex d’utiliser le préfixe command au lieu de yy. Les options -b et -p l’indiquent pour Bison. command.y est l’analyseur syntaxique. L’option -d provoque la génération d’un fichier command.tab.h contenant toutes les déclarations faites par Bison, qui seront nécessaires à Flex. Voici l’analyseur lexical :

%{
#include “command.tab.h”
%}
%option noyywrap
%%
-?[0-9]+ { commandlval.num = atoi(commandtext); return NUMBER; }
SET_POSITION { return SET_POSITION; }
SET_ENABLE { return SET_ENABLE; }
SET_OFFSET { return SET_OFFSET; }
SET_MIN { return SET_MIN; }
SET_MAX { return SET_MAX; }
GET_POSITION { return GET_POSITION; }
GET_ENABLE { return GET_ENABLE; }
GET_OFFSET { return GET_OFFSET; }
GET_MIN { return GET_MIN; }
GET_MAX { return GET_MAX; }
WRITE_CONFIG_FILE { return WRITE_CONFIG_FILE; }
SLEEP { return SLEEP; }
“,” { return COMMA; }
“(“ { return LPAR; }
“)” { return RPAR; }
“\”” { return DOUBLE_QUOTES; }
[ \t] ;
\n { return EOL; }
\r\n { return EOL; }
[0-9A-Za-z/\.\-_]+ { strcpy(commandlval.token_string, commandtext);
return FILE_PATH; }
. { return commandtext[0]; }
%%

La première section comprise entre %{ et %} contient du code C qui sera directement copié dans le fichier de sortie. command.tab.h contient les déclarations faites par Bison des constantes comme SET_POSITION et de l’union commandlval (qui correspond au yylval natif renommé par l’option -p de Bison). Vient ensuite l’option noyywrap qui stipule la non utilisation de la fonction yywrap(). Elle est utilisée uniquement lors de l’analyse de plusieurs fichiers, ce qui est sans objet dans notre cas. Puis la section encadrée par des %% est une liste d’expressions régulières que l’analyseur reconnaîtra, ce sont ses règles. Le code C entre accolades est exécuté lorsque l’expression régulière sur la même ligne est reconnue. L’absence de code C signifie que ce motif est reconnu mais ignoré, comme dans [ \t];, qui ignorera les espaces et les tabulations. La valeur retournée par le code C, comme { return COMMA; }, indiquera à l’analyseur syntaxique qu’un token de type COMMA a été reconnu sans valeur associée. Lorsqu’une variable a besoin d’être transmise à l’analyseur syntaxique, le texte reconnu par Flex contenu dans la chaîne commandtext (correspondant au yytext natif) est copié, après transformation si besoin, dans un membre de l’union commandlval. Celle-ci est définie dans l’analyseur syntaxique et permet de contenir les types nécessaires aux traitements ultérieurs. Ainsi, { commandlval.num = atoi(commandtext); return NUMBER; } retournera un token de type NUMBER lorsqu’un entier sera reconnu et sa valeur sera stockée dans le membre commandlval.num, qui est un int. Lorsqu’un analyseur syntaxique est utilisé conjointement à un analyseur lexical, ce dernier n’est pas invoqué directement. C’est l’analyseur syntaxique qui demande à l’analyseur lexical de lui fournir une liste de tokens. Voici son code source :

%{
#include<stdio.h>
#include “MotionServer.h”
extern commandlineno;
extern commandtext;
void commanderror (const char *);
%}
%union {
char token_string[128];
int num;
}
%token <num> NUMBER
%token <token_string> FILE_PATH
%token SET_POSITION SET_ENABLE SET_OFFSET SET_MIN SET_MAX
%token GET_POSITION GET_ENABLE GET_OFFSET GET_MIN GET_MAX
%token WRITE_CONFIG_FILE SLEEP
%token COMMA LPAR RPAR DOUBLE_QUOTES EOL
%%
COMMAND: SET_POSITION LPAR NUMBER COMMA NUMBER RPAR
{setServoPosition($3,$5);}
|SET_ENABLE LPAR NUMBER COMMA NUMBER RPAR {setServoEnable($3,$5);}
|SET_OFFSET LPAR NUMBER COMMA NUMBER RPAR {setServoOffset($3,$5);}
|SET_MIN LPAR NUMBER COMMA NUMBER RPAR {setServoMin($3,$5);}
|SET_MAX LPAR NUMBER COMMA NUMBER RPAR {setServoMax($3,$5);}
|GET_POSITION LPAR NUMBER RPAR {getServoPosition($3);}
|GET_ENABLE LPAR NUMBER RPAR {getServoEnable($3);}
|GET_OFFSET LPAR NUMBER RPAR {getServoOffset($3);}
|GET_MIN LPAR NUMBER RPAR {getServoMin($3);}
|GET_MAX LPAR NUMBER RPAR {getServoMax($3);}
|WRITE_CONFIG_FILE DOUBLE_QUOTES FILE_PATH DOUBLE_QUOTES
{writeDefaultConfig($3);}
|SLEEP LPAR NUMBER RPAR {sleep($3);}

|COMMAND EOL
|EOL
;
%%
void commanderror (const char *msg){
printf(“%d: %s at ‘%s’\n”,commandlineno,msg,commandtext);
}

Comme pour l’analyseur lexical, la première section est copiée verbatim dans le fichier de sortie. La déclaration %union permet de définir une union contenant les différents types des tokens. L’union commandlval vue précédemment est de ce type. Viennent ensuite les déclarations %token des tokens de la grammaire. Ils sont utilisés dans l’analyseur lexical en valeur de retour du code C associé à leur détection et dans les règles de l’analyseur syntaxique. Un token peut être non typé, comme %token COMMA. Dans ce cas, il ne contiendra pas de variable associée, ce qui convient pour les éléments de ponctuation et les mots-clés des commandes. Si la valeur d’un token doit être obtenue pour effectuer un traitement sur elle, ce token doit être typé et une variable de ce type lui est associée. %token <num> NUMBER définit que le token NUMBER a pour type num. num correspond au membre du même nom de l’union définie par %union. Les variables associées à ce token seront donc des int. La section entre %% définit les règles de production de la grammaire. De telles règles comportent un membre de gauche définissant un symbole non terminal, comme COMMAND et un membre de droite constitué de symboles terminaux (les tokens) ou non terminaux (des membres de gauche définis ailleurs). La fin de la ligne contient entre accolades le code C exécuté lorsque la règle correspond. Dans notre cas, la grammaire est très simple : un seul symbole non terminal et une seule règle sont définis. Cette règle comporte de nombreuses disjonctions exprimées par les barres verticales. Prenons un exemple concret de règle :

COMMAND: GET_POSITION LPAR NUMBER RPAR {getServoPosition($3);}

Elle définit le symbole non terminal COMMAND. Pour obtenir des tokens, l’analyseur syntaxique invoque l’analyseur lexical. Imaginons que ce dernier ait reçu en entrée la chaîne GET_POSITION ( 1 ). Grâce à ses règles, il la transformera en la suite de tokens GET_POSITION LPAR NUMBER RPAR qui sera transmise à l’analyseur syntaxique. Cette suite correspond à la règle de l’exemple et le code C getServoPosition($3); sera exécuté. La variable $3 correspond à la variable associée du troisième token de la règle, le token NUMBER. Dans le programme principal, l’analyse syntaxique est invoquée par :

[...]

char pBuffer[BUFFER_SIZE];

command_scan_string(pBuffer);

ret=commandparse();

[...]

La fonction command_scan_string(), générée automatiquement par Flex, initialise la chaîne de caractères à analyser et commandparse(), fournie par Bison, lance l’analyse. L’utilisation de Flex et Bison permet en 65 lignes de code de créer un mini langage à la fois simple, robuste et maintenable. En effet, la définition d’une nouvelle commande se fera simplement par l’ajout d’une expression régulière, d’une règle de production et d’une fonction de traitement.

2.2 Mise en œuvre du serveur de commandes

Le code source du serveur est situé dans le répertoire MotionServer de l’archive du projet. Une modification du fichier Makefile sera nécessaire pour adapter la variable ARMADEUS_TOOLCHAIN_PATH au chemin de la chaîne de compilation croisée sur votre système hôte. Voici comment lancer la compilation :

$ cd ~/sandbox/fpga-servo-controller-00.00.01/MotionServer

$ make

Copiez ensuite le fichier MotionServer sur le système cible et exécutez-le. Sur le système hôte, établissez une connexion TCP sur le port 1972 vers le système cible :

$ telnet 192.168.0.10 1972

SET_ENABLE ( 0 , 1 )

SET_ENABLE ( 0 , 1 )

SET_POSITION ( 0 , 4095 )

SET_POSITION ( 0 , 4095 )

GET_POSITION ( 0 )

4095

Les commandes définies plus haut peuvent directement être utilisées. Le serveur retourne la commande exécutée pour les commandes SET_* et la valeur requise pour les commandes GET_*. Ce mode de commande, qui ne nécessite que l’envoi de texte sur une connexion TCP, permet d’écrire des applications de commande du contrôleur de servos sur n’importe quel système d’exploitation en n’importe quel langage. Un exemple en est donné dans le paragraphe suivant.

3. L’application cliente graphique

La finalité de ce projet est de pouvoir commander simultanément plusieurs servos d’un dispositif mécanique dans un but précis. Cela pourrait être la coordination des différentes parties d’un bras robotique. La communication avec le serveur de commandes ne serait alors qu’une partie du logiciel animant le bras et elle serait enfouie dans celui-ci. L’application cliente proposée ici à titre d’exemple ne permet que de contrôler tous les paramètres des servos de manière graphique. Elle pourra être utilisée soit pour valider le bon fonctionnement de l’ensemble du système ou à des fins de mise au point dans l’élaboration de séquences de mouvements complexes. Cette application ServoGui est écrite en C++ avec Qt4. Vous devrez donc installer les paquets des bibliothèques Qt4 et les paquets de développement Qt4 sur votre distribution GNU/Linux favorite pour la compiler.

ServoGui

Fig. 1 : L’application ServoGui.

ServoGui est constituée de quatre onglets. Les trois premiers sont dédiés à la commande des servos. Chacun permet de gérer 8 servos, soit 24 au total. En cas de besoin de plus, le code d’un onglet pourra facilement être dupliqué. Pour chaque servo, les paramètres suivants sont ajustables :

  • Un slider fixe la position de consigne.
  • Des spinboxes permettent le réglage fin de la position de consigne, du décalage et des butées logicielles inférieure et supérieure.
  • Une checkbox gère l’activation du servo.

Un champ permet de renseigner le nom ou l’adresse IP du système Armadeus. Le bouton Connect établit la connexion TCP. Le quatrième onglet permet de lancer d’autres opérations. Save Config enregistre les paramètres courants dans un fichier sur le serveur au format .MotionServerrc. Pour finir, Send Batch File lance l’exécution d’un fichier texte contenant une suite de commandes, une par ligne, comprises par le serveur de commandes. En voici un exemple :

SET_POSITION (0 ,1000 )

SET_POSITION (1 ,3000 )

SLEEP (1)

SET_POSITION (1 ,1000 )

Je ne commenterai pas le code de ServoGui, car c’est une application Qt classique dont la description sort du cadre de l’article. Ces simples commandes permettent sa compilation et son exécution :

$ cd ~/sandbox/fpga-servo-controller-00.00.01/ServoGui

$ qmake-qt4

$ make

$ ./ServoGui

Conclusion et perspectives

Nous voici arrivés à la fin de l’aventure. Le contrôleur de servos, bien que complètement fonctionnel, pourrait se voir amélioré de fonctionnalités complémentaires. Il en est une qui serait particulièrement intéressante pour représenter des mouvements qui correspondent plus au monde réel. En effet, lorsque nous saisissons un objet, nous ne fixons pas la position absolue de chacune des articulations de notre main. Suivant la nature de l’objet, nous déclenchons une séquence de mouvements puis nous maintenons la position atteinte avec une certaine force qui dépend de l’objet. Un œuf n’est pas saisi avec la même vigueur qu’une boule de pétanque ! Ce sont nos capacités proprioceptives qui nous permettent cette faculté et un autre mode de commande des servos pourrait s’en inspirer. Au lieu de fixer une position de consigne, il consisterait à maintenir une certaine force appliquée de consigne. Comme les servos que nous avons utilisés ne permettent pas ce type de commande nativement, il faudrait adjoindre un artefact de capteur proprioceptif. Ce pourrait être simplement la mesure du courant consommé par un servo, qui reflète assez fidèlement l’effort exercé par celui-ci.

Plus globalement, ce projet montre les possibilités d’un système embarqué disposant du couple processeur traditionnel et FPGA. Ici, le contrôleur matériel implanté dans le FPGA reste très modeste, mais d’autres bien plus élaborés pourraient être créés, les seules limites étant l’imagination du concepteur et le nombre de bascules utilisées, bien sûr. Cette architecture permet la création de systèmes complexes basés sur un contrôleur matériel spécialisé tout en gardant la souplesse et la versatilité d’une conception purement logicielle. La puissance du système se trouve ainsi décuplée. Un fondeur tel que Xilinx l’a bien compris en proposant la plate-forme ZYNC™ [39] qui comporte sur la même puce un processeur double cœurs ARM Cortex A9 et un FPGA ayant entre eux un couplage fort.

Je tiens à remercier Julien, Fabien, Nicolas et Eric pour leurs relectures et leurs conseils.

Références

[1] Article Wikipédia présentant le Jitter : http://en.wikipedia.org/wiki/Jitter

[2] Article Wikipédia présentant le circuit Intel 8254 : http://en.wikipedia.org/wiki/Intel_8254

[3] GHDL : http://ghdl.free.fr

[4] Xilinx ISE : http://www.xilinx.com/tools/webpack.htm

[5] Article Wikipédia présentant les FPGA : http://en.wikipedia.org/wiki/FPGA

[6] GLMF HS 47 : Laura Bécognée, « Le VHDL pour les débutants » : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMFHS-047/Le-VHDL-pour-les-debutants 

[7] GLMF 126 : Yann Guidon, « Le VHDL pour ceux qui ont débuté » : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-126/Le-VHDL-pour-ceux-qui-ont-debute

[8] GLMF HS 38 : T. Rétornaz et J.-M Friedt, « Instrumentation Scientifique Reconfigurable »

[9] Armadeus APF9328 : http://www.armadeus.com/wiki/index.php?title=APF9328

[10] Armadeus APF27 : http://www.armadeus.com/francais/produits-cartes_microprocesseur-apf27.html

[11] Armadeus Project : http://www.armadeus.org

[12] GLMF 92 : Julien Boibessot, « Linux Embarqué pour tous »

[13] Armadeus APF9328 DevLight : http://www.armadeus.com/wiki/index.php?title=APF9328DevLight

[14] Armadeus APF27 Dev : http://www.armadeus.com/francais/produits-cartes_developpement-apf27_dev.html

[15] Installation du SDK Armadeus : http://www.armadeus.com/wiki/index.php?title=Toolchain

[16] Connecter la carte Armadeus au système hôte : http://www.armadeus.com/wiki/index.php?title=Communicate

[17] Installation logicielle sur le système cible : http://www.armadeus.com/wiki/index.php?title=Target_Software_Installation

[18] Armadeus Forum Mailing List : https://lists.sourceforge.net/lists/listinfo/armadeus-forum

[19] Datasheet de l’APF9328 DevLight : http://www.armadeus.com/_downloads/apf9328DevLight/documentation/dataSheet_APF9328_DevLight.pdf

[20] Datasheet de l’APF27 Dev : http://www.armadeus.com/_downloads/apf27Dev/documentation/DataSheet_APF27Dev.pdf

[21] Xilinx Spartan 3 Datasheet : http://www.xilinx.com/support/documentation/data_sheets/ds099.pdf (P131-132 pour le brochage)

[22] Xilinx Spartan 3A Datasheet : http://www.xilinx.com/support/documentation/data_sheets/ds529.pdf (P79-83 pour le brochage)

[23] Création d’un compte Xilinx : https://secure.xilinx.com/webreg/createUser.do.

[24] Téléchargement ISE® WebPACK™ Design Software : http://www.xilinx.com/support/download/index.htm

[25] Licence ISE® : http://www.xilinx.com/getlicense

[26] Glitch : http://en.wikipedia.org/wiki/Glitch

[27] Schéma électrique de l’APF9328 : http://www.armadeus.com/_downloads/apf9328/hardware/apf_schema.pdf

[28] Schéma électrique de l’APF27 : http://www.armadeus.com/_downloads/apf27/hardware/apf27_V1.2.pdf

[29] Communiquer avec une carte Armadeus : http://www.armadeus.com/wiki/index.php?title=Communicate

[30] Kermit et Armadeus : http://www.armadeus.com/wiki/index.php?title=Kermit

[31] Installer le SDK Armadeus : http://www.armadeus.com/wiki/index.php?title=Toolchain

[32] Installation des logiciels sur la cible : http://www.armadeus.com/wiki/index.php?title=Target_Software_Installation

[33] Le mode Bootstrap : http://www.armadeus.com/wiki/index.php?title=BootStrap

[34] Buildroot : http://buildroot.uclibc.org/

[35] GCC : http://gcc.gnu.org/

[36] GNU Binutils : http://www.gnu.org/software/binutils/

[37] uClibc : http://uclibc.org/

[38] Platform bus, sources du noyau Linux : http://lxr.free-electrons.com/source/Documentation/driver-model/platform.txt

[39] La plate-forme Xilinx ZYNC™ : http://www.xilinx.com/technology/roadmap/processing-platform.htm



Article rédigé par

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous