Catégories
Electronique Programmation

Piloter un moteur pas-à-pas PART 2 : le programme

Deuxième partie du projet de pilotage d’un moteur pas-à-pas.
Cette partie est dédié à la programmation de l’Arduino.

Le moment compliqué arrive… On va passer à la réalisation du programme.

On a vu dans la première partie (voir https://fabdiy.fr/piloter-un-moteur-pas-a-pas-part-1-lelectronique/) les différentes broches que nous allons utiliser, ainsi que leur rôle dans notre montage.

Je me permets ici de rappeler le tableau de ces broches :

Broche sur l’ArduinoAliasRôle
D2INT0gérer l’encodeur rotatif (DA)
D3INT1gérer l’encodeur rotatif (DT)
D8PCINT0gérer l’appui sur le switch de l’encodeur rotatif
D6/envoyer les pas au driver
A4SDAgérer les données sur le bus I2C (affichage lcd)
A5SCLgérer l’horloge du bus I2C (affichage lcd)
tableau résumé des broches retenues

C’est avec ce tableau, ainsi que la logique de fonctionnement que l’on souhaite, que l’on va concevoir notre programme.

Je vais présenter dans un premier temps, différents diagrammes qui vont vous permettre de comprendre le programme de manière plus aisée.

Dans un second temps, je présenterais la transcription de ce programme avec Arduino.

Conception du programme

Dans cette conception, on va tout aborder la logique de fonctionnement du programme. C’est-à-dire, l’enchaînement des actions dans le programme.

La première étape du programme est l’initialisation des différentes entrées/sorties. Cette étape simple à décrire peut parfois devenir complexe si le système l’est aussi. Coup de chance, rien de bien méchant ici. Nous aurons, en plus de l’initialisation des entrées/sorties, la configuration de la communication I2C avec l’écran LCD et la gestion de l’encodeur rotatif.

La seconde étape de notre programme est son fonctionnement normal. Cette partie sera responsable de la gestion de l’envoi du signal sur la broche D6 de l’Arduino, et donc de la vitesse du moteur. La vitesse pourra être modifiée par l’encodeur rotatif (incrémentation ou décrémentation de la vitesse). De plus, un message sera affiché sur l’écran LCD pour indiquer la vitesse du moteur.

Le début du programme n’est pas très complexe. Toutefois, le cœur de ce programme n’est pas là. En effet, j’ai souhaité utiliser le mécanisme de multithreading pour simplifier le programme au maximum.

J’ai choisi de découper mon programme en 3 threads séparés.

Le premier thread est dédié à l’affichage sur l’écran LCD, le second à la gestion du moteur et le dernier à gérer l’encodeur rotatif

Le programme

Vous pensez que le moment tant attendu arrive… et bien, pas encore.

En effet, un des points à rechercher est : quels éléments va-t-on utiliser afin de gagner du temps dans l’écriture du programme, mais aussi dans sa clarté ?

Cela revient à répondre avec : de quels briques logiciels ai-je besoin ?

On a vu précédemment que j’utilise des éléments préassemblés :

  • L’encodeur rotatif KY-040
  • L’écran LCD, avec son adaptateur I2C
  • Le driver du moteur pas-à-pas, A4988

Tout ces éléments sont exposés sur le net. Ici, nous n’allons pas réinventer la roue et utiliser l’existant.

Pour ceux qui veulent gagner du temps, la synthèse du programme est disponible en fin d’article, mais aussi sur github (https://github.com/FABDIY/Pilote_moteur_pas_a_pas_dremel)

Un peu d’UML

Je sais, vous n’aimez pas l’UML… Moi non plus en fait. Le but de l’UML est de transmettre un message le plus intelligible et compréhensible par tous. De toute façon, vous avez remarqué que je n’ai pas respecté la norme.

Dans la continuité du précédent diagramme, je vais vous présenter les différentes librairies que je vais utilisé par la suite. Ainsi, vous comprendrez certains choix que j’ai fait.

Diagramme des librairies nécessaires aux programmes

En premier lieu, ne prenez pas peur. Vous remarquez que j’ai utilisé 3 librairies. La librairie LiquidCrystal_I2C pour l’écran LCD, la librairie SimpleRotary pôur la gestion de l’encodeur rotatif, et la librairie ArduinoThread pour la gestion du multithreading. J’ai choisi ces librairies car elle correspondait à mon besoin et était rapide à mettre en œuvre comme vous le verrez dans le programme.

Vous allez me dire « Mais quid du pilotage du moteur pas-à-pas ? T’as pas une p’tite lib qui ferait le taf ? ». En fait, dans ce projet, je veux piloter la fréquence de rotation du moteur via le contrôle des pas. Coup de bol me direz-vous, la fonction « tone() » est disponible dans la librairie Arduino de base (https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/).

Je vous cite directement la documentation :

Generates a square wave of the specified frequency (and 50% duty cycle) on a pin

Avec un peu d’habillage autour de cette fonction (j’y reviendrais dans l’explication du programme), on peut piloter la pin du driver A4988.

Avec tout ces éléments, on peut s’attaquer au programme.

Le programme

Aller, je vois que vous êtes impatient, je vous mets le programme d’abord et les explications en dessous.

#include <LiquidCrystal_I2C.h> // https://github.com/johnrickman/LiquidCrystal_I2C
#include <SimpleRotary.h> // https://github.com/mprograms/SimpleRotary
#include <Thread.h> //https://github.com/ivanseidel/ArduinoThread

#define DEBUG 0 // define utilise pour debugger le programme

// Gestion du moteur pas a pas
#define stepsByRevolution 200 //variable dédiée à la gestion du moteur pas à pas. A adapter selon le moteur
#define stepPin 6
#define vitesseMin 0 // la vitesse minimale ne peut pas être inferieur a 0
#define vitesseMax 200
#define increment 10

volatile int VitesseCourante = 0;

Thread motorThread = Thread();
void motorCallback()
{
	// le signal a applique au moteur est obtenu avec la formule suivante :
	// on souhaite appliquer une frequence en Hz, alors que notre commande est en rpm.
	// du coup, on convertir le rpm souhaite en pas par seconde a envoyer, soit des Hz.
	int motorStepFreq = (VitesseCourante*stepsByRevolution)/60;
	if (motorStepFreq > 31) // https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/
		tone(stepPin, motorStepFreq, 5);
	else
		noTone(stepPin);
}

// Gestion de l'ecran LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
Thread lcdThread = Thread();

void printVitesse()
{
	char msg[21];
	sprintf(msg, "%-5d rpm", VitesseCourante);
	lcd.setCursor(0, 1);
	lcd.print(msg);
}

// Encodeur rotatif
SimpleRotary rotary(3, 2, 8);
Thread rotaryThread = Thread();

void rotaryCallback()
{
	byte rotation;

	if (rotary.push()) { // on arrete le moteur si il y a un appui long ou cours
		VitesseCourante = 0;
#if DEBUG
		Serial.println("arret moteur");
#endif
	}

	rotation = rotary.rotate();
	if (rotation == 1) { // decrementation
		if ((VitesseCourante - increment) > vitesseMin) {
			VitesseCourante -= increment;
		} else {
			VitesseCourante = 0;
		}
#if DEBUG
		Serial.println("Vitesse Courante --");
		Serial.println(VitesseCourante);
#endif
	} else if (rotation == 2) { // incrementation
		if ((VitesseCourante + increment) < vitesseMax) {
			VitesseCourante += increment;
		} else {
			VitesseCourante = vitesseMax;
		}
#if DEBUG
		Serial.println("Vitesse Courante ++");
		Serial.println(VitesseCourante);
#endif
	}
}

void setup()
{
  	// Initialisation des E/S
  	// Gestion de l'encodeur rotatif
	pinMode(2, INPUT_PULLUP);
	pinMode(3, INPUT_PULLUP);
	pinMode(8, INPUT_PULLUP);

	rotaryThread.onRun(rotaryCallback);
	rotaryThread.setInterval(1);

	// Gestion du moteur pas a pas
	pinMode(stepPin, OUTPUT);
	motorThread.onRun(motorCallback);
	motorThread.setInterval(5);

	// Gestion de l'écran LCD
	lcd.init();
	lcd.backlight();
	lcd.setCursor(0, 0); // on se place sur la première ligne, première colonne
	lcd.print("Vitesse moteur :");
	lcdThread.onRun(printVitesse);
	lcdThread.setInterval(100);

#if DEBUG
  	Serial.begin(9600);
#endif
}

void loop()
{
	if (lcdThread.shouldRun())
		lcdThread.run();

	if (rotaryThread.shouldRun())
		rotaryThread.run();

	if (motorThread.shouldRun())
		motorThread.run();
}
Langage du code : Arduino (arduino)

Les explications

Les threads

Comme je vous l’ai expliqué plus haut, j’utilise la librairie ArduinoThread (disponible sur https://github.com/ivanseidel/ArduinoThread). J’ai choisi cette librairie car elle permet de créer des threads en quelques lignes.

En effet, il faut simplement créer le Thread, sa callback associé et vérifier dans la boucle du programme si la callback doit être appelé.

Grâce à ceci, on se retrouve avec un pseudo multithreading sur l’Arduino facilement.

Par exemple avec la gestion de l’encodeur rotatif :

Thread rotaryThread = Thread();

void rotaryCallback()
....
rotaryThread.onRun(rotaryCallback);
rotaryThread.setInterval(1);
...
if (rotaryThread.shouldRun())
		rotaryThread.run();Langage du code : Arduino (arduino)

On a en premier la création du thread, ensuite son code associé dans la callback. Puis dans le setup du sketch Arduino, on définit la callback associé au thread, et son intervalle d’appel en millisecondes. Dans ce cas 1 millisecondes. Enfin, dans la loop du sketch, on utilise la méthode shouldRun() et run().

C’est ce qui m’a plu dans cette librairie. En quelques lignes vous pouvez faire un pseudo-multithreading sur Arduino.

Les deux autres threads sont similaires, la différence va être sur l’intervalle des appels aux callbacks, soit 100ms pour l’affichage LCD, et toutes les 5 millisecondes pour la gestion du moteur.

La gestion du moteur pas-à-pas

J’ai trouvé la fonction tone() après quelques recherches. En effet, on doit envoyer les pas à faire au driver. Or, le nombre de pas à faire par tour est de 200 dans le cas du moteur que j’utilise. Avec un petit calcul, on peut générer la bonne fréquence sur la broche de pilotage.

motorStepFreq = (VitesseCourante*stepsByRevolution)/60Langage du code : Arduino (arduino)

Cette formule permet de générer la bonne fréquence de pilotage. Malheureusement, la fonction tone, ne marche pas si la valeur mise est inférieure à 31Hz. Ceci est dû à des paramètres de l’ATMEGA (voir la documentation sur la fonction qui résume parfaitement ce point). Cette limitation n’est pas critique ici, donc je ne m’y attarderai pas.

Pour ceux qui ont lu le code de manière fine, ils auront remarqué que j’ai utilisé le dernier paramètres de la fonction, qui correspond à quel durée souhaite-t-on appliquer ce signal. J’ai choisi d’y appliquer l’intervalle d’appel de la callback dédiée au moteur. Ainsi, la valeur étant mise à jour régulièrement, le moteur a toujours son signal correctement généré.

Conclusion

Ces explications ont été courtes car je me suis attardé sur des points plus spécifiques à mon projet. Comme je l’ai dit, je n’ai pas réinventé la roue, je me suis appuyé sur la documentation des librairies que je vous invite à lire pour plus d’informations.

Vous pouvez retrouver le code de ce projet sur github à l’adresse suivante :

https://github.com/FABDIY/Pilote_moteur_pas_a_pas_dremel

En attendant la suite, bon dev !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.