Wysyłanie dużych ilości danych szeregowych


13

Dlatego w dziedzinie robotyki czasami potrzebujesz wielu tablic i / lub komputerów połączonych ze sobą, aby dzielić się informacjami lub zapisywać dane statystyczne. Obecnie muszę wysłać kilka różnych zmiennych przez połączenie szeregowe i zastanawiałem się, jaki byłby najlepszy sposób na zrobienie tego?

Do tej pory ustaliłem, że struktury wysyłające będą prawdopodobnie łatwiejszym sposobem przesyłania danych. Czy ktoś zna inny sposób, który mógłby być bardziej wydajny?

Należy pamiętać, że zasadniczo będę musiał przesłać dane dla 4 silników, sprężarki, różnych temperatur, losowych rzeczy i 3 sekcji ramienia.

Odpowiedzi:


9

Ponieważ moje osobiste przemyślenia na temat Struktur są najskuteczniejszym sposobem wysyłania wielu różnych zmiennych, stworzyłem bibliotekę, aby ułatwić wysyłanie struktur i zmiennych przez szereg. Kod źródłowy

W tej bibliotece ułatwia wysyłanie przez serial. Używałem ze sprzętem i oprogramowaniem szeregowym. Zwykle jest to używane w połączeniu z Xbee, dzięki czemu mogę bezprzewodowo wysyłać dane do iz robota.

Podczas przesyłania danych upraszcza to, ponieważ umożliwia wysłanie zmiennej lub struktury (to nie obchodzi).

Oto przykład wysłania prostego znaku przez serial:

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

Przykład wysłania prostej int przez serial:

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

Przykład wysyłania struktury przez serial:

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

Otrzymywanie przykładów:

Odbieranie znaku wysłanego za pośrednictwem 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');

Odbieranie int, który został wysłany za pośrednictwem 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');

Odbieranie struktury, która została wysłana za pośrednictwem 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');

Po odczytaniu danych za pomocą StreamSend::receiveObject()musisz wiedzieć, czy dane były DOBRE, nie znaleziono lub ZŁE.

Dobry = sukces

Nie znaleziono = nie znaleziono znaku prefiksu w określonym ostreamie

Źle = W jakiś sposób znaleziono znak prefiksu, ale dane nie są nienaruszone. Zazwyczaj oznacza to, że nie znaleziono znaku sufiksu lub dane nie miały prawidłowego rozmiaru.

Testowanie poprawności danych:

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

Klasa 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
Odpowiedzi na wszystkie kody, podobnie jak odpowiedzi na wszystkie linki, są odradzane. chyba że twój kod zawiera mnóstwo komentarzy, zaleciłbym wyjaśnienie tego, co się dzieje
TheDoctor

@Doctor, zaktualizowałem kod. Teraz powinno być więcej komentarzy
Steven10172

1

Jeśli naprawdę chcesz wysłać go szybko , polecam Full Duplex Serial (FDX). Jest to ten sam protokół, którego używają USB i Ethernet, i jest znacznie szybszy niż UART. Minusem jest to, że zwykle wymaga zewnętrznego sprzętu, aby ułatwić szybkie przesyłanie danych. Słyszałem, że nowe oprogramowanie Real obsługuje FDX, ale może być wolniejsze nawet niż sprzętowy UART. Aby uzyskać więcej informacji na temat protokołów komunikacyjnych, zobacz Jak połączyć dwa Arduino bez osłon?


Brzmi interesująco. Będę musiał przyjrzeć się temu głębiej.
Steven10172

Jak „ seryjny pełny dupleks ” może być „znacznie szybszy niż UART”, skoro jest to w rzeczywistości standardowa komunikacja UART?
David Cary

UART to komunikacja o stałej szybkości. FDX wysyła dane tak szybko, jak to możliwe i ponownie wysyła dane, które go nie utworzyły.
TheDoctor

Chciałbym dowiedzieć się więcej o tym protokole. Czy możesz dodać link do swojej odpowiedzi, który opisuje protokół szybszy niż UART? Czy mówisz o ogólnym pomyśle automatycznego powtarzania żądań przy użyciu ACK-NAK , czy jest jakiś konkretny protokół, który masz na myśli? Żadne z moich wyszukiwań w Google „FDX” lub „pełny dupleks” nie pasuje do twojego opisu.
David Cary

1

Wysłanie struktury jest dość proste.

Możesz zadeklarować strukturę w normalny sposób, a następnie użyć memcpy (@ myStruct, @ myArray), aby skopiować dane do nowej lokalizacji, a następnie użyć czegoś podobnego do poniższego kodu, aby zapisać dane jako strumień danych.

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 

Następnie możesz dołączyć procedurę przerwania do styku drugiego urządzenia, które wykonuje następujące czynności:

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

attachInterrupt(0,readSerial,Rising);  

// powiedz mcu, żeby zadzwonił do fxn, kiedy jest pinhigh. Stanie się to praktycznie w dowolnym momencie. jeśli nie jest to pożądane, usuń przerwanie i po prostu obserwuj nowe postacie w głównej pętli wykonawczej (czyli odpytywanie UART).

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

Składnia i użycie wskaźników będzie wymagać przeglądu. Wyciągnąłem prawie całą noc, więc jestem pewien, że powyższy kod nawet się nie skompiluje, ale pomysł istnieje. Wypełnij swoją strukturę, skopiuj ją, użyj sygnalizacji pozapasmowej, aby uniknąć błędów ramkowania, zapisz dane. Z drugiej strony, otrzymaj dane, skopiuj je do struktury, a następnie dane staną się dostępne za pomocą normalnych metod dostępu do elementu.

Korzystanie z pól bitowych również będzie działać, pamiętaj jednak, że skubie będą wyglądały na odwrócone. Na przykład próba zapisu 0011 1101 może spowodować pojawienie się 1101 0011 na drugim końcu, jeśli maszyny różnią się kolejnością bajtów.

Jeśli integralność danych jest ważna, możesz również dodać sumę kontrolną, aby upewnić się, że nie kopiujesz niepoprawnych danych śmieci. Jest to szybka i skuteczna kontrola, którą polecam.


1

Jeśli możesz tolerować ilość danych, debugowanie komunikacji jest o wiele łatwiejsze przy wysyłaniu ciągów niż przy wysyłaniu pliku binarnego; sprintf () / sscanf () i ich warianty są tutaj twoimi przyjaciółmi. Zawiąż komunikację w dedykowanych funkcjach we własnym module (plik .cpp); jeśli później trzeba zoptymalizować kanał - po uruchomieniu działającego systemu - można zastąpić moduł oparty na łańcuchach kodem dla mniejszych wiadomości.

Ułatwisz sobie życie, jeśli trzymasz się ściśle specyfikacji protokołu podczas transmisji i interpretujesz je luźniej podczas odbioru, uwzględniając szerokość pola, ograniczniki, zakończenia linii, nieznaczne zera, obecność +znaków itp.


Pierwotnie kod został napisany w celu przesyłania danych w pętli stabilizującej Quadkoptera, więc musiał być dość szybki.
Steven10172

0

Nie mam tutaj oficjalnych poświadczeń, ale z mojego doświadczenia wynika, że ​​sprawy potoczyły się dość skutecznie, gdy wybieram określoną pozycję (pozycje) postaci, aby zawierały stan zmiennej, aby można było wyznaczyć pierwsze trzy znaki jako temperaturę, a następne trzy jako kąt serwomechanizmu i tak dalej. Po stronie wysyłającej zapisywałbym zmienne indywidualnie, a następnie łączyłem je w ciąg, aby wysłać szeregowo. Po stronie odbierającej rozdzieliłem ciąg znaków, zbierając pierwsze trzy znaki i zmieniając je w dowolny potrzebny typ zmiennej, a następnie robiąc to ponownie, aby uzyskać następną wartość zmiennej. Ten system działa najlepiej, gdy wiesz na pewno, ile znaków zajmie każda zmienna, i zawsze szukasz tych samych zmiennych (co mam nadzieję, że jest dane) za każdym razem, gdy szeregowe dane zapętlają się.

Możesz wybrać jedną zmienną, aby wstawić ostatnią nieokreśloną długość, a następnie pobrać tę zmienną od pierwszego znaku do końca ciągu. To prawda, że ​​ciąg danych szeregowych może być naprawdę długi w zależności od typów zmiennych i ich dużej ilości, ale to jest system, którego używam i jak dotąd jedyną porażką, jaką uderzyłem, jest długość szeregowa, więc jest to jedyna wada wiedzieć o.


Jakiego rodzaju funkcji używasz do zapisania x liczby znaków w int / float / char?
Steven10172

1
Być może nie zdajesz sobie z tego sprawy, ale to, co opisujesz, to dokładnie to , jak structzorganizowana jest pamięć (bez uwzględnienia wypełniania) i wyobrażam sobie, że używane funkcje przesyłania danych będą podobne do tych omówionych w odpowiedzi Stevena .
asheeshr

@AsheeshR Miałem przeczucie, że struktury mogą być w ten sposób, ale osobiście staram się uderzyć w ścianę, gdy próbuję sformatować struktury, a następnie przeczytać je ponownie po drugiej stronie. Właśnie dlatego pomyślałem, że po prostu zrobię ten ciąg znaków, aby móc łatwo debugować, jeśli coś źle się odczyta, i żebym mógł nawet odczytać dane szeregowe, jeśli oznaczę je jako „MOTORa023 MOTORb563” i tak dalej, bez przestrzenie.
Newbie97

@ Steven10172 dobrze, przyznaję, że nie śledzę konkretnych funkcji, a raczej szukam konkretnej funkcji za każdym razem. String to int, String to float i String to char . Pamiętaj, że używam tych metod w zwykłym c ++ i sam nie wypróbowałem ich w Arduino IDE.
Newbie97

0

Wysyłaj dane strukturalne przez szeregowy

Nic fajnego. Wysyła strukturę. Używa znaku ucieczki „^” do rozdzielenia danych.

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

Kod 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.