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
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?
la source
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.
Ensuite, vous pouvez attacher une routine d'interruption à la broche de l'autre appareil qui effectue les opérations suivantes:
// 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).
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.
la source
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.la source
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.
la source
struct
est 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 .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
Code Python:
la source