Club robotique de Sophia-Antipolis

Accueil > Projets, études > Etudes techniques > Moteurs et performances > Moteur pas-à-pas piloté par GPIO du Raspberry Pi

Moteur pas-à-pas piloté par GPIO du Raspberry Pi

Eppur si muove (et pourtant il tourne) !

dimanche 24 février 2013, par Frédéric R.

Je sais à présent piloter un moteur pas à pas depuis mon Raspberry pi directement (en utilisant les ports GPIO).

Pour ceux qui seraient intéressés, voici comment j’ai procédé

  • Théorie sur les moteurs pas à pas
  • Piloter un moteur pas à pas (via un CI L293D) avec une Arduino
  • Programme Arduino
  • Théorie du GPIO de la Raspberry Pi
  • Assemblage de LEDs pilotées par la Raspberry
  • Allumer 4 LEDs avec un script en Python
  • Utilisation de la bibliothèque WiringPi en C
  • Programmation du pilote en C

Théorie sur les moteurs pas à pas

Pour cette expérience, j’ai utilisé un moteur pas à pas bipolaire (4 fils). On doit contrôler le sens courant dans chacune des bobines. Dans le cas de ce type de moteur, il n’y a qu’une seule bobine pour chaque pôle.

Ce type de configuration impose de pouvoir changer le sens du courant au niveau du système d’alimentation. Ces moteurs sont appelés moteurs pas à pas bipolaires, car lors de leur rotation chacune des bobines va être polarisée de deux façons différentes. Pour faire un tour complet, il faut 8 étapes. On parle alors de demi pas.

Pour faire avancer le moteur, il faudra donc réaliser les séquences suivantes :

En gris, les pôles ne sont pas alimentés ; en noir les pôles alimentés attirent le rotor de pas en pas pour effectuer un tour complet.

Piloter un moteur pas à pas (via un CI L293D) avec une Arduino

pas serait le bras musclé tandis que le pilotage devra se faire depuis le cerveau (dans notre cas l’Arduino puis le Raspberry pi).
Si le cerveau est capable de placer correctement les valeurs aux 4 pôles (ABCD) à chaque instant, il n’a pas la force physique de déplacer le moteur.
Le moteur nécessite un courant plus fort (de l’ordre de 1A à 2A). Le cerveau travaille quant à lui avec un courant de l’ordre de 20mA.

Si on branchait directement « les muscles » du cerveau sur le moteur, ce premier grillerait sur place (trop de puissance). C’est là qu’intervient le CI L293D. Le rôle de ce dernier est de fournir une tension dans un pôle chaque fois que « le cerveau » le demande mais avec les capacités physiques « des muscles ».

Lorsque le CI reçoit un courant faible en 2A, il retourne un courant fort en 2Y
Le CI est lui-même alimenté en courant faible Vcc1
Le courant fort est placé en Vcc2 (il est possible de shunter Vcc2 sur Vcc1 pour les PETITS moteurs pas à pas)
Certains moteurs ont besoin d’un courant fort pour déplacer le rotor mais il ne doit pas resté trop longtemps dans le moteur au risque de le griller. Pour éviter cela on peut cesser d’alimenter les pattes « Enable » (EN1et2 et EN3et4).

J’ai donc connecté à mon Arduino un shield de test sur lequel j’ai placé mon CI L293D.
J’ai relié les broches 13(Enable), 8 (Pôle A), 9 (Pôle B), 10 (Pôle C), 11 (Pôle D)

J’ai choisi d’implémenter une gestion du signal de PWM avec la broche 13. Toutefois, cette option n’étant pas nécessaire pour mon petit moteur, je ne l’implémenterai pas dans la version pour Raspberry. Et je modifierai le câblage en court-circuitant les broches Enable (sur Vcc1 +5V)

Programme Arduino

Pour alimenter le moteur, il faut réaliser les séquences vues précédemment, suffisamment rapidement pour que cela semble fluide puis envoyer ce signal au CI L293D.

Il existe des programmes tout fait donnés en exemple pour piloter un moteur pas à pas mais j’ai préféré le refaire par moi-même car cela devrait pouvoir me permettre de porter mon code plus facilement lorsque j’aurai besoin de migrer sur la Raspberry Pi.
Voici donc mon code :

/* 
Moteur pas à pas
Permet de faire piloter un moteur moteur
SANS utilisation de la librairie Stepper.h (moteur Bipolar) 

Functions
    Stepper(steps, pin1, pin2, pin3, pin4)
    setSpeed(rpm)
    step(steps) 
    
AUTEUR : Frédéric RALLO
 */
 
 
#include <MsTimer2.h> // inclusion de la librairie Timer2 

// dépend du moteur
const int nombreDePasParTour = 48;  
                                     


// --------------------------------------------------------------------------------------------------------------------------
//          METHODES PRIVEES
// --------------------------------------------------------------------------------------------------------------------------
// enable control (HIGH => Le moteur est pilotable)
int enable = 0;                                     
 
 // 4 fils du moteur pas à pas
int bobineA = 0;
int bobineB = 0;
int bobineA_barre = 0;
int bobineB_barre = 0;

byte etat = 0;
long nbInterruptions = 100; // mis à jour dans changerVitesse()

long vitesse = 0; //nb de tours par minute calculé dans init()
int nbInterruptionsParTour = 0; //calculé dans init()

// --------------------------------------------------------------------------------------------------------------------------
//          METHODES PUBLIQUES
// --------------------------------------------------------------------------------------------------------------------------



void initMoteur(byte nombreDePasParTour, int filEN, int fil1, int fil2, int fil3, int fil4) {
  // filEN, fil1..fil4 : numméro des E/S carte ARDUINO pour connection du moteur
  // nombreDePasParTour est le nombre de pas par tour
  // enable=HIGH --> le moteur est pilotable
  // chaque tour, on active succéssivement :
  // . etat=0 --> bobineA, 
  // . etat=1 --> bobineA + bobineB
  // . etat=2 --> bobineB, 
  // . etat=3 --> bobineB + bobineA_barre
  // . etat=4 --> bobineA_barre, 
  // . etat=5 --> bobineA_barre + bobineB_barre
  // . etat=6 --> bobineB_barre
  // . etat=7 --> bobineB_barre + bobineA
    enable = filEN;
    bobineA = fil1;
    bobineA_barre = fil2;
    bobineB = fil3;
    bobineB_barre = fil4;
    nbInterruptionsParTour = nombreDePasParTour * 8;
}


void changerVitesse(long v) {
  digitalWrite(enable, LOW); 
  if ( (v>0) && (nbInterruptionsParTour>0) ) {//on doit avoir initialisé avant
      //nbInterruptions = 1000/(nbInterruptionsParTour*v/60);
      nbInterruptions = 3600/(nbInterruptionsParTour*v/60);
      vitesse = v;
  }
  Serial.print("vitesse=");
  Serial.print(vitesse);
  Serial.print("     nombre_De_Pas_Par_Tour=");
  Serial.print(nombreDePasParTour);
  Serial.print("     frequence_interruption=");
  Serial.println(nbInterruptions);
  digitalWrite(enable, HIGH); 
}


void avancerUnPas() {
  switch (etat) 
    {
      case 0: //Serial.print("bobineA :"); 
              digitalWrite(bobineA, HIGH); 
              digitalWrite(bobineB, LOW); 
              digitalWrite(bobineA_barre, LOW); 
              digitalWrite(bobineB_barre, LOW);
              break;
      case 1: //Serial.print("bobineA bobineB :"); 
              digitalWrite(bobineA, HIGH); 
              digitalWrite(bobineB, HIGH); 
              digitalWrite(bobineA_barre, LOW); 
              digitalWrite(bobineB_barre, LOW);
              break;
      case 2: //Serial.print("bobineB :");
              digitalWrite(bobineA, LOW); 
              digitalWrite(bobineB, LOW); 
              digitalWrite(bobineA_barre, HIGH); 
              digitalWrite(bobineB_barre, LOW);
              break;
      case 3: //Serial.print("bobineA_barre bobineB :");
              digitalWrite(bobineA, LOW); 
              digitalWrite(bobineB, HIGH); 
              digitalWrite(bobineA_barre, HIGH); 
              digitalWrite(bobineB_barre, LOW);
              break;
      case 4: //Serial.print("bobineA_barre :"); 
              digitalWrite(bobineA, LOW); 
              digitalWrite(bobineB, LOW); 
              digitalWrite(bobineA_barre, HIGH); 
              digitalWrite(bobineB_barre, LOW);
              break;
      case 5: //Serial.print("bobineA_barre bobineB_barre :");
              digitalWrite(bobineA, LOW); 
              digitalWrite(bobineB, LOW); 
              digitalWrite(bobineA_barre, HIGH); 
              digitalWrite(bobineB_barre, HIGH);
              break;
      case 6: //Serial.print("bobineB_barre :");
              digitalWrite(bobineA, LOW); 
              digitalWrite(bobineB, LOW); 
              digitalWrite(bobineA_barre, LOW); 
              digitalWrite(bobineB_barre, HIGH);
              break;
      case 7: //Serial.print("bobineB_barre bobineA :"); 
              digitalWrite(bobineA, HIGH); 
              digitalWrite(bobineB, LOW); 
              digitalWrite(bobineA_barre, LOW); 
              digitalWrite(bobineB_barre, HIGH);
              //Serial.println("-------------> 1 pas");
              break;
    }//end Case
  etat = (etat+1) % 7;
}


// --------------------------------------------------------------------------------------------------------------------------
//          INITIALISATION
// --------------------------------------------------------------------------------------------------------------------------

void setup() {
  pinMode(enable, OUTPUT);
  Serial.begin(9600);
  
  
  initMoteur(nombreDePasParTour, 13,8,9,10,11);   
  pinMode(enable, OUTPUT); 
  pinMode(bobineA, OUTPUT); 
  pinMode(bobineB, OUTPUT); 
  pinMode(bobineA_barre, OUTPUT); 
  pinMode(bobineB_barre, OUTPUT);
  //rend le moteur devient pilotable
  digitalWrite(enable, HIGH); 
  
  //définit la vitesse du moteur
  changerVitesse(60);
  
   MsTimer2::set(nbInterruptions, avancerUnPas); // période (ms) 
   MsTimer2::start(); // active Timer2 
  // initialise le port serie
  
}



// --------------------------------------------------------------------------------------------------------------------------
//          BOUCLE INFINIE
// --------------------------------------------------------------------------------------------------------------------------
void loop() {
  
  //avance de 1 pas
  //avancerUnPas();
}

On notera que j’ai cherché à ne pas utiliser d’attente passible bloquante. La boucle principal ne fait finalement rien !

Le système gère des « interruptions » à la manière de Raspbian. Lorsque qu’une interruption apparaît, elle est traitée aussitôt que possible.

En utilisant ce code avec mon Arduino et le shield j’ai réussi à faire tourner le moteur pas à pas sur une Arduino.

Théorie du GPIO de la Raspberry Pi

J’ai décidé de regarder de près les curieux connecteurs GPIO de ma framboise (Raspberry Pi).
En fait les ports GPIO sont directement reliés au processeur. Cela implique plusieurs choses :

  • Ils sont très fragiles et on a vite fait d’en griller un de façon irrémédiable !
  • Ils fonctionnent en 3,3V (et non 5V)
  • Ils ont un courant TRES faible
  • Ils sont susceptibles d’être utilisé pas plusieurs applications (en parallèles) et donc avoir un comportement inattendu
  • Ils sont utilisables uniquement en mode administrateur

Il existe 8 ports GPIO (de quoi piloter 2 moteurs pas à pas).

Assemblage de 4 LED pilotées par la Raspberry

Attention, les connecteurs permettent de sortir une tension de 5V mais il faut absolument utiliser la sortie 3,3V (pin 2)

Les GPIO ne distinguent pas naturellement les Entrées et les Sorties. Il est donc possible de brancher des LEDs en Sortie (avec des résistances de 270 ohms) et des boutons poussoir en Entrées (avec une résistance de 10 Kilo ohms).
Si j’ai bien compris cette dernière valeur de résistance n’est pas très importante mais si elle est trop faible le circuit aura tendance à consommer du courant lorsqu’on sollicite le bouton poussoir…

Pour faire pus « propre » j’ai fabriqué une nappe à partir d’une nappe pour floppy de vieux PC mais il existe également des nappes spécialement conçues pour les ports GPIO !

Allumer les LEDs avec un script en Python

Dans ma version de système d’exploitation (Occidentalis (Adafruit)) je n’ai pas eu besoin d’installer Python… mais sinon :

 sudo  mkdir GPIO
 cd GPIO
 sudo wget http://pypi.python.org/packages/source/R/RPi.GPIO/RPi.GPIO-0.4.2a.tar.gz 

(la version peut changer)

 sudo tar –zxvf GPIO-0.4.2a.tar.gz
 cd RPi. GPIO-0.4.2
 sudo python setup.py install

Pour tester il faut lancer python :

 sudo python

importer la bibliothèque GPIO :

 import RPi.GPIO as GPIO

demander à utiliser le mode board

 GPIO.setmode(GPIO.BOARD)

connecter le port GPIO 0 (pin11) à la LED (+) (puis résistance et GND comme dans schéma)

ouvrir le port GPIO 11 en sortie

 GPIO.setup(11, GPIO.OUT)

Controler la LED (avec True ou False)

 GPIO.output(11, False)

pour quitter

 exit()

Utilisation de la bibliothèque WiringPi en C

Les tests étant concluants, je poursuis en chercher à coder en C (et non en python, pratique pour les tests mais lent).

J’ai suivi le tuto https://projects.drogon.net/raspber...

Installation de Git (si vous ne l’avez pas déjà)

 sudo apt-get install git-core

En cas d’erreur, saisir

 sudo apt-get update sudo apt-get upgrade

Télécharger wiringPi à partir de GIT

 git clone git://git.drogon.net/wiringPi
 cd wiringPi 
 git pull origin

Installer le tout

 cd wiringPi 
./build

Programmation du pilote en C

Voici le code

#include <wiringPi.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int vitesse = 10; //constante

// données sous forme de Triplets (led, etat, duree)
// led, Etat (1=On / 0=Off) , duree

uint8_t data [] =
{			//A=0, B=2, C=3, D=4 (init --> 	A b c d		S - N - )
          
            1, 1, 1, // on allume B -->		A B c d		S S N N
  0, 0, 1, // on éteint A 			-->		a B c d		- S - N
            3, 1, 1, // on allume C -->		a B C d		N S S N
  1, 0, 1, // on éteint B 			-->		a b C d		N - S -  
            4, 1, 1, // on allume D -->		a b C D		N N S S
  3, 0, 1, // on éteint C 			-->		a b c D		- N - S
            0, 1, 1, // on allume A -->		A b c D		S N N S  
  4, 0, 1, // on éteint D 			-->		A b c d		S - N -  (début de la séquence)

  9, 9, 9,	// marque de fin de la séquence
} ;



void testLed (int n) {
  int led, t;	
  for (t = 0 ; t < n ; ++t) { // on fait clignoter n fois
	  for (led = 0 ; led < 8 ; ++led) { // on allume les led
	     pinMode (led, OUTPUT) ;
	     digitalWrite (led, 1) ;
	  }
	  delay (100) ;
	  
	  for (led = 0 ; led < 8 ; ++led) { // on éteint les led
	     pinMode (led, OUTPUT) ;
	     digitalWrite (led, 0) ;
	  }
	  delay (100) ;
  }
} 

void init () {
  int led;
  
  testLed (3); //on vérifie que toutes les led s'allument
  pinMode (0, OUTPUT) ;// on allume A 
  digitalWrite (0, 1) ;
  
  for (led = 1 ; led < 8 ; ++led) { // on éteint les autres
     pinMode (led, OUTPUT) ;
     digitalWrite (led, 0) ;
  }

  delay (vitesse) ;
  //résultat : on a -->		A b c d		S - N - 
  
}

int main (void)
{
  int dataPtr ;
  int led, etat, duree ;

  printf ("Raspberry Pi wiringPi test program\n") ;

  if (wiringPiSetup () == -1) exit (1) ;
 
  init();
 
  dataPtr = 0 ;
  for (;;)
  {
    led = data [dataPtr++] ;	// LED
    etat = data [dataPtr++] ;	// State
    duree = data [dataPtr++] ;	// Duration (10ths)

    if ((led + etat + duree) == 27) // = End marker (9 + 9 + 9)
    {
      dataPtr = 0 ;
      continue ;
    }

    digitalWrite (led, etat) ;
    delay (duree * vitesse) ;
  }

  return 0 ;
}

Ce code (test_led0.c) reprend les grandes lignes du code pour l’Arduino.
Pour le compiler, placez-le dans GPIO/wiringPi/examples

 cd / GPIO/wiringPi/examples

Compiler

 make test_led0

A présent il faut adapter le shield initialement fait pour l’Arduino.

  • brancher les 2 Enable au +5V (pas besoin dans ce test)
  • branchez la sortie +5V pour alimenter le CI L293D
  • connectez les port GPIO 0,1,3 et 4 (j’ai grillé mon GPIO 2 ;( )

Lancer (exécuter) test_led0

sudo ./test_led0

IL TOURNE !
ici

Portfolio

Vos commentaires

  • Le 27 mai 2016 à 19:25, par G.A. En réponse à : Moteur pas-à-pas piloté par GPIO du Raspberry Pi

    dommage de ne pas faire tout cela en python ....
    utliser un raspberry l’avantage ben c’est justement parceque les ports GPIO sont accessibles facilement en python et en cpge ( par exemple ) .....
    l’argument de la rapidité pour faire tourner un moteur pas à pas pédagogique avec des diodes qui s’allument en temps réel pour matérialiser les phases ...euh ne porte pas ....rasberry et son python interprété irait aussi vite non ? ( je me trompe peut être ... )
    Merci néanmoins de mettre tout cela à la disposition du public , c’est apprécié !!!
    ++

    • Le 18 août 2016 à 18:56, par Eric P. En réponse à : Moteur pas-à-pas piloté par GPIO du Raspberry Pi

      Je ne peux qu’approuver l’appréciation portée à Python, étant moi-même Pythoniste convaincu (y compris à titre professionnel).
      Il y a cependant un facteur rapidité d’exécution important entre du Python interprété et du C/C++ compilé, ce qui peut être rédhibitoire selon l’objectif.
      En fait l’optimum est de conserver Python pour la partie haut niveau, modèle,... n’ayant pas d’exigence particulière en termes de performances, et d’implémenter les traitements critiques en C/C++ appelé depuis Python à l’aide du module ctypes.

    Répondre à ce message

Un message, un commentaire ?

modération a priori

Attention, votre message n’apparaîtra qu’après avoir été relu et approuvé.

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.