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.
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].
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).
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.
>> 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.
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 ».
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 »
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 ».
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.
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 ».
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 :
### 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 :
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 :
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 :
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 :
unzip('gestion_reservoir.zip'
, 'gestion_reservoir'
)
loadlibrary('gestion_reservoir_win32.dll'
, 'gestion_reservoir\gestion_reservoir.h'
,'mfilename'
,'mHeader'
)
unloadlibrary('gestion_reservoir_win32'
)
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 :
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 :
calllib('gestion_reservoir_win32'
, 'gestion_reservoir_initialize'
);
Note : sur les versions plus anciennes de MATLAB, il faut rajouter un argument de type UINT8.
calllib('gestion_reservoir_win32'
, 'gestion_reservoir_initialize'
, uint8(0));
Si cet argument est absent, MATLAB renvoie l'erreur suivante :
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 :
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 :
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 :
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 :
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 :
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 :
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.
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 :
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 :
#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 :
#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 :
void
(*
mdl_initialize)(
boolean_T);
L'initialisation de la fonction sur Windows :
mdl_initialize =
(
void
(*
)(
boolean_T))GetProcAddress
(
handleLib , "
gestion_reservoir_initialize
"
);
L'initialisation de la fonction sur Linux et Mac :
mdl_initialize =
(
void
(*
)(
boolean_T))dlsym
(
handleLib , "
gestion_reservoir_initialize
"
);
L'appel de la fonction :
mdl_initialize
(
);
Sur les versions plus anciennes de MATLAB, il faut rajouter un argument de type boolean_T :
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 :
void
(*
mdl_step)(
void
);
L'initialisation de la fonction sur Windows :
mdl_step =
(
void
(*
)(
void
))GetProcAddress
(
handleLib , "
gestion_reservoir_step
"
);
L'initialisation de la fonction sur Linux et Mac :
mdl_step =
(
void
(*
)(
void
))dlsym
(
handleLib , "
gestion_reservoir_step
"
);
L'appel de la fonction :
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 :
void
(*
mdl_terminate)(
void
);
L'initialisation de la fonction sur Windows :
mdl_terminate =
(
void
(*
)(
void
))GetProcAddress
(
handleLib , "
gestion_reservoir_terminate
"
);
L'initialisation de la fonction sur Linux et Mac :
mdl_terminate =
(
void
(*
)(
void
))dlsym
(
handleLib , "
gestion_reservoir_terminate
"
);
L'appel de la fonction :
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 :
struct
Parameters_gestion_reservoir_T_ (*
mdl_Pptr);
L'initialisation de la structure sur Windows :
mdl_Pptr =
(
struct
Parameters_gestion_reservoir_T_*
)GetProcAddress
(
handleLib , "
gestion_reservoir_P
"
);
L'initialisation de la structure sur Linux et Mac :
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 :
printf
(
"
%.2f
\n
"
, mdl_Pptr->
g);
Ce qui renvoie bien :
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 :
ExternalInputs_gestion_reserv_T (*
mdl_Uptr);
L'initialisation de la structure sur Windows :
mdl_Uptr =
(
ExternalInputs_gestion_reserv_T*
)GetProcAddress
(
handleLib , "
gestion_reservoir_U
"
);
L'initialisation de la structure sur Linux et Mac :
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 :
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 :
ExternalOutputs_gestion_reser_T (*
mdl_Yptr);
L'initialisation de la structure sur Windows :
mdl_Yptr =
(
ExternalOutputs_gestion_reser_T*
)GetProcAddress
(
handleLib , "
gestion_reservoir_Y
"
);
L'initialisation de la structure sur Linux et Mac :
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 :
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.
#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.
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 :
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 :
>> 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 :
>> 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 :
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 :
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 :
>> 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 :
SDKROOT = /Developer/SDKs/MacOSX10.6.sdk
MACOSX_DEPLOYMENT_TARGET = 10.5
par
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.