Partage
  • Partager sur Facebook
  • Partager sur Twitter

Arduino | Algorithme

    11 avril 2019 à 14:17:01

    Bonjour, j'aurais besoin de votre aide pour élaborer un petit algo. J'y réfléchis depuis une bonne journée et la seule idée que j'ai fait vraiment machine à gaz, j'espère que quelqu'un m'aidera a procéder plus simplement.

    Un peu de contexte, je dois faire de la communication série entre ma carte Arduino et une interface graphique réalisée avec Qt pour commander 3 moteurs pas à pas. 

    L'utilisateur peut entrer trois consignes de déplacement en mm pour les vis sans fin des moteurs dans des cases de l'interface et cliquer sur un bouton pour envoyer la consigne dans le port série de mon Arduino. La chaine de charactère envoyée est de la forme x,xx:y,yy:z,zz où x, y et z sont les chiffres entrés par l'utilisateur dans l'interface.

    Là où je bloque c'est pour reconstruire les valeurs saisies dans l'interface sous la forme d'un double sachant que je vais lire le message charactère par charactère dans l'Arduino.

    Première difficulté : La valeur saisie par l'utilisateur sera comprise entre 0,00 et 295,00. Cela veut dire que le début de la chaine de charactère correspondant à la valeur du déplacement souhaité sur l'axe x peut contenir 3, 4 ou 5 charactères. Il y aura toujours deux chiffres après la virgule et au moins un zéro pour le chiffre des unités mais il serait compliqué de forcer l'ajout d'un zéro dans la chaine de charactère pour les dizaines et les centaines.

    Pour éviter de faire un tableau dynamique (qui me semble assez compliqué à mettre en place en Arduino) dont j'augmenterais la taille en fonction du nombre de charactère reçus avant d'avoir le ':' qui sépare deux valeurs j'ai eu l'idée de faire un tableau de tableau avec 3 lignes et  5 colonnes initialisées à 0. Une ligne pour chacune des valeurs rentrées par l'utilisateur et une colonne pour chacun des charactères des valeurs.

    Mais ce que je n'arrive pas à faire c'est remplir ce tableau intelligemment. Si la prmeière valeur envoyée est par exemple 13,20, j'aimerais que la première ligne de mon tableau soit  [0 1 3 2 0] et si la valeur est 5,65 la ligne serait [0 0 5 6 5].

    Ca me permettrait de parcourir cette ligne en partant de la fin en faisant valeurX = maLigne[4]*10^-2 + maLigne[3]*10^-1 etc … et de faire la même chose pour chaque ligne et ainsi reconstruire mes 3 valeurs.

    Mais voilà, en lisant le premier charactère du message envoyé par Qt, je n'ai pas moyen de savoir si ce chiffre correspond à celui d'une centaine, d'une dizaine ou d'une unité et donc je ne vois pas comment savoir où le placer correctement dans ma ligne pour appliquer mon idée. 

    Si quelqu'un aurait une piste pour remplir mon tableau comme je l'ai décrit ou même si vous avez une idée totalement différente de celle-ci ça m'aiderait beaucoup ! :)

    Merci d'avance a ceux qui auront pris le temps de tout lire et qui essayeront de me débloquer :)

    • Partager sur Facebook
    • Partager sur Twitter
      12 avril 2019 à 14:25:47

      Je up le sujet car j'ai un algo qui marche depuis ce midi et qui fait le taff mais je le trouve très bourrin et un peu tordu, je suis sûr qu'il y a la possibilité de faire plus simple et plus efficace (en particulier en trouvant un moyen de virer la fonction pow() ).

      Maintenant que j'ai du code à vous présenter et pas seulement une pile de brouillon infructueux ça inspirera peut-être quelques personnes. Voici le code en question. 

      //p1,56:125,20:71,03:    Message type que j'utilise pour tester le code 
      
      String message; // Stockera tout le message reçu
      
      String valeurTab[3] = {}; // On stock le message coupé en 3 string avec les ':' comme séparation
      double valeurDouble[3] = {}; 
      
      int j = 0;
      
      void setup() {
        Serial.begin(9600);
        message = "";
      }
      
      void loop() {
        if(Serial.available()>0) {
          while (Serial.available() > 0) {
            char c = Serial.read();
            if (c=='p'){
              message = "";
              delay(20);
              while (Serial.available()>0){
                c = Serial.read();
                if ((c == ':') || ((c >= '0') && (c <= '9'))){
                  message += c;  // On a enlevé toutes les virgules 
                }
              }
              Serial.println(message); // Affiche 156:12520:7103
              
              j = 0;
              for (int i=0; i < 3 ;++i){
                while (message[j] != ':'){
                  valeurTab[i] += message[j];  // On découpe le message en 3 string en retirant les ':'
                  j++;
                }
                if(message[j] == ':'){
                  j++;            
                }
              }
              Serial.println(valeurTab[0]);  // Affiche 156
              Serial.println(valeurTab[1]);  // Affiche 12520
              Serial.println(valeurTab[2]);  // Affiche 7103 
              
      
              for(int k=0; k < 3; ++k){  // Pour chacun des strings de valeurTab ....
                for(int i=0; i < valeurTab[k].length(); ++i){   // Pour chaque charactère de valeurTab[k] ....
                  valeurDouble[k] += (valeurTab[k][valeurTab[k].length()-1-i]-'0')*pow(10,i-2);  // Conversion du char en int et multiplication par la bonne puissance de 10
                  Serial.println(valeurDouble[k]);
                }          
              }
              message = "";
              for(int k = 0; k < 3; ++k){     // Je réinitialise tout pour tester a la suite sans relancer
                valeurTab[k] = "";
                valeurDouble[k] = 0;
              }      
            }
          }    
        }  
      }

      Des idées pour un algo alternatif qui pourrait être plus simple ? :) 

      • Partager sur Facebook
      • Partager sur Twitter
        12 avril 2019 à 22:42:42

        Bonjour,

        Comme ce sont des nombres à virgule fixe, tu peux les lire dans des variables de type entier (le type int sur les microcontrôleurs AVR fait 16 bits signés, -32768 à 32767, ce qui est tout juste suffisant dans ton cas). L'AVR ne supporte pas nativement les opérations à virgule flottante, si tu peux rester avec des entiers de bout en bout de ton traitement, ce serait préférable.

        La lecture d'entiers ou de nombres à virgule fixe à la volée, sans buffer, est assez simple: tu initialises une variable à 0 et à chaque chiffre lu, tu multiplies la variable par 10 et tu ajoutes le chiffre. Par exemple, pour 5,65, la variable prendrait successivement les valeurs \(\{0, 5, 56, 565\}\).  Dès que tu as un caractère qui n'est pas un chiffre (sauf la virgule ou le point décimal que tu ignores), tu passes au nombre suivant.

        Tu devrais aussi utiliser un indicateur de fin de ligne (retour à la ligne ou autre), au lieu d'utiliser delay(20) pour t'assurer que tu as lu les 3 coordonnées en entier.

        • Partager sur Facebook
        • Partager sur Twitter
          15 avril 2019 à 10:00:49

          Bonjour Alexisdm et merci pour ta réponse. J'ai en effet bien simplifié le processus avec ce que tu as proposé, voici mon code actuel.

          //p1,56:125,20:71,03:    Message type que j'utilise pour tester le code 
          
          String message; // Stockera tout le message reçu
          
          double valeurDouble[3] = {0,0,0}; 
          bool negatif = false;
          int j = 0;
          
          void setup() {
            Serial.begin(9600);
            message = "";
          }
          
          void loop() {
            if(Serial.available()>0) {    
              char c = Serial.read();
              if (c=='p'){
                message = "";
                delay(20);
                while (Serial.available()>0){
                  c = Serial.read();
                  if ((c == ':') || (c == '-') || ((c >= '0') && (c <= '9'))){
                    message += c;  // On a enlevé toutes les virgules 
                  }
                }
                Serial.println(message); // Affiche 156:12520:7103
          
                      
                j = 0;
                for (int i=0; i<3; ++i){
                  while (message[j] != ':'){
                    valeurDouble[i] = valeurDouble[i] * 10 + (message[j]-'0');
                    Serial.println(valeurDouble[i]);
                    j++;
                  }
                  if (message[j] == ':'){
                    j++;
                    valeurDouble[i] = valeurDouble[i] / 100;
                    Serial.println(valeurDouble[i]);
                  }
                }
          
                 
                /*
                j = 0;
                for (int i=0; i<3; ++i){
                  while (message[j] != ':'){
                    if (message[j] != '-'){
                      valeurDouble[i] = valeurDouble[i] * 10 + (message[j]-'0');
                      Serial.println(valeurDouble[i]);
                      j++;
                    }
                    else{
                      negatif = true;
          	    j++;	
                    }
                  }
                  if (message[j] == ':'){
                    j++;
                    valeurDouble[i] = valeurDouble[i] / 100;
                    Serial.println(valeurDouble[i]);
                    if (negatif == true){
                      valeurDouble[i] = -valeurDouble[i];
                      negatif = false;
                    }
                  }
                }
                */
          
              }        
            }  
          }

          Je n'ai pas vraiment compris ce que tu proposais pour remplacer le delay(20). Sans ce delay le code ne marche absolument pas et affiche n'importe quoi dans le moniteur série … Ceci dit ça marche très bien comme ça et j'ai donc essayé de passer a l'étape suivant en prenant en compte la possibilité de recevoir une ou plusieurs valeurs négatives parmi les 3 envoyées (avec le bloc commenté).

          Cette partie de marche pas cependant, si j'ajoute un '-' dans le message que j'envoie (par exemple p-1,56:125,20:71,03: ) la ligne Serial;println(message); renvoie -156:12520:7103. Le ':' de la fin disparait et mon while qui attend ce caractère devient une boucle infinie et ça plante.

          EDIT : J'ai aussi remarqué que si j'utilisais que des valeurs positives mais toutes dans les centaines (par exemple p120,30:230;65:123,45:" la fin de mon message était aussi "mangé" et je repartais dans une boucle infinie à cause de l'absence du ':' à la fin. Y a-t-il une limite de taille dans le message que je peux envoyer ? Parce que je suis susceptible d'envoyer un message contenant trois valeurs négatives, chacune avec une centaine non nulle. Bien que peu probable, il faut que ce cas puisse être traiter sans planter ... 

          EDIT 2 : Bon ben problème résolu en augmentant le temps du delay à 30ms. Il semblerait que plus ce delay soit court, plus mon message doit être court pour ne pas être coupé a la fin et planter. Tout marche exactement comme j'en ai besoin même avec le message le plus long que je sois susceptible de recevoir en utilisant delay(30). En revanche, je ne comprend pas pourquoi j'ai besoin de ce delay vu que je suis censé faire de la lecture charactère par charactère et que le while(Serial.available()>0) est censé m'assurer que j'effectue toutes les instructions de ce while tant qu'il reste des charactères à lire ...

          Merci d'avance :)

          -
          Edité par ThibaultVenot 15 avril 2019 à 12:07:48

          • Partager sur Facebook
          • Partager sur Twitter
            17 avril 2019 à 19:33:10

            ThibaultVenot a écrit:

            En revanche, je ne comprend pas pourquoi j'ai besoin de ce delay vu que je suis censé faire de la lecture charactère par charactère et que le while(Serial.available()>0) est censé m'assurer que j'effectue toutes les instructions de ce while tant qu'il reste des charactères à lire ...

            Justement, non, il n'attend pas la fin du message, mais traite l'ensemble des données arrivées dans le buffer de réception. Or comme l'arduino traite les données plus vite qu'elles n'arrivent (16MHz avec autour d'une instruction par cycle contre 9600 bauds ou 960 octets par secondes), il a le temps d'épuiser le buffer entre chaque octet. C'est pour ça qu'il serait plus simple de mettre un vrai indicateur de fin de message pour soit attendre qu'un message entier soit arrivé dans le buffer avant de commencer à le traiter, soit en cas de décodage des messages à la volée de savoir qu'on est arrivé à la fin.

            Et quand je parle de parser à la volée, ça veut dire sans buffer intermédiaire, donc par exemple (sans gestion d'erreur):

            int valeurs[3] = {0, 0, 0};
            int indexValeur = -1;
            bool negatif = false;
            enum Etat {
              Etat_Initial,
              Etat_Coordonnees
            } etat;
            
            void setup() {
              Serial.begin(9600);
              etat = Etat_Initial;
            }
            
            void loop() {
              while (Serial.available() > 0) {
                char c = Serial.read();
                switch (etat) {
                  case Etat_Initial:
                    if (c == 'p')
                      etat = Etat_Coordonnees;
                    indexValeur = 0;
                    negatif = false;
                    valeurs[indexValeur] = 0;
                    break;
                  case Etat_Coordonnees:
                    if (c == '-') {
                      negatif = true;
                    } else if (c >= '0' && c <= '9') {
                      valeurs[indexValeur] = valeurs[indexValeur] * 10 + c - '0';
                    } else if (c == ':' && indexValeur < 2 ) {
                      // ajoute le signe à la valeur avant de passer à la suivante
                      if (negatif) {
                        valeurs[indexValeur] = -valeurs[indexValeur];
                        negatif = false;
                      }
                      indexValeur++;
                      valeurs[indexValeur] = 0;
                    } else if (c != '.' && c != ',') {  // caractère non reconnu, donc tous sauf les points décimaux => fin de lecture
                      // traite le dernier negatif
                      if (negatif) {
                        valeurs[indexValeur] = -valeurs[indexValeur];
                      }
                      for (int i = 0; i < 3; ++i) {
                        Serial.print(valeurs[i] / 100.0);
                        if (i < 2)
                          Serial.print(":");
                        else
                          Serial.println();
                      }         
                      etat = Etat_Initial;
                    }
                    break;
                }
              }
            }

            Comme on sort de la boucle avant la fin du message, la variable etat sert à savoir qu'on était en train de lire les coordonnées.



            -
            Edité par alexisdm 17 avril 2019 à 19:34:11

            • Partager sur Facebook
            • Partager sur Twitter

            Arduino | Algorithme

            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
            • Editeur
            • Markdown