Configuration pour animation tête cheval

Bonjour bernie31

Oui

Ou une longue pression sur le BP, dans les 2 cas une LED rouge clignote.

Non, tout les participants à l'émotion, les cerveaux se font indépendamment:
LEARNSeeiiaaa Apprend l'angle du servo de l'émotion ee interface ii angle aaa

Ou une pression normale sur le BP, les 2 LED sont vertes

Oui.

Je m'attaque, d'abord, au sens de rotation lors du homing.

Une toute belle journée à vous deux!
jpbbricole

Bonjour Jpbbricole,
Merci pour ces précisions .
De mon côté j'ai commencé à investigue du côté du mot02 qui lorsque j'essaye de faire un homing , tourne bien vers le MS, fait le contact puis entre en vibration en repartant en arrière.
J'ai ''décomposé ''le mouvement de ce moteur en utilisant uniquement la commande MOTCCW02aaaa et en faisant la rotation par incréments de 0050 puis 0020 et 0010 pour visualiser le contact avec le MS et obtenir le ''clic'' .Dès qu'il ''clique'' le moteur inverse sa rotation et repart en s'éloignant du MS quelque peu .
Alors que pour d' autres moteurs comme le 00 ou 04 par exemple ( j'ai voulu voir sur plusieurs moteurs s'ils avaient le même comportement ) ce n'est pas le cas . Une fois le clic fait , le moteur ne tourne pas en sens contraire
le zero offset est le même (10 pas ) pour ces moteurs mais en faisant cette manipe il n' entre pas en compte comme lors d'un homing .
Au niveau courant driver le courant de maintien du 02 est a priori le même que celui du 00 ( potard orienté pareillement )
Question poids / force exercé(e) sur l'axe moteur le moteur 3 a le même que le 2 , et ne présente pas ce pb
Sur table, à l'époque ( post #403 ), j'avais esquivé ces pbs avec les moteurs 02 et 06 ; Je pensais que c'était du à un frottement du globe sur les paupières . Ce n'est plus le cas au niveau du frottement suite à mon ponçage localisé des globes .
Bref une énigme pour l'instant ...
Bonne après midi
Bernie31

Bonsoir Jpbbricole,

J'ai continué les investigations sur le pb de homing du moteur 2
Sur son MS la broche du contact commun est bien à 5V quand il n' y a pas de contact ( clic) .
Démonter le moteur 2 est compliqué . Je pourrais au mieux mettre un moteur 2 spare externe connecté au driver et au FDC du moteur 2 et voir si le pb est toujours présent .
Autre piste et là c'est toi qui peut me dire si c'est plausible . Est ce que ce rebond ( oscillation, broutage , je ne sais pas comment le qualifier cf video post #601 ) ne pourrait pas traduire un délai trop court ( ou long ) quelque part ?
Bonne soirée
Bernie31

Bonsoir bernie31

Je m'y remets demain et te fait une version où le sens du homing est réglable.

Bonne soirée.
jpbbricole

Bonsoir Jpbbricole,
Merci plus plus .
Prend ton temps , rien ne presse .
Bernie31

Bonjour Jpbbricole,

C'est l'heure de la sieste de mon assistant et j'en profite pour essayer de glaner quelques infos à propos du pb du homing du moteur 2 ( sorte d'oscillation en touchant le MS ) .

Voici ce que j'ai trouvé sur le site et qui pourrait ressembler à une cause possible?
parasitage du moteur ( j'y crois moyennement , pourquoi ce moteur en particulier ? bizarre , mais bon )

https://lesporteslogiques.net/wiki/outil/cnc_colinbus-configuration

Bonne après midi à toi . Ici un grand soleil bienfaiteur :slight_smile:
Bernie31

Bonjour bernie31

Moi non plus.
Je me souviens plus, as tu essayé de croisé les paramètres du 2 avec un autre pour voir si le défaut change de place, croise à ce niveau

Bonne après-midi, ici temps maussade.
jpbbricole

Bonjour bernie31
Comment va ton Assistant?

Voilà, j'ai ajouté la possibilité de jouer les émotion dans les 2 sens et de par là, le homing également, ça se règle ici:
image

DIR inverted concerne le câblage et commande s'il faut "inverser les fils" et est testé par les commandes en mode trace MOTCW et MOTCCW, ces 2 commandes ne tiennent pas compte de emot inverted.
emot inverted dit s'il faut inverser toutes les opérations de ce moteur (je viens de m'apercevoir que emot inverted n'est pas très bon comme dénomination je vais changer ça), y compris le homing.
Essaies ça:

Le programme:

/*
Name:       bernie31_TeteDeCheval.ino
Created:	18.02.2024
Author:     jpbbricole/bernie31
			https://forum.arduino.cc/t/configuration-pour-animation-tete-cheval/1225121
Remarque:	Animation d'une tête de chaval aved des moteurs pas à pas

19.02.2024	Première version #25
04.05.2024	Ajout des servo (4 moteurs 1 servo) #271
20.05.2024	Modification introduction au mode trace #271
21.05.2024	En mode trace test LED
			Modifié homing avec pushButt #295
02.07.2024	AccelStepper et MultiStepper en local #431
17.07.2024	Introduction des groupes de moteurs #445
02.09.2024	Ajout de Dernière réception BT dans mode trace #478
09.09.2024	Ajout enregistrement des macro (10) #482
18.09.2024	Ajout commande des macro depuis MAI #489
04.10.2024	Ajout modifications du post #500/#501
30.10.2024	Toutes les bibliothèques dans le dossier src #536
16.11.2024	Ajout séquence de homing #584
18.11.2024	Ajout séquence de homing individuel
			En mode trace commandes via Bluetooth
			Paramètres servo avec #if #593
24.11.2024	en mode trace, ajout commande AUTO et TRACE
			Corrigé bug du homing moteur part à l'envers
			Commande de l'alimentation des servo #621
29.11.2024	Possibilité d'émotion sense CCW #649
*/
#include "src/AccelStepper.h"     // https://www.pjrc.com/teensy/td_libs_AccelStepper.html
#include "src/MultiStepper.h"     // https://www.airspayce.com/mikem/arduino/AccelStepper/classAccelStepper.html
#include "src/JC_Button.h">"     // https://github.com/JChristensen/JC_Button  Gestion du bouton poussoir du bouton rotatif
#include "src/Adafruit_NeoPixel.h"     // Gestion des LED RGB Neopixel     https://github.com/adafruit/Adafruit_NeoPixel
#include "src/VarSpeedServo.h"     // https://github.com/netlabtoolkit/VarSpeedServo
#include <EEPROM.h>     // Pour sauvegarde des paramètres en mémoire EEPROM

#define bernie31     // Pour activer les paramètres de bernie31

//------------------------------------- Etats du programme
const String prgEtatLabel[] = {"En Attente", "Joue", "Manuel", "En travail", "Warning"};
enum {prgEtatEnAttente, prgEtatPlaying, prgEtatManual, prgEtatEnTravail, prgEtatWarning};     // Liste des divers etats du programme
int prgEtat;     // Etat en cours du programme

//------------------------------------- Moteur pas à pas (mpap) et groupes de moteurs
enum {mpapGroupe1, mpapGroupe2, mpapGroupeNombre};     // Liste des groupes de moteurs

struct mpapParamDef     // Définition des paramètres des mpap
{
	const long pasTour;     // Nombre de pas par tout
	const int microPas;     // Nombre de micro pas
	float speed;     // Vitesse mximum
	float acceleration;     // Accélération
	long zeroOffset;     // Déplacement entre le homing et la position de départ
	const int dirPin;     // Direction
	const int stepPin;     // Pas
	const int homingPin;     // Position de départ
	const bool dirInverted;     // Direction inversée
	const bool emotInverted;     // Si le jeu de l'émotion est dans le sens inversé
	int groupe;     // Groupe de moteurs
};

mpapParamDef mpapParam[] =
{
#ifdef bernie31
//  Pas/tour   micro pas   vitesse   acceleration  zero Offset      DIR    STEP    HOME   DIR invert  emot invert    Groupe
{200,			1,			750.0,		800.0,			10,			22,		23,		A0,		false,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			24,		25,		A1,		false,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			26,		27,		A2,		false,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			28,		29,		A3,		true,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			30,		31,		A4,		true,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			32,		33,		A5,		true,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			34,		35,		A6,		true,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			36,		37,		A7,		false,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			38,		39,		A8,		true,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			40,		41,		A9,		false,		false,		mpapGroupe1},
{200,			1,			750.0,		800.0,			10,			42,		43,		A10,	false,		false,		mpapGroupe2},
{200,			1,			750.0,		800.0,			10,			44,		45,		A11,	false,		false,		mpapGroupe2},
{200,			1,			750.0,		800.0,			10,			46,		47,		A12,	false,		false,		mpapGroupe2},
#else
{200,			1,			250.0,		800.0,			10,			22,		23,		A0,		true,		false,		mpapGroupe1},
{200,			1,			250.0,		800.0,			10,			24,		25,		A1,		false,		false,		mpapGroupe1},
{200,			1,			250.0,		800.0,			10,			26,		27,		A2,		false,		false,		mpapGroupe2},
{200,			1,			250.0,		800.0,			10,			28,		29,		A3,		true,		true,		mpapGroupe2},
#endif
};
const int mpapNombre = sizeof(mpapParam) / sizeof(mpapParam[0]);     // Nombre de moteurs

/*
	Tablesu des pin d'enable des A4988, la dernière position est ENABLE en permanence
	les premières, seulement quand nécessaire
*/
const int driverEnablePin[] = {8, 9, 10};     // Pin ENABLE, la dernière est active en permanence
const int driverEnablePinNombre = sizeof(driverEnablePin) / sizeof(driverEnablePin[0]);     // Nombre de pin enable
const int driverENApinEtatOn = LOW;     // Pin ENABLE etat actif
const int mpapEnaSet = true; // Pour activer ENABLE
const int mpapEnaPartiel = true; // Pour activer ENABLE partiel, toutes sauf la dernière

const int mpapHomingEtatOn = LOW;     // Etat quand homing actif actif
enum {mpapDirCw, mpapDirCcw};     // Direstions des moteurs

//------------------------------------- Séquence du homing
#ifdef bernie31
	const int homingSequence[] = {0, 1, 3, 2, 4, 5, 6, 8, 7, 9, 10, 11, 12}; // Ordre des moteur pour le homing
#else
	const int homingSequence[] = {3, 2, 1, 0}; // Ordre des moteur pour le homing
#endif
//------------------------------------- Tableau des moteurs
AccelStepper mpap[] =
{
#ifdef bernie31
	{AccelStepper::DRIVER, mpapParam[0].stepPin, mpapParam[0].dirPin},
	{AccelStepper::DRIVER, mpapParam[1].stepPin, mpapParam[1].dirPin},
	{AccelStepper::DRIVER, mpapParam[2].stepPin, mpapParam[2].dirPin},
	{AccelStepper::DRIVER, mpapParam[3].stepPin, mpapParam[3].dirPin},
	{AccelStepper::DRIVER, mpapParam[4].stepPin, mpapParam[4].dirPin},
	{AccelStepper::DRIVER, mpapParam[5].stepPin, mpapParam[5].dirPin},
	{AccelStepper::DRIVER, mpapParam[6].stepPin, mpapParam[6].dirPin},
	{AccelStepper::DRIVER, mpapParam[7].stepPin, mpapParam[7].dirPin},
	{AccelStepper::DRIVER, mpapParam[8].stepPin, mpapParam[8].dirPin},
	{AccelStepper::DRIVER, mpapParam[9].stepPin, mpapParam[9].dirPin},
	{AccelStepper::DRIVER, mpapParam[10].stepPin, mpapParam[10].dirPin},
	{AccelStepper::DRIVER, mpapParam[11].stepPin, mpapParam[11].dirPin},
	{AccelStepper::DRIVER, mpapParam[12].stepPin, mpapParam[12].dirPin},
#else
	{AccelStepper::DRIVER, mpapParam[0].stepPin, mpapParam[0].dirPin},
	{AccelStepper::DRIVER, mpapParam[1].stepPin, mpapParam[1].dirPin},
	{AccelStepper::DRIVER, mpapParam[2].stepPin, mpapParam[2].dirPin},
	{AccelStepper::DRIVER, mpapParam[3].stepPin, mpapParam[3].dirPin},
#endif
};

MultiStepper mpapGroupe[mpapGroupeNombre];     // Groupes de moteurs

//------------------------------------- Servomoteurs (servo)
struct servoParamDef     // Définition des paramètres des servo
{
	const int pin;     // Pin de connexion
	int posMin;     // Position minimum en microsecondes
	int posMax;     // Position maximum en microsecondes
	byte speed;     // Vitesse du servo 0-255
	boolean wait;     // Si attendre fin du mouvement
	const int angleMax;     // Angle maximum du servo
	int zeroOffset;     // Déplacement entre le homing et la position de d part
};

//------------------------------------- Tableau des servo
servoParamDef servoParam[] =
{
#ifdef bernie31
	//   Pin   Pos min   Pos Max   Speed    Wait     angle   Zero offset
	{    50,	800,	  2200,		25,	false,    120,      90},
};
#else
	{    50,	800,	  2200,		25,	false,    120,      0},
};
#endif
const int servoNombre = sizeof(servoParam) / sizeof(servoParam[0]);     // Nombre de servo
VarSpeedServo servo[servoNombre];     // Tableau des servo

const int servoPowerSwitchPin = 5; // Activation de l'alimentation des servo
const int servoPowerSwitchOn = HIGH; // Etat pour activer

/*------------------------------------- Interfaces (intf)
ce tableau contient la répartition des divers ressources MPAP, servo ...
pour constituer l'ensemble nécessaire à l'animation
L'index est la position du MPAP dans le tableau des MPAP mpapParam[]
ou du servo dans le tableau servoParam[]
*/
enum {intfTypeMoteur, intfTypeServo, intfTypeInconnu};     // Liste des types d'interfaces

struct intfDef     // Définition des paramètres des interfaces
{
	const int type;     // Type de l'interface
	const int index;     // Index dans tableau de la resource mpapParam[] ou servoParam[]
};

intfDef intf[] =
{
#ifdef bernie31
	{intfTypeMoteur, 0},
	{intfTypeMoteur, 1},
	{intfTypeMoteur, 2},
	{intfTypeMoteur, 3},
	{intfTypeMoteur, 4},
	{intfTypeMoteur, 5},
	{intfTypeMoteur, 6},
	{intfTypeMoteur, 7},
	{intfTypeMoteur, 8},
	{intfTypeMoteur, 9},
	{intfTypeMoteur, 10},
	{intfTypeMoteur, 11},
	{intfTypeMoteur, 12},
	{intfTypeServo, 0},
#else
	{intfTypeMoteur, 0},
	{intfTypeMoteur, 1},
	{intfTypeMoteur, 2},
	{intfTypeMoteur, 3},
	{intfTypeServo, 0},
#endif
};
const int intfNombre = sizeof(intf) / sizeof(intf[0]);     // Nombre d'interfaces

struct intfParamDef     // Pour stocker les paramètres de l'interface en cours
{
	int type;     // Type de l'interface
	int index;     // Index dans tableau de la resource
};
intfParamDef intfParam;

const int emotNombre = 6;
int emotNum;     // Emotion en cours

//------------------------------------- Manuel
const int buttPpin = 7;     // Pin du bouton poussoir du codeur rotatif
const boolean buttPullup = true;     // PULLUP internes
const boolean buttInvert = true;     // Actif à LOW
const int buttDebounce = 20;     // Temps anti rebonds

Button pushButt(buttPpin, buttPullup, buttInvert, buttDebounce);

//------------------------------------- LED signalisation (signLed)
enum signLedIndex {signLedFctInfo, signLedFctWarning, signLedFctNombre};

const int signLedBusPin = 12;     // Bus des LED Neopixel
const int signLebBrightMax = 5;     // LED Luminosité maximum

Adafruit_NeoPixel signLed = Adafruit_NeoPixel(signLedFctNombre, signLedBusPin, NEO_GRB + NEO_KHZ800);     // Création de l'objet Neopixel signLed

enum signLedColorsIndex {signColGreen, signColBlue, signColRed, signColOrange, signColYellow, signColWithe, signColOff, signColLatest};
uint32_t signLedColors[signColLatest];     // Valeurs RGB pour les couleurs (Tableau)
const unsigned long signledBlinkTempo = 250;     // Periode de clignotement
unsigned long signledBlinkMillis = millis();     // Periode de clignotement, chrono

//------------------------------------- Communications externes (extser)
#define externSerial Serial2     // Connexion série externe
const String extFeedbSep = ",";     // Feedback, séparateur des paramètres

//------------------------------------- Communications Bluetooth
#define btSerial Serial3     // Connexion série Bluetooth

//------------------------------------- Macro commandes (mac)
String macCmdRx = "";     // Macro reçue
const String macCmdSeparator = "|";     // Séparateur des commandes dans la macro
unsigned long macTimerTempo = 0;     // Temporisation dans une macro (Commande TEMP)
unsigned long macTimerMillis = 0;     // Temporisation dans une macro, chrono
boolean macIsRunning = false;     // Si une macro est en cours d'exéécution

const int macArraySize = 10;     // Taille du tableau des macro
const int macArrayLenght = 40;     // Taille maximum d'une macro en tableau

//------------------------------------- Divers
const int traceModeOnPin = 11;    // Pin pour declencher le mode trace/mise au point
const int traceModeOnPinEtatOn = LOW;     // Etat quand actif
boolean traceAuto = true; // Affiche toutes les traceLedTempo millisecondes, la trace

//------------------------------------- Paramètres en EEPROM (eeparam)
struct eeparamDef     // Définition des paramètres en EEPROM
{
	int prgEtat;     // Etat du programme au moment de la sauvegarde
	int emotNum;     // Num ro de l'émotion au moment de la sauvegarde
	long intfEmotPos[emotNombre][intfNombre];     // Position des interfaces pour les n emotions
	long zeroOffset[intfNombre];     // Déplacement entre le homing et la position de départ
	char macArray[macArraySize][macArrayLenght];     // Tableau des macro
};
eeparamDef eeprParam;     // Paramàtres sauvés en EEPROM

const word eeprCheckWord = 0xCAFE;     // Pour repérer si c'est un premier usage
const int eeprOffsetCheckWord = 0;     // Adresse du début de la mémoire
const int eeprOffeeprParam = eeprOffsetCheckWord + sizeof(eeprCheckWord);     // Adresse du début de la mémoire des UID

void setup()
{
  Serial.begin(115200);
	btSerial.begin(9600);
	delay(500);
	Serial.println("Version:\t" + systemGetVersion());
	Serial.println("\t\t13 moteurs max.");

	pinMode(servoPowerSwitchPin, OUTPUT); // Commande de l'alimentation des servo
	digitalWrite(servoPowerSwitchPin, !servoPowerSwitchOn); // Couper l'alimentation
	pinMode(traceModeOnPin, INPUT_PULLUP);

	if (digitalRead(traceModeOnPin) != traceModeOnPinEtatOn)     // Si pas mode trace
	{
		//--------------------------------- Initialisation EEPROM
		word eeprCheck;     // Mot de test
		EEPROM.get(eeprOffsetCheckWord, eeprCheck);     // Lecture du mot test
		EEPROM.get(eeprOffeeprParam, eeprParam);     // Restauration des paramètres

		if (eeprCheck != eeprCheckWord)     // Si premier usage
		{
			EEPROM.put(eeprOffsetCheckWord, eeprCheckWord);     // Ecriture du mot de test
			Serial.println(F("\n!!!! PREMIER USAGE !!!!"));
			Serial.println(F("\tNecessite un homing !!!"));
			prgEtatChange(prgEtatWarning);
		}
		else
		{
			Serial.println(F("\tEEPROM OK"));
		}

		//--------------------------------- Contrôle des conditions d'arret du programme
		if (eeprParam.prgEtat != prgEtatEnAttente)     // S'il y a eu crash ou reset
		{
			Serial.println(F("\nNecessite un homing !!!"));
			Serial.println(F("Maintenez le bouton poussoir presse et faites un reset !!!"));
			Serial.println(F("ou"));
			Serial.println(F("Lancez la commande HOMING !!!"));
			prgEtatChange(prgEtatWarning);
		}
		else
		{
			emotRestore(eeprParam.emotNum);     // Restauration de l'émotion
			prgEtatChange(eeprParam.prgEtat);
		}
	}

	//-------------------------------- LED de signalisation
	signLedInitialisation();
	signLedDisplay(signLedFctInfo, signColBlue, 0);
	signLedDisplay(signLedFctWarning, signColRed, 0);

	Serial.println("\nAnimation avec " + String(intfNombre) + " interfaces");

	//------------------------------------ initialisation des moteurs
	for (int e = 0; e < driverEnablePinNombre; e ++)
	{
		pinMode(driverEnablePin[e], OUTPUT);
	}
	mpapDriverEnable(mpapEnaSet, !mpapEnaPartiel);

	for (int m = 0; m < mpapNombre; m ++)
	{
		mpap[m].setSpeed(mpapParam[m].speed);
		mpap[m].setMaxSpeed(mpapParam[m].speed);
		mpap[m].setAcceleration(mpapParam[m].acceleration);
		if (mpapParam[m].emotInverted) // Si émotion jouée CCW
		{
			mpap[m].setPinsInverted(!mpapParam[m].dirInverted); // Inverser le sens du moteur
		} 
		else
		{
			mpap[m].setPinsInverted(mpapParam[m].dirInverted); // Inverser le sens du moteur
		}
		mpapGroupe[mpapParam[m].groupe].addStepper(mpap[m]);     // Ajout de ce moteur au groupe
		pinMode(mpapParam[m].homingPin, INPUT_PULLUP);     // Pin du homing
	}

	//------------------------------------ initialisation des servo
	for (int s = 0; s < servoNombre; s ++)
	{
		servo[s].attach(servoParam[s].pin,servoParam[s].posMin, servoParam[s].posMax);     // Activation des servo
	}

	delay(1500);
	if (digitalRead(traceModeOnPin) != traceModeOnPinEtatOn)     // Si pas mode trace
	{
		prgEtatChange(eeprParam.prgEtat);
		pushButt.read();
		if (pushButt.isPressed())     // Si BP est pressé = homing
		{
			commandeExecute("HOMING");
		}
	}
	else
	{
		traceModeStart();
	}

	digitalWrite(servoPowerSwitchPin, servoPowerSwitchOn); // Alimenter les servo
	
	#ifdef bernie31 // Si l'utilisateur est
	#else
		//settingJpbbricole();
	#endif
}

void loop()
{
	//--------------------------------- Commandes moniteur
	if (Serial.available())
	{
		String cmdRx = Serial.readStringUntil('\n');     // Réception de la commande terminée par Nouvelle ligne
		commandeExecute(cmdRx);
	}
	//--------------------------------- Commandes externes
	if (btSerial.available())
	{
		String cmdRx = btSerial.readStringUntil('\n');     // Réception de la commande terminée par Nouvelle ligne
		commandeExecute(cmdRx);
	}

	//--------------------------------- Commande des moteurs par groupe
	for (int g = 0; g < mpapGroupeNombre; g ++)     // Liste des groupes
	{
		mpapGroupe[g].run();     // Marche des groupes
	}

	//--------------------------------- Surveillance des moteurs
	if (prgEtat == prgEtatPlaying)
	{
		int mpapRunningNbr = 0;
		for (int m = 0; m < mpapNombre; m ++)
		{
			if (mpap[m].distanceToGo() != 0)     // Si moteur pas arrivé
			{
				mpapRunningNbr ++;     // Compter ce moteur
			}
		}
		if (mpapRunningNbr == 0)     // Si tout les moteurs arrivés
		{
			prgEtatChange(prgEtatEnAttente);
		}
	}

	//------------------------------------ Lecture du bouton poussoir
	pushButt.read();
	if (pushButt.pressedFor(1000))     // Si BP presse 1 seconde
	{
		if(prgEtat == prgEtatEnAttente)     // Si en mode en attente
		{
			prgEtatChange(prgEtatManual);     // Retour en mode manuel
		}
	}
	else if (pushButt.wasPressed())     // Si BP a été pressé
	{
		if (prgEtat == prgEtatManual)     // Si en mode manuel, passer en attente
		{
			prgEtatChange(prgEtatEnAttente);     // Retour en mode en attente
		}
	}

	//--------------------------------- Clignotement LED warning
	if (prgEtat == prgEtatManual || prgEtat == prgEtatWarning)
	{
		if (millis() - signledBlinkMillis >= signledBlinkTempo)     // Si periode clignotement
		{
			static boolean blinkOn;
			if (blinkOn)
			{
				signLedDisplay(signLedFctWarning, signColRed, 0);     // Allumer la LED warning
			}
			else
			{
				signLedDisplay(signLedFctWarning, signColOff, 0);     // Eteindre la LED warning
			}
			blinkOn = !blinkOn;
			signledBlinkMillis = millis();
		}
	}

	//--------------------------------- Macro Commandes
	if (prgEtat == prgEtatEnAttente)
	{
		if (macTimerTempo == 0 && macIsRunning)     // Si pas de tempo en route et reste des macro à exécuter
		{
			macExecuteNext();     // Exécuter la macro suivante
		}
	}

	//--------------------------------- Macro Temporisation
	if (macTimerTempo != 0 && (millis() - macTimerMillis >= macTimerTempo))
	{
		Serial.println(F("Tempo FIN"));
		macTimerTempo = 0;     // Arrêt de la temporisation
	}
}

void emotPlay(int emotIndex)     // Jouer une émotion
{
	Serial.println("Jouer emotion : " + String(emotIndex));

	long mpapGrPos[mpapGroupeNombre][mpapNombre];     // Positions du groupe de moteurs
	int groupeIntPosition[mpapGroupeNombre];     // Position dans le groupe
	for (int g = 0; g < mpapGroupeNombre; g ++)
	{
		groupeIntPosition[g] = 0;
	}

	emotNum = emotIndex;

	for (int i = 0; i < intfNombre; i ++)     // Lister les interfaces
	{
		intfParam = intfParamGet(i);     // Recherche des paramètres de cet interface
		if (intfParam.type == intfTypeMoteur)     // Si c'est un moteur
		{
			int groupeIndex = mpapParam[intfParam.index].groupe;
			mpapGrPos[groupeIndex][groupeIntPosition[groupeIndex]] = eeprParam.intfEmotPos[emotIndex][i] * mpapParam[intfParam.index].microPas;
			groupeIntPosition[groupeIndex] ++;     // Position suivante dans le groupe
		}
		else if (intfParam.type == intfTypeServo)     // Si c'est un servo
		{
			servoGotoAngle(intfParam.index, eeprParam.intfEmotPos[emotIndex][i]);
		}
	}

	for (int g = 0; g < mpapGroupeNombre; g ++)
	{
		mpapGroupe[g].moveTo(mpapGrPos[g]);
	}
}

void emotLearn(int emotNum)   // Apprentissage d'une émotion
{
	Serial.println("Apprentissage emotion : " + String(emotNum));

	String emotPosListe = "";     // Liste des positions

	for (int i = 0; i < intfNombre; i ++)     // Lister les interfaces
	{
		intfParam = intfParamGet(i);     // Recherche des paramètres de cet interface
		if (intfParam.type == intfTypeMoteur)     // Si c'est un moteur
		{
			eeprParam.intfEmotPos[emotNum][i] = mpapLearning(emotNum, intfParam.index);     // Lecture de la position du moteur m
			emotPosListe = emotPosListe +String(eeprParam.intfEmotPos[emotNum][intfParam.index]) + ",";
		}
		else if (intfParam.type == intfTypeServo)     // Si c'est un servo
		{
		}
	}

	Serial.println("\tPositions : " + emotPosListe);
	exterFeedback("LEARN" + extFeedbSep + String(emotNum) + extFeedbSep + emotPosListe);
}

void emotRecord(String cmdRx)     // Enregistremenbt d'une émotion par commande emrec01,912, 33, 404, 55, 94
{
	String subCmd = cmdRx.substring(0, cmdRx.indexOf(","));
	int emotRec = subCmd.toInt();
	subCmd = cmdRx.substring(cmdRx.indexOf(",") +1);

	Serial.println("Enregistrement emotion : " + String(emotRec));

	int intfNum = 0;     // Numéro de l'interface
	String charStep;
	String intfPos = "";

	subCmd += ",";

	for (int l = 0; l < subCmd.length(); l ++)
	{
		charStep = subCmd.substring(l, l+1);
		if (charStep == ",")     // Si séparateur de pas de macro
		{
			if (intfNum < intfNombre)
			{
				eeprParam.intfEmotPos[emotRec][intfNum] = intfPos.toInt();
			}
			intfPos = "";
			intfNum ++;
		}
		else
		{
			intfPos +=charStep;
		}
	}
}

void emotRestore(int emotIndex)
{
	if (emotIndex > 0)     // Si n'était pas home
	{
		Serial.println("Restauration de l'emotion : " + String(emotIndex));
		
		emotNum = emotIndex;

		for (int i = 0; i < intfNombre; i ++)     // Lister les interfaces
		{
			intfParam = intfParamGet(i);     // Recherche des paramètres de cet interface
			if (intfParam.type == intfTypeMoteur)     // Si c'est un moteur
			{
				mpap[intfParam.index].setCurrentPosition(eeprParam.intfEmotPos[emotIndex][i]);     // Moteurs aux positions de l'émotion
			}
			else if (intfParam.type == intfTypeServo)     // Si c'est un servo
			{
				servoGotoAngle(intfParam.index, eeprParam.intfEmotPos[emotIndex][i]);
			}
		}
	}
	else
	{
		Serial.println(F("Restauration de la position : HOME"));
		for (int m = 0; m < mpapNombre; m ++)
		{
			mpap[m].setCurrentPosition(0);     // Moteurs aux positions de l'émotion
		}
	}
}

void emotList()     // Liszet les positions des émotions
{
	Serial.println(F("\nListe des positions pour backup:"));
	for (int e = 0; e < emotNombre; e ++)
	{
		String emotPosBackup = "EMREC" + String(e) + ",";
		for (int i = 0; i < intfNombre; i ++)  
		{
			emotPosBackup = emotPosBackup + String(eeprParam.intfEmotPos[e][i]) + ",";
		}
		Serial.println(emotPosBackup);
	}
	Serial.println("");
}

void prgEtatChange(int etatNum)
{
	prgEtat = etatNum;

	Serial.println("Etat du programme: " + prgEtatLabel[etatNum]);

	if (prgEtat == prgEtatEnAttente)
	{
		mpapDriverEnable(!mpapEnaSet, mpapEnaPartiel);     // Désactiver les premiers moteurs

		signLedDisplay(signLedFctInfo, signColGreen, 0);
		signLedDisplay(signLedFctWarning, signColGreen, 0);
	}
	else if (prgEtat == prgEtatPlaying)
	{
		mpapDriverEnable(mpapEnaSet, mpapEnaPartiel);     // Activer les premiers moteurs
		signLedDisplay(signLedFctInfo, signColBlue, 0);
	}
	else if (prgEtat == prgEtatEnTravail)
	{
		mpapDriverEnable(mpapEnaSet, mpapEnaPartiel);     // Activer les premiers moteurs
		signLedDisplay(signLedFctInfo, signColBlue, 0);
	}
	else if (prgEtat == prgEtatManual)
	{
		mpapDriverEnable(mpapEnaSet, mpapEnaPartiel);     // Désactiver les premiers moteurs
		signLedDisplay(signLedFctInfo, signColYellow, 0);
		mpapDriverEnable(!mpapEnaSet, !mpapEnaPartiel);     // Désactiver les moteurs
	}

	prgSettingSaveEEPROM();
}

//------------------------------------- Paramètres par défaut
void prgSettingDefault()
{
	Serial.println(F("!!! Parametres par defaut"));

	for (int e = 1; e < emotNombre; e ++)
	{
		for (int i = 0; i < intfNombre; i ++)
		{
			intfParam = intfParamGet(i);     // Recherche des paramètres de cet interface
			if (intfParam.type == intfTypeMoteur)     // Si c'est un moteur
			{
				eeprParam.intfEmotPos[e][i] = mpapParam[intfParam.index].zeroOffset;
			}
			else if (intfParam.type == intfTypeServo)     // Si c'est un servo
			{
				eeprParam.intfEmotPos[e][i] = servoParam[intfParam.index].zeroOffset;
			}
		}
	}

	macArrayInit();     // Initialisation de tablesu des macro
	
	prgEtatChange(prgEtatEnAttente);
}

void prgSettingSaveEEPROM()     // Sauvegarde des paramètres
{
	Serial.println("Sauvegarde setting, Etat " + prgEtatLabel[prgEtat]);
	eeprParam.prgEtat = prgEtat;     // Sauvegarde de l'état du programme

	eeprParam.emotNum = emotNum;     // Sauvegarde de l'émotion en cours
	EEPROM.put(eeprOffeeprParam, eeprParam);     // Sauvegarde des paramètres
}

//------------------------------------- Moteurs

void mpapDriverEnable(bool enable, boolean partiel) // Pin ENABLE, si partial, toutesa dernière
{
	int eLoop = (partiel == true) ? driverEnablePinNombre -1 : driverEnablePinNombre;
//Serial.println(eLoop);
	for (int e = 0; e < eLoop; e ++)
	{
		digitalWrite(driverEnablePin[e], (enable == true) ? driverENApinEtatOn : !driverENApinEtatOn);
	}
	Serial.print("Drivers : ");
	Serial.println((enable == true) ? "Enabled" : "Disabled");
}

long mpapHoming(int mpapNum)     // Retour position 0, retourne le nombre de pas
{
	signLedDisplay(signLedFctInfo, signColBlue, 0);
	Serial.println("\tHoming moteur " + String(mpapNum));
	mpap[mpapNum].setCurrentPosition(0);     // Moteur position 0
	long stepsToHome;     // Mémoire de la position actuelle
		
	mpap[mpapNum].moveTo(-32768);

	while (true)
	{
		if (digitalRead(mpapParam[mpapNum].homingPin) == mpapHomingEtatOn)     // Si moteur sur contact.
		{
			stepsToHome = abs(mpap[mpapNum].currentPosition()); // + mpapParam[mpapNum].zeroOffset;
			mpap[mpapNum].stop();
			mpap[mpapNum].setCurrentPosition(0);     // Moteur position 0
			break;
		}
		mpap[mpapNum].run();
	}

	signLedDisplay(signLedFctInfo, signColGreen, 0);
	return stepsToHome;
}

long mpapHomingAndOffset(int mpapNum)     // Retour position 0 + offset0
{
	long stepsToHome = mpapHoming(mpapNum);     // Mémoire de la position actuelle

	mpap[mpapNum].runToNewPosition(mpapParam[mpapNum].zeroOffset * mpapParam[mpapNum].microPas);


	return stepsToHome;
}

long mpapLearning(int emotNum, int mpapNum)     // Pour mémoriser la position du moteur
{
	long posActuelle;
	if (emotNum == 0)     // Si homing
	{
		posActuelle = mpap[mpapNum].currentPosition();
	}
	else
	{
		posActuelle = mpapHoming(mpapNum); // - (mpapParam[mpapNum].zeroOffset * mpapParam[mpapNum].microPas);
	}

	Serial.println("\t\tPosition apprise Emotion " + String(emotNum));
	Serial.println("\t\tMoteur " + String(mpapNum) + " " + String(posActuelle) + " pas");

	mpap[mpapNum].runToNewPosition(posActuelle); // + (mpapParam[mpapNum].zeroOffset * mpapParam[mpapNum].microPas));

	return posActuelle;
}

void mpapTestCwCcw(int mpapDir, String mpapCmd)     // Test des moteurs
{
	Serial.println(mpapCmd);
	int mpapNum = mpapCmd.substring(0, 2).toInt();
	long mpapSteps = mpapCmd.substring(2).toInt();
	Serial.println(mpapNum);

	if (mpapParam[mpapNum].emotInverted) // Si émotion CCW
	{
		mpapSteps = (mpapDir == mpapDirCw) ? -mpapSteps : mpapSteps;
	}
	else
	{
		mpapSteps = (mpapDir == mpapDirCw) ? mpapSteps : -mpapSteps;
	}

	Serial.println(mpapSteps);

	mpap[mpapNum].move(mpapSteps * mpapParam[mpapNum].microPas);
}

//------------------------------------- Servo
void servoGotoAngle(int servoNum, int servoAngle)     // Angle en degrés
{
	Serial.println("\tServo " + String(servoNum) + " > " + String(servoAngle));
	
	servo[servoNum].write(servoAngle, servoParam[servoNum].speed,  servoParam[servoNum].wait);
}

void servoTest(String servoCmd)     // Test des servo
{
	int servoNum = servoCmd.substring(0, 2).toInt();
	int servoAngle = servoCmd.substring(2).toInt();
	servoGotoAngle(servoNum, servoAngle);
}

//------------------------------------- Interfaces (intf)
intfParamDef intfParamGet(int intfNum)     // Recherche des paramètres de l'interface
{
	intfParamDef param;
	param.type = intfTypeInconnu;

	if (intfNum >= intfNombre)
	{
		param.type == intfTypeInconnu;
	}
	else
	{
		param.type = intf[intfNum].type;
		param.index = intf[intfNum].index;
	}
	return param;
}

//------------------------------------- Macro (mac)
void macExecuteNext()     // Exécute la macro suivante
{
	String macCmd = macCmdRx.substring(0, macCmdRx.indexOf(macCmdSeparator));     // Recherche jusqu'au prochain séparatweur
	macCmdRx = macCmdRx.substring(macCmdRx.indexOf(macCmdSeparator) +1);     // Supprimer la commande qui va s'exécuter

	if (macCmd.length() < 3)
	{
		macCmdRx = "";     // Macro terminée
		Serial.println(F("\t\tMacro Terminee"));
		exterFeedback("MACOK");
		macIsRunning = false;
	}
	else
	{
		Serial.println("Macro Comande " + macCmd);
		commandeExecute(macCmd);
	}
}

void macArrayRecord(String macro)    // Enregistrement d'une macro
{
	int macroIndex = macro.substring(0, 1).toInt();
	macroIndex = constrain(macroIndex, 0, macArraySize -1);     // Limiterl'index
	Serial.println("\t Macro #" + String(macroIndex));
	macro = macro.substring(2);     // Supprimer l'index et la virgule
	Serial.println("\t " + macro);
	
	strcpy(eeprParam.macArray[macroIndex], macro.c_str());
}

void macArrayInit()
{
  for (int m = 0; m < macArraySize; m ++)
  {
	strcpy(eeprParam.macArray[m], "EM0|EM1|EM0|EM1|EM0|EM1|");
  }
}

/*
Commandes depuis le moniteur:
HOMING	Retour sur la position de départ + zeroOffset
EMn		Joue l'émotion n
EMREC	Enregistrement d'une émotion EMRECee,I1pos, I2pos, I3pos (I = interface) EMREC02,60, 70, 90, 100, 95

LEARNMn	Apprend l'émotion n par enregistrement de la position des moteurs
LEARNSeeiiaaa	Apprend l'angle du servo de l'émotion ee, de l'interface ii et de l'angle aaa
LIST	Lister les positions des interfaces pour backup
MOTDISn	1 = moteurs désactivés 0 = activés
Attention !!! si un ou des moteurs ont été bougés, faire un HOMING ou un LEARNn
SERVOssaaa Fait aller le servo ss à l'angle aaa voir posMin, posMax et angleMax
DELnn	Temporisation de nn millisecondes
		
		% 0 macro
%RECn	%REC8,EM4|em5|EM2|EM1|EM3|EM1|  Pour enregistrer la macro 8 (0 à 9)
%PLAYn	Jouer la macro n 
%LIST	Lister les macro pour backup


FACTORY	Remet les paramètres du programme comme au départ
*/
void commandeExecute(String cmdRx)
{
	cmdRx.trim();     // Nettoyage des caractères indésirables, CR, LF, TAB... et les espace de début et de fin
	cmdRx.toUpperCase();     // Tout en majuscule
	cmdRx.replace(" ", "");     // Supprimer les espaces
	if (cmdRx.startsWith(F("EM")))     // Emotion
	{
		cmdRx.replace(F("EM"), "");     // On ne garde que le numéro de l'émotion
		if (cmdRx.startsWith(F("REC")))     // Si c'est pour enregistrer une émotion
		{
			cmdRx.replace(F("REC"), "");
			emotRecord(cmdRx);     // Enregistrement de l'émotion
			prgSettingSaveEEPROM();     // Sauvegarde de la nouvelle configuration
		} 
		else     // Jouer une émotion
		{
			int emotNum = cmdRx.toInt();
			if (emotNum >= emotNombre)
			{
				Serial.println("\tNumero invalide " + String(emotNum));
				signLedWarning(6, 150);     // LED Warning
			}
			else
			{
				emotPlay(emotNum);
				prgEtatChange(prgEtatPlaying);
			}
		}
	}
	else if (cmdRx.startsWith(F("HOMING")))     // Tout lkes moteurs au départ + zero offset
	{
		cmdRx.replace(F("HOMING"), "");
		Serial.println(F("Homing moteurs"));
		prgEtatChange(prgEtatEnTravail);

		if (cmdRx == "") // Si pas de paramètres
		{
			for (int m = 0; m < mpapNombre; m ++)  
			{
				mpap[m].setCurrentPosition(0);
			}
			for (int m = 0; m < mpapNombre; m ++)  
			{
				//mpapHomingAndOffset(m);
				mpapHomingAndOffset(homingSequence[m]); // Selon ordre des moteurs
				eeprParam.intfEmotPos[0][m] = mpapLearning(0, m);     // Lecture de la position du moteur m
			}
		} 
		else
		{
			int homeMotNum = cmdRx.toInt();
			if (homeMotNum < 0 || homeMotNum >= mpapNombre)
			{
				Serial.println("\tHoming moteur # invalide: " + String(homeMotNum));
			}	
			else
			{
				Serial.println("\tHoming moteur : " + String(homeMotNum));
				mpapHomingAndOffset(homeMotNum); // Selon ordre des moteurs
				eeprParam.intfEmotPos[0][homeMotNum] = mpapLearning(0, homeMotNum);     // Lecture de la position du moteur m
			}		
		}
		prgEtatChange(prgEtatEnAttente);
	}
	else if (cmdRx.startsWith(F("LEARNM")))     // Apprentissage d'une émotion, moteurs
	{
		Serial.println(F("Learning positions"));
		prgEtatChange(prgEtatEnTravail);

		cmdRx.replace(F("LEARNM"), "");
		int emotNum = cmdRx.toInt();     // Numéro de l'émotion

		if (emotNum >= emotNombre)
		{
			Serial.println("\tNumero invalide " + String(emotNum));
			signLedWarning(6, 150);     // LED Warning
		}
		else
		{
			emotLearn(emotNum);
		}

		prgEtatChange(prgEtatEnAttente);
	}
	else if (cmdRx.startsWith(F("LEARNS")))     // Apprentissage d'une émotion, servo ee ii aa
	{
		Serial.print(F("Learning positions Servo"));     // learns0104123
		prgEtatChange(prgEtatEnTravail);

		cmdRx.replace(F("LEARNS"), "");
		int emotNum = cmdRx.substring(0, 2).toInt();     // Numéro de l'émotion
		int intfNum = cmdRx.substring(2, 4).toInt();     // Numéro de l'interface
		int servoAngle = cmdRx.substring(4).toInt();     // Angle du servo

		intfParam = intfParamGet(intfNum);
		if (intfParam.type == intfTypeServo)     // Si c'est un servo
		{
			servoGotoAngle(intfParam.index, servoAngle);
			eeprParam.intfEmotPos[emotNum][intfNum] = servoAngle;     // Sauvegarde de l'angle
		}
		else
		{
			Serial.println("\nL'interface " + String(intfNum) + " n'est pas un SERVO !!!!\n");
		}

		prgEtatChange(prgEtatEnAttente);
	}
	else if (cmdRx.startsWith(F("LIST")))     // Lister les positions des émotions
	{
		emotList();
	}
	else if (cmdRx.startsWith(F("MOTDIS")))     // Désactiver/Activer les moteurs
	{
		prgEtatChange(prgEtatEnTravail);
		Serial.print(F("Moteurs disable "));

		cmdRx.replace(F("MOTDIS"), "");
		int disableMode = cmdRx.toInt();     // Mode Disable
		if (disableMode == 1)     // Disable motors
		{
			Serial.println(F(" OFF"));
			mpapDriverEnable(mpapEnaSet, !mpapEnaPartiel);
			prgEtatChange(prgEtatManual);     // En mode manuel
		}
		else
		{
			Serial.println(F(" ON"));
			prgEtatChange(prgEtatEnAttente);
		}
	}
	else if (cmdRx.startsWith(F("%")))     // Macro
	{
		cmdRx = cmdRx.substring(1);     // Pour supprimer le %
		if (cmdRx.startsWith(F("LIST")))     // Lister les macro
		{
			Serial.println(F("Macro liste pour backup"));
			for (int m = 0; m < macArraySize; m ++)
			{
				Serial.print("%REC" + String(m) + ",");
				Serial.println(eeprParam.macArray[m]);
			}
		}
		else if (cmdRx.startsWith(F("REC")))     // Enregistrer une macro
		{
			Serial.println(F("Macro Enregistrement "));
			macArrayRecord(cmdRx.substring(3));     // Supprimer REC

			prgEtatChange(prgEtatEnAttente);
		}
		else if (cmdRx.startsWith(F("PLAY")))     // Jouer une macro sauvée dans tableau
		{
			int macroIndex = cmdRx.substring(4).toInt();     // Supprimer PLAY
			macroIndex = constrain(macroIndex, 0, macArraySize -1);     // Limiterl'index
			Serial.println("Macro jouer # " + String(macroIndex));
			commandeExecute("%" + (String)eeprParam.macArray[macroIndex]);
			//macArrayRecord(cmdRx.substring(3));     // Supprimer REC

			prgEtatChange(prgEtatEnAttente);
		}
		else
		{
			Serial.println("Macro joue " + cmdRx);

			macCmdRx += cmdRx + macCmdSeparator;     // Ajout aux macro en cours
			Serial.println("\t" + macCmdRx);
			macIsRunning = true;
			exterFeedback("MACSTART");
		}
	}
	else if (cmdRx.startsWith(F("DEL")))     // Temporisation macro
	{
		cmdRx.replace(F("DEL"), "");
		macTimerTempo = cmdRx.toInt();     // Temps de la temporisation
		Serial.print(F("Temporisation "));
		Serial.println(macTimerTempo);
		macTimerMillis = millis();     // Démarrage du chrono
	}
	//else if (cmdRx.startsWith(F("MOTCW")))     // Test moteur sens des aiguilles d'une montre
	//{
		//Serial.print(F("Moteurs CW "));
		//prgEtatChange(prgEtatEnTravail);
//
		//cmdRx.replace(F("MOTCW"), "");
		//mpapTestCwCcw(mpapDirCw, cmdRx);
//
		//prgEtatChange(prgEtatEnAttente);
	//}
	//else if (cmdRx.startsWith(F("MOTCCW")))     // Test moteur sens contraire des aiguilles d'une montre
	//{
		//Serial.print(F("Moteurs CCW "));
		//prgEtatChange(prgEtatEnTravail);
//
		//cmdRx.replace(F("MOTCCW"), "");
		//mpapTestCwCcw(mpapDirCcw, cmdRx);
//
		//prgEtatChange(prgEtatEnAttente);
	//}
	else if (cmdRx.startsWith(F("SERVO")))     // Test des servo
	{
		Serial.print(F("Servo "));
		prgEtatChange(prgEtatEnTravail);

		cmdRx.replace(F("SERVO"), "");
		servoTest(cmdRx);

		prgEtatChange(prgEtatEnAttente);
	}
	else if (cmdRx.startsWith(F("TRACE")))     // Provisoire pour feedback
	{
	}
	else if (cmdRx.startsWith(F("FACTORY")))     // Paramètres par défaut
	{
		Serial.println("Factory setting");
		eeprParam.prgEtat = prgEtatWarning;
		eeprParam.emotNum = 0;
		//EEPROM.put(eeprOffeeprParam, eeprParam);     // Sauvegarde des paramètres
		prgSettingDefault();

		EEPROM.put(eeprOffsetCheckWord, eeprCheckWord -1);     // Effacement du mot de test
		Serial.println(F("!!!! LE PROGRAMME DOIT REDEMARRER"));

		while(true)
		{
			signLedDisplay(signLedFctWarning, signColRed, 0);     // Allumer la LED warning
			signLedDisplay(signLedFctInfo, signColOff, 0);     // Allumer la LED info
			delay(250);
			signLedDisplay(signLedFctWarning, signColOff, 0);     // Allumer la LED warning
			signLedDisplay(signLedFctInfo, signColRed, 0);     // Allumer la LED info
			delay(250);
		}
	}
	else if (cmdRx == F("TEST"))     //
	{
		Serial.println(F("Test fonction"));
	}
	else if (cmdRx == F("RESTAURE"))     //
	{
		Serial.println(F("Restaurer les paramètres"));
		commandeExecute(F("EMREC1,42,46,45,19,46,"));
		commandeExecute(F("EMREC2,66,62,90,35,66,"));
		commandeExecute(F("EMREC3,94,98,110,67,94,"));
		commandeExecute(F("EMREC4,138,146,150,19,146,"));
		commandeExecute(F("EMREC5,170,178,180,47,170,"));
		macArrayInit();
	}
	else
	{
		Serial.println("\t\t !!! Commande inconnue : " + cmdRx);
		signLedWarning(6, 150);     // LED Warning
	}
}

//------------------------------------- Communications externes (extser)
void exterFeedback(String feedbText)
{
	//Serial.println("> > > > > > > > " + feedbText);
	//externSerial.println("TRACE=" + feedbText);
}

//------------------------------------- LED de signalisation
void signLedDisplay(int ledIndex, int colorIndex, int timeDispl)
{
	signLed.setPixelColor(ledIndex, signLedColors[colorIndex]);
	signLed.show();

	if (timeDispl > 0)     // Si timeDispol > 0 millisec.
	{
		delay(timeDispl);
		signLed.setPixelColor(ledIndex, signLedColors[signColOff]);
		signLed.show();
	}
}

//------------------------------------- Commande de la LED warning
void signLedWarning(int ledLoop, unsigned long ledTime)     // Commande de la LED de warning
{
	for (int l = 0; l < (ledLoop *2); l ++)
	{
		if (l % 2 == 0)     // Une fois sur deux
		{
			signLedDisplay(signLedFctWarning, signColRed, ledTime);
		}
		else
		{
			signLedDisplay(signLedFctWarning, signColOff, ledTime);
		}
	}
	signLed.setPixelColor(signLedFctWarning, signLedColors[signColGreen]);
	signLed.show();
}

void signledTest(String ledCmd)     // Test des LED LED01R pour LED 1 en rouge
{
	Serial.println(ledCmd);
	int ledNum = ledCmd.substring(0, 2).toInt();
	String ledCouleur = ledCmd.substring(2);
	Serial.println(ledCouleur + "*");
	if (ledCouleur == "R")
	{
		signLedDisplay(ledNum, signColRed, 3000);
	}
	else if (ledCouleur == "V")
	{
		signLedDisplay(ledNum, signColGreen, 3000);
	}
	else if (ledCouleur == "B")
	{
		signLedDisplay(ledNum, signColBlue, 3000);
	}
	else
	{
		signLedDisplay(ledNum, signColOff, 3000);
	}
}

void signLedInitialisation()
{
	signLedColors[signColRed] = signLed.Color(255, 0, 0);     // Définition des couleurs
	signLedColors[signColGreen] = signLed.Color(0, 255, 0);
	signLedColors[signColBlue] = signLed.Color(0, 0, 255);
	signLedColors[signColYellow] = signLed.Color(255, 255, 0);
	signLedColors[signColOrange] = signLed.Color(255, 165, 0);
	signLedColors[signColWithe] = signLed.Color(255, 255, 255);
	signLedColors[signColOff] = signLed.Color(0, 0, 0);

	signLed.begin();
	signLed.setBrightness(signLebBrightMax);
}

//------------------------------------- Syst me de trace pour la mise au point
void traceModeStart()
{
	Serial.println(F("\n> > > > >MODE TRACE !!!"));
	delay(3000);

	const unsigned long traceLedTempo = 250;     // Tempo du clignotement
	unsigned long traceLedMillis = millis();     // Tempo du clignotement, chrono
	boolean traceLedToggle = true;     // Pour l'alternance
	String btRx = "-";     // Réception de BT

	const unsigned long traceDisplayTempo = 2000;     // Tempo d'affichage
	unsigned long traceDisplayMillis = millis();     // Tempo d'affichage, chrono

	while (true)     // Boucle infinie pour en sortie faire un reset de l'Arduino
	{
		if (digitalRead(traceModeOnPin) == traceModeOnPinEtatOn)
		{
			if (millis() - traceLedMillis >= traceLedTempo)     // Affichage des LED
			{
				if (traceLedToggle)
				{
					signLedDisplay(signLedFctWarning, signColBlue, 0);     // Allumer la LED warning
					signLedDisplay(signLedFctInfo, signColOff, 0);     // Allumer la LED info
				}
				else
				{
					signLedDisplay(signLedFctWarning, signColOff, 0);     // Allumer la LED warning
					signLedDisplay(signLedFctInfo, signColBlue, 0);     // Allumer la LED info
				}
				traceLedToggle = !traceLedToggle;
				traceLedMillis = millis();
			}

			if (traceAuto && (millis() - traceDisplayMillis >= traceDisplayTempo))     // Affichage des informations
			{
				traceAll();

				traceDisplayMillis = millis();
			}

			//--------------------------------- Commandes moniteur
			if (Serial.available())
			{
				traceCmdExecute(Serial.readStringUntil('\n'));
			}

			//------------------------- Réception Bluatooth
			if (btSerial.available())
			{
				traceCmdExecute(btSerial.readStringUntil('\n'));
			}

			for (int m = 0; m < mpapNombre; m ++)
			{
				mpap[m].run();
			}
		}
	}
}

void traceAll()
{
	traceFdcStatus();
	traceBoutonPoussoir();
	traceDriversEnable();
	//Serial.print(F("Derniere reception Bluetooth;"));
	//Serial.println("\t" + btRx);	
}

/*
	MOTCWnnpppp Fait tourner le moteur nn de pppp pas sens des aiguilles d'une montre
	MOTCCWnnpppp Fait tourner le moteur nn de pppp pas sens inverse des aiguilles d'une montre
*/
void traceCmdExecute(String cmdRx)
{
	cmdRx.trim(); // Nettoyer la chaîne
	cmdRx.toUpperCase();     // Commande recue en majuscules
				
	if (cmdRx.startsWith(F("MOTCW")))     // Test moteur sens des aiguilles d'une montre
	{
		Serial.print(F("Moteurs CW "));
		//prgEtatChange(prgEtatEnTravail);

		cmdRx.replace(F("MOTCW"), "");
		mpapTestCwCcw(mpapDirCw, cmdRx);

		//prgEtatChange(prgEtatEnAttente);
	}
	else if (cmdRx.startsWith(F("MOTCCW")))     // Test moteur sens contraire des aiguilles d'une montre
	{
		Serial.print(F("Moteurs CCW "));
		//prgEtatChange(prgEtatEnTravail);

		cmdRx.replace(F("MOTCCW"), "");
		mpapTestCwCcw(mpapDirCcw, cmdRx);

		//prgEtatChange(prgEtatEnAttente);
	}
		else if (cmdRx.startsWith(F("SERVO")))     // Test des servo
		{
			Serial.println("Trace commande servo : " + cmdRx);

			cmdRx.replace(F("SERVO"), "");
			servoTest(cmdRx);
		}
		else if (cmdRx.startsWith(F("LED")))     // Test des LED
		{
			Serial.println("Essai LED : " + cmdRx);

			cmdRx.replace(F("LED"), "");
			signledTest(cmdRx);
		}
		else if (cmdRx.startsWith(F("HOMING")))     // Homing des moteurs
		{
			commandeExecute(cmdRx);
		}
		else if (cmdRx.startsWith(F("FACTORY")))     // Test des servo
		{
			Serial.println("FACTORY !!! ");
			commandeExecute(F("FACTORY"));
		}
		else if (cmdRx == (F("TRACE")))     // Trace
		{
			traceAll();
		}
		else if (cmdRx == (F("AUTO")))     // Si trace auto toggle
		{
			traceAuto = !traceAuto; // Toggle
			Serial.print(F("\nTrace auto "));
			Serial.println((traceAuto == true) ? "ON" : "OFF");
		}
		else
		{
		}
}

void traceFdcStatus()     // Affichage de l'état des fin de course
{
	Serial.println(F("\nEtat des contacts de homing"));

	for (int m = 0; m < mpapNombre; m ++)  //***
	{
		Serial.print(String(m) + "=");
		Serial.print((digitalRead(mpapParam[m].homingPin) == mpapHomingEtatOn) ? "ON" : "OFF");
		Serial.print(", ");
		if (m % 3 == 2)
		{
			Serial.println("");
		}
	}
	Serial.println("");
}

void traceBoutonPoussoir()
{
	Serial.print(F("\nEtat du bouton poussoir : "));
	pushButt.read();
	if (pushButt.isPressed())     // Si BP est pressé = homing
	{
		Serial.println("PRESSE");
	}
	else
	{
		Serial.println("NON presse");
	}
}

void traceDriversEnable()
{
	Serial.println(F("\nEtat d'ENABLE des drivers : "));

	for (int e = 0; e < driverEnablePinNombre; e ++)
	{
		Serial.print("Driver sur pin " + String(driverEnablePin[e]));
		Serial.println((digitalRead(driverEnablePin[e]) == driverENApinEtatOn) ? " ENABLE" : " Disable");
	}
}

String systemGetVersion()
{
	return String(mpapNombre) + "M" + String(servoNombre) + "S" + String(mpapGroupeNombre) + "GR-HIPS"; // Homing Individuel et Partiel., Servo alim commandée
} 

void settingJpbbricole()
{
	commandeExecute("EMREC5,170,178,180,47,170,");
	commandeExecute("EMREC1,42,46,45,19,46,");
	commandeExecute("EMREC2,66,62,90,35,66,");
	commandeExecute("EMREC3,94,98,110,67,94,");
	commandeExecute("EMREC4,138,146,150,19,146,");
	commandeExecute("EMREC5,170,178,180,47,170,");

	commandeExecute("%REC1,EM4|em5|EM2|EM1|EM3|EM1|");
	commandeExecute("%REC2|EM4|DEL2000|EM2|EM3|DEL1000|EM1|");
}
/*	
	// bernie31
	EMREC0,10,10,10,10,10,10,10,10,10,10,10,0,
	EMREC1,170,330,0,89,233,361,0,258,970,1355,554,0,
	EMREC2,106,330,0,0,137,361,0,326,1418,1706,778,0,
	EMREC3,0,0,0,0,0,0,0,0,0,0,0,0,
	EMREC4,0,0,0,0,0,0,0,0,0,0,0,0,
	EMREC5,0,0,0,0,0,0,0,0,0,0,0,0,

// jpbbricole
	EMREC0,10,10,10,10,10,
	EMREC1,42,46,45,19,46,
	EMREC2,66,62,90,35,66,
	EMREC3,94,98,110,67,94,
	EMREC4,138,146,150,19,146,
	EMREC5,170,178,180,47,170,

	Macro:	%EM2|DEL2000|EM1|EM3|DEL1000|EM1|
%EM2|EM1|EM3|EM1|EM4|em5|

%REC8,EM4|em5|EM2|EM1|EM3|EM1|
*/

Une toute belle journée à vous deux!
jpbbricole

Bonjour Jpbbricole,

Merci pour tes informations et le nouveau programme( post #649 ) . Je vais essayer tout ça tranquillement d'ici que mon assistant ne parte pour la grande aventure :heart_decoration: lundi matin .
Comme tu me le suggérais j'ai fait des manipes sur le mot02 et croiser celui-ci au niveau des DIR et STEP du programme actuel( post #622 ) avec le moteur 00 qui a l'avantage d'avoir le même sens pour cliquer son MS ( en tournant CCW ).
Les résultats sont un peu surprenants ...
En mode trace :

j'ai d'abord fait un essai std (sans croiser les DIR et STEP )

Mot / Dir / Step / homing / résultat
00 / 22 / 23 / 0 / le bras du moteur 00 clique sur le MS puis recule un peu et les axes de tous les moteurs (!) sont débrayés ???

Mot / Dir / Step / homing / résultat
02 / 26 / 27 / 2/ le bras du moteur 02 clique sur le MS , entre en vibration puis s'arrête et les axes de tous les moteurs (!) sont débrayés

Puis j'ai fait la même chose en croisant

Mot / Dir / Step / homing / résultat
00/ 26/ 27 /0/ le moteur 02 a cherché le homing et est entré en vibration continuellement . J'ai du arrêter en enlevant le cable USB .Donc pas d'info si les axes de tous les moteurs (!) étaient débrayés ou pas

Mot / Dir / Step / homing / résultat
02/ 22/23 / 2 / le moteur 00 a cherché le homing et est entré en vibration continuellement . J'ai du arrêter en enlevant le cable USB .Donc pas d'info si les axes de tous les moteurs (!) étaient débrayés ou pas

Pour avoir le coeur net j'ai voulu voir sur un autre moteur ce qui se passait en faisant le HomingM
Sur le moteur 11 , le bras du moteur atteint le MS , clique , repart un peu en arrière et ....situation idem . axes de tous les moteurs débrayés ...

Voilà où j'en étais quand j'ai reçu tout dernier message avec le nouveau programme # post 649

Bonne fin d'après midi et encore merci pour tout le travail .

Bernie31

Bonsoir Jpbbricole.

Merci beaucoup pour cette dernière version . J'ai installé le .ino et le src comme dab mais j'ai eu ce message d'erreur lors de la compil .


Est ce que cela t'inspirerait quelquechose ( à part de la fatigue peut être :slight_smile: )?
Merci
Bernie31

NB : mon assistant est en pleine forme ,mais bombardé de piqures de mosquitos . Le coupable a été retrouvé et trucidé ... mais trop tard :slight_smile:

Bonsoir bernie31

J'opterai pour un accident de copier/coller :wink:
Refait l'opération.
Je l'ai faite, chez moi, sans problème.

Une toute belle soirée à vous!
jpbbricole

1 Like

Bonsoir Jpbbricole,

Bien vu l' Ami :+1: :+1:
Un paste mal fait :frowning:
Demain je teste tout ça.
Bonne soirée à toi
Bernie31

Bonsoir Jpbbricole,

Les essais avec le nouveau programme #649 sont très encourageants .
Les moteurs 9 et 12 font maintenant leur homing . ( true à la place de false sur le parametre emot invert . Point résolu :slight_smile:
Autre point : Le moteur 2 qui oscillait en touchant le contact MS arrive maintenant à ne pas osciller si le nbre de pas avant de toucher le contact est important ... Pourquoi je n'en sais rien . ( Vitesse du moteur trop rapide , accélération trop ... , autre paramètre ? )
J'ai aussi constaté cela sur un autre moteur comme le 5 qui n'avait jamais oscillé en touchant le contact auparavant ...J'avais du avoir de la chance ?
Autre chose . Quand j'ai fait le homing de chaque moteur un à un , j'ai remarqué que certains d'entre eux s'arrêtent sur le contact ( clic ) et ne bougent plus , alors que d'autres ''cliquent'' et repartent plus ou moins loin dans l'autre sens ...
Quand on démarre le test / les essais juste après le lancement du programme le premier homing réalisé sur un moteur met les autres moteurs dans un état tel que leurs axes sont mobiles ( pas maintenus ) .
Cela n'empêche pas de faire en suite les autres homings ...
Compte tenu de ces observations en positionnant bien les moteurs ( nbre de pas important avant contact MS ) j'essaierai de faire un homing général et voir si tout se passera bien.
Finalement Les 2 derniers points qui resteraient ensuite sont solutionnés :
la diminution du temps de maintien des moteurs qui n'ont pas de pb de poids à maintenir ( pinx )
la suppression des à coups du servo : installer le mosfet .
On y est presque ...
Bonne soirée à toi
Bernie31

Bonsoir bernie31

Beaucoup de positif :wink:

En mode trace?

Le test, c'est aussi en mode trace?

Il faut savoir que dans ce système le signal ENABLE est commun (quoi que sur 3 pin) à tout les moteurs donc les ENABLE fonctionnent ensemble.

Il faut quand se dire que tu as construit une "montagne" :wink:

Une toute belle soirée a vous tous!
jpbbricole

Bonjour Jpbbricole,

Petite réponse à tes questions avant de partir ...pour le CHU .
Tous les tests ont été réalisés en mode Trace.
Bonne journée à toi
Bernie31

Bonjour Jpbbricole,

ça y est je suis de retour . Mon assistant est en forme :slight_smile:
J'ai fait quelques essais supplémentaires

Pour revérifier j'ai fait à nouveau un test de homing spécifique sur le mot2 en positionnant le bras du moteur à une distance ( un nombre de pas ) importante du contact du MS .
Le moteur n'a pas oscillé après contact MS .
J'ai fait un homing général en préparant le bras du moteur 2 comme expliqué ci-dessus . Là le homing a débuté sans souci sur le MOT 00 01 03 et au 02 : Bingo oscillations...
J'ai alors décidé de mettre A2 à GND et là le Homing général s'est bien passé .Ouf

Donc je vais maintenant passer en mode normal et faire un homing général avec A2 = GND .

Je devrais recevoir le MOsfet d'ici peu et ferai le montage que tu m'a indiqué dans la foulée ... en espérant ne pas rencontrer d'autres problèmes que celui du mot2 .

Pour le dernier point temps de maintien des moteurs 2, 3, 6, 7, et 10 je pense préparer le cablage .
Peux tu me dire, stp, si ce qui suit est OK?
enable 8 moteurs : 00 01 11 et 12
enable 9: 04 05 08 09
enable 10: moteurs 02, 03, 06, 07, 10

Je suppose que la dernière version du programme prendrait en compte le mosfet et l'enable 10

Pour le mot2 les investigations se poursuivront après .

Bonne fin de journée à toi
Bernie31

Bonjour bernie31

Super :+1: :+1: :+1:

Oui, pour le mosfet pin 5
Ok aussi pour enable 10.

J'ai fait des essais en sumulant ta configuration, pour tes essais qui ne tournent pas bien, essaies de baisser la vitesse qui est de 750. Travailles tu en micro pas?

Bonne soirée
jpbbricole

Bonsoir Jpbbricole,

A propos de ta question

Sur la carte d'extension driver il y a 3 microswitches MS1 MS2 MS3 .
Tous les 3 sont sur la même position OFF
Sur la specification du Bigtreetech TMS2209 v1.3 il y a uniquement MS1 MS2 et la configuration est donnée sur le tableau suivant


A priori ce serait ou bien GND / GND ou VCC/VCC .

J'essaierai de diminuer la vitesse demain . C'est effectivement une piste intéressante .
Bonne soirée à toi
Bernie31

Bonsoir Jpbbricole,

J'ai avancé quelque peu sur le homing et c'est globalement très encourageant .
Je suis passé en mode normal et le homing global est passé sans souci ( avec le A2 du mot2 à GND ) sur tous les autres moteurs .
J'ai ensuite créé 2 émotions EM1 et EM2 , puis enregistré une macro %EM1 composée de EM1 et EM2 .( sans le servo donc ) . Je l'ai jouée et tout cela a bien fonctionné .
Nb; ces émotions n'incluaient pas le servo que j' avais débranché suite au souci d'à coups intempestifs évoqués précedemment.
J'ai voulu alors m'intéresser au servo et lui apprendre une ou plusieurs positions pour l'intégrer dans une ou plusieurs émotion(s) .
Je n'ai pas réellement su le faire d'autant que le servo avant même de commencer quoique ce soit s'était mis en position 0 alors que je souhaitais qu'il aille en position 90 ° ( a priori l' offset mis dans le tableau le concernant dans le programme devait être pris en compte ) ; J'étais surpris car en mode trace il restait bien à 90°
Je me suis alors posé la question de la séquence à réaliser pour mémoriser une position angulaire en sachant qu'un motDIS1 ne peut pas agir sur un servo et donc comment amener le servo à une position angulaire choisie .
J'ai pensé à utiliser Servonnaaa mais sans succès ... d'autant que fausse manipe ou pas je me suis retrouvé en mode trace?
Bref je suis un peu dans l'expectative .
Pourrais tu ,stp, me donner quelques indications sur un enchainement à suivre pour enregister une émotion Mn pour les moteurs MPAP et le servo , par exemple : MOTDIS1 , bouger les moteurs MPAP , commande xxxaaa pour bouger le servo à aaa, MOTDIS0 , learnMn?
Autre point : comme évoqué ci dessus mon servo ne se positionne pas en mode normal comme je le souhaiterais ; il va sur une position 0° ( et non pas 90° )
Est ce à cause du problème de montée des alimentations 5V ( ça peut arriver quand je branche l'alim 5V par précaution, avant de démarrer mon PC ) mais aussi une 2eme fois au moment où le programme .ino se charge et semble arriver à une indexation ( 1/35 2 /35 ... 35 / 35 ) ?
Dans les 2 cas le mouvement est rapide et de grande amplitude et le bras du servo se cale sur 0°.
Pour le moteur 2 je n'ai pas encore essayé de voir si une vitesse plus faible pourrait résoudre le pb d'oscillation.
J'espère pouvoir avancer sur le mosfet / servo la semaine prochaine quand la pièce arrivera .
Et si tout va bien finir avec l'enable 10 .
Bonne soirée à toi .
Bernie31

Bonjour bernie31

Pour enregistrer la position de servo dans une émotion, la commande est:
LEARNSeeiiaaa Apprend l'angle du servo de l'émotion ee, de l'interface ii et de l'angle aaa
ee = numéro de l'émotion
ii = index de l'interface du servo, de 0 à, dans la liste:

intfDef intf[] =
{
#ifdef bernie31
	{intfTypeMoteur, 0},
	{intfTypeMoteur, 1},
	{intfTypeMoteur, 2},
	{intfTypeMoteur, 3},
	{intfTypeMoteur, 4},
	{intfTypeMoteur, 5},
	{intfTypeMoteur, 6},
	{intfTypeMoteur, 7},
	{intfTypeMoteur, 8},
	{intfTypeMoteur, 9},
	{intfTypeMoteur, 10},
	{intfTypeMoteur, 11},
	{intfTypeMoteur, 12},
	{intfTypeServo, 0},
#else

Ainsi, pour mettre le servo dans l'émotion 3 à 127° la syntaxe est:
LEARNS0313127

Une toute belle journée à vous!
jpbbricole