Mengirim Sejumlah Besar Data Serial

13

Jadi di bidang robotika kadang-kadang Anda membutuhkan banyak papan dan atau komputer yang dihubungkan bersama untuk berbagi informasi atau menyimpan data statistik. Saat ini saya perlu mengirim beberapa variabel berbeda melalui koneksi serial dan bertanya-tanya apa cara terbaik untuk melakukannya?

Sejauh ini saya telah menentukan bahwa struktur pengiriman mungkin akan menjadi cara yang lebih mudah untuk mengirim data. Adakah yang tahu cara lain yang mungkin lebih efisien?

Harap diingat bahwa pada dasarnya saya harus mengirim data untuk 4 motor, kompresor, temperatur berbeda, benda acak, dan 3 bagian lengan.

Steven10172
sumber

Jawaban:

9

Dengan pemikiran pribadi saya tentang Structs menjadi cara paling efisien untuk mengirim banyak variabel yang berbeda, saya telah membangun sebuah perpustakaan untuk membantu membuatnya lebih mudah untuk mengirim struct dan variabel melalui serial. Kode sumber

Di perpustakaan ini membuat pengiriman melalui serial mudah. Saya telah menggunakan dengan perangkat keras dan perangkat lunak serial. Biasanya ini digunakan bersamaan dengan xbee sehingga saya bisa mengirim data ke dan dari robot secara nirkabel.

Saat mengirim data, sesederhana itu memungkinkan Anda mengirim variabel atau struct (tidak peduli).

Ini adalah contoh pengiriman char sederhana ke 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');

Contoh pengiriman int sederhana melalui 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');

Contoh pengiriman struct atas 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');

Menerima contoh:

Menerima char yang dikirim melalui 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');

Menerima int yang dikirim melalui 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');

Menerima Struct yang dikirim melalui 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');

Setelah Anda membaca data menggunakan StreamSend::receiveObject()Anda perlu tahu apakah data itu BAIK, Tidak Ditemukan, atau BURUK.

Bagus = Berhasil

Tidak Ditemukan = Tidak ada karakter awalan yang ditemukan di ostream yang ditentukan

Buruk = Entah bagaimana ada karakter awalan ditemukan, tetapi data tidak utuh. Biasanya itu berarti tidak ada karakter sufiks yang ditemukan atau data bukan ukuran yang benar.

Menguji Validitas Data:

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

Kelas 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
Steven10172
sumber
3
Semua jawaban kode, seperti semua jawaban tautan tidak disarankan. kecuali kode Anda memiliki banyak komentar, saya akan merekomendasikan untuk memberikan penjelasan tentang apa yang sedang terjadi
TheDoctor
@ TheDoctor, saya sudah memperbarui kodenya. Seharusnya ada lebih banyak komentar sekarang
Steven10172
1

Jika Anda benar-benar ingin mengirimnya dengan cepat , saya sarankan Full Duplex Serial (FDX). Ini protokol yang sama yang digunakan USB dan ethernet, dan jauh lebih cepat daripada UART. Kelemahannya adalah biasanya diperlukan perangkat keras eksternal untuk memfasilitasi kecepatan data yang tinggi. Saya pernah mendengar bahwa softwareSreial baru mendukung FDX, tetapi ini mungkin lebih lambat daripada UART perangkat keras. Untuk lebih lanjut tentang protokol komunikasi, lihat Bagaimana menghubungkan dua Arduino tanpa perisai?

Dokter
sumber
Ini terdengar menarik. Saya harus melihat lebih jauh ke dalamnya.
Steven10172
Bagaimana " serial dupleks penuh " menjadi "jauh lebih cepat daripada UART" padahal sebenarnya, itu adalah komunikasi UART standar?
David Cary
UART adalah komunikasi dengan kecepatan tetap. FDX mengirim data secepat mungkin dan mengirim ulang data yang tidak berhasil.
TheDoctor
Saya ingin mengetahui lebih lanjut tentang protokol ini. Bisakah Anda menambahkan tautan ke jawaban Anda yang menggambarkan protokol yang lebih cepat dari UART? Apakah Anda berbicara tentang ide umum permintaan ulangi otomatis menggunakan ACK-NAK , atau adakah protokol tertentu yang ada dalam pikiran Anda? Tak satu pun dari pencarian Google saya untuk "FDX" atau "serial dupleks penuh" tampaknya cocok dengan deskripsi Anda.
David Cary
1

Mengirim struktur cukup sederhana.

Anda bisa mendeklarasikan struktur seperti biasanya, dan kemudian menggunakan memcpy (@ myStruct, @ myArray) untuk menyalin data ke lokasi baru, dan kemudian menggunakan sesuatu yang mirip dengan kode di bawah ini untuk menulis data sebagai datastream.

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 

Kemudian Anda dapat melampirkan rutin interupsi ke pin di perangkat lain yang melakukan hal berikut:

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

attachInterrupt(0,readSerial,Rising);  

// beritahu MCU untuk memanggil fxn saat pinhigh. Ini akan terjadi hampir setiap saat. jika itu tidak diinginkan, hapus interupsi dan cukup perhatikan karakter baru di loop eksekutif utama Anda (alias, polling UART).

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

Sintaks dan penggunaan pointer akan memerlukan beberapa tinjauan. Saya menarik all-nighter jadi saya yakin kode di atas bahkan tidak dapat dikompilasi, tetapi idenya ada di sana. Isi struktur Anda, salin, gunakan pensinyalan out-of-band untuk menghindari kesalahan framing, tulis data. Di sisi lain, terima data, salin ke struct, dan kemudian data menjadi dapat diakses melalui metode akses anggota normal.

Penggunaan bitfields juga akan berfungsi, perlu diketahui bahwa camilan akan tampak mundur. Misalnya, mencoba menulis 0011 1101, dapat mengakibatkan 1101 0011 muncul di ujung yang lain jika mesin berbeda dalam urutan byte.

Jika integritas data penting, Anda juga dapat menambahkan checksum untuk memastikan Anda tidak menyalin data sampah yang tidak selaras. Ini adalah pemeriksaan cepat dan efektif yang saya rekomendasikan.

80HD
sumber
1

Jika Anda dapat mentolerir volume data, debugging Communicatons adalah begitu jauh lebih mudah ketika mengirim string daripada ketika mengirimkan biner; sprintf () / sscanf () dan variannya adalah teman Anda di sini. Lampirkan komunikasi dalam fungsi khusus dalam modul mereka sendiri (file .cpp); jika Anda perlu mengoptimalkan saluran nanti - setelah Anda memiliki sistem yang berfungsi - Anda dapat mengganti modul berbasis string dengan satu kode untuk pesan yang lebih kecil.

Anda akan membuat hidup Anda jauh lebih mudah jika Anda memegang erat dengan spesifikasi protokol pada transmisi dan menafsirkannya lebih longgar pada penerimaan, berkaitan dengan lebar bidang, pembatas, ujung garis, nol tidak signifikan, adanya +tanda, dll.

JRobert
sumber
Awalnya kode ini ditulis untuk mengirim kembali data dalam loop stabilisasi Quadcopter sehingga harus cukup cepat.
Steven10172
0

Saya tidak memiliki kredensial resmi di sini, tetapi dalam pengalaman saya, semuanya berjalan cukup efisien ketika saya memilih posisi karakter tertentu untuk memuat status variabel, sehingga Anda dapat menetapkan tiga karakter pertama sebagai suhu, dan selanjutnya tiga sebagai sudut servo, dan sebagainya. Pada akhir pengiriman saya akan menyimpan variabel secara individual dan kemudian menggabungkannya dalam string untuk mengirim secara seri. Pada sisi penerima saya akan membuat string dipisahkan, mendapatkan tiga karakter pertama dan mengubahnya menjadi tipe variabel apa pun yang saya butuhkan, kemudian melakukannya lagi untuk mendapatkan nilai variabel berikutnya. Sistem ini bekerja paling baik ketika Anda tahu pasti jumlah karakter yang akan diambil oleh setiap variabel, dan Anda selalu mencari variabel yang sama (yang saya harap diberikan) setiap kali data serial dilewati.

Anda dapat memilih satu variabel untuk menempatkan terakhir dengan panjang tak tentu dan kemudian mendapatkan variabel itu dari karakter pertama hingga akhir string. Memang, string data serial bisa sangat lama tergantung pada jenis variabel dan jumlah mereka, tapi ini adalah sistem yang saya gunakan dan sejauh ini satu-satunya kekurangan yang saya tekan adalah panjang serial, jadi itu satu-satunya kelemahan saya tahu.

Pemula97
sumber
Fungsi apa yang Anda gunakan untuk menyimpan x jumlah karakter ke int / float / char?
Steven10172
1
Anda mungkin tidak menyadari hal ini, tetapi apa yang Anda gambarkan adalah persis bagaimana a structdiatur dalam memori (abaikan padding) dan saya membayangkan fungsi transfer data yang Anda gunakan akan mirip dengan yang dibahas dalam jawaban Steven .
asheeshr
@ AsheeshR Saya sebenarnya memiliki perasaan struct mungkin seperti itu, tapi saya pribadi cenderung menabrak dinding ketika mencoba memformat struct dan kemudian membacanya lagi di sisi lain. Itu sebabnya saya pikir saya hanya akan melakukan hal string ini, sehingga saya bisa dengan mudah men-debug jika ada yang salah membaca, dan sehingga saya bahkan bisa membaca data serial sendiri jika saya menunjuknya seperti "MOTORa023 MOTORb563" dan seterusnya, tanpa ruang.
Newbie97
@ Steven10172 baik saya akui saya tidak melacak fungsi tertentu, melainkan saya google fungsi tertentu setiap kali. String to int, String to float, dan String to char . Ingatlah bahwa saya menggunakan metode ini dalam c ++ reguler dan belum mencobanya di IDE Arduino sendiri.
Newbie97
0

Kirim data struct melintasi serial

Tidak ada yang mewah. Mengirim sebuah struct. Ia menggunakan karakter pelarian '^' untuk membatasi data.

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

Kode 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
Guru Subramani
sumber