arduino multitâches?

Bonjour,
je motorise sur deux axes une monture de télescope
le but est d'obtenir un suivi constant de l'objet visé
pour cela il faut motoriser deux axes (néma 17 1:100)
le tout est dirigé en bluetooth par un PC (c'est lui qui fait tous les calculs et transmet les ordres)
dans le loop:

void loop()
      {
      int received;                   
  if (Serial.available()) {         
    received = Serial.read();
switch (received) {
// .............
   case 'k':
        suivi_az=1;
        suivi_azimut(aznbpas); //aznbpas a été envoyé auparavant, +-10000 pas mn
        suivi_altitude(alnbpas); // idem +-1500 pas mn
    break;
//***********************

les fonctions:

void suivi_azimut(int nb_pas_az) //suivi pour 1 mn pas de changement de sens la Terre tournant obstinément dans le même sens  :o )
{
int intervalle_az= abs(60000000/nb_pas_az);
int direction;
digitalWrite(azimEnablePin, LOW); //moteur azimut en prise
direction = HIGH;
digitalWrite(azimDirectionPin, direction);
for (unsigned long i = 0; i < nb_pas_az; i++){
digitalWrite(azimStepPin, HIGH);
delayMicroseconds(intervalle_az);
digitalWrite(azimStepPin, LOW);
}
}
//******************************************************
void suivi_altitude(int nb_pas_al) //suivi pour 1 mn
{
int direction_al;
if (aldir > 0){
direction_al = HIGH;
}else{
direction_al = LOW;
}
  digitalWrite(altEnablePin, LOW); //moteur altitude en prise
int intervalle_al= abs(60000000/nb_pas_al);
digitalWrite(altDirectionPin, direction_al); 
for (unsigned long i = 0; i < nb_pas_al; i++){
digitalWrite(altStepPin, HIGH);
delayMicroseconds(intervalle_al);
digitalWrite(altStepPin, LOW);
}
}
//******************

mon soucis c'est que seule une fonction est exécutée à la fois (je peux les intervertir, elles sont toutes deux fonctionnelles)
il est nécessaire qu'elles s'exécutent en même temps
comme les boucles n'ont jamais la même longueur je ne peux tout faire dans la même
je...nage :frowning:

Je suppose que intervalle_az est de l'ordre de quelques millisecondes?

si c'est le cas votre arduino va suivre sans problème et il faut éclater les 2 boucles for de pilotage en faisant deux éléments de gestion du temps à la blink without delay, un pour chaque moteur (+ un test sur un booléen pour dire si le moteur est activé)

Merci pour cette réponse rapide :slight_smile:
le délai peut-être inférieur à la millisecondes, l'utilisation des microsecondes est plus judicieux
je vais essayer quand même la solution blink without delay, millis() sera peut-être suffisant

faites blink without delay avec des microsondes, pas de soucis si c'est quelques centaines de microsondes

sinon dans votre code il vaut mieux rajouter ul dans cette expression si vous ne voulez pas de soucis

int intervalle_az= abs(60000000[color=red][b]ul[/b][/color]/nb_pas_az);

bonjour,
ca sent la nuit des étoiles on dirait :slight_smile:

tenez jetez un oeil à ce code, qui démontre l'idée en pilotant deux actions qui ne sont pas sur la même périodicité ni sur le même nombre d'évènements

const unsigned int tick1Count = 10;
const unsigned period1 = 250;
unsigned int t1; // initialized at 0
unsigned long tick1[tick1Count];
unsigned long lastTick1;// initialized at 0

const unsigned int tick2Count = 20;
const unsigned period2 = 150;
unsigned int t2; // initialized at 0
unsigned long tick2[tick2Count];
unsigned long lastTick2; // initialized at 0

void setup() {
  Serial.begin(115200);
}

void loop() {
  static boolean affichage = true;

  // ------------- LA PARTIE INTERESSANTE -------------------
  unsigned long t = micros();

  if ((t1 < tick1Count) && (t - lastTick1 >= period1)) {
    // faire l'action 1 ici
    tick1[t1++] = micros();
    lastTick1 += period1;
  }

  if ((t2 < tick2Count) && (t - lastTick2 >= period2)) {
    // faire l'action 2 ici
    tick2[t2++] = micros();
    lastTick2 += period2;
  }
  // -------------------------------------------------------

  if (affichage && t1 == tick1Count && t2 == tick2Count) { // dump results
    Serial.print(F("\nINSTANT DES TICK 1 (demande = "));
    Serial.print(period1);
    Serial.println(F(" micro secondes)"));
    for (int i = 0; i < tick1Count; i++) {
      Serial.print(i); Serial.print(F("\t")); Serial.print(tick1[i]);
      if (i >= 1) {
        Serial.print(F("\tDelta = "));
        Serial.println(tick1[i] - tick1[i - 1]);
      } else Serial.println();
    }

    Serial.print(F("\nINSTANT DES TICK 2 (demande = "));
    Serial.print(period2);
    Serial.println(F(" micro secondes)"));
    for (int i = 0; i < tick2Count; i++) {
      Serial.print(i); Serial.print(F("\t")); Serial.print(tick2[i]);
      if (i >= 1) {
        Serial.print(F("\tDelta= ")); Serial.println(tick2[i] - tick2[i - 1]);
      } else Serial.println();
    }
    affichage = false;
  }
}

Ce code enregistre dans un tableau le moment où l'action est vraiment déclenchée:

  • je demande aux ticks1 de se déclencher toutes les 250 micro-secondes, pendant 10 fois
  • je demande aux ticks2 de se déclencher toutes les 150 micro-secondes, pendant 20 fois
    une fois que tous les ticks sont finis, j'affiche les date de déclenchement ainsi que le delta avec le moment précédent

et on obtient cela:
INSTANT DES TICK 1 (demande = 250 micro secondes)
0 264
1 508 Delta = 244
2 764 Delta = 256
3 1008 Delta = 244
4 1256 Delta = 248
5 1504 Delta = 248
6 1756 Delta = 252
7 2008 Delta = 252
8 2264 Delta = 256
9 2508 Delta = 244
INSTANT DES TICK 2 (demande = 150 micro secondes)
0 160
1 308 Delta= 148
2 464 Delta= 156
3 612 Delta= 148
4 768 Delta= 156
5 904 Delta= 136
6 1060 Delta= 156
7 1212 Delta= 152
8 1360 Delta= 148
9 1512 Delta= 152
10 1664 Delta= 152
11 1812 Delta= 148
12 1964 Delta= 152
13 2112 Delta= 148
14 2268 Delta= 156
15 2404 Delta= 136
16 2560 Delta= 156
17 2708 Delta= 148
18 2860 Delta= 152
19 3008 Delta= 148

on voit bien sûr qu'il y a une approximation de l'ordre de 4 à 16 micro-secondes (en sachant que micros() ne donne un résultat qu'à 4 microsec près)

Si les actions que vous faites ne prennent pas trop longtemps (utiliser les PORT pour faire de digitalWrite() par exemple pour optimiser un peu) ça ne devrait poser aucun pb et au lieu de ma loop() écrivez une fonction suivi_azimut_et_altitude(aznbpas, alnbpas) qui va boucler tant que le nombre de pas total des 2 moteurs n'a pas été effectué par exemple (comme je fais en attendant mon affichage)

infobarquee:
bonjour,
ca sent la nuit des étoiles on dirait :slight_smile:

entre-autre :slight_smile:

J-M-L:
tenez jetez un oeil à ce code, qui démontre l'idée en pilotant deux actions qui ne sont pas sur la même périodicité ni sur le même nombre d'évènements

Oups!!! je suis largué là :frowning: il faudrait que je relise ça à tête reposée :o
pour l'heure, j'ai essayé la solution précédente (supprimer les delay)
dans le loop

     case 'K':
         suivi_altitude2(5500);
         suivi_azimut(7800);

    break;

les fonctions

void suivi_azimut2(int nb_pas_az) //suivi pour 1 mn
{
int intervalle_az= abs(60000000ul/nb_pas_az);
int direction;
digitalWrite(azimEnablePin, LOW); //moteur azimut en prise
direction = HIGH;
digitalWrite(azimDirectionPin, direction);
unsigned long previous = micros();
unsigned long current = micros();
previous = current-intervalle_az;
for (unsigned long i = 0; i < nb_pas_az; i++){
digitalWrite(azimStepPin, HIGH);
current = micros();
while((current-previous)<intervalle_az){
current = micros();  }
//delayMicroseconds(intervalle_az);
digitalWrite(azimStepPin, LOW);
}
}
void suivi_altitude2(int nb_pas_al) //suivi pour 1 mn
{
int direction_al;
if (aldir > 0){
direction_al = HIGH;
}else{
direction_al = LOW;
}
  digitalWrite(altEnablePin, LOW); //moteur altitude en prise
int intervalle_al= abs(60000000ul/nb_pas_al);
Serial.print(intervalle_al);
digitalWrite(altDirectionPin, direction_al); 
unsigned long previousMillis = micros();
unsigned long currentMillis = micros();
previousMillis = currentMillis-intervalle_al;
for (unsigned long i = 0; i < nb_pas_al; i++){
digitalWrite(altStepPin, HIGH);
currentMillis = micros();
//Serial.print(currentMillis);
while((currentMillis-previousMillis)<intervalle_al){
currentMillis = micros();  }
//delayMicroseconds(intervalle_al);
digitalWrite(altStepPin, LOW);
previousMillis=currentMillis;
}
}

malheureusement, les boucles "while" ont le même résultat que les delay, l'exécution des deux se fait à la queue leu leu
j'avais pensé pour la suite saucissonner les deux fonctions en 600 threed (soit 1/10 de s) en divisant les intervalles par 2
je vais relire tout ça :slight_smile:

J-M-L:
tenez jetez un oeil à ce code, qui démontre l'idée en pilotant deux actions qui ne sont pas sur la même périodicité ni sur le même nombre d'évènements

je crois que j'ai compris! j'essaie ce soir :slight_smile:

J-M-L:
tenez jetez un oeil à ce code...........

Voilà :slight_smile: c'est fait et....ça marche 8)

 //***********
    case 'K':
        suivi_(5025,1550);
    break;
//****************
void suivi_(int nb_pas_az, int nb_pas_al) //suivi pour 1 mn
{
int direction_al=0;
if (aldir > 0){
direction_al = HIGH;
}else{
direction_al = LOW;
}
digitalWrite(altEnablePin, LOW); //moteur altitude en prise
digitalWrite(azimEnablePin, LOW); //moteur azimut en prise
digitalWrite(azimDirectionPin, 1);
digitalWrite(altDirectionPin, direction_al); 

long intervalle_az= abs(60000000ul/nb_pas_az); 
long intervalle_al= abs(60000000ul/nb_pas_al); 
unsigned int az=0; // pas azimuth en cours 
unsigned long lastTick1=0;// initialized at 0
unsigned long lastTick2=0; // initialized at 0
unsigned int al=0; // initialized at 0
unsigned long t=0;
unsigned long debut=0;
unsigned long present=0;
//******************
debut= micros() ;
while(present<(debut+ 60000000))
{
t = micros(); //prise du temps
if ((az < nb_pas_az) && (t - lastTick1 >= intervalle_az)) {
digitalWrite(azimStepPin, HIGH);
digitalWrite(azimStepPin, LOW);
az++;
lastTick1 += intervalle_az;
}
if ((al < nb_pas_al) && (t - lastTick2 >= intervalle_al)) {
digitalWrite(altStepPin, HIGH);
digitalWrite(altStepPin, LOW);
al++;
lastTick2 += intervalle_al;
}
present = micros(); //prise du temps
}
Serial.print ("debut: "); Serial.println (debut);
Serial.print ("fin: "); Serial.println (present);
Serial.print ("pas azimuth: "); Serial.println (az);
Serial.print ("pas altitude: "); Serial.println (al);
Serial.print ("duree reelle: "); Serial.println (present-debut);
}

les nombres de pas demandés sont exécutés en temps voulu

Merci encore :slight_smile:

bravo!!!

(faites aussi ctrl-T (PC) or cmd-T (Mac) dans l'IDE pour que le code soit indenté correctement)

Attention à cette partie: while(present<(debut+ 60000000))

Quand vous jouez avec millis() ou micros() et des unsigned long, pour pouvoir gérer correctement le problème du débordement quand votre valeur dépasse 232 - 1 (soit 4294967295) il faut travailler en soustraction, pas en addition

Imaginez que debut soit 232 - 1 - 59999999 par exemple

Quand vous allez faire debut+ 60000000 vous avez donc

232 - 1 - 59999999 + 60000000 = 232-1+1 = 0 car en unsigned long on retourne à 0 après le plus grand nombre représentable.

votre while va donc devenir while(present<0) ce qui ne sera jamais le cas et vous n'exécuterez pas le mouvement

et cette erreur va vous ennuyez assez rapidement car 232 - 1 - 59999999 c'est 4234967296 micro secondes --> soit environ 4235 secondes, soit 1 heure 10 minutes et 35 secondes après le lancement de votre arduino

Bref il faut toujours écrire ce genre de test pour le temps comme vous l'avez fait dans la boucle avec (t - lastTick1 >= intervalle_az)

while (present-debut < 60000000ul)

Améliorations possibles:


Encore mieux bien sûr ce serait en début de code de mettre const unsigned long soixanteSecondes = 60000000ul;comme ça ensuite vous écrivez while (present-debut < soixanteSecondes)qui est plus lisible.

et profitez en alors pour modifier la durée entre deux pas que vous calculez (et adaptez aussi le format en unsigned long)

[color=red]unsigned[/color] long intervalle_az = abs(soixanteSecondes / nb_pas_az); 
[color=red]unsigned[/color] long intervalle_al = abs(soixanteSecondes / nb_pas_al);

la partie

int direction_al=0;
if (aldir > 0){
direction_al = HIGH;
}else{
direction_al = LOW;
}

pourrait être écrite avec l'opérateur ternaire comme cela tout en maintenant la rigueur sémantique (et en gagnant un octet au passage pour direction_al)byte direction_al = (aldir > 0) ? HIGH : LOW; et si vous voulez faire encore plus efficace (comme true = 1 = HIGH et 0 = LOW = false) vous pourriez carrément écrire (mais ce n'est pas bien sémantiquement)byte direction_al = aldir > 0;

Moi qui était tout content de mon code :grinning:
je reverrai ça mais les séquences durant une minute cela ne devrait pas déborder
je suis beaucoup plus à l'aise avec Lazarus qu'avec le C
alors c'est vrai que mon code marche mais...il marche trop bien...il faudrait que je puisse l’arrêter
durant ces séquences de suivi de 60 secondes, je devrais pouvoir utiliser un Pad (manette avec 4 boutons pour corrections fines de la position)
cela fonctionne sauf quand la boucle du suivi s'exécute
avec un Pad filaire je pourrais me servir des interruptions de l'arduino mais là c'est (par force) bluetooth
je cherche

Et bien au lieu de faire un while dans la fonction, déportez cela dans la boucle

La boucle pourrait ressembler à cela

void loop()
{
   Écouter le port série et si commande reçue  calculer l'action
   Vérifiez les boutons et calculer l'action si bouton appuyé
   Vérifier si c'est le moment de faire un pas de moteur et si oui Le faire
}

J-M-L:
Et bien au lieu de faire un while dans la fonction, déportez cela dans la boucle

La boucle pourrait ressembler à cela

void loop()

{
  Écouter le port série et si commande reçue  calculer l'action
  Vérifiez les boutons et calculer l'action si bouton appuyé
  Vérifier si c'est le moment de faire un pas de moteur et si oui Le faire
}

Ah oui, je vais essayer
merci :slight_smile:

gerard33:
Moi qui était tout content de mon code :grinning:

mais y'avait de quoi être fier d'un code fonctionnel ! c'est juste de la critique constructive pour aller plus loin

gerard33:
je reverrai ça mais les séquences durant une minute cela ne devrait pas déborder

oui si vous éteignez votre Arduino entre 2 séquences. s'il reste allumé plus d'une heure et dix minutes alors vous aller rencontrer le bug

J-M-L:
oui si vous éteignez votre Arduino entre 2 séquences. s'il reste allumé plus d'une heure et dix minutes alors vous aller rencontrer le bug

OK, je pensais que "micros()" donnait le temps depuis le début de la fonction et non depuis le boot de l'arduino

Je vais mettre un peu les pieds dans le plat mais l'approche n'est vraiment pas optimale. Il serait beaucoup plus judicieux de passer par des timers pour piloter chaque moteur par interruption. C'est beaucoup plus précis et là on fait du "presque parallèle" ce qui est critique en Alt/Az.

De même, quiz du temps de réponse de la liaison série avec le PC sensée renvoyer en continue la position Alt/Az? Déjà qu'en équatorial, il y a pas mal de choses à penser mais là les moteurs doivent évoluer de manière précise à vitesse variable pour suivre l'arc de cercle formé par la cible. Je suis vraiment pas convaincu par le setup. Cela va être très très approximatif comme suivi en l'état.

Pour faire avancer le shcmilblik, voici quelques librairies à creuser qui pourront peut-être aider:
http://em10-usd-arduino-takahashi.eliotis.com/librairies-arduino/index.html

Perso, je ferais faire le job de calcul de position au Arduino et non au PC. Voir la librairie Ephemeris qui permet aussi de faire des conversions équatoriales/horizontales et vice et versa.

Pour le pad, je viens de publier une librairie pour piloter un arduino (dans mon cas pour ma monture équatoriale) avec une manette de jeu si cela peut aider... :wink:

Bref, c'est loin d'être trivial comme projet. Je recommanderais de commencer par un bon bouquin sur la programmation Arduino afin de maîtriser un peu les subtilités avant de partir billet en tête. De mémoire, cet ouvrage d'Erik Bartmann était pas mal du tout...
http://livre.fnac.com/a7923412/Erik-Bartmann-Le-grand-livre-d-Arduino?oref=00000000-0000-0000-0000-000000000000&Origin=SEA_GOOGLE_PLA_BOOKS&mckv=_dc&pcrid=64457384783&ectrans=1&gclid=CPCYhLHTutQCFUqdGwodcf8C4Q

marscaper:
Il serait beaucoup plus judicieux de passer par des timers pour piloter chaque moteur par interruption.

Salut :slight_smile:
j'avais déjà repéré ton travail dans ton post sur Webastro
effectivement, c'est le chemin que j'ai choisi hier (les timers)
vouloir faire gérer tous ces calculs par l'arduino est illusoire (d'autant plus qu'il y a le PC à disposition) mais il y a aussi les limites du bluetooth
mes essais hier n'ont pas donné de résultats fiables: 15000 corrections/mn en azimut et 1500..2000 en altitude ça fait des communications toutes les 2..3 milli/s....ça bourre :stuck_out_tongue_closed_eyes:
du coup, ma tentative actuelle est de faire des séquence plus courtes (1 s au lieu de 60), j'ai écrit le code, j'essaierai aujourd'hui

Pourquoi 15000 correction/min?!? Cela me semble totalement démesuré.

En partant de la vitesse sidérale à 15"/s, en supposant qu'on veuille une résolution d'une seconde d'arc on aura au max besoin de 15 échantillons par secondes à vue d’œil soit 900 corrections/min.

Cela me semble tout à fait jouable en calcul direct par un Arduino.

Bah...avec les nouveaux CMOS, je serai peut-être tenté de troquer les oculaires par une caméra pour faire du ciel profond en poses rapides :wink: auquel cas il me faudra un suivi parfait
et pourquoi pas un dérotateur de champ sur le PO pour de la pose longue...hé hé j'y gamberge aussi :sunglasses: