Asservissement en vitesse d'un Moteur à Courant Continu

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.

multitache.svg


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 :

verif_te.png

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.
main.c
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 :

identification_eclipse.png

Je copie-colle alors ces données dans un tableur, afin de tracer la courbe vitesse=f(t) :

identification_tableur.png

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=300050=60 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)=G1+τ.s FTBO(s)=\frac{G}{1+\tau .s}
avec G=60, τ=53ms\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.


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).

1.png

Dimensionnement du correcteur

cf Réglage d’un Correcteur PI

Etant donné la constante de temps, nous fixons Ti

τ=53msTi=0.1τ \tau=53ms \rightarrow T_i = 0.1*\tau

Pour un correcteur numérique (discrétisation) :
Période d’échantillonage : Te=1ms>Ki=TeTi T_e=1ms --> K_i=\frac{T_e}{T_i}

Pour Kp=0.01 Kp=0.01

La marge de phase semble satisfaisante (au moins 40°) :

marge_phase_a.svg

En théorie, la réponse en boucle fermée corrigée devrait ressembler à cela :

Figure_2.png

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.

main.c
#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 :

ftbf_mesuree.png

REMARQUE : On pourra noter au passage une bonne prédiction de la simulation.

ftbf_commande.png


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 ).

main.c
#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 :

main.c
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 :

trace.png