Envoi de grandes quantités de données série


13

Ainsi, dans le domaine de la robotique, vous avez parfois besoin de plusieurs cartes et / ou ordinateurs reliés entre eux pour partager des informations ou enregistrer des données statistiques. Actuellement, je dois envoyer quelques variables différentes via une connexion série et je me demandais quelle était la meilleure façon de procéder?

Jusqu'à présent, j'ai déterminé que l'envoi de structures serait probablement le moyen le plus simple d'envoyer des données. Quelqu'un connaît-il un autre moyen qui pourrait être plus efficace?

Veuillez garder à l'esprit que je devrai essentiellement envoyer les données pour 4 moteurs, compresseur, différentes températures, des choses aléatoires et les 3 sections du bras.

Réponses:


9

Avec mes pensées personnelles sur Structs étant le moyen le plus efficace d'envoyer de nombreuses variables différentes, j'ai construit une bibliothèque pour faciliter l'envoi de structures et de variables en série. Code source

Dans cette bibliothèque, il est facile d'envoyer en série. Je l'ai utilisé avec du matériel et des logiciels en série. Habituellement, cela est utilisé en conjonction avec xbee afin que je puisse envoyer sans fil les données vers et depuis le robot.

Lors de l'envoi de données, il est simple car il vous permet d'envoyer une variable ou une structure (cela ne fait rien).

Voici un exemple d'envoi d'un caractère simple sur la série:

// Send the variable charVariable over the serial.
// To send the variable you need to pass an instance of the Serial to use,
// a reference to the variable to send, and the size of the variable being sent.
// If you would like you can specify 2 extra arguments at the end which change the
// default prefix and suffix character used when attempting to reconstruct the variable
// on the receiving end. If prefix and suffix character are specified they'll need to 
// match on the receiving end otherwise data won't properly be sent across

char charVariable = 'c'; // Define the variable to be sent over the serial
StreamSend::sendObject(Serial, &charVariable, sizeof(charVariable));

// Specify a prefix and suffix character
StreamSend::sendObject(Serial, &charVariable, sizeof(charVariable), 'a', 'z');

Exemple d'envoi d'un simple int sur la série:

int intVariable = 13496; // Define the int to be sent over the serial
StreamSend::sendObject(xbeeSerial, &intVariable, sizeof(intVariable));

// Specify a prefix and suffix character
StreamSend::sendObject(xbeeSerial, &intVariable, sizeof(intVariable), 'j', 'p');

Exemple d'envoi d'une structure via série:

// Define the struct to be sent over the serial
struct SIMPLE_STRUCT {
  char charVariable;
  int intVariable[7];
  boolean boolVariable;
};
SIMPLE_STRUCT simpleStruct;
simpleStruct.charVariable = 'z'; // Set the charVariable in the struct to z

// Fill the intVariable array in the struct with numbers 0 through 6
for(int i=0; i<7; i++) {
  simpleStruct.intVariable[i] = i;
}

// Send the struct to the object xbeeSerial which is a software serial that was
// defined. Instead of using xbeeSerial you can use Serial which will imply the
// hardware serial, and on a Mega you can specify Serial, Serial1, Serial2, Serial3.
StreamSend::sendObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct));

// Send the same as above with a different prefix and suffix from the default values
// defined in StreamSend. When specifying when prefix and suffix character to send
// you need to make sure that on the receiving end they match otherwise the data
// won't be able to be read on the other end.
StreamSend::sendObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct), '3', 'u');

Exemples de réception:

Réception d'un caractère envoyé via Streamsend:

char charVariable; // Define the variable on where the data will be put

// Read the data from the Serial object an save it into charVariable once
// the data has been received
byte packetResults = StreamSend::receiveObject(Serial, &charVariable, sizeof(charVariable));

// Reconstruct the char coming from the Serial into charVariable that has a custom
// suffix of a and a prefix of z
byte packetResults = StreamSend::receiveObject(Serial, &charVariable, sizeof(charVariable), 'a', 'z');

Réception d'un int envoyé via StreamSend:

int intVariable; // Define the variable on where the data will be put

// Reconstruct the int from xbeeSerial into the variable intVariable
byte packetResults = StreamSend::receiveObject(xbeeSerial, &intVariable, sizeof(intVariable));

// Reconstruct the data into intVariable that was send with a custom prefix
// of j and a suffix of p
byte packetResults = StreamSend::receiveObject(xbeeSerial, &intVariable, sizeof(intVariable), 'j', 'p');

Réception d'une structure envoyée via StreamSend:

// Define the struct that the data will be put
struct SIMPLE_STRUCT {
  char charVariable;
  int intVariable[7];
  boolean boolVariable;
};
SIMPLE_STRUCT simpleStruct; // Create a struct to store the data in

// Reconstruct the data from xbeeSerial into the object simpleStruct
byte packetResults = StreamSend::receiveObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct));

// Reconstruct the data from xbeeSerial into the object simplestruct that has
// a prefix of 3 and a suffix of p
byte packetResults = StreamSend::receiveObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct), '3', 'p');

Une fois que vous avez lu les données en utilisant, StreamSend::receiveObject()vous devez savoir si les données étaient BONNES, Introuvables ou MAUVAISES.

Bon = réussi

Not Found = Aucun préfixe n'a été trouvé dans l'ostream spécifié

Mauvais = D'une manière ou d'une autre, un préfixe a été trouvé, mais les données ne sont pas intactes. Cela signifie généralement qu'aucun caractère suffixe n'a été trouvé ou que les données n'étaient pas de la bonne taille.

Test de validité des données:

// Once you call StreamSend::receiveObject() it returns a byte of the status of
// how things went. If you run that though some of the testing functions it'll
// let you know how the transaction went
if(StreamSend::isPacketGood(packetResults)) {
  //The Packet was Good
} else {
  //The Packet was Bad
}

if(StreamSend::isPacketCorrupt(packetResults)) {
  //The Packet was Corrupt
} else {
  //The Packet wasn't found or it was Good
}

if(StreamSend::isPacketNotFound(packetResults)) {
  //The Packet was not found after Max # of Tries
} else {
  //The Packet was Found, but can be corrupt
}

Classe SteamSend:

#include "Arduino.h"

#ifndef STREAMSEND_H
#define STREAMSEND_H


#define PACKET_NOT_FOUND 0
#define BAD_PACKET 1
#define GOOD_PACKET 2

// Set the Max size of the Serial Buffer or the amount of data you want to send+2
// You need to add 2 to allow the prefix and suffix character space to send.
#define MAX_SIZE 64


class StreamSend {
  private:
    static int getWrapperSize() { return sizeof(char)*2; }
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize, unsigned int loopSize);
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize, unsigned int loopSize, char prefixChar, char suffixChar);
    static char _prefixChar; // Default value is s
    static char _suffixChar; // Default value is e
    static int _maxLoopsToWait;

  public:
    static void sendObject(Stream &ostream, void* ptr, unsigned int objSize);
    static void sendObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar);
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize);
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar);
    static boolean isPacketNotFound(const byte packetStatus);
    static boolean isPacketCorrupt(const byte packetStatus);
    static boolean isPacketGood(const byte packetStatus);

    static void setPrefixChar(const char value) { _prefixChar = value; }
    static void setSuffixChar(const char value) { _suffixChar = value; }
    static void setMaxLoopsToWait(const int value) { _maxLoopsToWait = value; }
    static const char getPrefixChar() { return _prefixChar; }
    static const char getSuffixChar() { return _suffixChar; }
    static const int getMaxLoopsToWait() { return _maxLoopsToWait; }

};

//Preset Some Default Variables
//Can be modified when seen fit
char StreamSend::_prefixChar = 's';   // Starting Character before sending any data across the Serial
char StreamSend::_suffixChar = 'e';   // Ending character after all the data is sent
int StreamSend::_maxLoopsToWait = -1; //Set to -1 for size of current Object and wrapper



/**
  * sendObject
  *
  * Converts the Object to bytes and sends it to the stream
  *
  * @param Stream to send data to
  * @param ptr to struct to fill
  * @param size of struct
  * @param character to send before the data stream (optional)
  * @param character to send after the data stream (optional)
  */
void StreamSend::sendObject(Stream &ostream, void* ptr, unsigned int objSize) {
  sendObject(ostream, ptr, objSize, _prefixChar, _suffixChar);
}

void StreamSend::sendObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar) {
  if(MAX_SIZE >= objSize+getWrapperSize()) { //make sure the object isn't too large
    byte * b = (byte *) ptr; // Create a ptr array of the bytes to send
    ostream.write((byte)prefixChar); // Write the suffix character to signify the start of a stream

    // Loop through all the bytes being send and write them to the stream
    for(unsigned int i = 0; i<objSize; i++) {
      ostream.write(b[i]); // Write each byte to the stream
    }
    ostream.write((byte)suffixChar); // Write the prefix character to signify the end of a stream
  }
}

/**
  * receiveObject
  *
  * Gets the data from the stream and stores to supplied object
  *
  * @param Stream to read data from
  * @param ptr to struct to fill
  * @param size of struct
  * @param character to send before the data stream (optional)
  * @param character to send after the data stream (optional)
  */
byte StreamSend::receiveObject(Stream &ostream, void* ptr, unsigned int objSize) {
    return receiveObject(ostream, ptr, objSize, _prefixChar, _suffixChar);
}
byte StreamSend::receiveObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar) {
  return receiveObject(ostream, ptr, objSize, 0, prefixChar, suffixChar);
}

byte StreamSend::receiveObject(Stream &ostream, void* ptr, unsigned int objSize, unsigned int loopSize, char prefixChar, char suffixChar) {
  int maxLoops = (_maxLoopsToWait == -1) ? (objSize+getWrapperSize()) : _maxLoopsToWait;
  if(loopSize >= maxLoops) {
      return PACKET_NOT_FOUND;
  }
  if(ostream.available() >= (objSize+getWrapperSize())) { // Packet meets minimum size requirement
    if(ostream.read() != (byte)prefixChar) {
      // Prefix character is not found
      // Loop through the code again reading the next char
      return receiveObject(ostream, ptr, objSize, loopSize+1, prefixChar, suffixChar);
    }

    char data[objSize]; //Create a tmp char array of the data from Stream
    ostream.readBytes(data, objSize); //Read the # of bytes
    memcpy(ptr, data, objSize); //Copy the bytes into the struct

    if(ostream.read() != (byte)suffixChar) {
      //Suffix character is not found
      return BAD_PACKET;
    }
      return GOOD_PACKET;
  }
  return PACKET_NOT_FOUND; //Prefix character wasn't found so no packet detected
}


boolean StreamSend::isPacketNotFound(const byte packetStatus) {
    return (packetStatus == PACKET_NOT_FOUND);
}

boolean StreamSend::isPacketCorrupt(const byte packetStatus) {
    return (packetStatus == BAD_PACKET);
}

boolean StreamSend::isPacketGood(const byte packetStatus) {
    return (packetStatus == GOOD_PACKET);
}

#endif

3
Les réponses tout code, comme les réponses tout lien, sont déconseillées. sauf si votre code contient des tonnes de commentaires, je recommanderais de mettre une explication de ce qui se passe
TheDoctor

@TheDoctor, j'ai mis à jour le code. Il devrait y avoir plus de commentaires maintenant
Steven10172

1

Si vous vouliez vraiment l'envoyer rapidement , je recommande le Full Duplex Serial (FDX). C'est le même protocole que celui utilisé par USB et Ethernet, et il est beaucoup plus rapide que UART. L'inconvénient est qu'il nécessite généralement du matériel externe pour faciliter les débits de données élevés. J'ai entendu dire que le nouveau logiciel Software prend en charge FDX, mais cela peut être plus lent que le matériel UART. Pour plus d'informations sur les protocoles de communication, voir Comment connecter deux Arduino sans blindages?


Cela semble intéressant. Je vais devoir approfondir la question.
Steven10172

Comment la « série duplex intégral » peut-elle être «beaucoup plus rapide que l'UART» alors qu'il s'agit, en fait, d'une communication UART standard?
David Cary

UART est une communication à taux fixe. FDX envoie des données aussi rapidement que possible et renvoie les données qui ne l'ont pas été.
TheDoctor

J'aimerais en savoir plus sur ce protocole. Pourriez-vous ajouter un lien à votre réponse qui décrit un protocole plus rapide que UART? Parlez-vous de l'idée générale d' une demande de répétition automatique en utilisant ACK-NAK , ou avez-vous un protocole spécifique en tête? Aucune de mes recherches Google pour "FDX" ou "série duplex intégral" ne semble correspondre à votre description.
David Cary

1

L'envoi d'une structure est assez simple.

Vous pouvez déclarer la structure comme vous le feriez normalement, puis utiliser memcpy (@ myStruct, @ myArray) pour copier les données vers un nouvel emplacement, puis utiliser quelque chose de similaire au code ci-dessous pour écrire les données en tant que flux de données.

unsigned char myArraySender[##];   //make ## large enough to fit struct
memcpy(&myStruct,&myArraySender);  //copy raw data from struct to the temp array
digitalWrite(frameStartPin,High);  //indicate to receiver that data is coming
serial.write(sizeof myStruct);     //tell receiver how many bytes to rx
Serial.write(&myArraySender,sizeof myStruct);   //write bytes
digitalWrite)frameStartPin,Low);   //done indicating transmission 

Ensuite, vous pouvez attacher une routine d'interruption à la broche de l'autre appareil qui effectue les opérations suivantes:

volatile unsigned char len, tempBuff[##];   
//volatile because the interrupt will not happen at predictable intervals.

attachInterrupt(0,readSerial,Rising);  

// dire au mcu d'appeler fxn quand pinhigh. Cela se produira à tout moment. si cela n'est pas souhaité, supprimez l'interruption et recherchez simplement de nouveaux personnages dans votre boucle exécutive principale (aka, interrogation UART).

void readSerial(unsigned char *myArrayReceiver){
    unsigned char tempbuff[sizeof myArrayReceiver];
    while (i<(sizeof myArrayReceiver)) tempBuff[i]=Serial.read();
    memcpy(&tempbuff,&myArrayReceiver);
    Serial.flush();
}

La syntaxe et l'utilisation des pointeurs devront être revues. J'ai tiré une nuit blanche donc je suis sûr que le code ci-dessus ne compilera même pas, mais l'idée est là. Remplissez votre structure, copiez-la, utilisez la signalisation hors bande pour éviter les erreurs de cadrage, écrivez les données. À l'autre extrémité, recevez les données, copiez-les dans une structure, puis les données deviennent accessibles via les méthodes d'accès aux membres normales.

L'utilisation de champs de bits fonctionnera également, sachez simplement que les grignotages sembleront être à l'envers. Par exemple, si vous essayez d'écrire 0011 1101, 1101 0011 peut apparaître à l'autre extrémité si les machines diffèrent dans l'ordre des octets.

Si l'intégrité des données est importante, vous pouvez également ajouter une somme de contrôle pour vous assurer que vous ne copiez pas de données incorrectes non alignées. C'est une vérification rapide et efficace que je recommande.


1

Si vous pouvez tolérer le volume de données, le débogage des communications est tellement plus facile lors de l'envoi de chaînes que lors de l'envoi de binaires; sprintf () / sscanf () et leurs variantes sont vos amis ici. Insérez la communication dans des fonctions dédiées dans leur propre module (fichier .cpp); si vous devez optimiser le canal plus tard - après avoir un système fonctionnel - vous pouvez remplacer le module basé sur des chaînes par un module codé pour les petits messages.

Vous vous simplifierez la vie si vous respectez les spécifications du protocole sur la transmission et les interprétez plus librement à la réception, en ce qui concerne les largeurs de champ, les délimiteurs, les fins de ligne, les zéros insignifiants, la présence de +signes, etc.


À l'origine, le code a été écrit pour renvoyer des données dans une boucle de stabilisation d'un Quadcopter, il devait donc être assez rapide.
Steven10172

0

Je n'ai pas d'informations d'identification officielles ici, mais d'après mon expérience, les choses se sont déroulées assez efficacement lorsque je choisis une ou plusieurs positions de caractères pour contenir l'état d'une variable, de sorte que vous puissiez désigner les trois premiers caractères comme température, et la suivante trois comme l'angle d'un servo, et ainsi de suite. Du côté de l'envoi, j'enregistrais les variables individuellement, puis les combinais dans une chaîne pour les envoyer en série. À la réception, je séparerais la chaîne, obtenant les trois premiers caractères et les transformant en n'importe quel type de variable dont j'ai besoin, puis en recommençant pour obtenir la valeur de variable suivante. Ce système fonctionne mieux lorsque vous savez avec certitude la quantité de caractères que chaque variable prendra et que vous recherchez toujours les mêmes variables (ce qui, je l'espère, est une donnée) à chaque fois que les données série bouclent.

Vous pouvez choisir une variable pour mettre le dernier de longueur indéterminée, puis obtenir cette variable de son premier caractère à la fin de la chaîne. Certes, la chaîne de données série peut devenir très longue en fonction des types de variables et de leur quantité, mais c'est le système que j'utilise et jusqu'à présent, le seul revers que j'ai rencontré est la longueur de la série, c'est donc le seul inconvénient que je entendu parler.


Quel type de fonctions utilisez-vous pour enregistrer x quantité de caractères dans un int / float / char?
Steven10172

1
Vous ne le réalisez peut-être pas, mais ce que vous décrivez est exactement la façon dont a structest organisé en mémoire (sans tenir compte du remplissage) et j'imagine que les fonctions de transfert de données que vous utilisez seront similaires à celles décrites dans la réponse de Steven .
asheeshr

@AsheeshR J'avais en fait l'impression que les structures pourraient être de cette façon, mais j'ai tendance à frapper un mur en essayant de reformater les structures, puis à les relire de l'autre côté. C'est pourquoi j'ai pensé que je ferais juste cette chaîne, pour que je puisse facilement déboguer si les choses sont mal lues, et pour que je puisse même lire les données série moi-même si je les désigne comme "MOTORa023 MOTORb563" et ainsi de suite, sans les espaces.
Newbie97

@ Steven10172 eh bien j'avoue que je ne garde pas la trace des fonctions spécifiques, plutôt je google la fonction particulière à chaque fois. String to int, String to float et String to char . Gardez à l'esprit que j'utilise ces méthodes en c ++ normal et que je ne les ai pas essayées moi-même dans l'IDE Arduino.
Newbie97

0

Envoyer des données de structure sur une série

Rien d'extraordinaire. Envoie une structure. Il utilise un caractère d'échappement '^' pour délimiter les données.

Code Arduino

typedef struct {
 float ax1;
 float ay1;
 float az1;
 float gx1;
 float gy1;
 float gz1;
 float ax2;
 float ay2;
 float az2;
 float gx2;
 float gy2;
 float gz2;

} __attribute__((__packed__))data_packet_t;

data_packet_t dp;

template <typename T> void sendData(T data)
{
 unsigned long uBufSize = sizeof(data);
 char pBuffer[uBufSize];

 memcpy(pBuffer, &dp, uBufSize);
 Serial.write('^');
 for(int i = 0; i<uBufSize;i++) {
   if(pBuffer[i] == '^')
   {
    Serial.write('^');
    }
   Serial.write(pBuffer[i]);
 }
}
void setup() {
  Serial.begin(57600);
}
void loop(){
dp.ax1 = 0.03; // Note that I didn't fill in the others. Too much work. ;p
sendData<data_packet_t>(dp);
}

Code Python:

import serial
from  copy import copy
from struct import *


ser = serial.Serial(
#   port='/dev/cu.usbmodem1412',
  port='/dev/ttyUSB0',
#     port='/dev/cu.usbserial-AL034MCJ',
    baudrate=57600
)



def get_next_data_block(next_f):
    if not hasattr(get_next_data_block, "data_block"):
        get_next_data_block.data_block = []
    while (1):
        try:
            current_item = next_f()
            if current_item == '^':
                next_item = next_f()
                if next_item == '^':
                    get_next_data_block.data_block.append(next_item)
                else:
                    out = copy(get_next_data_block.data_block)
                    get_next_data_block.data_block = []
                    get_next_data_block.data_block.append(next_item)
                    return out
            else:
                get_next_data_block.data_block.append(current_item)
        except :
            break


for i in range(1000): # just so that the program ends - could be in a while loop
    data_ =  get_next_data_block(ser.read)
    try:
        print unpack('=ffffffffffff', ''.join(data_))
    except:
        continue
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.