Invio di grandi quantità di dati seriali


13

Quindi nei settori della robotica a volte sono necessarie più schede e / o computer collegati per condividere informazioni o salvare dati statistici. Attualmente ho bisogno di inviare alcune variabili diverse su una connessione seriale e mi chiedevo quale fosse il modo migliore per farlo?

Finora ho determinato che le strutture di invio sarebbero probabilmente il modo più semplice per inviare dati. Qualcuno sa in qualche altro modo che potrebbe essere più efficiente?

Tieni presente che essenzialmente dovrò inviare i dati per 4 motori, compressore, temperature diverse, cose casuali e le 3 sezioni del braccio.

Risposte:


9

Dato che il mio pensiero personale su Struct è il modo più efficiente per inviare molte variabili diverse, ho creato una libreria per facilitare l'invio di strutture e variabili su seriale. Codice sorgente

In questa libreria facilita l'invio tramite seriale. Ho usato con hardware e software seriale. Di solito questo viene usato insieme a xbee, quindi posso inviare i dati in modalità wireless da e verso il robot.

Quando si inviano i dati lo rende semplice in quanto consente di inviare una variabile o una struttura (non importa).

Ecco un esempio di invio di un semplice carattere sul seriale:

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

Esempio di invio di un semplice int sul seriale:

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

Esempio di invio di una struttura tramite seriale:

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

Esempi di ricezione:

Ricezione di un carattere inviato tramite 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');

Ricezione di un int inviato tramite 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');

Ricezione di uno Struct che è stato inviato tramite 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');

Dopo aver letto i dati utilizzando StreamSend::receiveObject()è necessario sapere se i dati erano BUONI, non trovati o BAD.

Buono = Riuscito

Not Found = Nessun carattere prefisso trovato nello ostream specificato

Bad = In qualche modo è stato trovato un carattere prefisso, ma i dati non sono intatti. Di solito significa che non è stato trovato alcun carattere suffisso o che i dati non erano della dimensione corretta.

Test di validità dei dati:

// 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
Le risposte a tutti i codici, come le risposte a tutti i collegamenti, sono scoraggiate. a meno che il tuo codice non abbia tonnellate di commenti, ti consiglierei di dare una spiegazione di ciò che sta succedendo
TheDoctor,

@TheDoctor, ho aggiornato il codice. Ora dovrebbero esserci altri commenti
Steven10172,

1

Se vuoi davvero inviarlo velocemente , ti consiglio Full Duplex Serial (FDX). È lo stesso protocollo utilizzato da USB ed Ethernet ed è molto più veloce di UART. Il rovescio della medaglia è che di solito richiede hardware esterno per facilitare le alte velocità di dati. Ho sentito che il nuovo softwareSreial supporta FDX, ma potrebbe essere più lento anche dell'hardware UART. Per ulteriori informazioni sui protocolli di comunicazione, vedi Come collegare due Arduino senza schermature?


Questo sembra interessante. Dovrò approfondire.
Steven10172,

Come può il " seriale full duplex " essere "molto più veloce di UART" quando si tratta, in effetti, di una comunicazione UART standard?
David Cary,

UART è una comunicazione a tariffa fissa. FDX invia i dati il ​​più velocemente possibile e rinvia i dati che non li hanno creati.
TheDoctor

Mi piacerebbe saperne di più su questo protocollo. Potresti aggiungere un link alla tua risposta che descriva un protocollo più veloce di UART? Stai parlando dell'idea generale di richiesta di ripetizione automatica utilizzando ACK-NAK o hai in mente qualche protocollo specifico? Nessuna delle mie ricerche su Google per "FDX" o "seriale full duplex" sembra corrispondere alla tua descrizione.
David Cary,

1

L'invio di una struttura è abbastanza semplice.

È possibile dichiarare la struttura come di consueto, quindi utilizzare memcpy (@ myStruct, @ myArray) per copiare i dati in una nuova posizione, quindi utilizzare qualcosa di simile al codice seguente per scrivere i dati come un flusso di dati.

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 

Quindi è possibile collegare una routine di interruzione al pin sull'altro dispositivo che procede come segue:

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

attachInterrupt(0,readSerial,Rising);  

// dice a mcu di chiamare fxn quando pinhigh. Questo accadrà praticamente in qualsiasi momento. se ciò non è desiderato, rimuovi l'interruzione e cerca semplicemente nuovi personaggi nel tuo ciclo esecutivo principale (aka, sondaggio 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 sintassi e l'uso dei puntatori avranno bisogno di qualche revisione. Ho tirato fuori una notte, quindi sono sicuro che il codice sopra non verrà nemmeno compilato, ma l'idea è lì. Riempi la tua struttura, copiala, usa la segnalazione fuori banda per evitare errori di inquadratura, scrivi i dati. Dall'altro lato, ricevere i dati, copiarli in una struttura e quindi i dati diventano accessibili tramite i normali metodi di accesso dei membri.

Funzionerà anche l'uso di bitfield, basta essere consapevoli che gli stuzzichini sembreranno essere all'indietro. Ad esempio, il tentativo di scrivere 0011 1101 può far apparire 1101 0011 all'altra estremità se le macchine differiscono nell'ordine dei byte.

Se l'integrità dei dati è importante, puoi anche aggiungere un checksum per assicurarti di non copiare dati di immondizia non allineati. Questo è un controllo rapido ed efficace che raccomando.


1

Se si può tollerare il volume di dati, il debug communicatons è così molto più facile per l'invio di stringhe rispetto a quando l'invio di binario; sprintf () / sscanf () e le loro varianti sono i tuoi amici qui. Racchiudere la comunicazione in funzioni dedicate nel proprio modulo (file .cpp); se è necessario ottimizzare il canale in un secondo momento - dopo avere un sistema funzionante - è possibile sostituire il modulo basato su stringhe con uno codificato per messaggi più piccoli.

Renderai la tua vita molto più semplice se ti atterrai alle specifiche del protocollo sulla trasmissione e le interpreterai più liberamente alla ricezione, per quanto riguarda le larghezze di campo, i delimitatori, le terminazioni di linea, gli zeri insignificanti, la presenza di +segni, ecc.


Inizialmente il codice era stato scritto per inviare i dati in un ciclo di stabilizzazione di un Quadricottero, quindi doveva essere abbastanza veloce.
Steven10172,

0

Non ho credenziali ufficiali qui, ma nella mia esperienza le cose sono andate abbastanza efficacemente quando scelgo una o più posizioni di carattere per contenere lo stato di una variabile, in modo da poter designare i primi tre caratteri come la temperatura, e il successivo tre come l'angolo di un servo, e così via. All'estremità di invio vorrei salvare le variabili singolarmente e quindi combinarle in una stringa da inviare in serie. All'estremità ricevente avrei fatto separare la stringa, ottenendo i primi tre caratteri e trasformandoli in qualunque tipo di variabile avessi bisogno, quindi facendolo di nuovo per ottenere il valore della variabile successiva. Questo sistema funziona meglio quando si conosce con certezza la quantità di caratteri che ciascuna variabile occuperà, e si cercano sempre le stesse variabili (che spero sia dato) ogni volta che passano i dati seriali.

Puoi scegliere una variabile per inserire l'ultima di lunghezza indeterminata e quindi ottenere quella variabile dal suo primo carattere alla fine della stringa. Certo, la stringa di dati seriali potrebbe allungarsi molto a seconda dei tipi di variabili e della loro quantità, ma questo è il sistema che uso e finora l'unica battuta d'arresto che ho colpito è la lunghezza seriale, quindi questo è l'unico svantaggio che sapere di.


Che tipo di funzioni usi per salvare x quantità di caratteri in un int / float / char?
Steven10172,

1
Potresti non rendertene conto, ma quello che descrivi è esattamente come structè organizzato in memoria (trascurando il riempimento) e immagino che le funzioni di trasferimento dei dati che usi saranno simili a quelle discusse nella risposta di Steven .
asheeshr,

@AsheeshR In realtà avevo la sensazione che le strutture potessero essere così, ma personalmente tendo a colpire un muro quando provo a riformattare le strutture e poi a rileggerle dall'altra parte. Questo è il motivo per cui ho pensato di fare solo questa cosa di stringa, in modo da poter eseguire facilmente il debug in caso di errori di lettura e in modo da poter leggere i dati seriali da solo se li desidero come "MOTORa023 MOTORb563" e così via, senza gli spazi.
Newbie97,

@ Steven10172 bene, lo ammetto, non tengo traccia delle funzioni specifiche, piuttosto google la funzione particolare ogni volta. String su int, String su float e String su char . Tieni presente che utilizzo questi metodi in c ++ normale e non li ho provati nell'IDE di Arduino.
Newbie97,

0

Invia dati strutt attraverso seriale

Nulla di bello. Invia uno struct. Utilizza un carattere di escape '^' per delimitare i dati.

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

Codice 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.