Question

J'essaie donc de définir un protocole de communication pour la communication série, je veux pouvoir envoyer des numéros d'octets à l'appareil, mais je ne sais pas comment m'assurer que l'appareil commence à le récupérer sur le bon octet.

Par exemple si je veux envoyer

0x1234abcd 0xabcd3f56 ...

Comment puis-je m'assurer que l'appareil ne commence pas à lire au mauvais endroit et à obtenir le premier mot comme:

0xabcdabcd

Y a-t-il une façon intelligente de faire cela? J'ai pensé à utiliser un marqueur pour le début d'un message, mais que se passe-t-il si je veux envoyer le numéro que je choisis comme données?

Était-ce utile?

La solution

Pourquoi ne pas envoyer un start-of-message octet suivi d'un length-of-data BYTE Si vous savez quelle est la taille des données?

Alternativement, faites comme d'autres protocoles binaires et envoyez uniquement des tailles fixes de packages avec un en-tête fixe. Dites que vous n'enverrez que 4 octets, alors vous savez que vous aurez un ou plusieurs octets d'en-tête avant le contenu de données réel.

Éditer: Je pense que vous me comprenez mal. Ce que je veux dire, c'est que le client est censé considérer toujours les octets comme de l'en-tête ou des données, non basés sur la valeur mais plutôt sur la position du flux. Dites que vous envoyez quatre octets de données, alors un octet serait l'octet d'en-tête.

+-+-+-+-+-+
|H|D|D|D|D|
+-+-+-+-+-+

Le client serait alors une machine d'état assez basique, dans le sens de:

int state = READ_HEADER;
int nDataBytesRead = 0;
while (true) {
  byte read = readInput();
  if (state == READ_HEADER) {
    // process the byte as a header byte
    state = READ_DATA;
    nDataBytesRead = 0;
  } else {
    // Process the byte as incoming data
    ++nDataBytesRead;
    if (nDataBytesRead == 4) 
    {
      state = READ_HEADER;
    }
  }
} 

La chose à propos de cette configuration est que ce qui détermine si l'octet est un octet d'en-tête n'est pas le contenu réel d'un octet, mais plutôt la position dans le flux. Si vous souhaitez avoir un nombre variable d'octets de données, ajoutez un autre octet à l'en-tête pour indiquer le nombre d'octets de données qui les suivent. De cette façon, peu importe si vous envoyez la même valeur que l'en-tête du flux de données, car votre client ne l'interprègera jamais comme des données.

Autres conseils

netsstring

Pour cette application, peut-être le relativement simple "netsstring"Le format est adéquat.

Par exemple, le texte "Hello World!" code pour:

12:hello world!,

La chaîne vide code comme les trois caractères:

0:,

qui peut être représenté comme la série d'octets

'0' ':' ','

Le mot 0x1234abcd dans un netsstring (en utilisant Commande d'octets du réseau), suivi du mot 0xabcd3f56 dans un autre netsstring, code comme la série d'octets

'\n' '4' ':' 0x12 0x34 0xab 0xcd ',' '\n'
'\n' '4' ':' 0xab 0xcd 0x3f 0x56 ',' '\n'

(Le caractère Newline ' n' avant et après chaque netsstring est facultatif, mais facilite le test et le débogage).

Synchronisation du cadre

Comment puis-je m'assurer que l'appareil ne commence pas à lire au mauvais endroit

La solution générale au Synchronisation du cadre Le problème est de lire dans un tampon temporaire, en espérant que nous avons commencé à lire au bon endroit. Plus tard, nous effectuons quelques vérifications de cohérence sur le message dans le tampon. Si le message échoue le chèque, quelque chose a mal tourné, nous jetons donc les données dans le tampon et recommençons. (S'il s'agissait d'un message important, nous espérons que l'émetteur la renvoie).

Par exemple, si le câble série est branché à mi-chemin du premier netsstring, le récepteur voit la chaîne d'octet:

0xab 0xcd ',' '\n' '\n'  '4' ':' 0xab 0xcd 0x3f 0x56 ',' '\n'

Parce que le récepteur est assez intelligent pour attendre le ':' Avant de s'attendre à ce que l'octet suivant soit des données valides, le récepteur est en mesure d'ignorer le premier message partiel et de recevoir correctement le deuxième message.

Dans certains cas, vous savez à l'avance quelle sera la ou la durée (s) de messages valides; Cela rend encore plus facile pour le récepteur de détecter qu'il a commencé à lire au mauvais endroit.

Envoi du marqueur de début de message comme données

J'ai pensé à utiliser un marqueur pour le début d'un message, mais que se passe-t-il si je veux envoyer le numéro que je choisis comme données?

Après avoir envoyé l'en-tête NETSTRING, l'émetteur envoie les données brutes telles quelles - même si cela ressemble au marqueur de début de message.

Dans le cas normal, le récepteur a déjà une synchronisation de trame. L'analyseur NETSTRING a déjà lu la "longueur" et l'en-tête ":", donc l'analyseur netString place les octets de données bruts directement dans l'emplacement correct dans le tampon - même si ces octets de données ressemblent à l'en-tête ":" octet ou byte de pied de page ",".

pseudocode

// netstring parser for receiver
// WARNING: untested pseudocode
// 2012-06-23: David Cary releases this pseudocode as public domain.

const int max_message_length = 9;
char buffer[1 + max_message_length]; // do we need room for a trailing NULL ?
long int latest_commanded_speed = 0;
int data_bytes_read = 0;

int bytes_read = 0;
int state = WAITING_FOR_LENGTH;

reset_buffer()
    bytes_read = 0; // reset buffer index to start-of-buffer
    state = WAITING_FOR_LENGTH;

void check_for_incoming_byte()
    if( inWaiting() ) // Has a new byte has come into the UART?
        // If so, then deal with this new byte.
        if( NEW_VALID_MESSAGE == state )
            // oh dear. We had an unhandled valid message,
            // and now another byte has come in.
            reset_buffer();
        char newbyte = read_serial(1); // pull out 1 new byte.
        buffer[ bytes_read++ ] = newbyte; // and store it in the buffer.
        if( max_message_length < bytes_read )
            reset_buffer(); // reset: avoid buffer overflow

        switch state:
            WAITING_FOR_LENGTH:
                // FIXME: currently only handles messages of 4 data bytes
                if( '4' != newbyte )
                    reset_buffer(); // doesn't look like a valid header.
                else
                    // otherwise, it looks good -- move to next state
                    state = WAITING_FOR_COLON;
            WAITING_FOR_COLON:
                if( ':' != newbyte )
                    reset_buffer(); // doesn't look like a valid header.
                else
                    // otherwise, it looks good -- move to next state
                    state = WAITING_FOR_DATA;
                    data_bytes_read = 0;
            WAITING_FOR_DATA:
                // FIXME: currently only handles messages of 4 data bytes
                data_bytes_read++;
                if( 4 >= data_bytes_read )
                    state = WAITING_FOR_COMMA;
            WAITING_FOR_COMMA:
                if( ',' != newbyte )
                    reset_buffer(); // doesn't look like a valid message.
                else
                    // otherwise, it looks good -- move to next state
                    state = NEW_VALID_MESSAGE;

void handle_message()
    // FIXME: currently only handles messages of 4 data bytes
    long int temp = 0;
    temp = (temp << 8) | buffer[2];
    temp = (temp << 8) | buffer[3];
    temp = (temp << 8) | buffer[4];
    temp = (temp << 8) | buffer[5];
    reset_buffer();
    latest_commanded_speed = temp;
    print( "commanded speed has been set to: " & latest_commanded_speed );
}

void loop () # main loop, repeated forever
    # then check to see if a byte has arrived yet
    check_for_incoming_byte();
    if( NEW_VALID_MESSAGE == state ) handle_message();
    # While we're waiting for bytes to come in, do other main loop stuff.
    do_other_main_loop_stuff();

plus de conseils

Lors de la définition d'un protocole de communication série, je trouve que cela fait des tests et des débogages beaucoup Plus facile si le protocole utilise toujours des caractères de texte ASCII lisibles par l'homme, plutôt que des valeurs binaires arbitraires.

Synchronisation du cadre (encore)

J'ai pensé à utiliser un marqueur pour le début d'un message, mais que se passe-t-il si je veux envoyer le numéro que je choisis comme données?

Nous avons déjà couvert le cas où le récepteur a déjà une synchronisation de trame. Le cas où le récepteur n'a pas encore la synchronisation du cadre est assez désordonné.

La solution la plus simple est que l'émetteur envoie une série d'octets inoffensifs (peut-être des lignes de nouvelles ou des caractères d'espace), la longueur du message valide maximal possible, comme un préambule juste avant chaque netsstring. Peu importe dans quel état se trouve le récepteur lorsque le câble série est branché, ces octets inoffensifs finissent par conduire le récepteur dans l'état "Waiting_For_Length". Et puis lorsque le Tranmitteur envoie l'en-tête de paquet (longueur suivi de ":"), le récepteur le reconnaît correctement comme un en-tête de paquet et a récupéré la synchronisation du cadre.

(Il n'est pas vraiment nécessaire que l'émetteur envoie ce préambule avant tous paquet. Peut-être que l'émetteur pourrait l'envoyer pour 1 des 20 paquets; Ensuite, le récepteur est garanti pour récupérer la synchronisation des trames dans 20 paquets (généralement moins) après le branchement du câble série).

Autres protocoles

D'autres systèmes utilisent une somme de contrôle Fletcher-32 simple ou quelque chose de plus compliqué pour détecter de nombreux types d'erreurs que le format Netsstring ne peut pas détecter ( un, b ), et peut se synchroniser même sans préambule.

De nombreux protocoles utilisent un marqueur spécial "Start of Packet" et utilisent une variété de techniques "d'échappement" pour éviter d'envoyer un octet "de démarrage du paquet" littéral dans les données transmises, même si les données réelles que nous voulons envoyer se produisent pour avoir cette valeur. ( Farce d'octets aériens cohérents, Bit Farging, cité et d'autres types de Encodage binaire à texte, etc.).

Ces protocoles ont l'avantage que le récepteur peut être sûr que lorsque nous voyons le marqueur "Début du paquet", c'est le début réel du paquet (et non un octet de données qui, par coïncidence, a la même valeur). Cela facilite la perte de la perte de synchronisation - jetez simplement les octets jusqu'au prochain marqueur "Début du paquet".

De nombreux autres formats, y compris le format NetString, permettent de transmettre toute valeur d'octet possible en tant que données. Les récepteurs doivent donc être plus intelligents à l'égard de l'octet de début de tête qui force être un véritable début de tête, ou force Soyez un octet de données - mais au moins ils n'ont pas à faire face à "s'échapper" ou au tampon étonnamment important requis, dans le pire des cas, pour contenir un "message de données de 64 octets fixes" après avoir échappé.

Choisir une approche n'est vraiment pas plus simple que l'autre - elle pousse simplement la complexité à un autre endroit, comme prévu par théorie du lit d'eau.

Pourriez-vous vous déranger sur la discussion de diverses façons de gérer l'octet de début de tête, y compris ces deux façons, au WikiBook de programmation en sérieet éditer ce livre pour le rendre meilleur?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top