Compilation et déploiement d'un modèle Simulink avec Embedded Coder

Ce tutoriel s'adresse aux développeurs Simulink qui souhaitent rendre leur modèle indépendant de Simulink.

Il présente l'utilisation de l'outil Embedded Coder qui permet de compiler un modèle en bibliothèque.

Il présente également l'utilisation de la bibliothèque générée dans du code MATLAB et du code C externe.

Public visé : développeurs Simulink de niveau intermédiaire et plus.

Des connaissances avancées en langage C seront utiles pour bien comprendre la dernière partie du tutoriel.

Votre avis et vos suggestions sur cet article nous intéressent !
Alors après votre lecture, n'hésitez pas
 : 4 commentaires Donner une note à l'article (5)

Article lu   fois.

Les deux auteurs

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Simulink permet de modéliser graphiquement un système et surtout de simuler son comportement. L'utilisation de Simulink a plusieurs avantages. L'environnement graphique de Simulink permet une prise en main rapide et facilite la modélisation des systèmes. De plus, la lecture d'un modèle Simulink est facilitée grâce à l'organisation graphique du système. La simulation du modèle permet d'évaluer son comportement pour le valider ou d'évaluer l'influence de certains paramètres.

Une fois que le modèle est construit, on peut le considérer comme un programme de calcul représentant notre système. Toutefois, comment faire pour partager ce modèle avec d'autres personnes qui ne sont pas forcément familières avec Simulink ou qui n'ont peut-être pas Simulink à leur disposition ?

La solution consiste à générer une bibliothèque partagée de notre modèle. On pourra ensuite l'appeler depuis un code externe (C ou C++) ou même depuis un programme MATLAB qui peut être une interface graphique permettant d'interagir avec le modèle.

L'objectif de cet article est de décrire le processus à suivre pour générer une bibliothèque partagée d'un modèle Simulink et son utilisation pour exécuter la simulation dans MATLAB et dans un programme écrit en C (indépendamment de Simulink).

Les produit MathWorks nécessaires sont les suivants :

  • MATLAB ;
  • Simulink ;
  • MATLAB Coder ;
  • Simulink Coder ;
  • Embedded Coder.

Vous trouverez plus d'informations sur ces produits sur le site de MathWorks : Produits & Services.

Notes : il est tout à fait possible de piloter la simulation d'un modèle Simulink depuis MATLAB (sans générer de code C) mais ce ne sera pas traité dans cet article.

Le modèle Simulink utilisé comme exemple dans ce tutoriel est disponible ici (au format mdl et slx).

Les bibliothèques compilées pour Windows 32 et 64 bits, Linux 64 bits et Mac 64 bits sont disponibles ici.

Les programme tests MATLAB et C sont disponibles ici

II. Modèle

II-A. Présentation

Pour illustrer ce processus, nous allons prendre l'exemple d'un modèle de réservoir d'eau avec son contrôleur. Le réservoir comprend deux vannes : une pour l'entrée (remplissage) et l'autre pour la sortie (vidange). On souhaite contrôler le débit de sortie et le niveau dans le réservoir.

Image non disponible
Schéma-bloc du modèle de réservoir

Le modèle va permettre d'étudier l'évolution au cours du temps du niveau du réservoir ainsi que le débit de sortie en fonction des valeurs cibles.

II-B. Paramètres

Il est souhaitable de rendre le modèle paramétrable pour pouvoir changer facilement de configuration sans avoir nécessairement à modifier le modèle. C'est pourquoi les paramètres du modèle doivent être définis comme des variables, créés dans le workspace de MATLAB et appelés par leurs noms (de variable) dans les blocs Simulink.

Pour faciliter la gestion des paramètres et la modification de leurs valeurs, toutes ces variables sont regroupées dans un script MATLAB nommé « reservoir_param.m ».

II-B-1. Contrôleur

  • Kp_debit, Ki_debit : gains proportionnels et intégrales du contrôleur PI de gestion du débit ;
  • Kp_niveau, Ki_niveau : gains proportionnels et intégrales du contrôleur PI de gestion du niveau ;
  • Ts_controleur : pas d'échantillonnage du contrôleur.

II-B-2. Réservoir

  • pression_entree : pression en amont de la vanne d'entrée ;
  • section_entree, section_sortie : sections disponibles lorsque les vannes sont entièrement ouvertes ;
  • surface_reservoir, volume_initial_reservoir : caractéristiques géométriques du réservoir.

II-B-3. Constantes

  • rho : densité du fluide ;
  • g : accélération de la gravité (9,81 m/s²).

II-C. Entrées

On utilise deux blocs Inport (Simulink/Sources/In1) pour les entrées correspondant au niveau et au débit cible.

Les valeurs des signaux d'entrée sont définies par des variables du workspace de MATLAB. Comme on a deux entrées, on doit définir trois vecteurs colonnes (t, niveau_cible, debit_cible). Tous ces vecteurs doivent avoir le même nombre d'éléments, t représentant le temps alors que niveau_cible et debit_cible définissent les valeurs des entrées aux instants correspondants. Ces vecteurs sont définis dans un script MATLAB nommé « reservoir_input .m ».

Pour relier ces variables aux blocs Inport du modèle, on utilise l'interface de configuration accessible depuis le menu « Simulation/Configuration Parameters » ou avec le raccourci clavier « Ctrl+E ». Dans l'onglet « Data Import/Export », il faut cocher la case « Input » dans le panel « Load from workpace » et remplir le champ d'édition correspondant avec le nom des variables à importer entre crochets. La première variable doit être le vecteur temps (t), les variables suivantes sont associées aux ports d'entrée en fonction du numéro du port et de l'ordre des variables. Dans notre cas, on écrira donc [t, niveau_cible, debit_cible].

Image non disponible
Configuration Parameters, Data Import/Export,Load from workspace

II-D. Sorties

Les sorties sont représentées par deux blocs Outport (Simulink/Sinks/Out1) pour le niveau et le débit courant.

Pour sauvegarder les signaux de sortie dans le workspace de MATLAB à la fin de la simulation, on coche les cases « Time » et « Output » du panel « Save to workspace » de l'onglet « Data Import/Export ». On peut laisser les noms de variables par défaut pour ces deux champs (tout, yout).

Image non disponible
Configuration Parameters, Data Import/Export, Save to workspace

II-E. Simulation

Avant d'utiliser le modèle, il faut exécuter les scripts MATLAB nommés « reservoir_param.m » et « reservoir_input.m ». Il faut ensuite s'assurer que toutes les variables sont dans le workspace de MATLAB.

 
Sélectionnez
>> reservoir_input
>> reservoir_param
>> whos
  Name                          Size            Bytes  Class     Attributes

  Ki_debit                      1x1                 8  double              
  Ki_niveau                     1x1                 8  double              
  Kp_debit                      1x1                 8  double              
  Kp_niveau                     1x1                 8  double              
  Ts_controleur                 1x1                 8  double              
  debit_cible                   2x1                16  double              
  g                             1x1                 8  double              
  niveau_cible                  2x1                16  double              
  pression_entree               1x1                 8  double              
  rho                           1x1                 8  double              
  section_entree                1x1                 8  double              
  section_sortie                1x1                 8  double              
  surface_reservoir             1x1                 8  double              
  t                             2x1                16  double              
  volume_initial_reservoir      1x1                 8  double              

>> open_system('gestion_reservoir')

Une fois que le modèle est construit et paramétré, on va le simuler pour vérifier qu'il fonctionne.

Si la simulation ne se lance pas, il n'est pas possible de générer la bibliothèque partagée avec Embedded Coder.

La simulation permet également d'évaluer le comportement du modèle comme on peut le voir sur les captures des scopes ci-dessous.

Image non disponible
Image non disponible

III. Génération de la bibliothèque partagée avec Embedded Coder

Pour générer une bibliothèque partagée C à partir de notre modèle, on utilise Embedded Coder que l'on va configurer à partir de la fenêtre de configuration « Configuration Parameters » accessible depuis le menu « Simulation » ou via le raccourci clavier « Ctrl+E ».

III-A. Cible pour générer le code

La cible (ou « System Target File ») est le premier élément à configurer pour générer du code à partir d'un modèle Simulink. Le champ correspondant se trouve dans l'onglet « Code Generation » de l'interface de configuration (Configuration Parameters). Le bouton « Browse » permet de parcourir les cibles disponibles. Pour générer une bibliothèque partagée C, il faut choisir la cible « ert_shrlib.tlc ».

Image non disponible
Configuration Parameters : choix de la cible de génération de code

III-B. Solveur

La génération de code impose l'utilisation d'un solveur à pas fixe. Le choix du type de solveur s'effectue dans l'onglet « Solver » avec la propriété « Type ». Le modèle contenant des états continus (blocs Integrator), il faut utiliser un solveur de type « ODE » (pour Ordinary Differential Equations ou Équations différentielles ordinaires), nous choisirons le solveur ODE1. La valeur du pas doit être mise à « auto »

Image non disponible
Configuration Parameters : définition du solveur

Comme le modèle contient des états continus, il faut aussi activer le support du « temps continu » en cochant la case « Continuous time » dans l'onglet « Code Generation/Interface ».

Image non disponible
Configuration Parameters : état continu

III-C. Paramètres

Lorsque le code sera compilé, les paramètres ne seront accessibles que s'ils sont définis comme « tunable » dans le modèle Simulink. Pour qu'un paramètre puisse être « tunable », sa valeur doit être définie dans le workspace de MATLAB. L'option « Inline Parameters » de l'onglet (« Optimization/Signals and Parameters ») permet d'activer le support des « Tunable Parameters » dans le modèle.

Image non disponible
Configuration Parameters : Inline Parameters

Le bouton « Configure » permet d'ouvrir la fenêtre de sélection des paramètres « tunable ». Dans la liste de gauche, toutes les variables du workspace de MATLAB doivent apparaître. On sélectionne les paramètres du modèle et on les définit comme « Tunable » avec le bouton « Add to table ».

Image non disponible
Configuration Parameters : Tunable Parameters

III-D. Compilation du modèle

Pour pouvoir compiler le modèle, il doit être fonctionnel, c'est-à-dire que toutes les variables utilisées dans Simulink (comme paramètres) doivent être définies dans le workspace de base de MATLAB.

On génère la bibliothèque partagée en cliquant sur le bouton « Build » qui est disponible dans l'onglet « Code Generation » ou en utilisant le raccourci clavier « Ctrl+B » dans le modèle. Le succès de la compilation est précisé par la ligne suivante qui apparaît dans le Command Window de MATLAB :

 
Sélectionnez
### Successful completion of build procedure for model: gestion_reservoir

La compilation crée plusieurs fichiers et dossiers comme le montre la capture d'écran suivante réalisée sur Windows 32 bits :

Image non disponible

Selon le système d'exploitation, le fichier de la bibliothèque partagée portera le nom :

  • « gestion_reservoir_win32.dll » avec Windows 32 bits ;
  • « gestion_reservoir_win64.dll » avec Windows 64 bits ;
  • « gestion_reservoir.so » avec Linux 64 bits et Mac OS X 64 bits.

Le dossier « slprj » contient des fichiers temporaires utilisés uniquement lors de la génération et peut donc être supprimé.

Le dossier « gestion_reservoir_ert_shrlib_rtw » contient tous les fichiers créés par la génération de code. On y trouve entre autres, le code source de la bibliothèque partagée (gestion_reservoir.c et gestion_reservoir.h). Ce dossier devra être fourni avec la bibliothèque partagée lors de son déploiement.

III-E. Création du package des sources

Pour appeler une bibliothèque, il est souvent nécessaire de fournir les fichiers d'entête en plus de la bibliothèque elle-même. Comme il n'est pas toujours évident de retrouver tous les fichiers .h relatif à la bibliothèque générée par Embedded Coder, on va utiliser la fonction packNGo pour créer une archive ZIP regroupant tous les fichiers sources (.c et .h) utilisés par le modèle (et donc par la bibliothèque).

Depuis le répertoire où a été générée la bibliothèque partagée (qui doit être le répertoire courant de MATLAB), on lance les commandes MATLAB suivantes :

 
Sélectionnez
load('gestion_reservoir_ert_shrlib_rtw/buildInfo.mat')
packNGo(buildInfo)

Ceci a pour effet de créer l'archive « gestion_reservoir.zip » comme le montre la figure suivante :

Image non disponible

Avec les fichiers « gestion_reservoir_win32.dll » et « gestion_reservoir.zip », l'exécution du modèle est totalement indépendante de MATLAB/Simulink.

Rappel : le suffixe « _win32 » et l'extension « .dll » dépendent du système d'exploitation sur lequel le code a été compilé (ici Windows 32 bits).

Téléchargez les bibliothèques compilées pour Windows, Linux et Mac : lib.zip

IV. Appel de la bibliothèque partagée depuis MATLAB

La bibliothèque partagée qui vient d'être générée peut être utilisée pour simuler le modèle sans aucun besoin de MATLAB et/ou Simulink. On peut, par exemple, intégrer cette bibliothèque partagée dans un programme C sur un ordinateur n'ayant pas MATLAB.

Pour ce tutoriel, nous allons rester dans MATLAB pour appeler le code compilé du modèle. À partir de ce point, il n'est plus nécessaire d'avoir Simulink.

L'application étant indépendante de Simulink, il est possible d'utiliser toutes les possibilités de MATLAB comme les interfaces graphiques ou le déploiement du code avec MATLAB Compiler bien que ces points ne soient pas abordés dans ce tutoriel.

Rappel : le suffixe « _win32 » et l'extension « .dll » dépendent du système d'exploitation sur lequel le code a été compilé (ici Windows 32 bits).

IV-A. Création du fichier d'entête

Le chargement d'une librairie dans MATLAB nécessite un fichier d'entête (.m) qui contiennent toutes les informations sur les interfaces de la bibliothèque partagée. Ce fichier peut être généré facilement avec la fonction loadlibrary à partir de la bibliothèque partagée et de l'archive « gestion_reservoir.zip ».

La bibliothèque partagée et l'archive « gestion_reservoir.zip » doivent se trouver dans le répertoire courant de MATLAB.

Par exemple sur Windows 32 bits :

 
Sélectionnez
unzip('gestion_reservoir.zip', 'gestion_reservoir')
loadlibrary('gestion_reservoir_win32.dll', 'gestion_reservoir\gestion_reservoir.h','mfilename','mHeader')
unloadlibrary('gestion_reservoir_win32')
Image non disponible
Librairie et fichier d'entête associé

IV-B. Chargement de la bibliothèque

Dans le code MATLAB, charger la bibliothèque en utilisant la fonction loadlibrary. Il faut préciser le nom de la bibliothèque ainsi que l'identifiant de fonction (« @ ») vers le fichier d'entête « mHeader.m » créé précédemment.

Par exemple sur Windows 32 bits :

 
Sélectionnez
loadlibrary('gestion_reservoir_win32.dll', @mHeader)

On peut utiliser la fonction libisloaded pour vérifier si la bibliothèque est déjà chargée.

La fonction unloadlibrary permet de décharger la bibliothèque de la mémoire une fois qu'on a fini de l'utiliser.

Avant d'utiliser unloadlibrary, il faut s'assurer qu'aucune variable liée à la bibliothèque n'existe dans le workspace. Si c'est le cas, il faut les supprimer à l'aide de la fonction clear.

IV-C. Utilisation de la bibliothèque

Pour appeler les fonctions de la bibliothèque, il faut utiliser la fonction calllib, en précisant le nom de la bibliothèque, le nom de la fonction à appeler et, éventuellement, les paramètres de la fonction.

Les principales fonctions utilisées dans la bibliothèque (avec leur syntaxe d'appel) sont présentées ci-après.

Rappel : le suffixe « _win32 » dépend du système d'exploitation sur lequel le code a été compilé (Windows 32 bits dans les exemples suivants).

IV-C-1. Initialisation du modèle

La fonction dans la bibliothèque partagée s'appelle gestion_reservoir_initialize :

 
Sélectionnez
calllib('gestion_reservoir_win32', 'gestion_reservoir_initialize');

Note : sur les versions plus anciennes de MATLAB, il faut rajouter un argument de type UINT8.

 
Sélectionnez
calllib('gestion_reservoir_win32', 'gestion_reservoir_initialize', uint8(0));

Si cet argument est absent, MATLAB renvoie l'erreur suivante :

 
Sélectionnez
Error using calllib
No method with matching signature.

IV-C-2. Calcul d'un pas

La fonction gestion_reservoir_step permet d'effectuer un pas de calcul, pour faire une simulation. On peut donc utiliser une boucle for-end qui appelle cette fonction de la façon suivante :

 
Sélectionnez
calllib('gestion_reservoir_win32', 'gestion_reservoir_step');

IV-C-3. Finalisation

Une fois que la simulation est terminée, on appelle la fonction de finalisation :

 
Sélectionnez
calllib('gestion_reservoir_win32', 'gestion_reservoir_terminate');

IV-C-4. Paramètres

Pour accéder à la structure de paramètres, on utilise la commande suivante :

 
Sélectionnez
param = calllib('gestion_reservoir_win32', 'gestion_reservoir_P');

où la variable « param » est un pointeur vers la structure de paramètres.

Pour accéder et modifier les valeurs des paramètres, on utilise la syntaxe suivante :

 
Sélectionnez
param.Value.<nom_du_parametre>

IV-C-5. Entrées

Le fonctionnement est le même que pour les paramètres à la différence que la structure se nomme « _U » ce qui donne :

 
Sélectionnez
inputs = calllib('gestion_reservoir_win32', 'gestion_reservoir_U');

Les noms des champs sont définis par les noms des ports d'entrée.

IV-C-6. Sorties

Le fonctionnement est le même que pour les paramètres à la différence que la structure se nomme « _Y » ce qui donne :

 
Sélectionnez
outputs = calllib('gestion_reservoir_win32', 'gestion_reservoir_Y');

Les noms des champs sont définis par les noms des ports de sortie.

IV-D. Script MATLAB

En utilisant tous les principes vus dans cette section, on peut créer ce type de script (ou fonction) permettant de simuler notre modèle à partir de MATLAB en utilisant la bibliothèque partagée.

Les fichiers nécessaires à l'exécution du script sont la bibliothèque partagée (gestion_reservoir_win32.dll pour Windows 32 bits) et le fichier d'entête (mHeader.m) qui doivent se trouver dans le même répertoire que le script.

 
TéléchargerSélectionnez
modelname = 'gestion_reservoir';

arch = computer('arch');

if strcmp(arch,'win64') || strcmp(arch,'win32')
    ext = '.dll';
    libname = [modelname '_' arch];
else
    ext = '.so';
    libname = modelname;
end

zipfile = [modelname '.zip'];
libfile = [libname ext];

unzip(zipfile , modelname);
delete(zipfile)

loadlibrary(libfile, ['./' modelname '/' modelname '.h'],'mfilename','mHeader')
unloadlibrary(libname)

% Chargement de la bibliotheque apres verification qu'elle n'est pas deja chargee
if ~libisloaded(libname)
    loadlibrary(libfile,@mHeader)
end

% Initialisation de la simulation
calllib(libname, [modelname '_initialize'], uint8(0))

% Recuperation des pointeurs vers les structures des entrees, sorties et parametres
entrees = calllib(libname,[modelname '_U']);
sorties = calllib(libname,[modelname '_Y']);

% Definition des variables utilisees pour la simulation
N = 600;
niveau_cible = 2;
debit_cible = 0.0003;
niveau_courant = zeros(N,1);
debit_courant = zeros(N,1);

% Simulation sur 1000 pas de temps
for i = 1:N
    
    % Affectation des valeurs d'entree
    entrees.Value.niveau_cible = niveau_cible;
    entrees.Value.debit_cible = debit_cible;
    
    % Calcul d'un pas
    calllib(libname, [modelname '_step'])
    
    % Stockage des valeurs de sortie dans les variables correspondantes
    niveau_courant(i) = sorties.Value.niveau_courant;
    debit_courant(i) = sorties.Value.debit_courant;
    
end

% Finalisation de la simulation
calllib(libname, [modelname '_terminate'])

% Liberation de la memoire
unloadlibrary(libname)

% Trace des resultats
figure

subplot(211)
plot(1:N,niveau_courant)
title('Niveau courant')

subplot(212)
plot(1:N,debit_courant)
title('Debit courant')

clear entrees sorties

Voici les courbes de niveau et de débit obtenues :

Image non disponible

Elles sont similaires à celles obtenues avec Simulink (voir le chapitre II-E. Simulation).

V. Appel de la bibliothèque partagée depuis un code C

V-A. Chargement de la bibliothèque

Dans le code C, la bibliothèque se charge en utilisant les fonctions standards de la bibliothèque C.

Avec Windows :

 
Sélectionnez
#include <windows.h>

#if defined(_WIN64) /* Windows 64 bits */
#define LIBNAME "./gestion_reservoir_win64.dll"
#elif defined(_WIN32) /* Windows 32 bits */
#define LIBNAME "./gestion_reservoir_win32.dll"
#endif

int main(void) {

    void *handleLib;

    handleLib = LoadLibrary(LIBNAME);
    
    /* Utiliser les fonctions de la bibliothèque ici */
    
    FreeLibrary(handleLib);
 
    return 0;
    
}

Avec Linux ou Mac :

 
Sélectionnez
#include <dlfcn.h>

#define LIBNAME "./gestion_reservoir.so"

int main(void) {

    void *handleLib;

    handleLib = dlopen(LIBNAME);
    
    /* Utiliser les fonctions de la bibliothèque ici */
    
    dlclose(handleLib);
 
    return 0;
    
}

V-B. Utilisation de la bibliothèque

Les principales fonctions utilisées dans la bibliothèque (avec leur syntaxe d'appel) sont présentées ci-après.

Le corps des fonctions ainsi que leur déclaration sont contenus dans les fichiers « gestion_reservoir.c » et « gestion_reservoir.h » du dossier « gestion_reservoir_ert_shrlib_rtw »

V-B-1. Initialisation du modèle

La fonction dans la bibliothèque partagée s'appelle « gestion_reservoir_initialize ».

Le pointeur à initialiser :

 
Sélectionnez
void (*mdl_initialize)(boolean_T);

L'initialisation de la fonction sur Windows :

 
Sélectionnez
mdl_initialize = (void(*)(boolean_T))GetProcAddress(handleLib , "gestion_reservoir_initialize");

L'initialisation de la fonction sur Linux et Mac :

 
Sélectionnez
mdl_initialize = (void(*)(boolean_T))dlsym(handleLib , "gestion_reservoir_initialize");

L'appel de la fonction :

 
Sélectionnez
mdl_initialize();

Sur les versions plus anciennes de MATLAB, il faut rajouter un argument de type boolean_T :

 
Sélectionnez
boolean_T firstTime = 1;
					
mdl_initialize(firstTime);

V-B-2. Calcul d'un pas

La fonction « gestion_reservoir_step » permet d'effectuer un pas de calcul, pour faire une simulation. On peut donc utiliser une boucle for qui appelle cette fonction de la façon suivante :

Le pointeur à initialiser :

 
Sélectionnez
void (*mdl_step)(void);

L'initialisation de la fonction sur Windows :

 
Sélectionnez
mdl_step = (void(*)(void))GetProcAddress(handleLib , "gestion_reservoir_step");

L'initialisation de la fonction sur Linux et Mac :

 
Sélectionnez
mdl_step = (void(*)(void))dlsym(handleLib , "gestion_reservoir_step");

L'appel de la fonction :

 
Sélectionnez
mdl_step();

V-B-3. Finalisation

Une fois que la simulation est terminée, on appelle la fonction de finalisation « gestion_reservoir_terminate ».

Le pointeur à initialiser :

 
Sélectionnez
void (*mdl_terminate)(void);

L'initialisation de la fonction sur Windows :

 
Sélectionnez
mdl_terminate = (void(*)(void))GetProcAddress(handleLib , "gestion_reservoir_terminate");

L'initialisation de la fonction sur Linux et Mac :

 
Sélectionnez
mdl_terminate = (void(*)(void))dlsym(handleLib , "gestion_reservoir_terminate");

L'appel de la fonction :

 
Sélectionnez
mdl_terminate();

V-B-4. Paramètres

Les paramètres sont stockés dans une structure « Parameters_gestion_reservoir_T_ » (définie dans le fichier « gestion_reservoir.h »).

Le pointeur à initialiser :

 
Sélectionnez
struct Parameters_gestion_reservoir_T_ (*mdl_Pptr);

L'initialisation de la structure sur Windows :

 
Sélectionnez
mdl_Pptr = (struct Parameters_gestion_reservoir_T_*)GetProcAddress(handleLib , "gestion_reservoir_P");

L'initialisation de la structure sur Linux et Mac :

 
Sélectionnez
mdl_Pptr = (struct Parameters_gestion_reservoir_T_*)dlsym(handleLib , "gestion_reservoir_P");

Les paramètres disponibles sont ceux créés avec le script « reservoir_param.m »

Par exemple :

 
Sélectionnez
printf("%.2f\n", mdl_Pptr->g);

Ce qui renvoie bien :

 
Sélectionnez
9.81

V-B-5. Entrées

Le fonctionnement est le même que pour les paramètres à la différence que la structure se nomme « ExternalInputs_gestion_reserv_T » (définie dans le fichier « gestion_reservoir.h »).

Le pointeur à initialiser :

 
Sélectionnez
ExternalInputs_gestion_reserv_T (*mdl_Uptr);

L'initialisation de la structure sur Windows :

 
Sélectionnez
mdl_Uptr = (ExternalInputs_gestion_reserv_T*)GetProcAddress(handleLib , "gestion_reservoir_U");

L'initialisation de la structure sur Linux et Mac :

 
Sélectionnez
mdl_Uptr = (ExternalInputs_gestion_reserv_T*)dlsym(handleLib , "gestion_reservoir_U");

Les noms des champs sont définis par les noms des ports d'entrée.

Par exemple :

 
Sélectionnez
mdl_Uptr->niveau_cible = 2.0;

V-B-6. Sorties

Le fonctionnement est le même que pour les paramètres à la différence que la structure se nomme « ExternalOutputs_gestion_reser_T » (définie dans le fichier « gestion_reservoir.h »).

Le pointeur à initialiser :

 
Sélectionnez
ExternalOutputs_gestion_reser_T (*mdl_Yptr);

L'initialisation de la structure sur Windows :

 
Sélectionnez
mdl_Yptr = (ExternalOutputs_gestion_reser_T*)GetProcAddress(handleLib , "gestion_reservoir_Y");

L'initialisation de la structure sur Linux et Mac :

 
Sélectionnez
mdl_Yptr = (ExternalOutputs_gestion_reser_T*)dlsym(handleLib , "gestion_reservoir_Y");

Les noms des champs sont définis par les noms des ports de sortie.

Par exemple :

 
Sélectionnez
printf("%.6f", mdl_Yptr->niveau_courant);

V-C. Programme C « multiplateforme »

Voici un code C qui reprend tout ce qui a été présenté précédemment. Le fonctionnement de ce code est équivalent à celui du script MATLAB du chapitre IV-D.

 
TéléchargerSélectionnez
#if (defined(_WIN32)||defined(_WIN64)) /* Windows */
#include <windows.h>
#define GETSYMBOLADDR GetProcAddress
#define LOADLIB LoadLibrary
#define CLOSELIB FreeLibrary
 
#else /* UNIX - Linux - Mac */
#include <dlfcn.h>
#define GETSYMBOLADDR dlsym
#define LOADLIB dlopen
#define CLOSELIB dlclose
 
#endif
 
#if defined(_WIN64) /* Windows 64 bits */
#define LIBNAME "./gestion_reservoir_win64.dll"
#elif defined(_WIN32) /* Windows 32 bits */
#define LIBNAME "./gestion_reservoir_win32.dll"
#else /* UNIX - Linux - Mac */
#define LIBNAME "./gestion_reservoir.so"
#endif
 
#include "gestion_reservoir/gestion_reservoir.h"
#include <stdio.h>
#include <stdlib.h>
 
#define N 600
 
int main(void) {
 
    boolean_T firstTime = 1;
    int i;
 
    void *handleLib;
    void (*mdl_initialize)(boolean_T);
    void (*mdl_step)(void);
    void (*mdl_terminate)(void);
 
    double niveau_courant[N];
    double debit_courant[N];
 
    double niveau_cible = 2.0;
    double debit_cible = 0.0003;
 
    ExternalInputs_gestion_reserv_T (*mdl_Uptr);
    ExternalOutputs_gestion_reser_T (*mdl_Yptr);
 
    #if (defined(_WIN32)||defined(_WIN64)) /* Windows */
    handleLib = LOADLIB(LIBNAME);
    #else /* UNIX - Linux - Mac */
    handleLib = LOADLIB(LIBNAME, RTLD_LAZY);
    #endif

    mdl_initialize = (void(*)(boolean_T))GETSYMBOLADDR(handleLib , "gestion_reservoir_initialize");
    mdl_terminate = (void(*)(void))GETSYMBOLADDR(handleLib , "gestion_reservoir_terminate");
    mdl_step = (void(*)(void))GETSYMBOLADDR(handleLib , "gestion_reservoir_step");
 
    mdl_Uptr = (ExternalInputs_gestion_reserv_T*)GETSYMBOLADDR(handleLib , "gestion_reservoir_U");
    mdl_Yptr = (ExternalOutputs_gestion_reser_T*)GETSYMBOLADDR(handleLib , "gestion_reservoir_Y");
 
    mdl_initialize(firstTime);
 
    for(i=0; i<N; i++) {
 
        mdl_Uptr->niveau_cible = niveau_cible;
        mdl_Uptr->debit_cible = debit_cible;
 
        mdl_step();
 
        niveau_courant[i] = mdl_Yptr->niveau_courant;
        debit_courant[i] = mdl_Yptr->debit_courant;
 
    }
 
    mdl_terminate();
 
    for(i=0;i<N;i++)
        printf("%.6f %.6f\n", niveau_courant[i], debit_courant[i]);
 
 
    CLOSELIB(handleLib);
 
    return 0;
 
}

Voici un code MATLAB permettant de lancer automatiquement l'exécutable compilé à partir du code précédent.

 
TéléchargerSélectionnez
modelname = 'gestion_reservoir';

arch = computer('arch');

if strcmp(arch,'win64') || strcmp(arch,'win32')
    ext = '.dll';
    libname = [modelname '_' arch];
else
    ext = '.so';
    libname = modelname;
end

zipfile = [modelname '.zip'];
libfile = [libname ext];

unzip(zipfile , modelname);
delete(zipfile)

if exist('test.txt','file')==2
    delete('test.txt');
end

if exist('test','file')~=2
    error('executable not found')
end

if ispc
    system('test > test.txt');
else
    system('./test > test.txt');
end

if exist('test.txt','file')~=2
    error('txt not found')
end

x = load('test.txt');
N = size(x,1);

figure;

subplot(211);
plot(1:N,x(:,1));
title('Niveau courant');

subplot(212);
plot(1:N,x(:,2))
title('Debit courant');

Voici les courbes de niveau et de débit obtenues :

Image non disponible

Elles sont similaires à celles obtenues avec Simulink (chapitre II-E) et avec le script MATLAB (chapitre IV-D).

VI. Problèmes et solutions

Ce chapitre liste quelques problèmes que vous pourriez rencontrer lors de la simulation ou de la compilation du modèle.

VI-A. ??? File "gestion_reservoir.mdl" contains characters which are incompatible with the current character encoding, UTF-8

Cette erreur peut survenir lorsqu'on tente d'ouvrir sur Linux ou Mac, un modèle créé sur Windows.

Le message d'erreur sera similaire au suivant :

 
Sélectionnez
>> open_system('gestion_reservoir')
??? File "/media/D/programmation/matlab/tests/duf42/gr/model/gestion_reservoir.mdl" contains characters which are incompatible with the current character
encoding, UTF-8. To avoid this error, do one of the following:
     1) Use the slCharacterEncoding function to change the current character encoding to one of: windows-1252, ISO-8859-1.
     2) Remove the unsupported characters. The first unsupported character is at line 1225, byte offset 43.

La solution au problème se trouve dans le message d'erreur et consiste à utiliser la fonction slCharacterEncoding comme ceci :

 
Sélectionnez
>> slCharacterEncoding('windows-1252')
>> open_system('gestion_reservoir')

VI-B. undefined reference to 'sqrt'

Lors de la compilation du modèle avec Embedded Coder, le message suivant peut apparaitre :

 
Sélectionnez
gestion_reservoir.o: In function `gestion_reservoir_step':
gestion_reservoir.c:(.text+0x43d): undefined reference to `sqrt'
gestion_reservoir.c:(.text+0x629): undefined reference to `sqrt'
collect2: ld a retourné 1 code d'état d'exécution
gmake: *** [gestion_reservoir.so] Erreur 1

Ceci est dû au fait que Embedded Coder ne trouve pas automatiquement le chemin vers certaines bibliothèques standards (ici libmath).

La solution à ce problème se trouve dans le menu « Code Generation > Custom Code » et dans la partie en bas « Include list of additional ».

Il faut :

  • ajouter le chemin vers la bibliothèque non trouvée dans « include directories » ;
  • ajouter le nom du fichier (avec l'extension) de la bibliothèque non trouvée dans « Libraries ».

VI-C. Erreur « En-tête ELF invalide » au chargement de la bibliothèque

Cette erreur peut survenir à l'appel de la fonction loadlibrary. Pour la corriger, il suffit de mettre l'extension « .dll » ou « .so » du fichier :

 
Sélectionnez
loadlibrary('gestion_reservoir.so', './gestion_reservoir/gestion_reservoir.h','mfilename','mHeader')

VI-D. Nombreux « Warning » au chargement de la bibliothèque

Une liste d'avertissements peut apparaître lors du chargement de la bibliothèque avec loadlibrary :

 
Sélectionnez
>> loadlibrary('gestion_reservoir_win64', 'gestion_reservoir\gestion_reservoir.h','mfilename','mHeader')
Warning: The data type 'doublePtr#1' used by structure ODE1_IntgData does not exist.  The structure may not be usable. 
> In loadlibrary at 402 
Warning: The function 'gestion_reservoir_B' was not found in the library 
> In loadlibrary at 402 
Warning: The function 'gestion_reservoir_X' was not found in the library 
> In loadlibrary at 402 
Warning: The function 'gestion_reservoir_DWork' was not found in the library 
> In loadlibrary at 402

Ces avertissements n'ont pas de conséquence sur le bon fonctionnement du code de la bibliothèque partagée.

Ce problème est dû au fait que le fichier « mHeader.m » définit toutes les structures qui pourraient être contenues dans la bibliothèque. Mais, selon les modèles, certaines ne sont pas générées car non nécessaires. C'est le cas pour les fonctions « gestion_reservoir_B », « gestion_reservoir_X » et « gestion_reservoir_DWork » dans ce cas précis.

VI-E. « no such sysroot directory: '/Developer/SDKs/MacOSX10.6.sdk' »

Cette erreur est due à l'utilisation d'une ancienne version de MATLAB sur une version récente de Mac OS X (avec une version récente du SDK).

Dans ce cas, il faut modifier le chemin vers le SDK dans le fichier « /rwt/c/tools/unixtools.mk ».

Par exemple :

 
Sélectionnez
SDKROOT = /Developer/SDKs/MacOSX10.6.sdk
MACOSX_DEPLOYMENT_TARGET = 10.5

par

 
Sélectionnez
SDKROOT='/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/' 
MACOSX_DEPLOYMENT_TARGET = 10.8

Ou par un chemin équivalent à votre version de Mac OS X.

VII. Conclusion

L'objectif de ce tutoriel était de décrire les étapes à suivre pour compiler un modèle Simulink sous forme de bibliothèque partagée C et de décrire comment appeler celle-ci depuis MATLAB ou depuis un code externe en C.

Compiler un modèle Simulink avec Embedded Coder permet de :

  • lancer des simulations sur des postes n'ayant pas Simulink ;
  • accélérer les simulations (code compilé vs. code interprété) ;
  • protéger l'algorithme du modèle Simulink.

Piloter une simulation compilée depuis MATLAB permet de :

  • simplifier le paramétrage de la simulation et la visualisation des résultats à l'aide d'une interface graphique ;
  • déployer l'application avec le MATLAB Compiler pour une utilisation sur des postes n'ayant ni MATLAB ni Simulink.

Lancer une simulation compilée depuis un code C externe permet de :

  • distribuer l'application à des utilisateurs ne maîtrisant ni MATLAB ni Simulink.

Pour la création d'interfaces graphiques dans MATLAB, vous pouvez vous reporter aux tutoriels existants sur le site.

Remerciements

Nous remercions Torgar pour la correction orthographique de cet article.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2013 duf42 et Jérôme Briot. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.