Senden großer Mengen serieller Daten


13

In der Robotik sind manchmal mehrere Boards und / oder Computer erforderlich, um Informationen auszutauschen oder statistische Daten zu speichern. Momentan muss ich ein paar verschiedene Variablen über eine serielle Verbindung senden und habe mich gefragt, wie ich das am besten machen kann.

Bisher habe ich festgestellt, dass das Senden von Strukturen wahrscheinlich der einfachere Weg ist, Daten zu senden. Kennt jemand einen anderen Weg, der effizienter sein könnte?

Bitte denken Sie daran, dass ich im Wesentlichen die Daten für 4 Motoren, Kompressor, verschiedene Temperaturen, zufällige Dinge und die 3 Abschnitte des Arms senden muss.

Antworten:


9

Da meine persönlichen Gedanken zu Structs die effizienteste Methode zum Senden vieler verschiedener Variablen sind, habe ich eine Bibliothek erstellt, die das Senden von Structs und Variablen über die serielle Schnittstelle erleichtert. Quellcode

In dieser Bibliothek macht es das Senden durch serielle leicht. Ich habe mit mit Hardware und Software Serien verwendet. Normalerweise wird dies in Verbindung mit xbee verwendet, damit ich die Daten drahtlos zum und vom Roboter senden kann.

Beim Senden von Daten wird dies vereinfacht, da Sie entweder eine Variable oder eine Struktur senden können (das ist ihm egal).

Hier ist ein Beispiel für das Senden eines einfachen Zeichens über die Seriennummer:

// 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');

Beispiel für das Senden einer einfachen Ganzzahl über die Seriennummer:

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');

Beispiel für das Senden einer Struktur über serielle:

// 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');

Empfang Beispiele:

Empfangen eines Zeichens, das über Streamsend gesendet wurde:

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');

Empfangen eines Int, der über StreamSend gesendet wurde:

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');

Empfangen einer Struktur, die über StreamSend gesendet wurde:

// 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');

Wenn Sie die Daten mit gelesen haben, müssen StreamSend::receiveObject()Sie wissen, ob die Daten GUT, Nicht gefunden oder SCHLECHT waren.

Gut = erfolgreich

Nicht gefunden = Im angegebenen ostream wurde kein Präfixzeichen gefunden

Schlecht = Irgendwie wurde ein Präfixzeichen gefunden, aber die Daten sind nicht intakt. Normalerweise bedeutet dies, dass kein Suffixzeichen gefunden wurde oder die Daten nicht die richtige Größe hatten.

Gültigkeit der Daten prüfen:

// 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
}

SteamSend-Klasse:

#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
All-Code-Antworten, wie All-Link-Antworten, werden nicht empfohlen. es sei denn, Ihr Code hat Tonnen von Kommentaren, würde ich empfehlen, einige Erklärungen darüber
abzugeben,

@TheDoctor, ich habe den Code aktualisiert. Jetzt sollte es mehr Kommentare geben
Steven10172

1

Wenn Sie es wirklich schnell senden wollten , empfehle ich Full Duplex Serial (FDX). Es ist dasselbe Protokoll, das USB und Ethernet verwenden, und es ist viel schneller als UART. Der Nachteil ist, dass normalerweise externe Hardware erforderlich ist, um die hohen Datenraten zu ermöglichen. Ich habe gehört, dass die neue SoftwareSreial FDX unterstützt, dies kann jedoch langsamer sein als die Hardware-UART. Weitere Informationen zu Kommunikationsprotokollen finden Sie unter So verbinden Sie zwei Arduino ohne Abschirmungen?


Das hört sich interessant an. Ich muss noch genauer darauf eingehen.
Steven10172

Wie kann " Full Duplex Serial " "viel schneller als UART" sein, wenn es sich tatsächlich um Standard-UART-Kommunikation handelt?
David Cary

UART ist eine Festpreiskommunikation. FDX sendet Daten so schnell wie möglich und sendet die Daten, die sie nicht erstellt haben, erneut.
TheDoctor

Ich würde gerne mehr über dieses Protokoll erfahren. Könnten Sie Ihrer Antwort einen Link hinzufügen, der ein Protokoll beschreibt, das schneller als UART ist? Sprechen Sie über die allgemeine Idee der automatischen Wiederholungsanforderung mit ACK-NAK , oder gibt es ein bestimmtes Protokoll, an das Sie denken? Keine meiner Google-Suchanfragen nach "FDX" oder "Full Duplex Serial" scheint Ihrer Beschreibung zu entsprechen.
David Cary

1

Das Senden einer Struktur ist ziemlich einfach.

Sie können die Struktur wie gewohnt deklarieren und dann memcpy (@ myStruct, @ myArray) verwenden, um die Daten an einen neuen Speicherort zu kopieren. Verwenden Sie dann den folgenden Code, um die Daten als Datenstrom zu schreiben.

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 

Anschließend können Sie eine Interruptroutine an den Pin des anderen Geräts anhängen, die folgende Aktionen ausführt:

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

attachInterrupt(0,readSerial,Rising);  

// sage der mcu, dass sie fxn aufruft, wenn sie scharf ist. Dies wird praktisch jeden Moment passieren. Wenn dies nicht gewünscht ist, entfernen Sie den Interrupt und suchen Sie einfach nach neuen Charakteren in Ihrer Haupt-Executive-Schleife (auch bekannt als UART-Polling).

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

Syntax und Verwendung von Zeigern müssen überprüft werden. Ich habe einen All-Nighter gezogen, daher bin ich sicher, dass der obige Code nicht einmal kompiliert werden kann, aber die Idee ist da. Füllen Sie Ihre Struktur, kopieren Sie sie, verwenden Sie Außerbandsignale, um Rahmenfehler zu vermeiden, und schreiben Sie die Daten. Empfangen Sie die Daten auf der anderen Seite, kopieren Sie sie in eine Struktur, und dann können Sie über normale Member-Zugriffsmethoden auf die Daten zugreifen.

Die Verwendung von Bitfeldern funktioniert auch, beachten Sie jedoch, dass die Knabbereien rückwärts zu sein scheinen. Beispielsweise kann der Versuch, 0011 1101 zu schreiben, dazu führen, dass 1101 0011 am anderen Ende angezeigt wird, wenn sich die Maschinen in der Bytereihenfolge unterscheiden.

Wenn Datenintegrität wichtig ist, können Sie auch eine Prüfsumme hinzufügen, um sicherzustellen, dass Sie keine falsch ausgerichteten Abfalldaten kopieren. Dies ist eine schnelle und effektive Überprüfung, die ich empfehle.


1

Wenn Sie das Datenvolumen tolerieren können, ist das Debuggen von Kommunikationen beim Senden von Zeichenfolgen so viel einfacher als beim Senden von Binärdateien. sprintf () / sscanf () und ihre Varianten sind deine Freunde hier. Schließen Sie die Kommunikation in dedizierten Funktionen in einem eigenen Modul (CPP-Datei) ein. Wenn Sie den Kanal später optimieren müssen - danach Sie ein funktionierendes System haben -, können Sie das auf Zeichenfolgen basierende Modul durch ein Modul ersetzen, das für kleinere Nachrichten codiert ist.

Sie werden Ihr Leben leichter machen, wenn Sie die Protokollspezifikationen beim Senden festhalten und sie beim Empfang lockerer interpretieren, in Bezug auf Feldbreiten, Begrenzer, Zeilenenden, unbedeutende Nullen, Vorhandensein von +Zeichen usw.


Ursprünglich war der Code so geschrieben, dass er Daten in einer Stabilisierungsschleife eines Quadcopters zurücksendet, also musste er ziemlich schnell sein.
Steven10172

0

Ich habe hier keine offiziellen Anmeldeinformationen, aber meiner Erfahrung nach hat es sich als ziemlich effizient erwiesen, wenn ich bestimmte Zeichenpositionen auswähle, die den Status einer Variablen enthalten, sodass Sie die ersten drei Zeichen als Temperatur und das nächste Zeichen festlegen können drei als der Winkel eines Servos und so weiter. Auf der Sendeseite würde ich die Variablen einzeln speichern und sie dann zu einer Zeichenfolge kombinieren, um sie seriell zu senden. Am empfangenden Ende würde ich die Zeichenkette auseinander nehmen lassen, die ersten drei Zeichen erhalten und sie in den von mir benötigten Variablentyp umwandeln, um dann den nächsten Variablenwert zu erhalten. Dieses System funktioniert am besten, wenn Sie genau wissen, wie viele Zeichen jede Variable aufnehmen wird, und Sie bei jedem Durchlaufen der seriellen Daten immer nach denselben Variablen suchen (was hoffentlich eine Selbstverständlichkeit ist).

Sie können eine Variable auswählen, um die letzte Variable von unbestimmter Länge zu setzen, und diese Variable dann vom ersten Zeichen bis zum Ende der Zeichenfolge abrufen. Zugegeben, der serielle Datenstring kann je nach Variablentyp und Anzahl der Variablen sehr lang werden, aber dies ist das System, das ich verwende, und der einzige Nachteil, den ich getroffen habe, ist die serielle Länge. Das ist also der einzige Nachteil, den ich habe kennen.


Welche Art von Funktionen verwenden Sie, um x Zeichen in einem int / float / char zu speichern?
Steven10172

1
Sie werden dies vielleicht nicht bemerken, aber Sie beschreiben genau, wie a structim Speicher organisiert ist (ohne Berücksichtigung der Auffüllung), und ich stelle mir vor, dass die von Ihnen verwendeten Datenübertragungsfunktionen denen in Stevens Antwort beschriebenen ähnlich sind .
Asheeshr

@AsheeshR Eigentlich hatte ich das Gefühl, dass Strukturen so sein könnten, aber ich persönlich neige dazu, gegen eine Wand zu stoßen, wenn ich versuche, Strukturen neu zu formatieren und sie dann auf der anderen Seite erneut zu lesen. Das ist der Grund, warum ich dachte, ich würde einfach diese Zeichenfolge ausführen, damit ich leicht Fehler beheben kann, wenn etwas falsch gelesen wird, und sogar die seriellen Daten selbst lesen kann, wenn ich sie als "MOTORa023 MOTORb563" usw. bezeichne die Räume.
Newbie97

@ Steven10172 Nun, ich gebe zu, ich behalte die spezifischen Funktionen nicht im Auge, sondern google jedes Mal die jeweilige Funktion. String to int, String to float und String to char . Bedenken Sie, dass ich diese Methoden in regulärem c ++ verwende und sie nicht selbst in der Arduino IDE ausprobiert habe.
Newbie97

0

Senden Sie Strukturdaten über serielle

Nichts Besonderes. Sendet eine Struktur. Es verwendet ein Escapezeichen '^', um die Daten einzugrenzen.

Arduino-Code

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);
}

Python-Code:

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.