[Moteurs PAP] un code qui marche?

Salut la communauté!

Alors j'ai fait une petit électronique simple, on rentre avec deux fils et on sort sur les 4 bobines du moteur pas à pas. en fait, j'ai fait un simple démultiplexeur 2 => 4.

Mais voilà, histoire de, j'ai mi ce code là :

void loop(){
  PORTD += 0x04; // interface connectée sur D3 et D4, on fait tourner la séquence { 00, 01, 10, 11}
  delay(analogRead(A0);    // potar sur A0 pour la vitesse
}

ça marche, mais pas moyen d'aller vite, le moteur déraille tout de suite!

Alors j'ai regardé avec mon oscillo, ben franchement, le signal gigote dans tous les sens (entre le delay et l'analogRead...), donc normal que le moteur ne suive pas.

j'ai ressorti un vieux montage à base de CMOS, un oscillateur, compteur, démultiplexeur, et là, le moteur répond impec (tant qu'on lui laisse le temps d'accélérer bien sur!)

Quelqu'un a un bout de code à base de vraies interruptions qui marche bien pour faire aller le moteur super vite? Sinon, je le ferai, mais bon, autant travailler ensemble :wink:

j'ai ouvert la lib stepper, bouh que c'est laid! que des int 16 bits, des digitalWrite()... Bref, tout ce qu'il faut pour faire une catastrophe!

J'ai ça (voir code dans fichier attaché) qui fonctionne nickel avec ce moteur (Pololu - Stepper Motor: Unipolar/Bipolar, 200 Steps/Rev, 42×48mm, 4V, 1.2 A/Phase) et ce driver (Pololu - A4988 Stepper Motor Driver Carrier, Black Edition).

Je ne suis pas sûr que ça corresponde vraiment à ce que tu veux mais si ça peut te donner des idées, ça sera déjà pas mal.

FrequencySweep.ino (1.12 KB)

Salut et merci,

Il y aurait de l'idée, mais l'utilisation de la fonction tone() ne me convient pas trop. D'accord, ça permet certainement de générer une belle horloge pour le driver, mais avec ça, tu ne contrôles pas du tout le nombre de pas. Mon idée est de pouvoir "dire" faire x pas en avant ou en arrière, et que tout ça passe bien sûr par une phase d'accélération et décélération, avec toutes les contraintes de fonctionnement (vitesse max, conditions de charge...).

J'ai commencé à plancher sur l'utilisation d'un timer qui génère une horloge de fonctionnement, et quelques contrôles autour, mais j'imagine que quelqu'un a déjà planché sur le truc, je ne voudrais pas réinventer l'eau chaude, simplement adapter sa température...

Faudrait regarder dans les codes des CNC ou des imprimantes 3D.
Il y en a plusieurs qui utilisent des cartes arduino

De tous les projets de CNC que j'ai pu voir, les déplacements sont extrêmement lents, et je ne pense pas que ce soit dû aux "vitesses de coupe", mais plutôt de la difficulté de faire tourner deux moteurs PAP en même temps, et surtout synchros dans le cas de diagonales.

Je n'en ai encore vu aucun proposant des déplacements "rapides", les vitesses des moteurs semblent fixes. Le plus dur étant la gestion de l'accélération (surtout de plusieurs moteurs qui ne tournent pas à la même vitesse mais doivent être synchros) qui permettrait de gagner énormément. Je pense que tout est faisable avec un petit AVR, mais va falloir lui rentrer profondément dedans pour arriver à ce que j'en attends.

Je vais quand même retourner faire un tour sur gogol...

Google et le forum sont tes amis:
2 ème résultat en faisant une recherche sous google avec les mots clés "arduino stepper motor library"
Peut être intéressant http://forum.arduino.cc/index.php/topic,22400.0.html

Bah GRBL niveau optimisation y'a pas mieux ... mais je comprend pas vraiment ton problème de vitesse : un moteur pas à pas, d'une manière générale il est difficile de dépasser 10 000 pas/s, et encore tu n'as plus qu'1/4 du couple ... et 10khz c'est tenable, même avec des digitalWrite()

B@tto:
et 10khz c'est tenable, même avec des digitalWrite()

pas tant que ça... car un digitalWrite() avec du delay(), ça sort tout sauf une fréquence assez stable pour un PAP. arrivé à une certaine vitesse, un petit écart de tempo dans un cycle représente une accélération qui souvent peut être fatale (perte de pas, voire décrochage complet du moteur).

J'ai regardé GRBL, en effet, il semblerait que ça soit pas mal, à regarder de près. (mais c'est lourd!)

Sinon, pour la lib de mikem, il faut lancer le plus souvent possible stepper.run(), sinon, le moteur ne tourne pas. Et de cette manière, on a toutes les chances de taper à côté et encore une fois, perdre toute la stabilité. C'est surtout ça que je recherche : la stabilité. GRBL se base sur une interruption timer, donc devrait être stable.

Bon... par rapport à ce que j'essaie de faire, GRBL va être trop long à nettoyer...

j'ai donc fait quelques calculs savants, en fonction des différents moteurs que j'ai sous la main et de leurs réducteurs associés...

comme ils viennent tous d'imprimantes et scanners, ils offrent des déplacements de courroie équivalents à des résolutions de 600 ou 1200DPI. J'ai regardé ce qu'un timer 8bits pouvait m'offrir en terme de vitesse de la courroie / fréquence de pas.

ma formule donne V(mm/s) = F(Hz) x 25,4 / R. (R = résolution en DPI ou en pas par pouce).

Je vous cache pas que derrière, il y a peut-être une éventuelle idée de CNC... j'ai déjà réussi à adapter l'algorithme de Bresenham pour faire un déplacement en ligne droite en 3D (du point (X1, Y1, Z1) au point (X2, Y2, Z2) ) avec accélération et décélération, à partir d'une interruption timer. Sur papier, car je n'ai rien monté en mécanique pour l'instant...

Mais voilà, je bute (plus pour très longtemps) sur l'accélération, car je voudrais dans un premier temps qu'elle soit constante et c'est pas évident, car la config du timer se fait en secondes² alors que l'accélération est en 1/s². je peux passer par tableau, mais il va me falloir plusieurs tableaux... ou utiliser un timer dédié qui génère l'accélération, mais là encore, ça me fera deux timers par moteur, on va vite être limité.

Bref, pour rester à 1 timer par moteur (pour gérer au besoin des déplacements indépendants), avec une accélération moyenne de 10mm.s-2 et rester dans des calculs 8 bits, j'obtiens de vitesse comprise entre 3 et 41mm.s-1 (donc normalement, j'atteins la vitesse max en 3 secondes)... Mais il y a un mais, car les changements de vitesses se font par paliers, et pas sûr que la moteur accepte le dernier palier (de 919Hz à 976Hz d'un coup, comme ça!) va falloir tester! Et bien sûr, en charge, car l'inertie d'une charge joue beaucoup sur la capacité à accélérer!

je vous tiens au courant...

Salut,

J'ai bidouillé un code pour gérer 3 moteurs pour un robot "delta" avec une rampe linéaire pour éviter de sauter des pas au démarrage en charge, ça marche très bien chez moi, j'ai utilisé l'algorithme de Bresenham.

voila un bout de code:

  if(val[0]-prev[0]>0){digitalWrite(3,HIGH);}else{digitalWrite(3,LOW);} //determination du sens
     if(val[1]-prev[1]>0){digitalWrite(6,HIGH);}else{digitalWrite(6,LOW);}
     if(val[2]-prev[2]>0){digitalWrite(9,HIGH);}else{digitalWrite(9,LOW);}

     for(int i=0;i<3;i++){d[i] = val[i] - prev[i];}  //calcul de la course a parcourir   

     long m= max(max(abs(d[0]),abs(d[1])),abs(d[2])); // identifier distance maxi pour denominateur:

     for(int i=0;i<3;i++){r[i]=(float)abs(d[i]) / (float) m;a[i]=0;} // debut de calcul des ratios, le plus grand sera égal à 1
      
      float v=m*1/Rampe;     // defini la plage d'acceleration et de deceleration (10% de la course en acceleration et 10% en deceleration, 80% en vitesse max);
      float w=m-v;
      
      
     for(float n=0;n<m;n++){ //debut boucle de mouvement
       float CoefSpeed=1.00; // par defaut coef de vitesse au max(ni acceleration ni deceleration)
         if (n<v){CoefSpeed=map(CoefSpeed,n,v,1.70,1.00);}
         if (n>w){CoefSpeed=map(CoefSpeed,n,w,1.00,1.70);}

      
        	for(int i=0;i<3;i++){a[i]=a[i]+r[i];} // on incrémente:
   
   	 //si erreur >=1 on fait une step, puis on decremente l'erreur
   	 //les stepX... juste pour info du nombre réel de steps effectué
   	 if(a[0]>=1.00){a[0]--;reel[0]++;digitalWrite(2, HIGH);delayMicroseconds(delayUs);digitalWrite(2, LOW);}
   	 if(a[1]>=1.00){a[1]--;reel[1]++;digitalWrite(5, HIGH);delayMicroseconds(delayUs);digitalWrite(5, LOW);} 
   	 if(a[2]>=1.00){a[2]--;reel[2]++;digitalWrite(8, HIGH);delayMicroseconds(delayUs);digitalWrite(8, LOW);}   
   	 delayMicroseconds(Speed*CoefSpeed);
 
 } //vitesse globale des moteurs à définir suivant installation
  
  while(1){
    
    boolean rattrapageOK = true;
  if(reel[0] < abs(d[0])){reel[0]++;digitalWrite(2, HIGH);delayMicroseconds(delayUs);digitalWrite(2, LOW);rattrapageOK = false;}
  if(reel[1] < abs(d[1])){reel[1]++;digitalWrite(5, HIGH);delayMicroseconds(delayUs);digitalWrite(5, LOW);rattrapageOK = false;}
  if(reel[2] < abs(d[2])){reel[2]++;digitalWrite(8, HIGH);delayMicroseconds(delayUs);digitalWrite(8, LOW);rattrapageOK = false;}
  delayMicroseconds(Speed);
  if(rattrapageOK = true){break;}
  }
  
    // RAZ DELTA CAR MOUVEMENT EFFECTUE
    for(int i=0;i<3;i++){prev[i] = val[i];prevt[i] = t[i];reel[i]=0;}
} // if error ==0
  return(error);

Je vois, mais tu utilises des digitalWrite() et delay(), et ces fonctions ne sont pas assez temporellement stables pour envoyer du boudin dans un PAP. En plus, les calculs sur float ne prennent jamais deux fois le même temps d'exécution. Je cherche à pondre des fréquences de ouf (quelques KHz). mais si à un moment, sur un régime établi, un pas décide de mettre du temps à venir, alors ça équivaudrait à ralentir le moteur. On s'en fout un peu, car on les compte, les pas, donc on sait où on est. sauf que si le moteur n'arrive pas à ralentir assez, ça peut suffire à ce qu'il soit quand même trop loin du pas suivant, et donc il déraille.

L'exemple d'un défilé militaire : tant que tout le monde marche au pas, ok, ça pète! Mais si un fantassin met le pied à côté, c'est tous les autres derrière qui vont le suivre, et là, un 14 juillet sur les champs, ça va pas être dans le goût du chef, blâme général.

Ou encore le coup de l'escalier, si tu rates une marche, tu as toutes les chances de rater toutes les autres et ça fait mal. (amuse-toi à changer la hauteur d'une seule marche de 2cm, plus personne n'arrivera à monter ou descendre l'escalier sans se casser lag.).

sur des fréquences jusqu'à 100Hz, bah... ça le fait, les temps sont assez long pour que le courant dans les bobines prenne le dessus. mais si on va vite, le courant reste faible, donc il n'y a plus cet effet bourrin qui force le pas...

il faut que je teste mon code pour voir jusqu'où peut aller un PAP. je prends pour exemple une bécane que j'ai chez moi (une découpeuse de vinyl de 2m de long, une CNC de rêve), sa résolution est de 1016 DPI (400 ppcm), et quand tu vois le cutter parcourir les 2m (80000pas) en 4 secondes (20KHz?), on se dit que les PAP, bien réglés, ça fait des malheurs (vu le prix de la machine, elle ne rate jamais un pas)! d'ailleurs, les CNC homemade, ça tourne pas bien vite, et j'imagine que justement, c'est une question de stabilité des pas qui fait qu'on ne peut pas aller plus vite...

delay() est basé sur un timer, donc parfaitement temporellement stable. Si tu passes par des manipulations de port directe alors il n'y a aucune raison que ta fréquence fasse du boudin

unsigned long micros() {
	unsigned long m;
	uint8_t oldSREG = SREG, t;
	
	cli();
	m = timer0_overflow_count;
	t = TCNT0;
	if ((TIFR0 & _BV(TOV0)) && (t < 255)) m++;
	SREG = oldSREG;
	
	return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

void delay(unsigned long ms)
{
	uint16_t start = (uint16_t)micros();

	  while (ms > 0) {
		if (((uint16_t)micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

On est quand même dans l'approximatif, là, non? Quand je parle de stabilité, c'est que sur plusieurs appels, la fonction produit exactement le même temps. là, c'est loin d'être le cas.

Je vois pas l'approximation :o

micros() est incrémenté par l'overflow du timer, je vois pas ce qu'il y a de hasardeux !

Et puis il faut avoir le sens des réalités : à 10 khz ça fait des périodes de 100 µs, même à 5% d'erreur c'est pas ça qui fera décroché ton PAP ... digitalWrite(), lui, est hasardeux (je suis tombé sur un topic d'un mec qui avait testé, en moyenne c'est 5-7µs, mais des fois ça tape sans raison à 17-18).

Après en gestion multi moteurs ça devient particulièrement pertinent d'utiliser les timers car en delay() c'est ingérable

Une solution peut être d'utiliser digitalWriteFast. Explications et code ici:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1267553811/0
http://code.google.com/p/digitalwritefast/

oui ou encore mieux la manipulation des port directement :wink:

C'est exactement là-dessus que je travaille (enfin, pour l'instant, c'est une calvitie grandissante).

je définis une table de 8 pas (0001, 0011, 0010, 0110, 0100, 1100, 1000, 1001), et là-dessus, j'ai une int timer qui agit comme suit :

ISR(TIMER1_COMPA_vect){
  motor_step++;
  motor_step &= 0x07;
  PORTX &= 0xF0;
  PORTX |= table_step[motor_step];
}

Je ne pense pas qu'on puisse faire plus optimal. digitalWriteFast() m'oblige à faire pas mal de tests pour savoir sur quelle pin je mets false ou true. Et j'ai pour principe qu'un projet est dès le début bien pensé, donc le câblage ne changera jamais (il faut rêver dans la vie).

le truc, c'est que j'ai pas encore testé... et que je n'ai trouvé aucun code de ce genre déjà fait. (je ne vous ai pas mis les phases accélération, c'est juste pour poser les bases...)

Je comprend pas cette ligne :
PORTX &= 0xF0;

D'une part, tu écrit sur tes sorties un état intermédiaire non voulu (toutes tes sorties passe à zéro). Ensuite, au pire pour corriger ça, il suffit de faire :

PORTX &= 0xF0 | table_step[motor_step];

Mais la solution optimale c'est tout simplement :

PORTX &= table_step[motor_step];

Avec table_step[] directement rempli avec les bons bytes. Genre :

byte table_step[]={0b11110011,0b11110110,0b11111100,0b11111001};

Mais la solution optimale c'est tout simplement :

PORTX &= table_step[motor_step];

mais non...
exemple : étata initial de PORTX : xxxx0011
on devrait donc passer à l'état suivant (table[step] = 11110110 ), donc faire PORTX = xxxx0011 & 11110110 = xxxx0010 on en a raté un.

Dans un souci de rapidité, je suis obligé de le faire en deux coups.

le premier (PORTX &= 0xF0) met mes 4 bits de sortie (0 à 3) à 0,
le second (PORTX |= table[step]) met les sorties à 1 selon le masque contenu dans table[step].

un &= ne peut que mettre des bits à 0, |= ne peut que mettre des bits à 1. on ne peut pas faire autrement que d'utiliser les deux. La première opération ne devrait durer que 3 cycles d'horloge, soit 190ns. Je ne pense pas qu'une transition à 0 si courte passe à travers le driver du moteur, et encore moins influer sur le courant dans les bobines...

PORTX = PORTX & 0x0F | table[step]; serait peut-être mieux, si le compilateur décide de passer par une variable intermédiaire, en décomposant l'opération par :

byte temp = PORTX;
temp &= 0xF0;
temp |= table[step];
PORTX = temp;

Du moment que ça prend toujours le même temps d'exécution! :slight_smile:

Autant pour moi c'est un ou exclusif qu'il faut, et en plus tu n'as plus besoin que d'une valeur : 0101

A supposer état initial xxxx0011

  1. xxxx0011 ^=00000101 ==> xxxx0110
  2. xxxx0110^= 00000101<<1 ==> xxxx0110^= 00001010 ==> xxxx1100
  3. xxxx1100 ^=00000101 ==> xxxx1001
  4. xxxx1001^= 00000101<<1 ==> xxxx1001^= 00001010 ==> xxxx0011

Super_Cinci:
à travers le driver du moteur, et encore moins influer sur le courant dans les bobines...

Le problème c'est la coupure de courant et donc la décharge des bobines ...

Super_Cinci:

byte temp = PORTX;

temp &= 0xF0;
temp |= table[step];
PORTX = temp;




Du moment que ça prend toujours le même temps d'exécution! :)

Oué mais plus de passage à zéro :wink: