dSpace DS1102 / DS1104 : C programs for induction motor vector control
dSpace DS1102 / DS1104 : programmes en C de commande vectorielle de machine asychrone


How to implement vector control of induction motors on dSpace single boards "all in one" DS1102 and DS1104.
Executable and sources are provided.

Version Fr

Présentation des cartes :

Les cartes "dSpace single boards" sont idéales pour les applications pédagogiques ou de recherche à budget limité puisqu'elles sont disponibles en ACE kit, c'est-à-dire en kit éducation à des prix très attractifs.

DS1103 and DS1104 single board

Elles disposent d'un DSP (Master) de calcul en virgule flottante et d'un DSP (Slave), en virgule fixe, qui s'occupe de la PWM et des entrées sorties numériques.
Elles intègrent, en ce qui nous concerne, des convertisseurs analogiques-numériques (ADC), numériques-analogiques, interfaces série, des timers…
La gamme est composée de :
Attention les cartes DS1102/1103 ne peuvent être placées que sur un bus ISA, vérifier alors la carte mère du PC hôte.
La carte DS1104 est de type PCI et s'intègre dans des PC plus récents. C'est le choix que l'on préconise pour ce type d'application.

Notion de DSP :

Un DSP est un processeur spécialisé dans le traitement de signal. Il intègre des périphériques comme un microcontrôleur (interface de communication Série (SPI, SCI), PWM, ADC...) mais il a la puissance de calcul d'un micro-processeur (multiplication).
Aujourd'hui, la frontière est floue entre DSP et microcontrôleur. Certain de ces derniers, notamment ceux dédiés au contrôle-commande, intègre un noyau DSP.

Voici l'exemple d'une chaîne de traitement numérique du signal. On distingue clairement, les composants d'acquisition (filtre, ADC (échantillonneur, convertisseur), de traitement (DSP) et de restitution (DAC, filtre).

DSP traitement numérique

Ce diagramme illustre le traitement d'un son par exemple.


Notre but ici n'est pas de faire un cours sur les DSP mais de présenter des exemples d'utilisation en commande de machine avec fourniture du code source.
schema du banc expérimental

Schéma synoptique du banc de machine (photos) que nous utilisons.


Les programmes sont écrits en C pour les cartes DS1102 et DS1104.
Ce n'est pas de la programmation visuelle à travers Matlab-Simulink-RTI mais j'utilise la RTlib de dSpace pour accéder aux périphériques de la carte.
Les noms des fonctions sont similaires aux noms des blocs de la RTI.

Les programmes présentés :


DS1102 : DS1104 : Ce lien vous renvoie sur un cours sur la modélisation et la commande de machines asynchrones.





DS1102 :

Commande vectorielle IRFO de la MAS, régulation des courants, de la vitesse et de la position.


Il faut comprendre d'abord la structure du programme :
Rajoutons les fichiers qui servent à gérer le projet et à l'interfacer avec ControlDesk : Astuce :
Il n'y a pas sur la carte DS1102 une synchronisation par défaut entre le TMS320 C31 et le Slave : TMS320 P14.
Il est cependant nécessaire que la mesure du courant (lancement des ADC par le Master DSP) soit synchronisée avec la PWM (gérée par le Slave DSP). Nous avons alors modifié le code de la PWM du Slave de manière à introduire un retard de lancement d'un 2ème timer par rapport à celui de la PWM de manière à générer un pulse sur la pin n°28 IOP0 au milieu de la période de la PWM.
Comme c'est une PWM symétrique, cela veut dire que la mesure de courant de phase sera la moins perturbée possible et donnera le courant moyen sur la période PWM.

Côté Master DSP, on installe une routine d'interruption (ligne 15 et 50-53 de Indc.c). Elle sera appelée quand la pin n°6 INTEXT .
Pour que cela marche il ne reste plus qu'à relier les pins 28 IOP0 et 6 INTEXT entre elles (voir Pinout de la carte DS1102).


Examinons les lignes de code suivantes (voir commentaires en dessous):
Le fichier principal : Indc.c
   
  1. #include <brtenv.h>         /* basic real-time environment */
  2. #include <math.h>  /* sin cos */
  3. #include "ctrl.h"  // Declaration des fonctions + macros
  4.                     //+ includes Declaration de variables + initialisation
  5.  
  6. /*---------------------------------------------------------------------------*/
  7. /* pour l'interruption */
  8. #define ioctl   ((volatile long *) 0x20000)
  9. #define dspeoi0 0x00100000
  10. #define iomsk   0x00008000
  11. #define int0vec ((void (* volatile *)()) 0x0001)
  12.  
  13. unsigned long count0;                             /* timer0 time count */
  14.  
  15. void c_int01()          /* routine de gestion de int0 */
  16. {
  17.   /*  ds1102_da(2, 0.5);   signal 2 ! out */
  18.   ds1102_ad_start();      /* capture les courants */
  19.   RTLIB_TIC_START();                              /* start time measurement */
  20.   /*  ds1102_da(2, 0.0); signal 2 ! out */
  21.  *ioctl = (*ioctl & iomsk) | dspeoi0;  /* clear int0 request */
  22.  *ioctl;                               /* sync to posted write               */
  23.   asm ("  ANDN  01H,IF");              /* clear int0 flag  */
  24.   switch(countCurrent)
  25.     {
  26.     case 0  : RegulI_Control();
  27.               break;
  28.     case 1  : Regul_Vitesse();
  29.               host_service(1, 0);    /* Data Acquisition service */
  30.               break;
  31.     }
  32.   if (++countCurrent==DS_IReg)  countCurrent=0;
  33.   exec_time = RTLIB_TIC_READ(); /*  calculate execution time */
  34. }
  35.  
  36.  
  37. /*---------------------------------------------------------------------------*/
  38. void main(void)
  39. {
  40.   init();                                /* initialize hardware system */
  41.   initVar();      // initialise les variables locales
  42.  
  43.   /* download standard communication and PWM code to P14 slave processor */
  44.   load_slave();
  45.   ds1102_inc_clear_counter( 1);       /* clear incremental encoder 1 */
  46.  
  47.   msg_info_set(MSG_SM_RTLIB, 0, "System started from Lotfi.");
  48.   RTLIB_TIC_INIT();                 /* enable execution time measurement */
  49.  
  50. /* -----------   Installation de l'interruption  ------------ */
  51.   *int0vec = c_int01;                   /* install interrupt vector           */
  52.   asm ("  OR  01H,IE");               /* enable int0 interrupts             */
  53.   global_enable();                      /* enable interrupts                  */
  54.  
  55.   /* Background tasks */
  56.   while(msg_last_error_number() == DS1102_NO_ERROR)            /* loop */
  57.     {
  58.     RTLIB_BACKGROUND_SERVICE();                        /* background service */
  59.     }
  60.   /* Reset PWM inverter en cas d'erreur */
  61.   ds1102_p14_phase_voltages( (long) 0, (long) 0, (long) 0 ); /* erreur, on sort */
  62. }
  63.  


Le programme commence en ligne 38 avec la fonction void main(void) va appeler les routines d'initialisation du matériel ligne init(); ligne 40 et celle de nos variables qui n'ont pas été initialisées lors de la déclaration ligne 41 :
Extrait du fichier : Var.h
   
void initVar()
{
  Idsref=IdsrefBase; /* 1.4 A */
}
En effet, le compilateur n'accepte pas d'initialiser les variables à leur déclaration avec le contenu d'autres variables. On ne peut le faire qu'avec des constantes. Pour contourner ce problème, il faut alors rajouter cette fonction dans Var.h en y incorporant les variables que l'on souhaite initialiser avec des valeurs modifiables comme en ligne 38 de Var.h :
   
double IdsrefBase=1.4;

Ensuite, en ligne 44, c'est au tour du Slave DSP TMS320 P14, on lui charge la librairie de communication et la PWM particulière expliquée plus haut.
En ligne 45, on initialise le compteur du codeur incrémental qui sera utilisé pour avoir la position et la vitesse de la machine.
En ligne 47, la fonction msg_info_set sert à sortir un message sur le fenêtre du ControlDesk, elle permet de savoir quel programme vient de démarrer. Cela peut être utile si l'on a plusieurs versions du même programme.
En ligne 48, RTLIB_TIC_INIT() permet l'initialisation du système de mesure du temps d'exécution des tâches (routines).

On installe ensuite l'ISR (Interrupt Service Routine) c_int01 sur le vecteur d'interruption de int0 (ligne 51) puis on autorise les interruptions (lignes 52 et 53).
A partir de cet instant, dès que le Master DSP TMS320 C31 reçoit une impulsion logique (+5V) sur la pin 6 INTEXT, il s'arrête d'exécuter la tâche de fond et saute dans la routine ISR c_int01.
Ceci va se produire toutes les 100 us, précisément au milieu du motif MLI.

Les lignes 56 à 59, comportent une boucle while qui exécute la tâche de fond d'échange avec ControlDesk (appuis sur les boutons, les sliders, mise à jour des variables du programme qui sont représentés sur le panneau de ControlDesk etc...). Cette boucle s'exécute tant qu'il n'y a pas d'erreur.
Si jamais une erreur survient (dépassement de temps de calcul, débordement...), on sort de la boucle et immédiatement, les 3 tensions sont mises à zéro. C'est-à-dire que le rapport cyclique est forcé à 50% via la routine de dialogue avec le Slave DSP TMS320 P14.


Examinons maintenant c_int01(), la routine d'interrutpion ou plus justement la routine de service de l'interruption (ISR) :


Il faut faire attention à ce que la tâche d'interruption ne consomme pas excessivement de temps CPU, de sorte à laisser suffisamment de temps pour que la tâche de fond (background) puisse s'exécuter.


Notre routine ISR s'exécute toutes les 100 us :
   
  1. void c_int01()          /* routine de gestion de int0 */
  2. {
  3.   /*  ds1102_da(2, 0.5);   signal 2 ! out */
  4.   ds1102_ad_start();      /* capture les courants */
  5.   RTLIB_TIC_START();                              /* start time measurement */
  6.   /*  ds1102_da(2, 0.0); signal 2 ! out */
  7.  *ioctl = (*ioctl & iomsk) | dspeoi0;  /* clear int0 request */
  8.  *ioctl;                               /* sync to posted write               */
  9.   asm ("  ANDN  01H,IF");              /* clear int0 flag  */
  10.   switch(countCurrent)
  11.     {
  12.     case 0  : RegulI_Control();
  13.               break;
  14.     case 1  : Regul_Vitesse();
  15.               host_service(1, 0);    /* Data Acquisition service */
  16.               break;
  17.     }
  18.   if (++countCurrent==DS_IReg)  countCurrent=0;
  19.   exec_time = RTLIB_TIC_READ(); /*  calculate execution time */
  20. }

Ce que l'on aperçoit en commentaire ds1102_da(2, ?); permet, s'il est activé, de mesurer le temps d'exécution d'une partie de la routine à l'aide d'un oscilloscope branché sur la sortie analogique (voie 2). Il verra 5V après ds1102_da(2, 0.5) puis 0V après ds1102_da(2, 0.0).

Une autre manière de mesurer est d'utiliser les fonctions de la RTLib :
Ligne 5, RTLIB_TIC_START(); lance le chronomètre
Ligne 19, exec_time = RTLIB_TIC_READ(); calcule le temps écoulé depuis le lancement du chronomètre.

Les lignes 7 à 9 permettent d'abaisser le flag de l'interruption int0, c'est-à-dire de renseigner le DSP que cette interruption à été traitée.

Les lignes 10 à 18 permettent d'appeler les routines de régulation.
On distingue 2 phases suivant la variable countCurrent qui ne peut prendre que 2 valeurs :
La figure ci-dessus explique comment se déroulent les deux tâches par alternance à chaque appel d'ISR. Il est important qu'il n'y ait pas de dépassement de temps de calcul par rapport à la période d'échantillonnage (100 us) pour chacune des deux tâches de l'ISR.


Nous pouvons maintenant examiner le contenu du fichier Regul.c. Il concerne la première tâche, elle est donc appelé à s'exécuter toutes les 200 us par RegulI_Control() :

   
  1. void RegulI_Control()
  2. {
  3. /* get courants  et fems */
  4. /* inputs via DS1102 on-board 16 et 12 bits ADC channel 1, 2, 3 et 4 */
  5.   inputI_E( Ias, Ibs, Ealphas, Ebetas);
  6.   /* inputI( Ias, Ibs); */
  7. /* avec offset LEM ! */
  8.  
  9. /*  ds1102_da( 2, 0.6);  signal out */
  10.   C22t( Ias, Ibs, Ialphas, Ibetas );
  11.   CalcPark( th, s, c);
  12.   ParkInv( Ialphas, Ibetas, s, c, Ids, Iqs);
  13.  
  14. /*  ds1102_da( 2, 0.5);  signal out */
  15.   Calc_thetas;    /* update thetas and Phidr*/
  16. /*  ds1102_da( 2, 0.4);  signal out */
  17.  
  18.   Wm=WmMesure;
  19.   /*  ds1102_da( 2, 0.3);   signal out */
  20.   /* Defluxage s'il y a lieu */
  21.   absWm=fabs(Wm);
  22.  
  23.   switch ( Deflux_Type )
  24.     {
  25.     case 1/* Deflux palier */
  26.           if ( absWm<=Wmlim1b )             Idsref=Idsref1;
  27.           if ( absWm >= Wmlim1 && absWm <= Wmlim2b )  Idsref=Idsref2;
  28.           if ( absWm >= Wmlim2 )                      Idsref=Idsref3;
  29.           break;
  30.     case 2/* Deflux continu */
  31.           if ( absWm<=WmBaseB ) Idsref=IdsrefBase;
  32.           if ( absWm>WmBase )  Idsref=Kdeflux/absWm;
  33.           break;
  34.     }
  35.  
  36.   if ( CurrentControllerType )  { /* Controlleur Flou   */
  37.                   BoucleFloueId();       /*regul FLOUE de Ids */
  38.                   BoucleFloueIq();       /*regul FLOUE de Iqs */
  39.                   }
  40.         else      {  /* controlleurs classiques */
  41.                   BoucleId();
  42.                   VqsrefMax=sqrt(E_2*E_2-Vdsref*Vdsref);
  43.                   BoucleIq();
  44.                   }
  45. // type de Cmde vect ou V/f (independante ou liée)
  46.   switch ( VectorControlFlag )
  47.     {
  48.     case 0//  V_f independants
  49.               Vdsref=Vsref;
  50.               Vqsref=0.0;
  51.           break;
  52.     case 2//  V/f liés par K_V_f
  53.               Vsref=K_V_f*fs;
  54.               Wsref=2*PI*fs;
  55.               Vdsref=Vsref;
  56.               Vqsref=0.0;
  57.               /* limiteur de tension */
  58.               if ( Vdsref>270.0)  Vdsref=270.0;
  59.               if ( Vdsref<-270.0) Vdsref=-270.0;
  60.           break;
  61.     default : //  case 1 ou autre : Vector control
  62.           break;
  63.     }
  64.  
  65.   Park( Vdsref, Vqsref, s, c, Valphas, Vbetas);
  66.   C32( Valphas, Vbetas, Vas, Vbs, Vcs );
  67.  
  68. /*  ds1102_da( 2, -1.0);   signal out */
  69.  
  70.   /* write PWM Duty cycle to slave DSP and test for error */
  71.   if  (InhibFlag)  /* met les tensions à 0 */
  72.                     ds1102_p14_phase_voltages ( (long) 0, (long) 0, (long) 0 );
  73.           else      ds1102_p14_phase_voltages
  74.                      (  (long) (-Vas * BY32767_E_2),         /*  /E_2*32767  */
  75.                         (long) (-Vbs * BY32767_E_2),
  76.                         (long) (-Vcs * BY32767_E_2) );
  77. /*  ds1102_da( 2, -0.5);   signal out */
  78. }

On commence en ligne 5 par un appel de la lecture des ADC. Rappelons que la conversion s'est déjà effectué lors du c_int01() à travbers la commande ds1102_ad_start()
La lecture des ADC se fait à l'aide d'une macro contenue dans le fichier Acqui.c :
   
#define inputI_E( i1, i2, E1, E2) \
{ \
  i1 = -0.03  + 20.0*ds1102_ad(1); \
  i2 =  0.002 + 20.0*ds1102_ad(2); \
  E1 = 20.0*ds1102_ad(3); \
  E2 = 200.0*ds1102_ad(4); \
}
Remarquez le rapport de conversion (20.0*) de la chaîne de conversion de la mesure des courants de phase Ias et Ibs ainsi que l'ajustement de l'offset (-0.03 et 0.002) des sondes à effet Hall utilisées.

Ensuite (lignes 10 à 12), on transforme ces grandeurs de phase (Ias, Ibs) vers le repère alpha beta statorique (Ialphas, Ibetas) à l'aide de la transformation de Clarke C22t( Ias, Ibs, Ialphas, Ibetas ) puis dans le repère tournant CalcPark( th, s, c) à l'aide d'une rotation ParkInv( Ialphas, Ibetas, s, c, Ids, Iqs).
Nous utilisons les macros contenues dans le fichier Trans.c :
   

#define C22t( Xa, Xb, Xalpha, Xbeta ) \
{ \
  Xalpha=Xa; \
  Xbeta=0.5773502692*Xa+1.154700538*Xb; \
}

#define CalcPark( th, sin_th, cos_th) \
{ \
  sin_th=sin(th); \
  cos_th=cos(th); \
}

#define ParkInv( Xalpha, Xbeta, sin_th, cos_th, Xd, Xq) \
{ \
  Xd=Xalpha*cos_th+Xbeta*sin_th; \
  Xq=Xbeta*cos_th-Xalpha*sin_th; \
}


En ligne 15, nous calculons l'angle de Park à l'aide de Calc_thetas :
   
/* calcul de thetas, Wsl, Ws et du flux */
#define Calc_thetas \
{ \
  Wsl= Iqsref / Tr / Idsref; \
  Phidr+=DT*( M*Ids-Phidr)/ Tr; \
\
  /* Commune */ \
  if (VectorControlFlag==1)  Ws=P*Wm+Wsl; \
  if (VectorControlFlag!=1)  Ws=Wsref; \
  th+=DT*Ws;    /* 200 us */ \
  if ( th > PI)  th-=TWOPI; \
  if ( th < -PI)    th+=TWOPI; \
}
Ce sont les formules de calcul de la pulsation de glissement Wsl puis de la pulsation statorique Ws.
Nous implantons deux manières de calculs suivant le type de commande : vectorielle ou scalaire). Ceci est géré via la variable VectorControlFlag qui peut être changée en ligne grâce à ControlDesk.
Pour plus d'information, veuillez vous reporter au cours de commande vectorielle de machines asynchrones

On mesure la vitesse (ligne 18) grâce à la macro :
   
#define inputWm \
{ \
  /* read incremental encoder counter */ \
  inc_k = read_inc( 1); \
 \
  /* calculate mechanical angle rd  _ avec inversion _*/ \
  mpos=POStoRD*(float)inc_k; \
 \
  /* calculate mechanical speed rd/s */ \
  FIR_Wm[0]=POStoRD_S*(float)( inc_k - inc_kOld ); \
  inc_kOld = inc_k; \
 \
  WmMesure=0.2*(FIR_Wm[0]+FIR_Wm[1]+FIR_Wm[2]+FIR_Wm[3]+FIR_Wm[4]); \
  FIR_Wm[4]=FIR_Wm[3]; \
  FIR_Wm[3]=FIR_Wm[2]; \
  FIR_Wm[2]=FIR_Wm[1]; \
  FIR_Wm[1]=FIR_Wm[0]; \
}
Cette macro lit l'incrément actuel du codeur, puis calcule la position en radian et la vitesse en rd/s, elle la filtre en faisant la moyenne sur les 5 dernières mesures avant de renvoyer le tout sur la variable WmMesure.

Puis on procède au défluxage s'il a lieu. Il existe trois modes : Viens ensuite (lignes 41 à 43), la régulation des deux courants Ids (contrôle du flux rotorique) et Iqs (contrôle du couple électromagnétique) à l'aide de régulateurs Proportionnel-Integral PI (BoucleId() et BoucleIq()). Remarquons que l'on peut choisir un régulateur flou de courant qui est lui aussi implanté dans le fichiers Regul.c.
Remarquons également, que la limitation
   
VqsrefMax=sqrt(E_2*E_2-Vdsref*Vdsref);
en sortie du régulateur de Iqs, est calculé en ligne afin de ne pas saturer ou écréter l'onde de tension résultante.

Un régulateur PI s'écrit très simplement en C ainsi :
   
void BoucleIq()
{
  e=Iqsref-Iqs;
  Vqsref = Kpq*e + xeq;
  if ( fabs(Vqsref)<=VqsrefMax )  xeq += Kiq*e;
  Vqsref+=sigma*Ls*Ws*Ids+M/Lr*Ws*Phidr;  /* decouplage */
  /* limiteur de tension */
  if ( Vqsref>VqsrefMax) Vqsref=VqsrefMax;
  if ( Vqsref<-VqsrefMax) Vqsref=-VqsrefMax;
}
La ligne suivante
   
if ( fabs(Vqsref)<=VqsrefMax )  xeq += Kiq*e;
évite de continuer à intégrer l'erreur quand le régulateur rentre en limitation. C'est une solution plus simple que l'Anti-Windup introduit dans le régulateur IP de vitesse que l'on verra par la suite.
On remarquera l'introduction des termes de découplage statique entre les axes d et q.
   
Vqsref+=sigma*Ls*Ws*Ids+M/Lr*Ws*Phidr;  /* decouplage */
Pour le régulateur du courant Ids :
   
Vdsref-=sigma*Ls*Ws*Iqs;    /* decouplage */


Ensuite, en fonction du mode de contrôle (varaible VectorControlFlag) : On calcule les tension de références dans le repère dq (ligne 45 à 63).

Ensuite (lignes 65 et 66), on transforme ces grandeurs de Park (Vdsref, Vqsref) vers le repère alpha beta statorique (Valphas, Vbetas) à l'aide d'une rotation Park( Vdsref, Vqsref, s, c, Valphas, Vbetas) puis dans le repère triphasé à l'aide de la transformation de Clarke C32( Valphas, Vbetas, Vas, Vbs, Vcs ) .
Nous utilisons les macros contenues dans le fichier Trans.c :
   

#define Park( Xd, Xq, sin_th, cos_th, Xalpha, Xbeta) \
{ \
  Xalpha=Xd*cos_th-Xq*sin_th; \
  Xbeta=Xd*sin_th+Xq*cos_th; \
}

#define C32( Xalpha, Xbeta, Xa, Xb, Xc ) \
{ \
  Xa=Xalpha; \
  Xb=0.8660254038*Xbeta-0.5*Xalpha; \
  Xc=-0.5*Xalpha-0.8660254038*Xbeta; \
}

Ces tensions triphasées sont les références à appliquer au moteur.
Pour ce faire, nous devons calculer le rapport cyclique correspondant à la valeur de chaque tension et l'envoyer au Slave DSP TMS320 P14 :.
Le signe et le coefficient dépendent de l'électronique (xor entre les sortie PWM pour reconstituer le signal final qui est symétrique et "actif haut".
L'équivalent en simulation est donnée par la formule suivante :
   
tau=TMLI/4*(1-Vasref/Es2);
      if ( tm<tau || tm>TMLI-tau ) VaMLI=-Es2;
        else      VaMLI=Es2;

Si la variable InhibFlag est mise, alors on envoie un rapport cyclique de 50% donnant une tension nulle sur chaque phase (lignes 71 à 72).

C'est la fin de notre routine RegulI_Control().


Nous allons examiner la routine void Regul_Vitesse() qui s'exécute aussi toutes les 200 us et qui comportent d'autres routines de régulation de vitesse et de position.
Ces routines s'exécutent à 1 ms et 2 ms respectivement, c'est-à-dire, moins fréquemment que celle de la régulation de courant (200 us).
   
  1. void Regul_Vitesse()
  2. {
  3.   /* ds1102_da( 3, 1);  */
  4.   if ( ++countSpeed!=DS_WReg )  return;
  5.   countSpeed=0;         
  6.     /* Interruption a 1 ms */
  7.  
  8.   inputWm;      /* lire La vitesse et la POSITION a partir d'une macro */
  9.   Wm=WmMesure;    // pas de sensorless ds cette version
  10.   if ( PosControlFlag )
  11.       if ( ++countPos==DS_PReg )  {
  12.                                   countPos=0;
  13.                                   BouclePosition()/* regul posisition */
  14.                                   }
  15.  
  16.   /* ds1102_da( 3, 0.5);     signal 3 ! out */
  17.  
  18. // if no steps are applied
  19.     if ( SpeedControlFlag )
  20.         switch ( SpeedControllerType )
  21.             {
  22.             case 1 :  BoucleVitesseFloue(); break/* Controlleur Flou */
  23.             default : BoucleVitesse();   /*classique */
  24.             }
  25.  
  26.         }
  27.   /* ds1102_da( 3, 0.0);   signal 3 ! out */
  28. }


En ligne 4, on vérifie à l'aide de la variable countSpeed que l'on a bien atteint la valeur DS_WReg=5, ce qui correspond à 5*200 us = 1 ms.
Puis on remet le compteur countSpeed à 0 et on mesure la vitesse. Les lignes 10 à 14 vérifient si la régulation de position est également demandée. Si c'est le cas, il y a également un compteur qui rend la boucle de position encore plus lente que la boucle de vitesse d'un facteur de 2.
On exécute la routine de régulation de position BouclePosition() qui est un simple régulateur proportionnel :
   
void BouclePosition()
{ /* regul P classique */
  Wmref= KpP * ( mposref-mpos);
  /* limiteur de vitesse */
  if ( Wmref>WmrefMax)    Wmref=WmrefMax;
  if ( Wmref<-WmrefMax)  Wmref=-WmrefMax;
}
En effet, le bouclage comporte déjà un intégrateur.


Les lignes 19 à 24, appelle la routine de régulation de vitesse (Flou ou IP anti Windup). Voici la structure IP anti Windup et en commentaire une structure IP classique :
   
/* attention regul sur la vitesse Wm ! */
void BoucleVitesse()
{
  /* La vitesse a ete calculee juste avant ds une macro */
  /* regul IP anti Windup */
  xeW += KiW / KpW*( Wmref - Wm - KTiW*(OldSortie - OldSortieSat) );
  /* limite le non sature pour eviter tt pb de debordement !!! */
  if ( xeW>xeWMax)  xeW=xeWMax;
  if ( xeW<-xeWMax) xeW=-xeWMax;
  OldSortie = KpW*( xeW -Wm);

  OldSortieSat=OldSortie;
  if ( OldSortieSat>IqsrefMax)  OldSortieSat=IqsrefMax;
  if ( OldSortieSat<-IqsrefMax) OldSortieSat=-IqsrefMax;
  Iqsref=OldSortieSat;

  /* limiteur de courant */
  /* regul IP classique */
/*
  Iqsref=KpW*(xeW-Wm);
  if ( fabs(Iqsref)<=IqsrefMax )  xeW+=KiW/KpW*(Wmref-Wm);
  if ( Iqsref>IqsrefMax)  Iqsref=IqsrefMax;
  if ( Iqsref<-IqsrefMax) Iqsref=-IqsrefMax;
*/

}


La routine void Regul_Vitesse() que nous avons présenté est une version simplifiée de celle donnée dans le programme. La version complète comporte tout un bloc d'instructions qui permettent de faire des échelons ou des montées triangulaires sur la vitesse de référence ou bien encore des échelons sur Iqsref (image du couple électromagnétique _à flux constant_).

Nous avons présenté l'essentiel du programme de commande vectorielle de MAS implanté sur une carte DSP dSpace DS1102. Il reste bien sûr à examiner les variable Var.h ansi que leur implantation dans Indc.trc. Ce fichier sert à déclarer les variables auxquelles on voudrait accéder dans ControlDesk.
Les programmes suivants (DS1104) reprendrons l'essentiel de la structure et l'adapteront au niveaux des fonction RTLib et des possibilités supplémentaires qui y seront incorporées.


Download executable and sources.

DS1104 :

Commande vectorielle IRFO de la MAS, régulation des courants, de la vitesse et de la position. Pilotage via liaison série ou bluetooth (voir programmes sur PocketPC / Windows associés.


Il faut comprendre d'abord la structure du programme, elle ressemble à celle du DS1102 :
Rajoutons les fichiers qui servent à gérer le projet et à l'interfacer avec ControlDesk : Il n'y a plus la partie du Slave puisque la PWM symétrique (appelée également PWM centrée) est déjà programmée dans le firmware du Slave TMS320 F240 de la carte (en mémoire flash).

Examinons le programme principal Indc.c :
   
  1. /*
  2.   Commande Vectorielle Indirecte par Orientation du Flux Rotorique
  3.   ------------  reconfiguration complète Pour DS1104  ------------
  4.   avec regulateur flou de vitesse et de Courant
  5.   + multi ctrl type ( class / flou )
  6.  
  7.   avec regul de position Treg=2*1ms, Proportionel
  8.   avec stator ind flux orientation
  9.   et defluxage progressif
  10.  
  11.   Grande Machine
  12.   Lotfi BAGHLI        19/06/1996  rev 27/03/1997
  13.  
  14.   reconfiguration complète Pour DS1104
  15.   Ajout reception via RS232 UART de SpeedRef  + Volatile declaration 30/05/2004
  16.   light version Lotfi BAGHLI        rev 12/12/2006
  17. */
  18.  
  19. #include <brtenv.h>                           /* basic real-time environment */
  20. #include <math.h>  /* sin cos */
  21. #include "ctrl.h"  /* Declaration des fonctions + macros + includes */
  22.                     /*    Declaration de variables + initialisation  */
  23.  
  24. /*---------------------------------------------------------------------------*/
  25. /* interrupt service routine for PWM sync interrupt */
  26.  
  27. void PWM_sync_interrupt(void)
  28. {
  29.   ds1104_bit_io_set(DS1104_DIO0 | DS1104_DIO1);
  30.  
  31.   RTLIB_TIC_START();                              /* start time measurement */
  32.   ds1104_bit_io_clear(DS1104_DIO0);
  33.  
  34.   switch(countCurrent)
  35.     {
  36.     case 0  : RegulI_Control();
  37.               break;
  38.     case 1  : Regul_Vitesse();
  39.               //old host_service(1, 0);               /* Data Acquisition service */
  40.               /* read level of SW-FIFO */
  41.               rec_fifo_level = dsser_receive_fifo_level(serCh);
  42.               break;
  43.     }
  44.   if (++countCurrent==DS_IReg)  countCurrent=0;
  45. // new host interface at dt=1e-4 s
  46.   host_service(1, 0)
  47.   ds1104_bit_io_clear(DS1104_DIO1);
  48.   exec_time = RTLIB_TIC_READ();
  49. }
  50.  
  51. /*--- UART sub-interrupt handler --------------------------------------------*/
  52. void Myserial_subint_handler(dsserChannel* serCh, const UInt32 subint_no)
  53. {
  54.   switch (subint_no)
  55.     {
  56.     case DSSER_TRIGGER_LEVEL_SUBINT:                   /* read received data */
  57. //          rec_error = dsser_receive_term(serCh, rec_datalen, (UInt8*) rec_data, (UInt32 *) &rec_count, 0);
  58.           rec_error = dsser_receive(serCh, rec_datalen, (UInt8*) rec_data, (UInt32 *) &rec_count);
  59.           if (rec_count==1)
  60.                 {
  61.                 if (InIndex!=0 && (rec_data[0] & 0x80)==0x80)  InIndex=0// remet au début si caractère de controle
  62.                 InData[InIndex++]=rec_data[0];
  63.                 if (InIndex==3) InIndex=0;
  64.                 if (InIndex==0 && (InData[1] & 0x80)==0 && (InData[2] & 0x80)==0 )
  65.                                 {
  66.                                 switch(InData[0])
  67.                                   {
  68.                                   case ID_ModeControleVitesse : DataRef=InData[1] | (InData[2]<<7);
  69.                                               if (DataRef>8191) DataRef-=16384;
  70.                                               // en rapport avec le programme d'envoi
  71.                                               if (DataRef>=-2400 && DataRef<=2400)  Wmref=DataRef*trm2rds/4.0;
  72.                                               break;
  73.                                   case ID_ModeControlePosition :  DataRef=InData[1] | (InData[2]<<7);
  74.                                               if (DataRef>8191) DataRef-=16384;
  75.                                               // en rapport avec le programme d'envoi
  76.                                               if (DataRef>=-8000 && DataRef<=8000)  mposref=DataRef*TWOPI/4.0;
  77.                                               break;
  78.                                   case ID_ModeControle :  DataRef=InData[1];
  79.                                               if (DataRef==4) PosControlFlag=1// Contrôle Position
  80.                                                   else        PosControlFlag=0// Contrôle vitesse
  81.                                               break;
  82.                                   }                
  83.                                 }
  84.                 }
  85.           break;
  86. //    case DSSER_TX_FIFO_EMPTY_SUBINT:                        /* transmit data */
  87. //          trans_error = dsser_transmit(serCh, trans_datalen, (UInt8*) trans_data, (UInt32 *) &trans_count);
  88. //          break;
  89.     }
  90. // new host interface for serial interrupt
  91.   host_service(2, 0)
  92. }
  93.  
  94. /*---------------------------------------------------------------------------*/
  95. void main(void)
  96. {
  97.   init();                             /* DS1104 and RTLib1104 initialization */
  98.   msg_info_set(0, 0, "Systeme : MAS controle vectoriel avec pilotage par liaison série, started.");
  99.   /* init incremental encoder channel 1  : input signal for channel 1 via RS422 */
  100.   ds1104_inc_init(1, DS1104_INC_MODE_RS422);
  101.   ds1104_inc_set_idxmode(1, DS1104_INC_IDXMODE_OFF);
  102.  
  103.   /* config IO 0 et 1 output */
  104.   ds1104_bit_io_init( DS1104_DIO0_OUT | DS1104_DIO1_OUT);
  105.   ds1104_bit_io_clear(DS1104_DIO0 | DS1104_DIO1);
  106.  
  107.   /* config DAC */
  108.   ds1104_dac_init( DS1104_DACMODE_TRANSPARENT);
  109.  
  110.   /* initialization of slave DSP communication */
  111.   ds1104_slave_dsp_communication_init();
  112.  
  113.   /* init and start of 3-phase PWM generation on slave DSP */
  114.   ds1104_slave_dsp_pwm3_init(task_id, MLIperiod, duty1, duty2, duty3, deadband, sync_pos);
  115.   ds1104_slave_dsp_pwm3_start(task_id);
  116.   /* registration of PWM duty cycle update command */
  117.   ds1104_slave_dsp_pwm3_duty_write_register(task_id, &index);
  118.   /* ADC Init and Sync */
  119.    ds1104_adc_mux( 1); /* le 16 bit ADC sur channel 1) */
  120.     /* 12 bits ADC 1 to 4 ie 2 to 5 are Courants Ias, Ibs et f.e.m. Ealphas et Ebetas */
  121.    ds1104_adc_trigger_setup( 1, DS1104_TRIGGER_ENABLE);
  122.    ds1104_adc_trigger_setup( 2, DS1104_TRIGGER_ENABLE);
  123.    ds1104_adc_trigger_setup( 3, DS1104_TRIGGER_ENABLE);
  124.    ds1104_adc_trigger_setup( 4, DS1104_TRIGGER_ENABLE);
  125.    ds1104_adc_trigger_setup( 5, DS1104_TRIGGER_ENABLE);
  126.    ds1104_syncin_edge_setup( DS1104_SYNC_TRIGGER_RISING);
  127.  
  128.   /* initalize UART channel structure */
  129.   serCh = dsser_init(DSSER_ONBOARD, 0, 64);
  130.   /* install UART sub-interrupt handler */
  131.   dsser_subint_handler_inst(serCh, (dsser_subint_handler_t)Myserial_subint_handler);
  132.   /* configure the UART */
  133.   dsser_config(serCh, DSSER_FIFO_MODE_OVERWRITE, 9600, 8, DSSER_1_STOPBIT, DSSER_NO_PARITY,
  134.           DSSER_1_BYTE_TRIGGER_LEVEL, DSSER_DEFAULT_TRIGGER_LEVEL, DSSER_RS232);
  135.   /* enable the required sub-interrupts (RX only) */
  136.   dsser_subint_enable(serCh, DSSER_TRIGGER_LEVEL_SUBINT_MASK); // |  DSSER_TX_FIFO_EMPTY_SUBINT_MASK);
  137.  
  138.   /* initialization of PWM sync interrupt */
  139.   ds1104_set_interrupt_vector(DS1104_INT_SLAVE_DSP_PWM,
  140.                               (DS1104_Int_Handler_Type) &PWM_sync_interrupt, SAVE_REGS_ON);
  141.   ds1104_enable_hardware_int(DS1104_INT_SLAVE_DSP_PWM);
  142.   RTLIB_INT_ENABLE();
  143.  /* Background tasks */
  144.   while(1)
  145.   {
  146.     RTLIB_BACKGROUND_SERVICE();                        /* background service */
  147.   }
  148. }




Examinons la routine RegulI_Control() du fichier Regul.c :
La routine est semblable à celle de la version DS1102 ci-dessus, à l'exception de la sortie sur l'DAC
   
ds1104_dac_write( 2, -0.5);
et de l'écriture des rapports cycliques PWM du slave :
   
  /* write PWM Duty cycle to slave DSP and test for error */
  if  (InhibFlag)  {  /* met les tensions à 0 */
                    duty1=0.5;
                    duty2=0.5;
                    duty3=0.5;
                    }
          else      {
                    duty1=Vas*BY_2E_2+0.5;
                    duty2=Vbs*BY_2E_2+0.5;
                    duty3=Vcs*BY_2E_2+0.5;
                    }
  ds1104_slave_dsp_pwm3_duty_write(task_id, index, duty1, duty2, duty3);


capture écran d'une acquisition de signaux suite à un échelon de position de référence

Capture écran de ControlDesk :
Acquisition de signaux suite à un échelon de position de référence en régulation de position.
On distingue en bas à droite (position de référence : échelon en rouge, position réelle : courbe en vert).
En haut au milieu (vitesse de référence : bleu foncé, vitesse réelle : cyan). En haut à droite (courant Iqs ref : cyan, Iqs réel : bleu foncé, Ids ref : rouge, Ids réel : jaune).
On voit bien la qualité de la réponse sur un système avec 4 boucles de régulation (position, vitesse, Ids, Iqs).

La position atteint la valeur de consigne sans dépassement et en douceur. Pour ce faire, le régulateur de position sort une consigne de vitesse qui atteint une valeur de palier de 600 tr/mn tant que le moteur se trouve loin de la position finale puis quand il s'en approche, le régulateur de position diminue progressivement la vitesse de référence.
Cette vitesse de référence change donc sans arrêt dans ce régime de poursuite et on voit bien que le régulateur de vitesse asservi la vitesse de la machine tout au long des changements de référence en imposant le couple maximal de la machine.
Le couple est représenté par le courant Iqs. Au début, la machine est en accélération maximale Iqs=16.5 A, puis quand elle atteint la vitesse de palier, le régulateur de vitesse diminue la consigne de courant. Ensuite, lorsque le régulateur veut freiner la machine, il passe le moteur en accélération négative à Iqs= –16.5 A, puis diminue afin que le positionnement se fasse sans dépassement.


programme PocketPC pour envoyer des commandes à la ds1104    programme windows pour envoyer des commandes à la ds1104.jpg Module Bluetooth utilisé

Branchement TDK bluetooth (RS232) à la carte DS1104 (RS232) : Droit, non croisé :
TDK   :   DS1104
4 -|
6 -|

7 -|
8 -|

5 --------- 5
2 --------- 2
3 --------- 3


Download executable and sources.

DS1104 :

Commande vectorielle d'une MAS double étoile.


Une version du programme de commande de la machine asynchrone mais profondément modifié pour commander une machine double étoile (2 enroulements triphasés au stator décalés de 30° électrique).

La version originale du firmware du 320F240 ne permet pas l'utilisation simultané et synchronisé des simple et full PWM. Nous avons alors implanté de speciales "user functions" qui permettent d'utiliser 6 Full PWM and 3 Simple PWM en même temps et de les synchroniser. Ensuite on reconstitue les 3 signal PWM complémentaires des 3 simple PWM à l'aide d'inverseurs sur une carte électronique externe.
Voici le code associé à cette démarche, la version compète du programme de commande vectoriel qui utilise ces "users functions" est donné par ds1104_masde.zip.


usrdsp.h :
   
/******************************************************************************
* MODULE
*   TMS320F240 slave DSP user module template.
* FILE
*   usrdsp.h
* RELATED FILES
*   usrdsp.c
*
* DESCRIPTION
*   This module may contain user defined functions for the TMS320F240 slave DSP.
*   Each additional function must be registered in the fw240.c module by
*   using the function slvdsp_usrfct_install().
* AUTHOR(S)
*   M. Heier, V. Lang (dSPACE)
*  &  L. BAGHLI (GREEN-UHP)
******************************************************************************/


#ifndef __USRDSP_H__
#define __USRDSP_H__

/*------------------- user firmware revision numbers ------------------------*/
#define USER_MAJOR_RELEASE      0
#define USER_MINOR_RELEASE      1
#define USER_SUBMINOR_RELEASE   2

#ifndef _DS1104
#include <slvdsp.h>
#endif /* !_DS1104 */

/******************************************************************************
* constants and defines
******************************************************************************/


/*------------- opcodes for user functions (0x0300 - 0x03FF -----------------*/

     #define SLVDSP_USRFCT_SIMPLEPWMSYNCH  0x301
     #define SLVDSP_USRFCT_SIMPLEPWMUPDATE  0x302

/* parameters for user defined functions */
#define SLVDSP_USRFCT_SIMPLEPWMUPDATE_PCNT    3                      /* parameter count */
#define SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY1_LOC 0                 /* phase 1 SCMPR1 duty cycle */
#define SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY2_LOC 1                 /* phase 2 SCMPR2 duty cycle */
#define SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY3_LOC 2                 /* phase 3 SCMPR3 duty cycle */
#define SLVDSP_USRFCT_SIMPLEPWMUPDATE_DINCNT  0                       /* no input data */
#define SLVDSP_USRFCT_SIMPLEPWMUPDATE_DOUTCNT 3                   /* output data count */


#ifndef _DS1104

/******************************************************************************
* global objects and variables
******************************************************************************/


#define slvdsp_usr_firmware_rev_set()           {  \
  user_major_release    = USER_MAJOR_RELEASE;      \
  user_minor_release    = USER_MINOR_RELEASE;      \
  user_subminor_release = USER_SUBMINOR_RELEASE; }

/******************************************************************************
* function prototypes
******************************************************************************/


     void slvdsp_usrfct_simplePWMsynch(UInt16 index);
     void slvdsp_usrfct_simplePWMupdate(UInt16 index);

#endif /* !_DS1104 */

#endif /* __USRDSP_H__ */
 


Implantation des user functions dans usrdsp.c :
   
/******************************************************************************
* MODULE
*   Common TMS320F240 slave DSP user module.
* FILE
*   usrdsp.c
* RELATED FILES
*   usrdsp.h, reg240.h, fw240.c
*
* DESCRIPTION
*   This module may contain user defined functions for the TMS320F240 slave DSP.
*   Each additional function must be registered in the fw240.c module by
*   using the function slvdsp_usrfct_install().
* AUTHOR(S)
*   M. Heier (dSPACE)
*  &  L. BAGHLI (GREEN-UHP)
******************************************************************************/


#include <usrdsp.h>                                   /* related header file */
#include <dstypes.h>                                /* data type definitions */
#include <dsscom.h>          /* general master-slave communication functions */
#include <slvdsp.h>                                   /* slave DSP functions */
#include <reg240.h>                           /* TMS320C240 register defines */

/******************************************************************************
* user functions
******************************************************************************/


/******************************************************************************
* FUNCTION
*   Utilise le meme timer (1) pour la Simple que pour la full PWM
*
* SYNTAX
*   slvdsp_usrfct_simplePWMsynch(UInt16 index)
*
* DESCRIPTION
*
* PARAMETERS
*   aucun
******************************************************************************/

void slvdsp_usrfct_simplePWMsynch(UInt16 index)
{
 // pour voir si ça marche, faudrait sortir un IOPA0 à 5V puis 0V...
  *COMCON=0x0307;  // FIRST enable PWM operation
                // Reload Full Compare when T1CNT=0
                // Disable Space Vector
                // Reload Full Compare Action when T1CNT=0
                // Enable Full Compare Outputs
                //  Enable Simple Compare Outputs
                //  GPTimer 1 for simple PWM
                //  Reload SIMPLE Compare Action when T1CNT=0
                // Full Compare Units in PWM Mode
  *COMCON=0x8307;     // THEN enable Compare operation
}

/******************************************************************************
* FUNCTION
*   Mise à jour des SCMPRx de la Simple PWM (transmission
*  des 3 SCMPRX à la fois)
*
* SYNTAX
*   slvdsp_usrfct_simplePWMupdate(UInt16 index)
*
* DESCRIPTION
*
* PARAMETERS
*   index               command table index
*   parms               parametres contenant les SCMPRx values
******************************************************************************/

void slvdsp_usrfct_simplePWMupdate(UInt16 index)
{
  UInt16 parms[SLVDSP_USRFCT_SIMPLEPWMUPDATE_PCNT];

  slvdsp_usrfct_comm_read(SLVDSP_USRFCT_SIMPLEPWMUPDATE_PCNT, parms)/* read parameters from communication buffer */

  *SCMPR1 = parms[SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY1_LOC];         /* update PWM1 compare register */
  *SCMPR2 = parms[SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY2_LOC];         /* update PWM2 compare register */
  *SCMPR3 = parms[SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY3_LOC];         /* update PWM3 compare register */
}
 


Partie de mise à jour des rapport cycliques dans Regul.c :
   

  /* write PWM Duty cycle to slave DSP and test for error */
  if  (InhibFlag)  {  // met les tensions à 0
                    duty1=0.5;
                    duty2=0.5;
                    duty3=0.5;
                    dduty1=0.5; // double étoile
                    dduty2=0.5;
                    dduty3=0.5;
                    }
          else      {
                    duty1=Vas*BY_2E_2+0.5;
                    duty2=Vbs*BY_2E_2+0.5;
                    duty3=Vcs*BY_2E_2+0.5;
                    // double étoile
                    dduty1=dVas*BY_2E_2+0.5;
                    dduty2=dVbs*BY_2E_2+0.5;
                    dduty3=dVcs*BY_2E_2+0.5;
                    }
    ds1104_slave_dsp_pwm3_duty_write(task_id, index, duty1, duty2, duty3);

  scmpr_duties[SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY1_LOC]=(1-dduty1)*1000;
  scmpr_duties[SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY2_LOC]=(1-dduty2)*1000;
  scmpr_duties[SLVDSP_USRFCT_SIMPLEPWMUPDATE_DUTY3_LOC]=(1-dduty3)*1000;
  ds1104_slave_dsp_usrfct_execute(task_id, SLVDSP_USRFCT_SIMPLEPWMUPDATE,
                                           SLVDSP_USRFCT_SIMPLEPWMUPDATE_PCNT, scmpr_duties);
 


Download executable and sources.

Download :
ds1102_vect.zip   Sources for DS1102 vector control (IRFO) of IM.
ds1104_vect_serial.zip   Sources for DS1104 vector control (IRFO) of IM with remote control via RS232 serial link to the board.
ds1104_masde.zip   Sources for DS1104 vector control (IRFO) of Double Star Induction Motor.

commDSP_ppc.zip   Sources in Visual Studio 2005 (Smart Devices) PocketPC project so send data via bluetooth to the bluetooh card connected to the DS1104 board.
commDSP_ppc_exe.zip   Ready to use PocketPC / ARM application that sends the data via bluetooth.
commDSP_midlet.zip   Ready to use midlet for MIDP 2 Java enabled phones that sends the data via bluetooth.

bds2006_cpp_vectserial.zip   Sources in Borland BDS 2006 cpp files to send data over serial to the DS1104 board. Note that I use a ComPort components available here
commdsp_vectserial.zip   Ready to use Windows application to send data over serial to the DS1104 board.

mat2dat.m   Fichier Matlab pour convertir les captures faites avec ControlDesk en un format.dat lisible par Excel ou tout autre tableur.
sixim.m   Fichier Matlab pour le calcul des matrices de transformation de la MAS double étoile (Transformation matrices for 6PIM modelling and control under fault conditions).


Back to homepage

Last update : 20/01/2007