PROJET SOURCE
WORKSPACE_F411_MCC_CUBEIDE.zip
REMARQUE : cf tuto STM32CUBEIDE
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.
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.
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 \( G=\frac{3000}{50}=60 \)
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 \( \tau \) 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.
\( FTBO(s)=\frac{G}{1+\tau .s} \)
avec G=60, \(\tau=53ms \)
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.
Etant donné la constante de temps, nous fixons Ti
\( \tau=53ms \rightarrow T_i = 0.1*\tau \)
Pour un correcteur numérique (discrétisation) :
Période d’échantillonage : \( T_e=1ms –> K_i=\frac{T_e}{T_i} \)
Pour \( Kp=0.01 \)
La marge de phase semble satisfaisante (au moins 40°) :
En théorie, la réponse en boucle fermée corrigée devrait ressembler à cela :
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.
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 :