Go Down

Topic: bibliothèque ArpStepper pour moteur pas à pas à tester (Read 510 times) previous topic - next topic

bouault

Bonjour à tous,
Je suis en train d'écrire une bibliothèque pour asservir un ou plusieurs moteurs pas à pas, en position et en vitesse.
Si vous souhaitez la tester vos retours, questions, conseils, idées seront les bienvenus.
Jacques
arpschuino.fr

lelectronlibre

Bonjour Jacques
Je vous remercie de votre réponse et je vais essayer de comprendre le fonctionnement de votre programme et peut être l'adapter pour mon projets qui est simple en programmation mais pas pour moi :)
car en fin de compte c'est exactement cela qu'il me faut

Je vous souhaite, bonne continuation et bien sur je vous donnerais des nouvelles. Patrick 

Ton code m'intéresse surtout pour le calcul de la vitesse. Il a le gros avantage d'être lisible.

Je suis aussi en train de faire la même chose, avec des objectifs légèrement différents. Je vais passer par les interruptions pour pouvoir utiliser au mieux le temps qu'il me reste. Je me pose aussi la question de l'accélération.

Quelques remarques note bien que ce qui est positif, ne fera pas l'objet de remarques et que si je fais des remarques c'est parce que je suis allé plus loin qu'un simple regard.
 

Je n'ai pas compris le paramètre resolution


calcul_nbr_steps() retourne un float alors que c'est un uint_16 qui contient la valeur à retourner. Autant mettre void, et supprimer le return, de toute façon le float retourné n'est pas utilisé.


Code: [Select]
  bool sens;
  if (m_nbr_pas>0)                  sens=LOW + m_reversed;
  else                              sens=HIGH - m_reversed;
  //bool sens =(!m_nbr_pas)+ m_reversed;???????

Pour la faire en une fois, il faut utiliser le ou exclusif:
bool sens =(m_nbr_pas>0) ^ m_reversed;
MAIS! le calcul prend alors 13 cycles d'horloge u lieu de 11 (avec l'optimisation par défaut)

Code: [Select]
  m_change_sens=(sens != m_previous_sens); //equivalent plus rapide a :
//  if (sens != m_previous_sens)      m_change_sens = 1;
//  else                              m_change_sens = 0;

Celui ci il est plus drôle: J'ai mesuré
10 cycles d'horloge pour m_change_sens=(sens != m_previous_sens);
9 cycles d'horloges pour if (sens != m_previous_sens)  m_change_sens = 1; else m_change_sens = 0;
Pour moins cher c'est plus simple à comprendre...


Ca se complique vraiment avec les calculs de vitesses... Je ne suis pas sûr qu'une erreur  de 1 au maximum soit problématique.


Code: [Select]
m_nbr_pas(0),//int16_t suffisant si commande en 16 bits ?
Chacun son truc, mon papier avance de 4cm par tour de moteur (200 pas) et je préfère peut être le 1/16 pas, je pense que cela devrait donner moins de vibrations. Il faudra que je fasse l'essai. Avec un uint_16 je peux avancer de 65535/800= 80cm. C'est suffisant pour beaucoup, mais mon papier fait 17 m de long. C'est un cas particulier, pour moi la solution est de travailler en relatif uniquement, ce que propose certaines bibliothèques. Passer par des long complique et ralenti (surtrout avec l'équivalent de  map)


Je n'ai pas bien compris l'utilité de init. Pourquoi ne pas l'avoir intégré au constructeur?

bouault

Bonjour à tous,

oui donne nous des nouvelles Patrick et n'hésite pas à poser des questions.

Merci Vileroi pour tes remarques constructives.

La résolution : Il s'agit de la résolution de la commande, dans l'exemple avec le potentiomètre c'est 1023 ou 10 bits, pour une commande en midi ce serait 7 bits, en DMX 8 bits, 16 bit avec 2 canaux DMX, voir 1 bit avec un bouton poussoir . La résolution peut être n'importe quelle valeur comprise entre 1 et 1023. On peut aussi écrire _7bits,
 _8bits, _10bits, _12bits, _16bits.


calcul_nbr_steps() retourne un float . Bien vu, c'est une erreur !  Je corrige en laissant pour l'instant le return, au cas ou.
Penses tu que la vonction void s'exécute plus rapidement ?

Si je te suis bien, pour la rapidité, il vaux mieux utiliser :

 
Code: [Select]
bool sens;
  if (m_nbr_pas>0)                  sens=LOW + m_reversed;
  else                              sens=HIGH - m_reversed;

et :

 
Code: [Select]
if (sens != m_previous_sens)      m_change_sens = 1;
  else                              m_change_sens = 0;

Mais dis moi, comment fais tu pour mesurer ça, ça m'intéresse !

Le map est terriblement long (oui tout est relatif!) : 60us. Une partie des calculs à exécuter peut se faire dans le setup, pourquoi les refaire à chaque foi ? On gagne environ 30us.
Dans le calcul restant, je n'ai pas mesuré de différence, en terme de vitesse, entre le calcul en floats et le calcul en integer. Dans ce cas autant prendre le plus précis, même si en effet une erreur de 1 n'est pas problématique.


Je répond aux deux dernières questions tout à l'heure, dans un prochain message (obligation familliale!)
Jacques
arpschuino.fr

J-M-L

Je n'ai pas bien compris l'utilité de init. Pourquoi ne pas l'avoir intégré au constructeur?
En fait il faudrait modifier les 2 :)

Le code actuel d'init() et ses paramètres sont éventuellement utilisables au moment de l'appel du constructeur et vous pourriez les déplacer.

Par contre le code du constructeur
Code: [Select]
{
    pinMode (m_pull, OUTPUT);
    pinMode (m_dir, OUTPUT);     
}
devrait aller dans init() car au moment de l'appel du constructeur vous n'êtes pas garanti par le compilateur que l'environnement Arduino ne viendra pas mettre le bazar ensuite sur vos pins et les repasser en INPUT.

=> Le conseil est donc d'avoir une fonction init() ou begin() qui est appelée à partir de setup(), après la création de l'objet. A ce moment là vous êtes sûr que le matériel est dans un état nominal connu.


Petit commentaire sur la forme (pas stratégique mais ça a son importance), vous mélangez le français et l'anglais dans le noms de variables (eg m_previous_pas,  sens/reversed, calcul_step_duration, ...), vous devriez utiliser l'anglais partout (y compris les commentaires) si vous voulez toucher un public plus large, sinon mettez tout en français

Aussi sur la cohérence de type, quand vous avez un booléen, retournez true ou false, pas 0 ou 1. je sais que ça finit pareil au bout du compte, mais d'un point de vue conceptuel  bool est un vrai type de vérité logique, pas un nombre.

Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

bouault

De retour !

Dans le constructeur :

 ArpStepper stepper0(2,3,200);

on déclare un moteur à 200 pas.
Tu as besoin de faire 425 tours (1700cm/4). Donc dans le setup on fait :

stepper0.init(5, 200, 425);
stepper0.position_resolution(_16bits);

Avec :

stepper0.perform(65535, 40000);

les 17m devraient bien être parcouru. Avec une précision de 0,26mm (17 000/65 535), je ne sais pas si c'est suffisant.
Sinon, stepper0.continuous renvoie 1 à chaque pas, on peut donc compter les pas effectués.

Pourquoi init ?
Dans le constructeur c'est la configuration hardware, le nombre de pas du moteur, les sorties auxquelle il est relié.
Dans init on définit ce qu'on va lui demander, vitesse, nombre de tour.
Dans l'exemple joins, il est appellé 2 fois : entre les deux il y a une procédure d'auto calibration du nombre de tour, avec 2 fin de course . Ca se passe dans le setup. Dans cet exemple le moteur est commandé en DMX mais peu importe.
Je viens de voir la répons de JML, j'y répond après avoir réfléchis !





Jacques
arpschuino.fr

J-M-L

une autre suggestion pendant que vous réfléchissez :)

dans do_step() commencez par tester si m_nbr_pas est nul ça ira plus vite que d'enchaîner les 2 tests positif et négatif et pas besoin des else puisque la condition fait un return. Si on n'a aucun pas à faire, je suppose qu'on n'a pas non plus à tester le changement de sens, donc on peut le mettre après

Code: [Select]
int8_t ArpStepper::do_step(uint16_t val_speed)
{

   if(m_nbr_pas == 0) return 0;
   set_direction();

   if(m_nbr_pas>0) {
        m_previous_pas += step_plus(val_speed);
        return 1;             
   }
     
  // on sait qu'on est négatif
  m_previous_pas -= step_plus(val_speed);
  return -1;
}


cela dit comme set_direction() va à nouveau tester si m_nbr_pas est positif ou pas pour éviter un appel de fonction et la duplication de test, j'intègrerais "à la main" la gestion de la direction à la fonction do_step().

il y a plusieurs fonctions qui ne sont utilisées qu'une seule fois (calcul_nbr_steps()) ça vaudrait peut-être le coup de les déclarer au moins inline et éviter de retourner des valeurs qui ne sont pas utilisées.

Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

bouault

OK je vais faire des corrections et vous envoie le résultat.
Jacques
arpschuino.fr

Je suis en train de raisonner dans un autre sens pour faire avancer le moteur pas à pas. Si je travaille en 16ème de pas, l'ai une résolution de 80 pas par mm. Et c'est à partir de là que je fais le reste. Le nombre de tours du moteur est un intermédiaire qui ne m'intéresse pas. J'ai d'un côté des impulsions à mettre, de l'autre des mm. Mon résultat c'est plutôt les mm.
Pour moi, le nombre de tours est intéressant quand on met le moteur sur la table, nu, avec un bout de scotch pour indiquer le repère, et que l'on fait des essais pour voir si il décroche ou pas.
Du coup, si je parle en mm, je n'ai plus de problèmes de résolution, je suis toujours au maximum de résolution. Et quand j'ai parcouru 17m les déplacements relatifs à la fin sont toujours avec la même précision.




Pour la mesure du temps et du nombre de cycles je fais comme tout le monde, on  lit micros, on fait des boucles, on relit micros et on fait la différence divisé par le nombre de boucles. Je me suis fait un petit programme qui me permet facilement une fois pour toutes de mettre tout e place.

Dans ce que j'utilise, il y a deux boucles, la première est la tare et ne comporte qu'un NOP. la deuxième comporte un NOP et le code à mesurer. Ainsi en faisant la différence je n'ai que le temps de ce que je cherche à mesurer.

La résolution de micros est de 4µs avec une Uno, il faut au moins faire 4000 boucles pour avoir une résolution de 1ns (un nop dure 62,5 ns). Je pourrais faire 8000 boucles et afficher le nombre juste, mais cela n'a pas une grossi importance. Cela compliquerait un peu l'affichage.

Le chronométrage est interrompu par le timer 0 qui s'occupe de micros. En faisant plus de boucles la division va éliminer le petit temps dû au rafraîchissement de micros et millis. J'ai donc choisi de faire 100000 boucles. Pour mesurer un NOP, j'utilise ce nombre et j'ai le temps affiché à une ns près. Si c'est pour une fonction qui dure plus longtemps, je divise par 1000 le nombre de boucles et pense que les ns sont en réalité des ms... Si c'est très long, je ne fait qu'une boucle et j'ai le résultat en 1/10s. Ça marche comme cela avec un peu de calcul mental, je ne me suis pas fatigué plus.

Dans les tests,on utilise des 8, des 16 bits. j'en déclare quelques uns au début, une fois pour toutes.
Pour chaque variable, je lui affecte une valeur inconnue du compilateur pour qu'il ne transforme pas ma variable en constante, et je l'affiche en fin de programme pour qu'il ne me la supprime pas. le test est toujours faux, mais le compilateur ne le sait pas (encore).

Voici mon code:
Code: [Select]
byte b, b2, b3;
word w, w2;
float f;

void setup()
{
  randomSeed(analogRead(0));
  b = random(2);
  b2 = random(2);
  b3 = random(2);
  w = random(0x10000);
  w2 = random(0x10000);
  f = random(0x10000);



  Serial.begin(115200);
  unsigned long t1, t2, t3; // Chronomètres
  t1 = micros();
  for (unsigned long x = 100000ul; x > 0; x--) _NOP(); // Tarer le temps d'une boucle
  t2 = micros();
  for (unsigned long x = 100000ul; x > 0; x--) // Boucle de mesure
  {
    _NOP();


    // Mettre ici le code à tester





  }
  t3 = micros();
  t1 = t3 + t1 - 2 * t2;
  Serial.println();
  Serial.println(String(t1 / 100ul) + "ns");
  Serial.println(String(t1 / 6250ul) + "cycle(s)\n");
  if (t1 > 1000000000l)
  {
    Serial.print(b);
    Serial.print(b2);
    Serial.print(b3);
    Serial.print(w);
    Serial.print(w2);
    Serial.print(f);
  }
}

void loop()
{
}

bouault

Voici les fichiers avec les corrections suivantes :
 * int16_t calcul_nbr_steps a la place de float calcul_nbr_steps. un clean final la rendra surement void, je laisse un return pour l'instant
 * end_of_pulse() supprimé
 * calcul de m_change_sens : retour à la méthode classique
 * ajout de la fonction set_wait() (sur les conseils d'un ami), m_wait en millisecondes avec 20ms par defaut
 * initialisation des pins en sortie déplacé dans init()
 * m_nbr_pas remplace par m_nbr_steps
 * m_previous_pas remplace par m_previous_steps
 * m_previous_sens remplace par m_previous_direction
 * m_change_sens remplace par m_change_direction
 * true et false pour les booléens
 * commentaires en anglais
  Une petite question, je débute avec la programmation orienté objet : faut-il un destructeur ?
Jacques
arpschuino.fr

J-M-L

Quote
Une petite question, je débute avec la programmation orienté objet : faut-il un destructeur ?
le destructeur ne sert que si vous avez fait de l'allocation dynamique de mémoire, pris le contrôle d'une resource matérielle, si vous avez des variables de classe qui dépendent du nombre d'instances, etc.. --> dans ce cas vous libérez ces resources dans le destructeur.

- manque une petite adaptation linguistique sur calcul_nbr_steps

- continuous, step_plus ne retournent pas un vrai bool (true ou false)

- do_step n'a pas changé

vous pouvez peut être éviter la passage en calcul flottant pour step_duration dans calcul_step_duration en faisant juste
Code: [Select]
return 600000000UL / (RPM * step_per_revolution)
Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

bouault

Je vais tenir compte de tout ce que vous me dites et y répondre.
Mais je bloque pour l'instant sur une chose que j'aimerais résoudre avant :
Dans la fonction step_plus :
la condition : if(m_change_direction==true) est toujours vrais.
Pour le mettre en évidence j'ai fait : m_wait*1000;
c'est sans doute un truc tout bête mais là je m'arrache les cheveux !
Je joins la derniere version (016)
Jacques
arpschuino.fr

bouault

Au fait, 17m ce ne serait pas la longueur d'un carton d'orgue de barbarie par hasard ?   :)
Jacques
arpschuino.fr

Quote
Au fait, 17m ce ne serait pas la longueur d'un carton d'orgue de barbarie par hasard ?
Damned je suis fait. Effectivement, le papier d'origine (rouleau de caisse enregistreuse) fait 50 m.


Je pense aussi à la prise d'origine: faire tourner le moteur jusqu'à un capteur de fin de course par exemple.

Autre point d'intérêt: mesurer la vitesse maximale des moteurs ce qui permet un rembobinage.

Go Up