6 - Contrôle d’un moteur "pas à pas" avec quatres "GPIO out"

De Wiki L.A.B
Aller à : navigation, rechercher

Contrôle d’un moteur "pas à pas" avec quatres "GPIO out"

Dans cet atelier, on va regarder comment utiliser des GPIO in pour commander un moteur pas à pas.
Dans notre exemple nous allons utiliser la carte “Nucleo64” de référence “NUCLEO-L476RE”.

On va utiliser le moteur BYJ48. Une description (en anglais) se trouve sur cette page. Il fonctionne avec une tension de 5 V fournie par la carte nucleo, qui elle même devrait être connectée avec l'ordinateur via un USB hub alimenté.

La page donne les infos techniques importantes suivantes:

  • "Gear ration": 64
  • "Stride angle": 5.625°

Ainsi on peut calculer, que pour un tour complet, il faut commander au moteur de faire 4096 pas: "nombre de pas pour un tour complet" = ( 360° / "Stride angle" ) * "Gear ratio" = 64 * 64 = 4096

  • Le moteur est un moteur "unipolaire". Ainsi on peut filtrer toute information / chapitre dans les documentations qu'on trouve sur les pages citées plus bas.

Introduction et description des moteurs "pas à pas"

Pour une introduction sur les moteurs "pas à pas", on va lister quelques sites qui illustrent de façon pédagogique leur fonctionnement.

Dans les descriptions, on comprend que le moteur contient "quatre bobines magnétiques" à commander avec 4 GPIO via un "driver" (la petite carte électronique sur les photos du moteur "BYJ48"), car la puissance distribuée par un GPIO (faible curent à 3.3 V) est trop faible pour les bobines.

Ces 4 GPIO (on va utiliser dans cet atelier les GPIO pin 5, 6, 7 et 8 du port C) doivent être alimentés et éteints d'une façon coordonnée.

Dans la section "Half Step Sequence" de la page "Rickey's World", il y a une jolie animation "Stepmode" qui représente les signals à générer par logiciel sur ces GPIO (nommés A, B, A\, B\).

Pour générer une séquence de "demi pas", il faut réaliser le tableau suivant:

GPIO \ Pas Step0 Step1 Step2 Step3 Step4 Step5 Step6 Step7
PC_5 1 1 0 0 0 0 0 1
PC_6 0 1 1 1 0 0 0 0
PC_7 0 0 0 1 1 1 0 0
PC_8 0 0 0 0 0 1 1 1

Signification du tableau:

  • Les "Step?" dans la première ligne sont les 8 pas à exécuter les uns après les autres.
  • Les "PC_?" dans la première colonne sont les noms des pins utilisés.
  • Le tableau va être implémenté par un "array" (au sens de C). Les indices d'un "array" commences à 0 et s'arrêtent au "n-1". Ainsi pour notre tableau de 8 pas, on indexe les pas de 0 à 7.

Si le moteur doit tourner dans un sens, il faut parcourir le tableau dans un sens (par exemple en incrémentant l'index de l'array). Si on arrive au dernier pas, il faut recommencer à zéro. (Après pas 7, il faut exécuter le pas 0).

Pour tourner dans le sens inverse, il faut simplement exécuter les pas à l’inverse en décrémentant l'index de l'array. (Bien sûr après l'index 0, il faut continuer avec l'index 7).

Implémentation de l’algorithme de contrôle de moteur "pas à pas"

On va développer une fonction qui pourrait être appelée à un moment donné et qui contrôle les GPIO pour faire tourner le moteur.

Conversion entre angle et steps

Comme notre fonction prend comme paramètre un "angle de rotation", mais en interne, il faut travailler en pas, il nous faut un petit calcul de conversion:

#define STEPS_PER_360  4096

 int32_t Step = ( Angle * STEPS_PER_360 ) / 360;

Utilisation de la librairie "HAL" sur STM32

Comme on avait déjà vu dans l'atelier 1 - Premier programme “blinky”, on a à notre disposition dans la librairy pour STM32 la fonction

   HAL_GPIO_WritePin( GPIOC, GPIO_PIN_3, GPIO_PIN_SET );

pour mettre la broche PC3 à "logical high" (3.3 V sur le pin pour allumer une LED) et

   HAL_GPIO_WritePin( GPIOC, GPIO_PIN_3, GPIO_PIN_RESET );

pour mettre la broche PC3 à "logical low" (0 V sur le pin pour éteindre une LED).

Les états des broches

Pour notre algorithme de contrôle de moteur, ça implique qu'il faut regarder ne pas les états des pins pour exécuter un step x mais plutôt le changement des états: Quels sont les pins qu'il faut éteindre et quels sont les pins qu'il faut allumer?

Mathématiquement, ces changements des états correspondent à "la dérivée de la fonction des états".

Ainsi on arrive à concevoir deux tableaux:

1. Broches à allumer: Ce tableau correspond exactement au tableau des états en haut.

GPIO \ Pas Step0 Step1 Step2 Step3 Step4 Step5 Step6 Step7
PC_5 1 1 0 0 0 0 0 1
PC_6 0 1 1 1 0 0 0 0
PC_7 0 0 0 1 1 1 0 0
PC_8 0 0 0 0 0 1 1 1

2. Pins à éteindre: Ce tableau contient les valeurs inverses du premier tableau.

GPIO \ Pas Step0 Step1 Step2 Step3 Step4 Step5 Step6 Step7
PC_5 0 0 1 1 1 1 1 0
PC_6 1 0 0 0 1 1 1 1
PC_7 1 1 1 0 0 0 1 1
PC_8 1 1 1 1 1 0 0 0

Donc comme exemple, pour établir l'état du "Step3", il faut d'abord appeler la fonction

   HAL_GPIO_WritePin( GPIOC, GPIO_PIN_5 | GPIO_PIN_8, GPIO_PIN_RESET );

pour effacer les anciens états des Step2 ou Step4 (dépendant de la direction de tourner) et

   HAL_GPIO_WritePin( GPIOC, GPIO_PIN_6 | GPIO_PIN_7, GPIO_PIN_SET );

pour établir l'état du "Step3".

Il faut savoir, qu'il n'y a pas d’impact, si on allume une broche déjà allumée ou si on éteint une broche déjà éteinte. Ainsi notre logique simplifie...

Comme décrit plus haut, on va utiliser les broches PC5, PC6, PC7, PC9. On pourrait utiliser des broches d'index "non continues", ou même des broches de port différents ( e.g.: GPIOA et GPIOB ), mais ça compliquerait trop pour cet atelier.

Pour l'efficacité de l'exécution du programme, ces deux tableaux vont être stockés dans des "array" du language "C":

1. Broches à allumer

 const int16_t PinSet[ 8 ] =
 {
  GPIO_PIN_5,
  GPIO_PIN_5 | GPIO_PIN_6,
               GPIO_PIN_6,
               GPIO_PIN_6 | GPIO_PIN_7,
                            GPIO_PIN_7,
                            GPIO_PIN_7 | GPIO_PIN_8,
                                         GPIO_PIN_8,
  GPIO_PIN_5 |                           GPIO_PIN_8,
 };

2. Broches à éteindre:

 const int16_t PinReset[ 8 ] =
 {
               GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8,
                            GPIO_PIN_7 | GPIO_PIN_8,
  GPIO_PIN_5 |              GPIO_PIN_7 | GPIO_PIN_8,
  GPIO_PIN_5 |                           GPIO_PIN_8,
  GPIO_PIN_5 | GPIO_PIN_6 |              GPIO_PIN_9,
  GPIO_PIN_5 | GPIO_PIN_6,
  GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7,
               GPIO_PIN_6 | GPIO_PIN_7,
 };

Chaque ligne des tableaux correspond à un pas à établir.

Le compteur des pas pour parcourir les tableaux

La fonction peut être appelée plusieurs fois à la suite, mais il faut connaître l'index des tableaux actuels pour incrémenter (et gérer le passage du dernier index 7 à premier 0).

Ceci va être implémenté par une "variable static" du langage "C":

void Move( int32_t Angle )
{
 static int32_t CurrentStep = 0;

}

Cette variable "CurrentStep" va être initialisée (comme les variables globales) au démarrage avant l'appel de la fonction "main". Ensuite, la fonction "Move" pourra la modifier et retrouver la valeur au prochain appel.

Pour le parcours des tableaux, on va comparer le nombre des steps à exécuter (valeur de la variable "Step" initialisée par le paramètre "Angle" décrit en haut) avec "CurrentStep". Le signe de la variable "Step" détermine, si on doit incrémenter ou décrémenter. A la fin du parcours, "CurrentStep" va avoir la nouvelle valeur "actuelle".

 if ( Step > 0 )
 {
  Step += CurrentStep;
  while ( CurrentStep < Step )
  {
   CurrentStep ++;
   // ...
  }
 }
 else
 {
  Step += CurrentStep;
  while ( CurrentStep >= Step )
  {
   CurrentStep --;
   // ... 
  }
 }

Plus haut a été décrit que, si on accède au dernier pas (dernière index de l'array), il faut continuer au premier pas. Ceci est implémenté par la fonction "modulo", représentée par le signe "%" dans le source code "C". Par exemple:

 int j;
 for ( int i = 0; i < 24; i ++ )
  j = i % 8;

Ainsi la variable "j" va obtenir les valeurs 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 pendant "i" incrémente de 0 à 23 et donc peut être utilisé pour accéder à un array d'une façon circulaire.

La fonction complète de l’algorithme

#define STEPS_PER_360  4096

void Move( int32_t Angle )
{
 static int32_t CurrentStep = 0;
 const int16_t PinSet[ 8 ] =
 {
  GPIO_PIN_5,
  GPIO_PIN_5 | GPIO_PIN_6,
               GPIO_PIN_6,
               GPIO_PIN_6 | GPIO_PIN_7,
                            GPIO_PIN_7,
                            GPIO_PIN_7 | GPIO_PIN_8,
                                         GPIO_PIN_8,
  GPIO_PIN_5 |                           GPIO_PIN_8,
 };
 const int16_t PinReset[ 8 ] =
 {
               GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8,
                            GPIO_PIN_7 | GPIO_PIN_8,
  GPIO_PIN_5 |              GPIO_PIN_7 | GPIO_PIN_8,
  GPIO_PIN_5 |                           GPIO_PIN_8,
  GPIO_PIN_5 | GPIO_PIN_6 |              GPIO_PIN_9,
  GPIO_PIN_5 | GPIO_PIN_6,
  GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7,
               GPIO_PIN_6 | GPIO_PIN_7,
 };
 int32_t Step = ( Angle * STEPS_PER_360 ) / 360;
 if ( Step > 0 )
 {
  Step += CurrentStep;
  while ( CurrentStep < Step )
  {
   CurrentStep ++;
   HAL_GPIO_WritePin( GPIOC, PinReset[ CurrentStep % 8 ], GPIO_PIN_RESET );
   HAL_GPIO_WritePin( GPIOC, PinSet[ CurrentStep % 8 ], GPIO_PIN_SET );
   HAL_Delay( 1 );
  }
 }
 else
 {
  Step += CurrentStep;
  while ( CurrentStep >= Step )
  {
   CurrentStep --;
   HAL_GPIO_WritePin( GPIOC, PinReset[ CurrentStep % 8 ], GPIO_PIN_RESET );
   HAL_GPIO_WritePin( GPIOC, PinSet[ CurrentStep % 8 ], GPIO_PIN_SET );
   HAL_Delay( 1 );
  }
 }
}

Utilisation dans un exemple de test

On va combiner le contrôle du moteur pas à pas avec le code de l'atelier 5 - Utiliser un "bouton poussoir" avec un "GPIO in" .

La fonctionalité de notre programme est la suivante:

  1. On attend que l'utilisateur pousse le bouton bleu.
  2. Pendant que le bouton est poussé, le moteur tourne et la LED verte s'allume.
  3. Quand le bouton est relâché, la LED s'éteint et le moteur change la direction.
  4. Dans une boucle infinie, on recommence l'étape 1
uint8_t ButtonPressed;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
 if ( GPIO_Pin == GPIO_PIN_13 )
 {
  GPIO_PinState PinState;
  PinState = HAL_GPIO_ReadPin( GPIOC, GPIO_PIN_13);
  if ( PinState == GPIO_PIN_RESET )
   ButtonPressed = 1;
  else
   ButtonPressed = 0;
 }
}

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  ButtonPressed = 0;
  int32_t Angle = 1;
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();

  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
   while ( ButtonPressed == 0 )
   {
   }
   HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);

   while ( ButtonPressed == 1 )
   {
    Move( Angle );
   }
   HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
   Angle = -Angle;
  }