Asservissement en vitesse d'un Moteur à Courant Continu
PROJET SOURCE
WORKSPACE_F411_MCC_CUBEIDE.zip
REMARQUE : cf tuto STM32CUBEIDE
Multitache dans le microcontrôleur STM32F411
Le RTOS FreeRTOS est utilisé pour gérer la synchronisation les tâches :
-
FAST_TASK (la plus prioritaire) : Calcul du nouveau rapport cyclique
-
PRINT_TASK : envoi des données sur la liaison RS232 afin de les tracer sur le PC.
-
tickTimer impose la fréquence d’échantillonage Fe (période Te=5ms)
-
à chaque interruption fin de comptage, un jeton est donné au sémaphore SEM. Cela libère la tache FAST TASK.
-
La tache FAST Task est chargée de calculer les nouveaux rapports cycliques.
-
lorsque FAST_TASK reprend un jeton à SEM, elle est mise en attente.
-
PRINT_TASK peut alors s’exécuter pour envoyer des informations sur la liaison série.
-
pwmTimer Evolue à la fréquence 100kHz ; le rapport cyclique est représenté par le registre CCR.
Vérification de la période d’Echantillonnage
Tout ce qui est vérifiable doit être vérifié.
Une manière simple de tester la période d’échantillonnage est de piloter une sortie GPIO :
static void fast_task(void *pvParameters)
{
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 1);
...
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 0);
xSemaphoreGive( xSemaphore_Slow);
xSemaphoreTake( xSemaphore_Fast, portMAX_DELAY );
}
}
On observe alors à l’oscilloscope la broche PC0 :
PC0 se met bien à 1 toutes les ms
Ce test simple permet également de vérifier si on a suffisamment de temps pour effectuer les calculs à chaque période d’échantillonnage.
Identification du système en boucle ouverte
- Pour le moment nous ne connaissons rien au système à asservir.
- Il faut donc au moins déterminer le gain et la constante de temps principale de ce système.
- Pour cela on applique une variation de commande du hacheur, ce qui entraine une variation de vitesse.
int tab_speed[500];
static void fast_task(void *pvParameters)
{
static int i = 0;
int speed;
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 1);
motor_SetDuty(150);
speed = quadEncoder_GetSpeedL();
if (i < 500)
{
tab_speed[i]= speed;
i++;
}
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 0);
xSemaphoreGive( xSemaphore_Slow);
xSemaphoreTake( xSemaphore_Fast, portMAX_DELAY );
}
}
à l’issue de la mise en rotation du moteur, je mets le programme en pause, et j’observe le tableau tab_speed :
Je copie-colle alors ces données dans un tableur, afin de tracer la courbe vitesse=f(t) :
On note une vitesse en régime permanent d’environ 3000 tr/min, correspondant à une variation de rapport cyclique de 50 ( pour rappel, 100 étant l’état de repos, si j’applique 150, j’ai bien une variation de 50 par rapport à l’état de repos ).
Le gain de notre système est donc
63% du régime permanent correspond à la vitesse 0.63*3000=1890.
Sachant que j’ai un point à chaque ms ( période d’échantillonnage ou d’acquisition ); j’atteins les 63% du régime permanent à t=53ms. Cela correspond à la constante de temps du système en boucle ouverte.
REMARQUE : Pour qu’un asservissement soit fonctionnel, il faut un minimum de 10 points dans le régime transitoire. Le choix de la période d’échantillonnage doit donc tenir compte de la constante de temps du système à asservir.
avec G=60,
REMARQUE : Le système est en réalité un système du 2ème ordre ; nous avons donc ici négligé la constante de temps la plus rapide (constante de temps électrique).
Cela n’est pas gênant et simplifie les calculs de correcteur.
Réglage d’un Correcteur PI
- L’opération de correction consiste à comparer une consigne de vitesse (ici en tr/min) avec une vitesse.
- L’erreur (Consigne - Mesure) est corrigée pour appliquer une commande.
- Nous choisissons ici un correcteur PI pour avoir une erreur statique nulle (la mesure doit rejoindre l’échelon de consigne).
Dimensionnement du correcteur
Etant donné la constante de temps, nous fixons Ti
Pour un correcteur numérique (discrétisation) :
Période d’échantillonage :
Pour
La marge de phase semble satisfaisante (au moins 40°) :
En théorie, la réponse en boucle fermée corrigée devrait ressembler à cela :
Test sur cible
La variation du rapport cyclique résulte désormais d’un calcul.
Je propose dans ce qui suit une consigne de 800 tr/min.
Afin d’éviter les problèmes de saturation ( variation de commande au delà de l’intervalle [-100 100] ), il est important de mesurer au passage la commande.
#define Kp 0.01
#define Te 1.0
#define tau 53.0
#define Ki (Te/(0.1*tau))
int tab_speed[500];
int tab_cde[500];
static void fast_task(void *pvParameters)
{
static int i = 0;
int speed;
float up, ui = 0.0;
float err;
static int i = 0;
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 1);
speed = quadEncoder_GetSpeedL();
err = 800-(float)speed;
up = Kp*err;
ui = ui+Kp*Ki*err;
motor_SetDuty(100+(int)(up + ui));
if (i < 500)
{
tab_speed[i]= speed;
tab_cde[i]=up+ui;
i++;
}
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 0);
xSemaphoreGive( xSemaphore_Slow);
xSemaphoreTake( xSemaphore_Fast, portMAX_DELAY );
}
}
Je relève à nouveau tab_speed et tab_cmd, afin de faire un tracé grâce au tableur :
REMARQUE : On pourra noter au passage une bonne prédiction de la simulation.
Utilisation de l’outil de tracé
Je considère désormais que la consigne est un signal carré ( pulse ) mis à jour dans la callback d’interruption du timer 5.
l’amplitude de pulse correspond à la valeur mesurée au niveau du potentiomètre ( on utilise la voie 0 de l’ADC ).
#define Kp 0.01
#define Te 1.0
#define tau 60.0
#define Ki (Te/(0.1*tau))
int tab_speed[500];
int tab_cde[500];
static void fast_task(void *pvParameters)
{
static int i = 0;
int speed;
float up, ui = 0.0;
float err;
static int i = 0;
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 1);
adc1_Get(&potentiometre);
speed = quadEncoder_GetSpeedL();
err = pulse-(float)speed;
up = Kp*err;
ui = ui+Kp*Ki*err;
motor_SetDuty(100+(int)(up + ui));
if (i < 500)
{
tab_speed[i]= speed;
tab_cde[i]=up+ui;
i++;
}
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 0);
xSemaphoreGive( xSemaphore_Slow);
xSemaphoreTake( xSemaphore_Fast, portMAX_DELAY );
}
}
La tache print_task permet d’envoyer les données mesurées sur la liaison RS232 :
static void print_task(void *pvParameters)
{
char buf[6];
for (;;)
{
buf[5] = speed>>8;
buf[4] = speed;
buf[3] = (pulse >> 8) ;
buf[2] = pulse;
buf[1] = 0;
buf[0] = 123; // START OF FRAME
uart_Write(buf, 6);
xSemaphoreTake( xSemaphore_Slow, portMAX_DELAY );
}
}
Lançons le programme python RTScope.py disponible dans l’archive : RTScope.zip :