Aide sur gestion Moteur pas-à-pas avec vis sans fin et écrou à billes

Bonjour à tous,
je bloque sur un projet de motorisation de 2 charges via 2 ensembles moteurs pas à pas et vis sans fin/écrou à billes.

Dans les détails (pour 1 charge, la deuxième étant identique):

  • arduino nano
  • alimentation 24V 16.7A (une seule pour l'ensemble)
  • bouton type volet roulant 3 boutons "montée/stop/descente"
  • moteur NEMA 23 5A
  • driver DM556S
  • vis à bille Ø20mm longueur 1500mm pas de 5mm
  • capteurs de fin de course type contact sec utilisés en NO

mes cours de C/C++ remontant à loin, un ami m'a fait un code de base que j'ai essayé d'implémenter via l'IA (pas une très bonne idée visiblement).

le problème que j'ai est que les moteurs ne se lancent pas normalement mais saccadent et vibrent quand j'appui sur l'un des boutons. de plus, les deux boutons entrainent le moteur dans le même sens...

voici le code:

#include <AccelStepper.h>

// Définir les connexions des moteurs pas à pas
#define dirPin1 2
#define stepPin1 3
#define enablePin1 10

// Définir les pins des capteurs de fin de course
#define endstopPin1A A0
#define endstopPin1B A1

// Définir les pins des interrupteurs biposition
#define switchPin1A 6
#define switchPin1B 7

// Créer une nouvelle instance de la classe AccelStepper
AccelStepper stepper1 = AccelStepper(1, stepPin1, dirPin1);

// Définir la position cible
#define TARGET_POSITION 58360

void setup() {
  Serial.begin(9600); // Initialiser la communication série
  
  stepper1.setMaxSpeed(1500); // Définir la vitesse maximale
  stepper1.setAcceleration(200); // Définir l'accélération
  
  stepper1.setEnablePin(enablePin1);
  stepper1.setPinsInverted(false, false, true);
  
  pinMode(endstopPin1A, INPUT_PULLUP);
  pinMode(endstopPin1B, INPUT_PULLUP);
  
  pinMode(switchPin1A, INPUT_PULLUP);
  pinMode(switchPin1B, INPUT_PULLUP);
  
  stepper1.disableOutputs();
}

void loop() {
  // Lire l'état de l'interrupteur
  bool switchA = digitalRead(switchPin1A) == LOW;
  bool switchB = digitalRead(switchPin1B) == LOW;

  // Lire les capteurs de fin de course
  bool endstopA = digitalRead(endstopPin1A) == LOW;
  bool endstopB = digitalRead(endstopPin1B) == LOW;

  // Contrôler le moteur
  if (switchA && !switchB) {
    // Si le bouton A est enfoncé et le bouton B est relâché
    if (!endstopA) {
      stepper1.moveTo(TARGET_POSITION); // Déplacer le moteur vers la position cible
      stepper1.enableOutputs(); // Activer le moteur
      Serial.println("Moteur activé, se déplace vers la position cible");
    } else {
      stepper1.stop();
      stepper1.disableOutputs(); // Désactiver le moteur
      Serial.println("Moteur arrêté, fin de course A atteinte");
    }
  } else if (!switchA && switchB) {
    // Si le bouton A est relâché et le bouton B est enfoncé
    if (!endstopB) {
      stepper1.moveTo(0); // Déplacer le moteur vers la position 0 (home)
      stepper1.enableOutputs(); // Activer le moteur
      Serial.println("Moteur activé, se déplace vers la position home");
    } else {
      stepper1.setCurrentPosition(0); // Définir la position courante à 0 (position "home")
      stepper1.stop();
      stepper1.disableOutputs(); // Désactiver le moteur
      Serial.println("Moteur arrêté, fin de course B atteinte, position home définie");
    }
  } else {
    // Si aucun bouton n'est enfoncé
    stepper1.stop();
    stepper1.disableOutputs(); // Désactiver le moteur
    Serial.println("Moteur à l'arrêt");
  }

  // Faire tourner le moteur
  stepper1.run();
}

j'oublie peut-être des infos donc mes excuses par avance si c'est le cas et merci pour l'aide que vous pourrez m'apporter !

Bonjour Jean_la_boissellerie

Quel est la fonction de ce montage?
Est ce que c'est toujours:
Départ de 0 vers TARGET_POSITION (switchA && !switchB)
et TARGET_POSITION vers 0 (!switchA && switchB)
sans arrêts intermédiaires.

Pourquoi 2 endstop (endstopA et endstopB) en principe avec un moteur pas à pas, il n'en faut qu'un pour avoir la position de départ, l'arrivée étant définie par TARGET_POSITION?

A+
Cordialement
jpbbricole

Bonjour,
le but est de motoriser un dressing sur coulisse (2 armoires qui sortent d'une niche dans un mur). donc il est en position home (rentré) au repos et en position "target" sorti complètement.
Je voudrais que le déplacement s'arrête si le bouton revient en position "stop" ou si le déplacement arrive en butée fin de course.
j'ai mis 2 fin de course par sécurité et pour pouvoir réinitialiser la position au cas ou le moteur l'aurait perdu entre temps.

Merci pour le temps passé à étudier mon projet :wink:

Les 2 installations sont indépendantes?

oui, il y a le dressing de M et de Mme qui peuvent être ouverts de manière différenciée

Ok, je vais regarder ça :wink:

Cela veut dire qu'il faut maintenir la pression pendant le déplacement?
Donc quand les 2 boutons (switchA et switchB) sont relâchés, on est en position stop?

non, l'interrupteur est à crans, quand on appui sur "sortie", le bouton reste enfoncé jusqu'à ce qu'on appui sur "stop"

Bonsoir Jean_la_boissellerie

J'ai, un peu, détaillé ton programme et, pour moi, il faudrait le reprendre en travaillant avec une machine à états, je te laisse regarder sur le forum il y a un tuto de @J-M-L

Jusqu'à demain soir je n'ai plus suffisamment de temps à te consacrer :woozy_face:, après, je veux bien :wink:

Pour ce qui est de l'irrégularité de rotation du moteur, il y a de fortes chances que tout les Serial.print() en soient la cause, il faut en mettre un minimum.

Bonne soirée
jpbbricole

Il y a aussi le fait que la boucle repasse à chaque itération par moveTo() alors que c'est parfaitement inutile. Il faut invoquer moveTo() pour initier le mouvement et c'est tout, cela fait perdre du temps car on recalcule à chaque fois la vitesse. Ensuite c'est run() qui gère l'avance.

@Jean_la_boissellerie regarde comment est fait bounce dans les exemples installés avec la librairie.
Dans ta loop() à chaque itération tu ne devrais tester que si tu as atteint le fin de course, si c'est le cas tu arrêtes le moteur.
Et tu appelles run() systématiquement.

@jpbbricole j'avais vu ce tuto et il me paraissait intéressant pour ne travailler qu'avec un bouton poussoir avec l'ordre "sortir" - "stop" - "rentrer" - stop - "sortir" etc... mais est-ce compatible avec mes inter 2 positions ? ou est-ce finalement mieux de basculer sur un bouton poussoir seul ...?

En tout cas, bien vu pour les serial.print, c'était bien ce qui faisait saccader les moteurs ! ça tourne rond maintenant.

@fdufnews , je pensais que la répétition du MoveTo me permettait de redémarrer avec la rampe même en cas d'arrêt avec le bouton "stop" mais ça ne fonctionne pas.

comment je pourrais combiner l'optimisation dont tu parles avec le fait de démarrer et d'arrêter le moteur systématiquement avec la rampe même en cas d'appui sur "stop" ?

je n'arrive pas à ouvrir l'exemple Bounce, ni aucun de la librairie accelstepper alors que les autres fonctionnent. je vais essayer de la désinstaller puis réinstaller.

grand merci à tous les deux pour votre aide

Bonjour Jean_la_boissellerie

Non, c'est valable pour quasiment tout programme plus ou moins évolué.
Dès que j'ai du temps, j'essaeyrai d'adapter ton programme.

A+
Bonne journée.
jpbbricole

Bonjour Jean_la_boissellerie

Je ne t'ai pas oublié, mais, ces temps je suis assez occupé :woozy_face:
Et il fait beau :wink:
Ce sera, je l'espère, pour ce week-end.

Bonne soirée.
jpbbricole

Ce sont 2 événements différents qu'ils faut traiter séparément.
Si tu démarres ou redémarre tu fais un moveTo() qui va calculer les paramètres du déplacement.
Si tu avances tu fais seulement run().

Bonjour Jean_la_boissellerie.

Voilà tel que je vois ton programme d’ouverture et fermeture d’un dressing tel que décrit dans le post#3.
Au départ, le programme va caler le moteur sur le contact vabEndstopHomePin.
Ceci n’est pas indispensable, si tu veux t’en passer, mets cette ligne en remarque :
//mpapToEndstopHome();

Tu as un outil pour monitorer tes entrées/sorties :
toolMonitor(); // Pour afficher l'état des ports E/S
Si tu décommentes cette ligne et que tu recharges le programme, dans le moniteur, à 115200, tu vois l’état de tes contacts et boutons :

Endstop HOME ON
Endstop TARGET OFF
Bouton SORTIE OFF
Bouton ENTREE ON
Moteur ENABLE OFF

Le programme ne fait que ça.
Pour sortir de ce mode, il faut re commenter la ligne et recharger le programme. Ceci sert surtout au tout début du développement.

Pour ce qui est du programme, j’ai mis un maximum de commentaires et suis à ton entière disposition pour toutes questions.

Le programme :

/*
    Name:       AF_boissellerie_MoteurVisAbille.ino
    Created:	28.08.2024
    Author:     BUREAU\admin
	Remarque:	Comande d'un moteur pas à pas et vis à bille pour tiroir.
				https://forum.arduino.cc/t/aide-sur-gestion-moteur-pas-a-pas-avec-vis-sans-fin-et-ecrou-a-bille/1295712/11
	01.09.2024	Première version #15
*/

#include <AccelStepper.h>  // https://www.airspayce.com/mikem/arduino/AccelStepper/classAccelStepper.html

//------------------------------------- Moteur pas à pas (mpap)
// Définir les connexions des moteurs pas à pas
const int  mpapDirPin = 2; // Pin qui commande la direction
const int  mpapStepPin = 3; // Pin qui commande les pas
const int  mpapEnablePin = 10; // Pin d'activation du mpap

AccelStepper mpap(1, mpapStepPin, mpapDirPin);  // Créer une nouvelle instance de la classe AccelStepper

const long mpapTargetPosition =  58360;  // Définir la position cible

const boolean mpapBitInvertDir = true;  // Pour inverser le bit DIR
const boolean mpapBitInvertStep = true;  // Pour inverser le bit STEP
const boolean mpapBitInvertEna = true;  // Pour inverser le bit ENA

enum {mpapDirToHome, mpapDirToTarget};
int mpapDirTo = mpapDirToTarget;

//------------------------------------- Endstop vis à bille (vab)
// Définir les pins des capteurs de fin de course
const int vabEndstopHomePin = A0;  // Contact de départ
const int vabEndstopTargetPin = A1; // Contact d^'arrivée
const int vabEndstopEtatOn = LOW; // Etat quand actif

//------------------------------------- Boutons (btn)
// Définir les pins des interrupteurs biposition
const int btnSortiePin = 11; // 6 // Pin de l'interrupteur Sortie
const int btnEntreePin = 3; // 7 // Pin de l'interrupteur Entrée
const int btnEtatOn = LOW; // Etat quand actif

//------------------------------------- Etats du programme
const String prgEtatLabel[] = {"Initial", "en Marche", "en Pause"};
enum {prgEtatInitial, prgEtatEnMarche, prgEtatEnPause};
int prgEtat;  // Etat du programme

void setup()
{
	Serial.begin(115200);
	delay(500);
	Serial.println("\nMoteur pas a pas avec vis a billes");

	mpap.setMaxSpeed(1500); // Définir la vitesse maximale
	mpap.setAcceleration(200); // Définir l'accélération
	
	mpap.setEnablePin(mpapEnablePin);
	//                    DIR                      STEP               ENA
	mpap.setPinsInverted(!mpapBitInvertDir, !mpapBitInvertStep, mpapBitInvertEna);
	
	pinMode(vabEndstopHomePin, INPUT_PULLUP);
	pinMode(vabEndstopTargetPin, INPUT_PULLUP);
	
	pinMode(btnSortiePin, INPUT_PULLUP);
	pinMode(btnEntreePin, INPUT_PULLUP);
	
	mpap.disableOutputs();

	toolMonitor(); // Pour afficher l'état des ports E/S

	prgEtatChange(prgEtatEnPause);
	mpapDirTo = mpapDirToTarget;

	mpapToEndstopHome();
}

void loop()
{
	if (prgEtat == prgEtatEnPause)
	{
		if (digitalRead(btnSortiePin) == btnEtatOn) // Si le bouton sortie est pressé
		{
			if (digitalRead(vabEndstopTargetPin) == !vabEndstopEtatOn)  // Si pas sur contact arrivée
			{
				Serial.println(F("Bouton SORTIE"));
				prgEtatChange(prgEtatEnMarche);
				mpap.setPinsInverted(!mpapBitInvertDir, !mpapBitInvertStep, mpapBitInvertEna);
				mpapDirTo = mpapDirToTarget;
				mpap.moveTo(mpapTargetPosition);
			}
		} 
		else if (digitalRead(btnEntreePin) == btnEtatOn) // Si le bouton entrée est pressé
		{
			if (digitalRead(vabEndstopHomePin) == !vabEndstopEtatOn)  // Si pas sur contact départ
			{
				Serial.println(F("Bouton ENTREE"));
				prgEtatChange(prgEtatEnMarche);
				mpap.setPinsInverted(mpapBitInvertDir, !mpapBitInvertStep, mpapBitInvertEna);
				mpapDirTo = mpapDirToHome;
				mpap.moveTo(mpapTargetPosition);
			}
		}
	}


	if ((digitalRead(btnSortiePin) != btnEtatOn && digitalRead(btnEntreePin) != btnEtatOn) && prgEtat == prgEtatEnMarche) // Si le bouton sortie et entrée sont relâchés
	{
		Serial.println(F("Boutons STOP"));

		mpapStop(); // Arrêt du moteur
		prgEtatChange(prgEtatEnPause);
	}

	if (prgEtat == prgEtatEnMarche) // Faire tourner le moteur, si nécessaire
	{
		mpap.run();
	}

	//--------------------------------- Si moteur en fin de course
	if ((digitalRead(vabEndstopHomePin) == vabEndstopEtatOn) && mpapDirTo == mpapDirToHome && prgEtat == prgEtatEnMarche) // Si moteur déjà au départ
	{
		mpapStop();
	}

	if ((digitalRead(vabEndstopTargetPin) == vabEndstopEtatOn) && mpapDirTo == mpapDirToTarget && prgEtat == prgEtatEnMarche) // Si moteur déjà aun target
	{
		mpapStop();
	}
}

//------------------------------------- Etats du programme
void prgEtatChange(int etatNew)
{
	Serial.println(F("Changement d'etat"));
	Serial.println("\tde " + prgEtatLabel[prgEtat]);

	Serial.println("\ta " + prgEtatLabel[etatNew]);

	prgEtat = etatNew;

	if (prgEtat == prgEtatEnPause)
	{
		mpap.disableOutputs();
	}
	else if (prgEtat == prgEtatEnMarche)
	{
		mpap.enableOutputs();
	}
}

//------------------------------------ Arrêt du moteur
void mpapStop()
{
	mpap.stop();
	mpap.setCurrentPosition(0);
}

//------------------------------------ Moteur en position endstopHome
void mpapToEndstopHome()
{
	if (digitalRead(vabEndstopHomePin) != vabEndstopEtatOn)     // Si moteur pas sur contact.
	{
		prgEtatChange(prgEtatEnMarche);
		Serial.println(F("\tMoteur vers endstopHome "));
		mpap.setCurrentPosition(0);     // Moteur position 0
	
		mpap.moveTo(-mpapTargetPosition * 2);

		while (true)
		{
			if (digitalRead(vabEndstopHomePin) == vabEndstopEtatOn)     // Si moteur sur contact.
			{
				mpap.setCurrentPosition(0);     // Moteur position 0
				mpap.stop();
				break;
			}
			mpap.run();
		}
		prgEtatChange(prgEtatEnPause);
	}
	mpapDirTo = mpapDirToTarget;  // Prochaine direction

	Serial.println(F("\tMoteur sur endstopHome "));
}

/*
    Moniteur des E/S
	Attention, reste dans cette boucle.
	Pour arrêter, commenter la ligne:
			//toolMonitor(); // Pour afficher l'état des ports E/S
*/
//------------------------------------- Moniteur des E/S 
void toolMonitor()
{
	while (true)
	{
		Serial.print(F("\nEndstop HOME\t")); Serial.println((digitalRead(vabEndstopHomePin) == vabEndstopEtatOn) ? "ON" : "OFF");
		Serial.print(F("Endstop TARGET\t")); Serial.println((digitalRead(vabEndstopTargetPin) == vabEndstopEtatOn) ? "ON" : "OFF");

		Serial.print(F("Bouton SORTIE\t")); Serial.println((digitalRead(btnEntreePin) == btnEtatOn) ? "ON" : "OFF");
		Serial.print(F("Bouton ENTREE\t")); Serial.println((digitalRead(btnSortiePin) == btnEtatOn) ? "ON" : "OFF");

		Serial.print(F("Moteur ENABLE\t")); Serial.println((digitalRead(mpapEnablePin) == LOW) ? "ON" : "OFF");

		delay(1000);
	}
}

Dans le moniteur à 115200, tu as des indications quant au fonctionnement du programme :

Bouton SORTIE
Changement d'etat
	de en Pause
	a en Marche
Boutons STOP
Changement d'etat
	de en Marche
	a en Pause
Bouton SORTIE
Changement d'etat
	de en Pause
	a en Marche
Boutons STOP
Changement d'etat
	de en Marche
	a en Pause

PS : Sur mon installation, je n’ai pas les mêmes ports que sur la tienne, alors attention !! des fois que j’aurai mal corrigé

Bonne journée
jpbbricole

Bonjour jpbbricole,
un grand merci pour ton aide qui a du demander pas mal de temps !
je suis en train d'essayer de comprendre le programme et je le testerai dès que ce sera clair pour moi.
Dans l'immédiat, je ne comprends pas les commandes d'inversion:

const boolean mpapBitInvertDir = true;  // Pour inverser le bit DIR
const boolean mpapBitInvertStep = true;  // Pour inverser le bit STEP
const boolean mpapBitInvertEna = true;  // Pour inverser le bit ENA

je vois quand ces commandes sont utilisées dans le programme mais je ne saisi pas leur utilité.

Bonjour Jean_la_boissellerie

Oui, ça m'a fait un peu fumer les neurones, mais ça fait du bien :wink:

Ces commandes servent à dire à AccelStepper, comment commander le driver du moteur.
Par défaut, l'impulsion de pas (PULSE) est une impulsion positive.
L'état pour la direction (DIR) pour tourner CW (sens des aiguilles d'une montre) également positive.
L'état pour enable (ENABLE) aussi positive.
Maintenant comme il y a des exceptions comme le célèbre A4988
image
où le signal ENABLE est actif à 0 (LOW), il faut inverser:
mpap.setPinsInverted(!mpapBitInvertDir, !mpapBitInvertStep, mpapBitInvertEna);
Ca sert aussi pour des drivers comme les TB6... ou DM556S où l'on entre sur un optocoupleur ce qui permet de commander avec des HIGH ou des LOW, dépendant du câblage.

Bonne après-midi.
jpbbricole

Bonjour JP,
Dans ton code tu fais un !mpapBitInvertDir
Donc tu dis à AccelStepper de ne pas inversé ou ton commentaire est ambiguë?

Bonjour terwal

En effet, ça peut prêter à confusion :woozy_face:, j'utilise les 2:
mpap.setPinsInverted(!mpapBitInvertDir, !mpapBitInvertStep, mpapBitInvertEna);

et
mpap.setPinsInverted(mpapBitInvertDir, !mpapBitInvertStep, mpapBitInvertEna);
Ce qui me permet d'inverser le sens du moteur, sans passer par des pas négatifs ou une vitesse négative.

Bonne après-midi
jpbbricole

Dans ce cas, je ne sais pas si cela a du sens, mais ta variable pourrait se nommer mpapBitPositifDir, ?
tu indique true ou false et tu supprime le !

Par contre ta variable mpapBitInvertStep est utilisé uniquement avec un non, alors que ca valeur est true.
Et mpapBitInvertEna, c'est l'inverse.