Avant de commencer, le lecteur me pardonnera, je l’espère, ce titre empreint de facilité. La question réelle de ce billet, c’est comment commander la tortue de sol que l’on vient d’imprimer (voir ici), puis d’assembler (voir ici). Ce billet sera, par ailleurs, exceptionnellement long puisqu’il s’agit davantage de documenter le code que d’un travail de rédaction.
Avant de commencer
Si vous en êtes là, c’est que vous êtes plus ou moins convaincus de l’intérêt de cette machine pour réfléchir à un « numérique » qui ne soit ni enfermé dans une boite noire ni pour imaginer que les machines soient douées de la moindre forme d’animisme ni conditionné par l’indigence profonde du prétendu débat sur #lézécrans.
Nous avons donc un tas de PLA, de composants à base de silicium, des câbles en cuivre enrobés dans du thermoplastique, quelques mécanismes et plusieurs vis en acier qui ne demandent qu’à être mis en mouvement par la volonté du programmeur. (Vous noterez que je suis jeune depuis suffisamment longtemps pour ne pas écrire codeur).
Le billet qui suit reprend le programme de mise en fonctionnement de la tortue (téléchargeable ici) pour le disséquer. Cette mise à nu ayant pour seul objectif de vous donner envie de reprendre ces instructions pour que vous fassiez faire à la tortue ce que vous voulez.
Les choix retenus pour la communication
Le robot est commandé par une communication sur un port série Bluetooth. L’idée est d’envoyer au robot un caractère ASCII sur le port série du robot. Chaque caractère est associé à une commande et, le plus souvent, à une variable. Cette commande permet l’exécution d’une opération élémentaire du robot. Si vous pensez que les phrases précédentes comprennent des jurons, ce n’est pas grave, je vais vous faire un schéma.

Ainsi, si une fonction « avancer de x centimètres » est programmée dans la tortue de sol et que ce programme est associé (arbitrairement) à la lettre K; le fait d’envoyer la lettre K sur le port série lance le sous-programme « avancer de x centimètres » au robot qui l’exécute.
Les bibliothèques et les déclarations de variables
Le code débute par l’appel aux bibliothèques du servomoteur, de la LED RGB et du module Bluetooth.
/////////////////
//Bibliothèques//
/////////////////
#include <Servo.h> // bibliothèque servomoteur
#include <Adafruit_NeoPixel.h> // bibliothèque LED RGB
#include <SoftwareSerial.h> // bibliothèque BlueTooth
Le servomoteur est ensuite configuré. Il est connecté sur la broche digitale 7. Il effectue une rotation de 90° entre la position basse (stylo en position d’écriture) et la position haute (stylo en position de repos). Il est préférable de monter la came lorsque le servomoteur est en position basse. Le servomoteur sera appelé Servo_stylo dans le programme.
// configuration servo
int broche_servo = 7; // défini la broche du servomoteur
int angle_stylo_B = 0; // angle du servo avec le stylo en bas
int angle_stylo_H = 90; // angle du servo avec le stylo en bas
Servo Servo_stylo; // nom du servo
Le port série Bluetooth sera déclaré de la façon suivante. D’une part, nous allons créé une variable d’écoute du port, et d’autre part, nous déclarons le module Bluetooth en écoute sur la broche 0 et de transmission sur la broche 1. La variable d’écoute permettra de n’interpréter que le premier caractère d’une chaine transmise, par exemple lors de la transmission directe grâce à un téléphone.
// Variables d'ecoute du port série
String ent_serie; // récupère la chaine de caractère entrée sur le port série
int ENTREE;
// configuration du port série BlueTooth
int Rx=0;
int Tx=1;
SoftwareSerial BTmySerial(Rx, Tx); // RX, TX
Les variables crées ici sont peut-être les plus « sensibles » du robot, puisque ce sont elles qui permettront son étalonnage. Le diamètre des roues permet de régler l’avance en déplacement. L’entraxe permettra de régler les déplacements angulaires.
ATTENTION: votre robot est un robot artisanal, il sera difficile de lui faire faire des figures aussi parfaites qu’avec un logiciel. Néanmoins, un calibrage précis peut donner des figures dont la précision avoisine le millimètre.
Le nombre de pas par tour dépend du modèle de moteur pas à pas (512 est le nombre de pas par tour du moteur préconisé).
Le délai entre pas. La préconisation est de 10ms entre pas. On peut descendre en dessous, mais le risque de sauter des pas augmente avec la diminution de la temporisation.
Je propose de créer une variable flottante qui calcule l’avance de la roue du robot pour un pas de moteur. Elle sera utilisée ultérieurement pour les calculs de déplacement.
// Variables de déplacement
float dia_roue=59.9; //diamètre des roues (augmentation = diminution distance parcourue)
float entraxe=112.9; //entraxe (augmentation = augmentation de la rotation angulaire)
int Paspartour=512; //voir la documentation du réducteur
int duree_delai=10; //attente entre pas in ms (10 mini)
float AvanceParPas = (3.1415926 * dia_roue)/Paspartour; //calcule l'avance d'un pas
Nous allons maintenant créer des variables et des matrices pour le déplacement du moteur pas à pas. Nous retrouvons tout d’abord les déclarations des broches mobilisées par ces moteurs gauche (G) ou droite (D) voir ici pour le câblage physique.
// Variables moteurs pas à pas
int broches_MoteurG[4] = { 2, 3, 4, 5};
int broches_MoteurD[4] = { 10, 11, 12, 13};
Pour les matrices permettant la rotation des moteurs, nous envisageons que le moteur puisse tourner dans le sens horaire (dextro) ou antihoraire (levo) en regardant depuis le moteur vers la roue.
const byte dextro[4][4] = { //rotation sens horaire depuis le moteur vers l'extérieur
{HIGH, LOW, LOW, HIGH},
{LOW, LOW, HIGH, HIGH},
{LOW, HIGH, HIGH, LOW},
{HIGH, HIGH, LOW, LOW},
};
const byte levo[4][4] = { //rotation sens anti-horaire depuis le moteur vers l'extérieur
{HIGH, HIGH, LOW, LOW},
{LOW, HIGH, HIGH, LOW},
{LOW, LOW, HIGH, HIGH},
{HIGH, LOW, LOW, HIGH},
};
Vous trouverez de nombreuses pages sur le fonctionnement des moteurs pas à pas qui vous permettront de comprendre la logique de ce codage. Ce qu’il faut comprendre ici, c’est qu’un moteur pas à pas est composé de 4 électroaimants qui sont alternativement alimentés (HIGH) ou coupés (LOW) pour déplacer le moteur d’un pas.
Le speaker est configuré sur la broche 6. Nous profitons de cette déclaration pour créer un flag pour empêcher ou autoriser le déclenchement des bips.
// configuration speaker
int broche_speaker = 6; // Broche du speaker
int speak = 0; // Flag pour empêcher le bip après l'appel d'une boucle sonore
La LED RGB est configurée selon les préconisations du site Grove SEEED.
// configuration led RGB
int broche_RGB =8; // Broche de la led multicolore
Adafruit_NeoPixel led_RGB = Adafruit_NeoPixel(1, broche_RGB, NEO_GRB + NEO_KHZ800);
Les déclarations sont terminées, nous allons maintenant passer à l’initialisation de la carte.
L’initialisation du robot – void setup()
Nous allons maintenant nous intéresser à la fonction setup. Elle commence par l’ouverture du port série Bluetooth. Le modèle Grove SEEED retenu fonctionne à 9600 Baud, mais cela peut changer si vous utilisez d’autres modules Bluetooth.
oid setup()
{
// Initialise le port série
BTmySerial.begin(9600); // ouvre le port série et fixe le débit à 9600 bauds
Ensuite, c’est le servomoteur qui est initialisé. La broche (D7) est déclarée, elle est configurée en mode sortie. La rotation du servo met la came du style, et par conséquent le stylo, en position haute. Enfin l’alimentation de la broche est coupée pour essayer d’économiser les batteries. Un délai de 200 ms est demandé pour attendre la fin de l’opération.
// initialisation le Servo_stylo
Servo_stylo.attach(broche_servo); // déclaration de la broche
pinMode(broche_servo, OUTPUT); // met la broche en mode sortie
stylo_H(); // monter le porte stylo en haut
digitalWrite (broche_servo, LOW); // coupe la broche du servo pour économiser les batteries
delay(200);
Les moteurs pas à pas sont ensuite initialisés, et l’alimentation des moteurs est coupée également pour économiser les batteries. Cette instruction est assez inutile dans le programme actuel, puisque nous allons démarrer très prochainement les moteurs pas à pas, mais j’ai estimé qu’il était important de la conserver pour des évolutions ultérieures de programmation.
// initialisation Moteurs pas à pas
for(int pin=0; pin<4; pin++)
{
pinMode(broches_MoteurG[pin], OUTPUT); // met les broches du moteur gauche en mode sortie
digitalWrite(broches_MoteurG[pin], LOW); // coupe la broche du moteur gauche pour économiser les batteries
pinMode(broches_MoteurD[pin], OUTPUT); // met les broches du moteur droit en mode sortie
digitalWrite(broches_MoteurD[pin], LOW); // coupe la broche du moteur droit pour économiser les batteries
}
La fonction setup appelle ensuite plusieurs fonctions pour démarrer le robot. Nous en verrons le codage plus tard. Ces fonctions sont:
- Configuration du Bluetooth avec purge du buffer du port série;
// configure le BT
BTConfig();
delay(1000);
BTmySerial.flush(); // Purge le buffer
- Une petite « danse de démarrage » pour vérifier si le robot le robot est fonctionnel;
// Fais bouger le robot
avance(5);
recule(5);
tournegauche(5);
tournedroite(5);
- Une mélodie de fin pour signaler que le robot est prêt à recevoir des consignes. J’ai repris, comme dans la tortue Jeulin, « Au clair de la lune », mais si vous vous sentez l’âme musicienne, il est facile de changer la mélodie comme nous le verrons plus loin.
// joue la mélodie pour signaler que la tortue est prête
melodie();
La fonction setup est terminée, nous allons nous intéresser à la boucle principale.
La boucle principale – void loop()
La boucle principale débute par l’écoute du port série. J’ai mis une « sécurité » en place qui consiste à n’interpréter que le premier caractère d’une chaine, grâce à la variable ENTREE, déclarée au début du programme. Il fait suite à quelques essais et bugs de communication via smartphone. Ce choix est discutable et peut être modifié par l’utilisateur, si besoin.
void loop()
{
// ecoute du port série
if (BTmySerial.available()) //ecoute le port série si reçoit une donnée
{
ent_serie = BTmySerial.readString(); // lit la chaine envoyée sur le port série
ENTREE = ent_serie.charAt(0); // extrait le premier caractère (sécurité pour traiter les commandes une à une);
BTmySerial.println(ENTREE); // lit le premier caractère
La partie qui suit me semble être le cœur du programme. En fonction du caractère reçu sur le port Bluetooth, le programme principal appelle la fonction associée. Par exemple, si le caractère H (code décimal 72) est lu sur le port série, la fonction stylo_H est appelée. Autre exemple, si le port série reçoit le caractère A (code décimal 65), la fonction avance est appelée avec une variable 100, qui correspond à la longueur en mm de l’avance.
C’est ici que vous pouvez créer de « nouvelles commandes » personnalisées en utilisant les caractères ASCII non référencés. Par exemple, les fonctions test_carre() et demi_tour() sont des fonctions spécifiques à l’étalonnage de la tortue. Elles permettent d’améliorer la précision de dia_roue et entraxe.
if (ENTREE == 66){stylo_B();} // B= stylo bas
else if (ENTREE == 72){stylo_H();} // H = stylo haut
else if (ENTREE == 97){avance(0);} // a = avance d'un pas
else if (ENTREE == 115){avance(10);} // s = avance de 10 mm
else if (ENTREE == 65){avance(100);} // A = avance de 100 mm
else if (ENTREE == 70){avance(200);} // F = avance de 200 mm
else if (ENTREE == 114){recule(0);} // r = recule d'un pas
else if (ENTREE == 116){recule(10);} // t = recule de 10 mm
else if (ENTREE == 82){recule(100);} // R = recule de 100 mm
else if (ENTREE == 85){recule(200);} // U = recule de 200 mm
else if (ENTREE == 103){tournegauche(0);} // g = tourne d'un pas gauche
else if (ENTREE == 118){tournegauche(1);} // v = tourne 1 degré gauche
else if (ENTREE == 71){tournegauche(15);} // G = tourne 15 degré gauche
else if (ENTREE == 99){tournegauche(30);} // c = tourne 30 degré gauche
else if (ENTREE == 102){tournegauche(45);} // f = tourne 45 degré gauche
else if (ENTREE == 105){tournegauche(60);} // i = tourne 60 degré gauche
else if (ENTREE == 108){tournegauche(90);} // l = tourne 90 degré gauche
else if (ENTREE == 111){tournegauche(120);} // o = tourne 120 degré gauche
else if (ENTREE == 100){tournedroite(0);} // d = tourne d'un pas droite
else if (ENTREE == 117){tournedroite(1);} // u = tourne 1 degré droite
else if (ENTREE == 68){tournedroite(15);} // D = tourne 15 degré droite
else if (ENTREE == 98){tournedroite(30);} // b = tourne 30 degré droite
else if (ENTREE == 101){tournedroite(45);} // e = tourne 45 degré droite
else if (ENTREE == 104){tournedroite(60);} // h = tourne 60 degré droite
else if (ENTREE == 107){tournedroite(90);} // k = tourne 90 degré droite
else if (ENTREE == 110){tournedroite(120);} // n = tourne 120 degré droite
else if (ENTREE == 49){avantdroite(100);} // 1 = arc de cercle av dr rayon 100 mm
else if (ENTREE == 53){avantdroite(200);} // 5 = arc de cercle av dr rayon 200 mm
else if (ENTREE == 50){avantgauche(100);} // 2 = arc de cercle av g rayon 100 mm
else if (ENTREE == 54){avantgauche(200);} // 6 = arc de cercle av g rayon 200 mm
else if (ENTREE == 51){arrieregauche(100);} // 3 = arc de cercle ar g rayon 100 mm
else if (ENTREE == 55){arrieregauche(200);} // 7 = arc de cercle ar g rayon 200 mm
else if (ENTREE == 52){arrieredroite(100);} // 4 = arc de cercle ar dr rayon 100 mm
else if (ENTREE == 56){arrieredroite(200);} // 8 = arc de cercle ar dr rayon 200 mm
else if (ENTREE == 74){melodie();} // J = joue une mélodie
else if (ENTREE == 75){bip();} // K = bip
else if (ENTREE == 80){rale();} // P = rale
else if (ENTREE == 43){allume_blanc();} // + = allume la led en blanc
else if (ENTREE == 45){eteins_led();} // - = allume la led en blanc
else if (ENTREE == 33){allume_bleu();} // ! = allume la led en bleu
else if (ENTREE == 34){allume_vert();} // " = allume la led en vert
else if (ENTREE == 35){allume_rouge();} // # = allume la led en rouge
else if (ENTREE == 36){allume_violet();} // $ = allume la led en violet
else if (ENTREE == 37){allume_turquoise();} // % = allume la led en turquoise
else if (ENTREE == 38){allume_jaune();} // & = allume la led en jaune
else if (ENTREE == 88){test_carre();} // X réalise un carré de 150 TG
else if (ENTREE == 121){demi_tour();} // y 2 segments de 300 séparés par un 1/2 tour
les fonctions
La gestion du porte stylo
Elle est appelée par les caractères suivants
if (ENTREE == 66){stylo_B();} // B= stylo bas
else if (ENTREE == 72){stylo_H();} // H = stylo haut
Le porte stylo comporte 2 états stylo en haut ou stylo en bas. Le fonctionnement est identique pour les fonctions stylo_H et stylo_B. la broche est alimentée, puis après une temporisation, le servo moteur est amené à l’angle souhaité (angle_stylo_h ou angle_stylo_b). Après une nouvelle temporisation, l’alimentation de la broche est coupée.
/***************************************************************************
* gestion du porte sylo
***************************************************************************/
///////////////////////////////////
// relève la came du porte-stylo //
///////////////////////////////////
void stylo_H ()
{
digitalWrite (broche_servo, HIGH); // allume la broche du servo
delay(250);
Servo_stylo.write(angle_stylo_H); // met le servo sur la position haute
delay(250);
digitalWrite (broche_servo, LOW); // coupe la broche du servo (economie)
}
///////////////////////////////////
// baisse la came du porte-stylo //
///////////////////////////////////
void stylo_B () // baisse la came du porte-stylo en haut
{
digitalWrite (broche_servo, HIGH); // allume la broche du servo
delay(250);
Servo_stylo.write(angle_stylo_B); // met le servo sur la position basse
delay(250);
digitalWrite (broche_servo, LOW); // coupe la broche du servo (economie)
}
Les fonctions avance et recule
Elles sont appelées par les caractères suivants:
else if (ENTREE == 97){avance(0);} // a = avance d'un pas
else if (ENTREE == 115){avance(10);} // s = avance de 10 mm
else if (ENTREE == 65){avance(100);} // A = avance de 100 mm
else if (ENTREE == 70){avance(200);} // F = avance de 200 mm
else if (ENTREE == 114){recule(0);} // r = recule d'un pas
else if (ENTREE == 116){recule(10);} // t = recule de 10 mm
else if (ENTREE == 82){recule(100);} // R = recule de 100 mm
else if (ENTREE == 85){recule(200);} // U = recule de 200 mm
Avec les cartes, je me contente, de 3 valeurs d’avance 10, 100 et 200 mm ( et une fonction avance d’un pas pour test. J’ai pour projet d’utiliser des variables ultérieurement. Ces valeurs sont toutefois suffisantes pour mes essais actuels.
Les fonctions « avance » ou « recule » font tourner les moteurs pas à pas gauche et droite du nombre de pas calculé par la routine step alternativement. Les sens de rotation des moteurs sont appelés par les matrices dextro[mask][pin] ou levo[mask][pin]. Il faut qu’un des moteurs tourne dans un sens horaire et l’autre dans un sens antihoraire pour que la tortue avance ou recule.
/***************************************************************************
* déplacement avant arrière
* utilise la sous routine "step" pour calculer le nombre de pas
***************************************************************************/
/////////////////////////////////////////
// avance de la distance définie en cm //
/////////////////////////////////////////
void avance (float distance)
{
int steps = step(distance); // affecte le nombre de pas à steps
for(int step=0; step<steps; step++) // boucle sur le nombre de pas moteurs à effectuer
{
for(int mask=0; mask<4; mask++)
{
for(int pin=0; pin<4; pin++)
{
digitalWrite(broches_MoteurG[pin], dextro[mask][pin]); // avance le moteur gauche d'un pas
digitalWrite(broches_MoteurD[pin], levo[mask][pin]); // avance le moteur droit d'un pas
}
delay(duree_delai);
}
}
fin();
}
/////////////////////////////////////////
// recule de la distance définie en cm //
/////////////////////////////////////////
void recule (float distance) // recule de la distance définie en cm
{
int steps = step(distance);
for(int step=0; step<steps; step++) // boucle sur le nombre de pas moteurs à effectuer
{
for(int mask=0; mask<4; mask++)
{
for(int pin=0; pin<4; pin++)
{
digitalWrite(broches_MoteurG[pin], levo[mask][pin]); // recule le moteur gauche d'un pas
digitalWrite(broches_MoteurD[pin], dextro[mask][pin]); // recule le moteur droit d'un pas
}
delay(duree_delai);
}
}
fin();
}
///////////////////////////////////////////
// sous routine "step" //
//établit le nombre de pas de déplacement//
///////////////////////////////////////////
int step(float distance) // retourne le nombre de pas pour réaliser la distance
{
int steps = distance / AvanceParPas; // calcule le nombre de pas
if (steps==0) // permet les avances d'un pas en affectant 0
{
steps=1;
}
return steps; // retourne le nombre de pas
}
Les rotations angulaires autour du stylo
Cette fonction est sans doute la moins précise de toutes, car elle nécessite une qualité de fabrication irréprochable. Autrement dit, plus la construction est soignée, plus cette rotation sera précise. L’impression 3D, si elle offre de la souplesse, nécessite une maitrise de processus d’impression importante pour obtenir des tolérances dimensionnelles relativement peu satisfaisantes pour un mécanicien. J’ai fait le choix de privilégier la facilité de réalisation et la maitrise des couts en sacrifiant une partie de la précision (voir ici). C’est dans cette rotation angulaire que cette difficulté est la plus difficile à surmonter. les fonctions tournegauche et tournedroite peuvent prendre les valeurs angulaires suivantes 1,15,30,45,60,120 degré. On retrouve également la rotation de 1 pas. Ici aussi, ces valeurs peuvent être changé ou complétées, si besoin.
else if (ENTREE == 103){tournegauche(0);} // g = tourne d'un pas gauche
else if (ENTREE == 118){tournegauche(1);} // v = tourne 1 degré gauche
else if (ENTREE == 71){tournegauche(15);} // G = tourne 15 degré gauche
else if (ENTREE == 99){tournegauche(30);} // c = tourne 30 degré gauche
else if (ENTREE == 102){tournegauche(45);} // f = tourne 45 degré gauche
else if (ENTREE == 105){tournegauche(60);} // i = tourne 60 degré gauche
else if (ENTREE == 108){tournegauche(90);} // l = tourne 90 degré gauche
else if (ENTREE == 111){tournegauche(120);} // o = tourne 120 degré gauche
else if (ENTREE == 100){tournedroite(0);} // d = tourne d'un pas droite
else if (ENTREE == 117){tournedroite(1);} // u = tourne 1 degré droite
else if (ENTREE == 68){tournedroite(15);} // D = tourne 15 degré droite
else if (ENTREE == 98){tournedroite(30);} // b = tourne 30 degré droite
else if (ENTREE == 101){tournedroite(45);} // e = tourne 45 degré droite
else if (ENTREE == 104){tournedroite(60);} // h = tourne 60 degré droite
else if (ENTREE == 107){tournedroite(90);} // k = tourne 90 degré droite
else if (ENTREE == 110){tournedroite(120);} // n = tourne 120 degré droite
Du point de vue informatique, la programmation reste simple et reprend le principe des fonctions avance ou recule, sauf que, cette fois, les sens de rotation des moteurs sont identiques (levo/levo ou dextro/dextro).
/***************************************************************************
* rotation sur l'axe du stylo
* utilise la sous routine "stepR" pour calculer le nombre de pas
***************************************************************************/
////////////////////////////////////////////
// tourne sur la gauche d'un angle défini //
////////////////////////////////////////////
void tournegauche (float angle)
{
int steps = stepR(angle); // demande le calcul dun nombre de pas
for(int stepR=0; stepR<steps; stepR++) // boucle sur le nombre de pas moteurs à effectuer
{
for(int mask=0; mask<4; mask++)
{
for(int pin=0; pin<4; pin++)
{
digitalWrite(broches_MoteurG[pin], levo[mask][pin]); // recule le moteur gauche d'un pas
digitalWrite(broches_MoteurD[pin], levo[mask][pin]); // avance le moteur droit d'un pas
}
delay(duree_delai);
}
}
fin();
}
////////////////////////////////////////////
// tourne sur la droite d'un angle défini //
////////////////////////////////////////////
void tournedroite (float angle) // tourne sur la droite d'un angle défini
{
int steps = stepR(angle); // demande le calcul dun nombre de pas
for(int stepR=0; stepR<steps; stepR++) // boucle sur le nombre de pas moteurs à effectuer
{
for(int mask=0; mask<4; mask++)
{
for(int pin=0; pin<4; pin++)
{
digitalWrite(broches_MoteurG[pin], dextro[mask][pin]); // avance le moteur gauche d'un pas
digitalWrite(broches_MoteurD[pin], dextro[mask][pin]); // recule le moteur droit d'un pas
}
delay(duree_delai);
}
}
fin();
}
//////////////////////////////////////////////
// sous routine "stepR" //
//établit le nombre de pas pour la rotation //
//////////////////////////////////////////////
int stepR(float angle){
int stepsR = ((entraxe * angle* 3.1415926)/360) * Paspartour / (dia_roue * 3.1415926); // Calcule le nombre de pas pour tourner d'un angle défini
if (stepsR==0) // permet les avances d'un pas en affectant 0
{
stepsR=1;
}
return stepsR; // retourne le nombre de pas
}
Créer des arcs de cercle
La tortue peut créer des quarts de cercle. Ce choix est directement issu de la tortue Jeulin. Comme, je n’ai pas encore travaillé sur la notion de cercle avec des élèves, je ne sais pas si le quart de cercle est plus pertinent que le cercle. En démonstration, c’est assez étonnant de tracer des quarts de cercle avec ou sans segments.
ici aussi, les valeurs des rayons des cercles sont prédeterminées (100 ou 200mm), et la tortue peut les réaliser en avançant ou reculant, en tournant à droite ou à gauche.
else if (ENTREE == 49){avantdroite(100);} // 1 = arc de cercle av dr rayon 100 mm
else if (ENTREE == 53){avantdroite(200);} // 5 = arc de cercle av dr rayon 200 mm
else if (ENTREE == 50){avantgauche(100);} // 2 = arc de cercle av g rayon 100 mm
else if (ENTREE == 54){avantgauche(200);} // 6 = arc de cercle av g rayon 200 mm
else if (ENTREE == 51){arrieregauche(100);} // 3 = arc de cercle ar g rayon 100 mm
else if (ENTREE == 55){arrieregauche(200);} // 7 = arc de cercle ar g rayon 200 mm
else if (ENTREE == 52){arrieredroite(100);} // 4 = arc de cercle ar dr rayon 100 mm
else if (ENTREE == 56){arrieredroite(200);} // 8 = arc de cercle ar dr rayon 200 mm
Les cercles sont relativement précis (moins que les longueurs des segments, mais plus que les rotations angulaires). Dans l’absolu, il ne s’agit par réellement de cercles, mais d’une approximation de cercle. En effet, le programme calcule le nombre de pas qui correspond au périmètre du parcours du quart de cercle de la roue intérieure au cercle (routine stepint) et fait la même chose pour la roue extérieure.
/////////////////////////////////////////////////////////////////////
// sous routine "stepext" //
//établit le nombre de pas pour la rotation pour la roue exterieure//
/////////////////////////////////////////////////////////////////////
int stepsext(float dia_rot)
{
float perim_ext= (3.1415926 * ((dia_rot) + (entraxe)))/4; // calcule le périmètre parcouru par la roue extérieure
int steps_ext = perim_ext/AvanceParPas ; // nombre de pas nécessaires
return steps_ext;
}
/////////////////////////////////////////////////////////////////////
// sous routine "stepint" //
//établit le nombre de pas pour la rotation pour la roue exterieure//
/////////////////////////////////////////////////////////////////////
int stepsint(float dia_rot)
{
float perim_int= (3.1415926 * ((dia_rot) - (entraxe)))/4; // calcule le périmètre parcouru par la roue intérieure
int steps_int = perim_int/AvanceParPas ; // nombre de pas nécessaires attention la valeur est négative si entraxe < diamètre
return steps_int;
}
À partir de ces deux périmètres, le programme gère les avancées relatives des roues. Par exemple, pour le quart de cercle avant droit. Le programme calcule également le ratio entre les périmètres intérieurs et extérieurs.
Une simple boucle fait avancer la roue externe jusqu’à ratio-1, à ratio, les deux roues tournent et relance une boucle, jusqu’à épuisement du périmètre des deux roues. L’arc de cercle est ainsi composé de petits arcs de cercle dont le rayon est l’entraxe des roues et dont la position du centre est corrigée à la fin de chaque boucle de ratio.
Si le rayon du cercle est inférieur à l’entraxe, il faut que la roue intérieur recule (boucle if(steps_int<=0){}), au lieu d’avancer.
//////////////////////////////////////////////
// 1/4 de cercle en avançant sur la droite //
//////////////////////////////////////////////
void avantdroite (float dia_rot)
{
float steps_ext = stepsext(dia_rot); // demande le nombre de pas de la roue extérieure
float steps_int = stepsint(dia_rot); // demande le nombre de pas de la roue intérieure
float ratio_init = steps_ext/abs(steps_int); // calcule le ratio entre les pas ext et les pas int
int step=0; // affecte 0 à step qui comptera les pas extérieurs
float ratio_dyn=0; // affecte 0 à ratio dyn qui permettra de gérer les avances différentielle entre roues
float test_ratio = ratio_init-1; // calcule test_ratio qui déclenchera l'avance de la roue intéreur quand le ratio sera correct
while( step<steps_ext)// lance la boucle tant que la roue extérieure n'aura pas finit le nombre de pas
{
for(int mask=0; mask<4; mask++)
{
for(int pin=0; pin<4; pin++)
{
digitalWrite(broches_MoteurG[pin], dextro[mask][pin]); // fait avancer la roue gauche (ext)
}
delay(duree_delai);
}
ratio_dyn=ratio_dyn+1; // incrémente le ratio dynamique
step++; // incrémente le nombre de pas ext réalisés
if (ratio_dyn>= test_ratio) // si le ratio est atteint moins un pas
{
for(int mask=0; mask<4; mask++)
{
for(int pin=0; pin<4; pin++)
{
digitalWrite(broches_MoteurG[pin], dextro[mask][pin]); // fait avancer la roue gauche (ext)
if (steps_int<=0) // si la roue intérieure doit reculer (diamètre du cercle <entraxe du robot)
{
digitalWrite(broches_MoteurD[pin], dextro[mask][pin]); // fait reculer la roue droite (int)
}
else
{
digitalWrite(broches_MoteurD[pin], levo[mask][pin]); // fait avancer la roue droite (int)
}
}
delay(duree_delai);
}
step++; // incrémente le nombre de pas ext réalisés
ratio_dyn= ratio_dyn-test_ratio; // calcule le ratio dyn (conserve les décimales pour les incrémenter de nouveau)
}
}
fin();
}
Les quart de cercles « reculer », « tourner à gauche »sont programmés de la même façon, mais en inversant les roues externes et interne (tourne gauche) ou les sens de rotation (recule).
Les fonctions accessoires
- les fonction sonores
Comme la tortue est équipée d’un module Haut-Parleur, j’ai reproduis trois fonctions sonores présents dans la tortue Jeulin. La première est un bip de validation.
/////////////////
// Joue un bip //
/////////////////
void bip ()
{
tone(broche_speaker, 600, 250); // Bip
delay(300); // permet de séparer les notes
noTone(broche_speaker); // stoppe le son
speak = 1 ; // Met le flag boucle sonore à 1
}
La seconde est un bip d’erreur, reprenant le rôle de la fonction « Râle ». La fréquence du Bip pas de 600Hz (bip) à 140Hz (bip grave).
///////////////////////
// Joue un bip grave //
///////////////////////
void rale ()
{
tone(broche_speaker, 140, 800); // Bip grave
delay(1000); // permet de séparer les notes
noTone(broche_speaker); // stoppe le son
speak = 1 ; // Met le flag boucle sonore à 1
}
La dernière est la mélodie déjà évoquée plus haut. Le tableau melody[] indique les fréquences des notes et le tableau durée[], leur durée. Il est aisé de modifier la mélodie ou d’en ajouter une autre.
//////////////////////
// Joue une mélodie //
//////////////////////
void melodie ()
{
int melody[] =
{
349,349,349,392,440,392,349,440,392,392,349, // Au clair de la lune
};
int duree[] =
{
200,200,200,200,500,500,200,200,200,200,200, // durée des notes
};
for (int thisNote = 0; thisNote < 11; thisNote++)
{
tone(broche_speaker, melody[thisNote], duree[thisNote]); // Joue la note
delay(duree[thisNote]+100); // permet de séparer les notes
noTone(broche_speaker); // stoppe le son
}
speak = 1 ; // Met le flag boucle sonore à 1
}
- les fonctions lumineuses
Le phare avant est équipé d’une led RGB. Il est alors facile de modifier l’aspect du phare en jouant sur les teintes RGB.
else if (ENTREE == 43){allume_blanc();} // + = allume la led en blanc
else if (ENTREE == 45){eteins_led();} // - = allume la led en blanc
else if (ENTREE == 33){allume_bleu();} // ! = allume la led en bleu
else if (ENTREE == 34){allume_vert();} // " = allume la led en vert
else if (ENTREE == 35){allume_rouge();} // # = allume la led en rouge
else if (ENTREE == 36){allume_violet();} // $ = allume la led en violet
else if (ENTREE == 37){allume_turquoise();} // % = allume la led en turquoise
else if (ENTREE == 38){allume_jaune();} // & = allume la led en jaune
Le phare blanc s’obtient avec :
void allume_blanc ()
{
led_RGB.setBrightness(255); // luminosité max
led_RGB.begin(); //démarre l'allumage
led_RGB.setPixelColor(0, led_RGB.Color(255,255,255)); //allume toutes les leds = blanc
led_RGB.show(); //allume la led
fin();
}
Tandis que le bleu s’obtient avec les paramètres suivants :
led_RGB.setPixelColor(0, led_RGB.Color(0,0,255)); //allume B = bleu
pour les couleurs retenues, les valeurs sont:
Couleur | Red | Green | Blue |
---|---|---|---|
Blanc | 255 | 255 | 255 |
Bleu | 0 | 0 | 255 |
Vert | 0 | 255 | 0 |
Rouge | 255 | 0 | 0 |
Violet | 255 | 0 | 255 |
Turquoise | 0 | 255 | 255 |
Jaune | 125 | 125 | 0 |
Éteins | 0 | 0 | 0 |
- les fonctions de maintenance
Ces fonctions sont programmées pour faciliter le réglage de la précision de la tortue. Ainsi que je l’ai déjà évoqué, la précision d’un robot artisanal est relative, mais il est possible d’opérer quelques réglage et étalonnage afin d’avoir une tortue relativement précise. Un billet sera d’ailleurs prochainement consacré à ce point spécifique prochainement. Il existe deux fonctions actuellement pour étalonner la tortue.
La première fonction est la fonction demi-tour, la tortue avance de 40 cm, fait demi-tour puis avance de nouveau de 40 cm. La mesure de la longueur permet de vérifier l’exactitude de la variable float dia_roue.
//////////////////////////
// figure test 1/2 tour //
// gestion des angles //
//////////////////////////
void demi_tour()
{
stylo_B();
avance(400);
stylo_H();
tournegauche(180);
stylo_B();
avance(400);
stylo_H();
bip();
bip();
}
La seconde permet de vérifier la valeur de l’entraxe entre roues, float entraxe. Il s’agit de dessiner une série de 3 carrés imbriqués. La rotation relative entre les trois carrés doit être minimisée par le réglage de la valeur de l’entraxe.
////////////////////////
// figure test carré //
// gestion des angles //
////////////////////////
void test_carre()
{
stylo_B();
for(int ncarres=0; ncarres<3; ncarres++)
{
avance(100);
tournegauche(90);
avance(100);
tournegauche(90);
avance(100);
tournegauche(90);
avance(100);
tournegauche(90);
}
stylo_H();
bip();
bip();
}
La fin de la transmission
La fin de la transmission d’une opération est signalée par l’envoi d’un caractère arobase sur le port série. Cette fonction est inutile avec les cartes, mais elle est prévue pour des évolutions futures.
////////////////////
//Fin transmission//
////////////////////
void fin ()
{
BTmySerial.println ("@"); // envoir un arobace à la fin de la transmission
if (speak == 0) // vérfie si on ne sort pas d'une boucle sonore
{
bip ();
}
speak = 0;
}
C’est la fin
Les principales fonctions implémentées dans la tortue sont développées ici. Vous avez la possibilité de les faire évoluer ou de les améliorer. N’oubliez pas la tortue est open source, donc pensez à partager votre travail!