IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

Transfert de données UART : comment distinguer des "packets" ?


Sujet :

Arduino

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2021
    Messages
    13
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2021
    Messages : 13
    Par défaut Transfert de données UART : comment distinguer des "packets" ?
    Bonjour !

    J'ai 2 montages a base de STM32 qui communiquent entre eux par des modules à 2.4Ghz. Ces modules utilisent l'UART pour communiquer avec les cartes.

    J'ai pu configurer mes modules, j'arrive à échanger quelques données, mais sur ce dernier point, c'est le bordel.

    Je transfert des paquets de 30 octets avec 1 premier octet d'entête, puis 14 int en 16 bits et un char à la fin que je laisse à 0 pour le moment. Je met tout ça dans un grand buffer de 30 bytes du coté de l'émetteur, j'écris tout d'un bloc avec serial.write(buffer, 30); et ça part (j'ai configuré mon module pour des paquets de 32 octets max).

    De l'autre coté, j'ai tenté pas mal de truc, mais tout tourne autour de l'idée suivante dans le loop() :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    while(mySerial.available() ) {
        readBuf[e] = (byte)mySerial.read();
        e++;
    }
    Le problème, c'est que les paquets sont découpés un peu au grès du vent (la doc du module dit pourtant bien qu'en dessous de la valeurs maximale d'un paquet, tout est envoyé en un seul paquet. mais bon, admettons.)

    Je pourrais recoller les morceaux de paquets que je reçoit, ce n'est pas trop le soucis, mais comment diable avec un protocole de communication tel que l'UART fait-on pour structurer un peu ses données ? Je ne cherche pas forcément une vraie sérialisation, mais comment faire pour savoir quand on commence un nouveau paquet vu que je transporte des variables susceptibles de contenir n'importe quel valeur (et donc y compris la valeur d'un entête que je pourrais arbitrairement spécifier).

    En vous remerciant

  2. #2
    Membre Expert
    Avatar de jpbbricole
    Homme Profil pro
    Retraité des réseaux informatiques
    Inscrit en
    Février 2013
    Messages
    1 017
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Suisse

    Informations professionnelles :
    Activité : Retraité des réseaux informatiques
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2013
    Messages : 1 017
    Par défaut
    Bonjour vieuxjeune

    Il faut mettre tes variables à transmettre dans une structure, l'envoyer sur l'USART par un Serial.write et reçevoir ta structure par un Serial.read.

    Si tu veux un exemple...

    Cordialement
    jpbbricole

  3. #3
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 921
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 921
    Par défaut
    Citation Envoyé par vieuxjeune Voir le message
    De l'autre coté, j'ai tenté pas mal de truc, mais tout tourne autour de l'idée suivante dans le loop() :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    while(mySerial.available() ) {
        readBuf[e] = (byte)mySerial.read();
        e++;
    }
    c'est l'idée mais votre MCU tourne bcp plus vite que l'arrivée des données, donc le buffer se vide avant d'avoir lu les 30 octets et vous sortez de la boucle while sans tout avoir lu ==> donc il faut lire de manière asynchrone pour reconstruire la trame.

    l'approche de @jpbricole est fonctionnelle si vous pouvez garantir la synchro (ie le récepteur sera toujours allumé et en écoute avant l'émetteur pour ne pas rater un seul octet)

    Sinon il faut durcir un peu la transmission et 1 octet pour le début de trame c'est un peu short pour être différentiant et se synchroniser (la valeur retenue pourrait se trouver dans un des octets de vos nombres comme vous le dites). ce serait pas mal d'en mettre plus si vous pouvez, par exemple 2 octets suivi d'un octet pour la taille du message et le marqueur de fin - avec cela ça permet de confirmer qu'on a reçu quelque chose d'un peu correct.

    Quel est le système de transmission ? certains assurent que le payload arrivent correctement (ça rajoute une checksum pour vous) - pour d'autres il faut le faire soi même

  4. #4
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2021
    Messages
    13
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2021
    Messages : 13
    Par défaut
    Merci pour vos réponses !

    Pour le système utilisé, ce sont des modules ebyte qui normalement doivent prendre en charge tout ce qui est relatif à la transmission et au checksum... enfin au moins pour les données qui passent... Celui sur lequel je suis en train de faire des essais est un E220-900T22S avec un LLCC68 comme chip. Je pensais utiliser des modules 2.4 a terme, donc avec des chip différents.

    Passer une structure, ça me parait voué à l'échec vu que les paquets arrivent morcelés. Je me trompe ?
    Comment on récupère une structure complète au fait ? Les fonctions read() ne récupèrent que du byte à byte non ?

    Je pense que je vais partir sur des entête plus grosses et reconstruire le message moi même voir faire un checksum.
    C'est un peu agaçant comme solution et ça me complique beaucoup la gestion de mon timing, mais bon...

    Je reste preneur de toute solution plus élégante.

    En vous remerciant à nouveau

  5. #5
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 921
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 921
    Par défaut
    OK ce sont des modules LORA on dirait

    s'ils vous offrent une interface série, vous pouvez "tricher" (comme le propose @jpbricole) en faisant juste un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    while (Serial.peek() == -1) ; // on attend de recevoir au moins un octet et on demande d'en lire 30
    if (Serial.readBytes(&buffer, 30) == 30) { // si on a bien reçu les 30 octets avant le timeout
      memcpy(&maStructure, &buffer, 30); // on initialise la structure avec le contenu du buffer (éviter les cast sauvages en C++)
      // ici on a les données reçues dans la structure
      ...
    }
    la fonction readBytes() va se charger d'attendre les 30 octets et ne va s'interrompre que si (par défaut) rien n'est reçu pendant 1 seconde (c'est réglable).

    l'inconvénient cependant c'est qu'on n'a pas la synchro si on prend un message en cours de route.

    ==> d'où la recommandation de traiter la synchro avant de recevoir le payload

    une idée de structure du code (tapé ici donc je vous laisse tester)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    const byte enTete[] = {0xDE, 0xAD, 0xBE, 0xEF}; // DEADBEEF
    const byte tailleEnTete = sizeof enTete;
     
    struct __attribute__((packed)) t_payload {
      int16_t data[14];
      byte endMarker;
    };
     
     
     
    bool gotHeader() {
      static byte position = 0;
      int r = Serial.read();
      if (r != -1) { // -1 veut dire rien à lire, plus rapide que d'appeler available()
        if (r == enTete[position]) {
          position++;
          if (position >= tailleEnTete) {
            position = 0; // pour la prochaine fois
            return true;
          }
        } else {
          position = 0;  // mauvaise séquence, on recommence
          return false;
        }
      }
      return false; // rien de dispo à lire
    }
     
     
    void setup() {
      Serial.begin(115200); Serial.println();
    }
     
    void loop() {
      if (gotHeader()) {
        // on a bien reçu le header
     
        byte buffer[sizeof(t_payload)];
        t_payload maStructure;
     
        while (Serial.peek() == -1) ; // on attend de recevoir au moins un octet et on demande d'en lire le nombre adéquat pour la structure
     
        if (Serial.readBytes(buffer, sizeof(t_payload)) == sizeof(t_payload)) { // si on a bien reçu tous les octets avant le timeout
          memcpy(&maStructure, &buffer, sizeof(t_payload)); // on initialise la structure avec le contenu du buffer (éviter les cast sauvages en C++)
          // ici on a les données reçues dans la structure
          // ...
        } else {
          // Okay, Houston, I believe we've had a problem here
        } 
      }
    }
    On peut aussi se passer de readBytes() et faire la lecture de la structure de la même façon qu'on a géré l'en-tête, en asynchrone.

  6. #6
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2021
    Messages
    13
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2021
    Messages : 13
    Par défaut
    Merci beaucoup d'avoir pris le temps de faire une réponse si complète !

    Je test ça demain ou un peu plus tard (j'enchaine les gardes de demain jusqu'à la fin du WE) mais je reviendrai donner le résultat.

    Ce que j'ai fait moi n'est pas du tout concluant, et sans debugger, c'est juste affreux.
    Juste pour infos, ça ressemblait à ça (j'avais réduit ma structure à 8 int16 pour les tests)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    // [0xC5] [0b11xxxxxx] [chksum] [H0][L0] [H1][L1] [H2][L2] [H3][L3] [H4][L4] [H5][L5] [H6][L6] [H7][L7] [DATA] // 20 bits
    byte readBuf[32] = {0}, lastValidMsg[32] = {0};
    uint8_t iBuf = 0;
    byte lastByte = 0, checksum;
    bool endMsg = false;
     
    void loop() 
    {
    byte ic = 0;
    while(mySerial.available() && !endMsg) {
      ic = mySerial.read();
      if(ic == 0xC5 && lastByte & 0b11000000 == 0b1100000000) { // j'utiliserai les 4 derniers bits pour indiquer le nombre d'int16 a récupérer
        iBuf = 0;
        checksum = 0;
      } else {
        readBuf[iBuf] = ic;
        checksum += ic;
        iBuf++;
        if(iBuf == 20) {
          if(checksum != readBuf[2]) {
            endMsg = true;
          }
        }
      }
      lastByte = ic;
    }
     
    if(endMsg == true)
      memcpy(lastValidMsg, readBuf, 20); // c'est pour l'affichage
     
    }
    A noter que j'ai pris ces entêtes car les valeurs contenus dans les INT ne devraient pas dépasser 2000 (oui, j'aurais pu optimiser en prenant que 11 bits par valeur, mais c'est chiant)

  7. #7
    Membre Expert
    Avatar de jpbbricole
    Homme Profil pro
    Retraité des réseaux informatiques
    Inscrit en
    Février 2013
    Messages
    1 017
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Suisse

    Informations professionnelles :
    Activité : Retraité des réseaux informatiques
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2013
    Messages : 1 017
    Par défaut
    Bonjour vieuxjeune

    Citation Envoyé par vieuxjeune Voir le message
    Passer une structure, ça me parait voué à l'échec vu que les paquets arrivent morcelés. Je me trompe ?
    Comment on récupère une structure complète au fait ? Les fonctions read() ne récupèrent que du byte à byte non ?
    Soit une structure:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    struct __attribute__((packed)) donneesMesureesDef
    {float voltageA; float voltageB; float voltageC;};
    donneesMesureesDef donneesAtransmettre;
    Pour envoyer la structure donneesAtransmettre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Serial1.write((byte *)&donneesAtransmettre, sizeof(donneesAtransmettre));
    Structure transmise "en une pièce".

    Pour recevoir la structure donneesAtransmettre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Serial1.readBytes((byte*)&donneesAtransmettre, sizeof(donneesAtransmettre));
    Structure reçue "en une pièce".

    Cordialement
    jpbbricole

  8. #8
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2021
    Messages
    13
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2021
    Messages : 13
    Par défaut
    Je ne connaissais pas la fonction readBytes(). A ce sujet, je trouve d'ailleurs la documentation Arduino extrêmement mal faite.

    Je n'utilise pas un "vrai" Serial, mais un hardwareSerial(). J'ai pris quelques secondes pour voir si ça compilait avec readBytes(), et ça semble être le cas.
    Je vais donc tester tout ça en profondeur un peu plus tard, car je suis vraiment trop sur les rotules pour tester ça les jours où je bosse :p

    En attendant, mes sincères remerciement pour votre coup de pousse !

  9. #9
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 921
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 921
    Par défaut
    Serial est une instance de la classe HarwareSerial

    Et la fonction readBytes provient de la classe Stream qui en est une classe parent

Discussions similaires

  1. Réponses: 4
    Dernier message: 18/06/2007, 09h11
  2. Traitement d'image, comment distinguer des types?
    Par inai dans le forum Traitement du signal
    Réponses: 15
    Dernier message: 07/06/2005, 01h09
  3. Réponses: 5
    Dernier message: 30/05/2005, 16h58

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo