SSV DNP5370 (Blackfin)

From Electrolab
Jump to: navigation, search

Blackfin DNP5370

Retour à Projets:Perso:2013:Module de localisation
Retour à User:Mael
Configuration d'une image et d'un bootloader (en anglais) : DNP5370

µCLinux et cross compilation

Le code du Blackfin est développé en C sous linux. La portabilité entre Linux et µCLinux permet de développer et tester son code sur hôte dans un premier temps (PC) puis ensuite sur cible une fois que les principales fonctions sont opérationnelles. Il n’y a pas de modification majeurs entre un code hôte et un code cible, tout se joue essentiellement au niveau des interfaces et du Makefile. Il est nécessaire de « bouchonner » les interfaces et d’utiliser un Makefile spécifique à la machine exécutant le code.
La fonction d’un Makefile est de compiler le(s) fichier(s) source au format machine afin de pouvoir les exécuter. Un Makefile est associé à un exécutable, ainsi qu’à un type de machine.
Un fichier Makefile en C sous Linux se nomme Makefile.nom, nom étant l’identifiant du Makefile.
Les fichiers Makefile peuvent être édités sous Kate aisément, leur typologie étant cependant différentes d’un fichier C standard (Un Makefile n’est pas dédié à un code C). Aujourd’hui, ce type de compilation est en règle générale intégrée à l’environnement de développement.
Un fichier Makefile définit les variables suivantes :

  • CROSS : Chemin du compilateur ainsi que des dépendances associées, par exemple les fichiers headers utilisés à la compilation,
  • CC : nom de l’exécutable du compilateur,
  • EXEC : Nom de l’exécutable produit à l’issue de la compilation,
  • OBJS : définit les fichiers objets à créer pour pouvoir générer l’exécutable. Les fichiers objets sont définis par le nom du fichier source avec le suffixe « *.o »,
  • CFLAGS : paramètre du compilateur, utilisé pour définir certaines caractéristiques à appliquer au code lors de la compilation (par exemple pour assurer la reproductibilité de la compilation, qui peut varier en fonction des machines ou des recompilations, etc.).
  • LFLAGS : Idem.

Les fichiers sont ensuite compilés un par un, en utilisant la structure :

$(CC) $(CFLAGS) -c nom_de_la_source.c

Puis une compilation globale est lancée pour créer les exécutables :

$(CC) $(LFLAGS) -o $(EXEC) $(OBJS) -lm

Un Makefile s’exécute par la commande make –f Makefile.nom. Il est possible de lancer plusieurs Makefile à la suite en utilisant un script shell.
Un exemple de Makefile est présenté ci-dessous :

/***********************************************************************************/

CROSS	  = /opt/opt/uClinux/bfin-uclinux/bin/bfin-uclinux-
CC	  = $(CROSS)gcc
EXEC	  = SCH_PROC
OBJS      = SCH_main.o SCH_calcul.o SCH_process.o SCH_time.o GEN_IPC.o SCH_RS232.o ITF_calcul.o COM_calcul.o
CFLAGS	  = -O2
LFLAGS    = -Wl,-elf2flt

all: 
	$(CC) $(CFLAGS) -c SCH_main.c
	$(CC) $(CFLAGS) -c SCH_calcul.c
	$(CC) $(CFLAGS) -c GEN_IPC.c
	$(CC) $(CFLAGS) -c SCH_process.c
	$(CC) $(CFLAGS) -c SCH_time.c
	$(CC) $(CFLAGS) -c SCH_RS232.c
	$(CC) $(CFLAGS) -c ITF_calcul.c
	$(CC) $(CFLAGS) -c COM_calcul.c
	$(CC) $(LFLAGS) -o $(EXEC) $(OBJS) -lm
#-----------------------------------------------
clean: 
	rm -f *.o *.gdb *.elf $(EXEC)

/***********************************************************************************/
Ce Makefile permet la compilation d’un code C pour une exécution directement sur une cible de type Blackfin en µClinux. L’exemple ci-dessous est utilisable pour une exécution sur PC x86 :
/***********************************************************************************/

PATH	  := $(PATH):/usr/bin:/usr/local/bin
CC	  = gcc
EXEC	  = SCH_PROC
OBJS      = SCH_main.o SCH_calcul.o SCH_process.o SCH_time.o GEN_IPC.o SCH_RS232.o
CFLAGS 	  =  -Wall 

all: $(EXEC)

$(EXEC): $(OBJS)
	$(CC) $(CFLAGS) -O3 -o $@ $(OBJS) -lc -lm
	chmod 777 $@
	
#-----------------------------------------------

clean: 
	rm -f $(EXEC) *.elf *.gdb *.o

/***********************************************************************************/
A noter, il n’est pas nécessaire de compiler les fichiers source pour en faire des fichiers objets dans ce cas, la compilation est automatique.
Ce type de compilation sera utilisé par exemple pour le débogage de l’application et des fonctionnements internes du système avant son déploiement sur cible.

Script Shell

Les batch shells sous linux sont des exécutables en lignes de commande, très pratique pour automatiser des opérations de type copie de fichier, insertion de noyau ou ouverture de droits.
Un batch doit toujours être nommé : nom.sh. Il s’exécute par la commande : sh nom.sh
Il doit toujours commencer par la commande #!/bin/sh en tête du fichier de script. Ci-dessous des exemples de commande standard utilisables en shell, même si en principe toutes les commandes batch sont utilisables en shell (en principe, cf remarques ci-après) :
Insertion de modules dans le noyau :

  • insmod i2c-dev.ko
  • insmod i2c-bfin-twi.ko
  • insmod can.ko

Compiler plusieurs exécutables :

make clean
#nettoyage des fichiers existants, plus propre
make -f Makefile.INIT_target
make -f Makefile.CAN_target
make -f Makefile.SCH_target

Le script ci-dessous permet d’envoyer les fichiers exécutables ainsi que les fichiers nécessaires à l’exécution, comme les scripts par exemple, sur la cible via un lien ftp (ultra secure certes mais bon…)

ftp -i -n -g <<END_SCRIPT
open 192.168.0.126
#adresse de l’équipements cible
user nom_user mot_de_passe
#user suivit du nom utilisateur et mot de passe pour se connecter à la cible. 
type binary
lcd /home/mael/Travail/drone/drone_labo #dossier origine
cd /media/mmc #dossier cible
system
send CAN_PROC
chmod 777 CAN_PROC
send INIT_PROC
chmod 777 INIT_PROC
send SCH_PROC
chmod 777 SCH_PROC
send i2c-bfin-twi.ko
chmod 777 i2c-bfin-twi.ko
send i2c-dev.ko
chmod 777 i2c-dev.ko
send can.ko
chmod 777 can.ko
send autostart_target.sh
chmod 777 autostart_target.sh
END_SCRIPT

On ouvre une communication ftp, on se logue, puis on indique source ert origine des fichiers.
La procédure est d’envoyer le fichier avec la commande « send » puis de le rendre exécutable avec « chmod 777 ».
  Remarque :

  • La commande stty –icanon, permettant de passer des commandes à un terminal sans utiliser [Entrée] pour valider, ne peut pas être utilisée dans un batch car elle doit être entrée dans le terminal lui-même,
  • La commande sudo chmod 0777 /dev/ttyUSB0 par exemple visant à donner un accès en lecture/écriture à un port, nécessite les droits admin (d’où le sudo). Dans le cas où une commande de ce type est passée dans un shell, le mot de passe sera demandé en plus dans tous les cas dans le terminal.

Architecture du logiciel embarqué

Le logiciel embarqué sur le blackfin est conçu sur la base de processus fonctionnant en parallèle, disposant chacun de fonctions particulières. Les fonctions sont activées séquentiellement par le scheduler sur la base de registres d’activation partagés, et associés chacun à un processus.
Les données sont créées sur la base d’un même fichier de définition, et l’échange de données entres fonctions et processus se fait exclusivement par IPC, des zones de mémoire allouées et partagées entre toutes les fonctions.

Présentation de l'architecture Blackfin

L’architecture du logiciel embarqué sur le Blackfin est présentée dans le schéma ci-dessous :

Architecture BF.jpg

Toutes les structures de données, les macros ou les données statiques (#define) sont définies dans un même fichier {definition.h}. L’ensemble des fichiers sources partagent donc les mêmes ressources logicielles. Cette notion est fondamentale pour l’utilisation par la suite du partage de mémoire par IPC.

Le logiciel est architecturé autour d’un scheduler disposant d’une horloge pour la synchronisation, et de plusieurs processus exécutés en parallèles qui sont activés tour à tour par le scheduler. L’activation est réalisée par l’envoi d’un signal de réveil au processus endormi.

Le choix d’activation de chaque processus est laissé au programmeur. En effet, il est possible de classer les fonctions associées à chaque processus par fréquence d’activation, et d’utiliser un compteur dans le scheduler avec une horloge sur la fréquence la plus haute pour activer tour à tour les processus.

Ce fonctionnement ne permet pas d’avoir un temps réel dur, mais avec certaines tolérances les performances obtenues sont tout à fait acceptables.

La création des structures de données et leur initialisation est réalisée dans un processus indépendant, appelé une seule fois au démarrage du logiciel, et ceci afin de simplifier par la suite l’utilisation du scheduler.

Structures de données partagées : definition.h

Les structures de données sont définies dans un "Interface Control Document" globale.

Les structures de données regroupes les valeurs par thématique : données de navigation, données inertielles, valeurs des capteurs de position, ou encore registres d’activation. Elles sont basées sur des types génériques définies dans le fichier {definition.h}.

Les structures de données utilisées sont ensuite nommées avec un paramétrage spécifique :

  • nom_de_la_structure_ suivie d’un C indique qu’il s’agit de valeur de consigne ;
  • nom_de_la_structure_ suivie d’un A indique des valeurs courantes de l’application ;
  • nom_de_la_structure_ suivie d’un S indique qu’il s’agit de valeur par défaut ;
  • nom_de_la_structure_ suivie d’un paramètre spécifique (AVD, AR, etc.) renvoi à un sous ensemble particulier (typiquement une famille d’actionneur).

Le partage des données dans les structures entre les différents processus impose une certaine rigueur dans la construction du logiciel : une donnée ne doit pas être modifiée alors qu’elle est en cours d’utilisation par un autre processus, de même qu’elle doit être cohérente d’un point de vue temporelle avec les autres données (risque de désynchronisation entre les processus, un processus est dit activé dans un registre d’activation par exemple alors que la tache ne s’est pas encore exécutée, le résultat de cette tache n’est donc pas à jour).

Les registres d'activation

Le logiciel embarqué utilise le principe des registres d’activation pour lancer ou non une fonction à chaque activation du processus. Chaque processus dispose d’une structure de donnée « registre », dans laquelle à chaque bit correspondra une fonction ou une procédure.

A l’exécution du processus, pour chaque bit de son registre d’activation, le processus exécute la fonction associée si l’état du bit est à 1, il ne l’exécute pas sinon.

Registre activation.jpg

Chaque fonction à la possibilité d’activer une autre fonction en positionnant son bit d’activation à 1, ou de la reseter. Ce principe offre une très grande liberté de développement, mais il peut également rapidement virer au cauchemar si l’activation / désactivation des fonctions n’est pas gérée convenablement.

Registre activation - utilisation.jpg

Les registres d’activation sont enregistrés dans les ICP afin d’être accessibles par tous.

Afin d’éviter de se perdre dans les activations multiples du programme, il est nécessaire de construire en premier lieu un diagramme type UML afin de dépister les branchements (ie les activations ou désactivations commandées dans le code).

L’exemple ci-dessous est le schéma associé à la figure précédente :

Registre activation - sequence.jpg

Dans le processus A, les fonctions 2 et 3 sont activées systématiquement, alors que la fonction 5 alterne avec les fonctions 4 et 6 du processus B.

En réalité, le code de la fonction A.3 va modifier les registres d’activation en fonction d’un test qu’il réalise en interne, par exemple :

  • Si (a) {Active A.5 ET Désactive (B.4 et B.6)}
  • A chaque exécution du scheduler, il réveillera le processus A qui lancera les fonctions 2 et 3, cette dernière permettant potentiellement d’exécution ensuite de la fonction 5 ou alors l’endormissement du processus A pour que le processus B puisse s’activer et exécuter 4 et 6.
struct process_FUNC
{
unsigned int PROC_A_FUNC;
unsigned int PROC_B_FUNC;
unsigned int PROC_C_FUNC;
etc.
};

Il est bien entendu possible de créer autant de registres que de processus exécutant des fonctions.

Le très grand intérêt de cette méthode est la rapidité d’intégration d’une nouvelle fonction. En effet, dès la fonction ajoutée sur le schéma et testée unitairement, il ne reste qu’à ajouter les consignes d’activation ou d’inactivation correspondant dans les différentes fonctions.

Le code ci-dessous est un exemple de registre d’activation permettant l’activation de 2 fonctions, l’une active une seule fois et l’autre de manière réccurente.

for (i = 1; i<= NB_IRD_FUNC; i++)
{
if(BIT_SET(process_FUNC_C->IRD_function,i))
{
switch(i)
{
case 1 :
init_ird();
  • Fonction d’initialisation appelée une fois, elle se désactive elle-même après exécution
break;
case 4 :
extract_CI_values ();
  • Fonction récurrente, elle est active tant que les données CI (centrale inertielle) sont requises par le système, seul un changement d’état du système pourra la désactiver (fonction spécifique)
break;
}
}
}

La macro BIT_SET(registre, bit) est définie dans le fichier definition.h et précisée dans le paragraphe [x.y : Macros]. Dans le code, activation et désactivation sont commandées par les deux macros suivantes :

  • Activation : SET_BIT(process_FUNC_C->IRD_function,9);
  • Désactivation : CLEAR_BIT(process_FUNC_C->MOD_function,5);

Création et partage d'une IPC

> coming soon, il faut que je reprenne mes notes <