Büyük Miktarda Seri Veri Gönderme


13

Bu nedenle, robotik alanlarında bazen bilgi paylaşmak veya istatistiksel verileri kaydetmek için birbirine bağlı birden fazla panele ve / veya bilgisayara ihtiyacınız vardır. Şu anda bir seri bağlantı üzerinden birkaç farklı değişkenler göndermek gerekir ve böyle yapmak için gitmek için en iyi yolu ne olduğunu merak ediyordum?

Şimdiye kadar, yapı göndermenin muhtemelen veri göndermenin daha kolay yolu olacağını belirledim. Daha verimli olabilecek başka bir yol bilen var mı?

4 motor, kompresör, farklı sıcaklıklar, rastgele şeyler ve kolun 3 bölümü için veri göndermem gerektiğini lütfen unutmayın.

Yanıtlar:


9

Yapılar hakkındaki kişisel düşüncelerim, birçok farklı değişkeni göndermenin en etkili yolu olduğundan, yapıları ve değişkenleri seri üzerinden göndermeyi kolaylaştırmak için bir kütüphane oluşturdum. Kaynak kodu

Bu kütüphanede seri olarak göndermeyi kolaylaştırır. Donanım ve yazılım seri ile kullandım. Genellikle bu, xbee ile birlikte kullanılır, böylece verileri robota ve robottan kablosuz olarak gönderebilirim.

Veri gönderirken, bir değişken veya bir yapı göndermenize izin verdiği için basitleşir (umursamaz).

Seri üzerinden basit bir karakter gönderme örneği:

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

Seri üzerinden basit bir int gönderme örneği:

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

Seri üzerinden bir yapı gönderme örneği:

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

Alma Örnekleri:

Streamsend aracılığıyla gönderilen bir karakter alma:

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

StreamSend aracılığıyla gönderilen bir int alma:

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

StreamSend aracılığıyla gönderilen bir Yapıyı alma:

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

Verileri kullanarak okuduktan sonra verilerin StreamSend::receiveObject()İYİ, Bulunamadı veya KÖTÜ olup olmadığını bilmeniz gerekir.

İyi = Başarılı

Bulunamadı = Belirtilen ostream'de önek karakteri bulunamadı

Kötü = Bir şekilde bir önek karakteri bulundu, ancak veriler bozulmamış. Genellikle sonek karakteri bulunmadığı veya verilerin doğru boyutta olmadığı anlamına gelir.

Verilerin Geçerliliğinin Test Edilmesi:

// 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 Sınıfı:

#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
Tüm kod yanıtları, tüm bağlantı yanıtları gibi önerilmez. kodunuz tonlarca yorum olmadıkça , neler olup bittiğine dair bazı açıklamalar
koymanızı

@TheDoctor, kodu güncelledim. Şimdi daha fazla yorum olmalı
Steven10172

1

Gerçekten hızlı bir şekilde göndermek istiyorsanız , Tam Dubleks Seri (FDX) öneririm. USB ve ethernet'in kullandığı protokolle aynıdır ve UART'tan çok daha hızlıdır. Dezavantajı, yüksek veri hızlarını kolaylaştırmak için genellikle harici donanım gerektirmesidir. Yeni yazılımınSreial'ın FDX'i desteklediğini duydum , ancak bu donanım UART'tan bile daha yavaş olabilir. İletişim protokolleri hakkında daha fazla bilgi için, bkz. İki Arduino'yu kalkan olmadan bağlama?


Kulağa ilginç geliyor. Daha yakından bakmam gerekecek.
Steven10172

" Tam çift yönlü seri ", aslında standart UART iletişimi olduğunda "UART'tan çok daha hızlı" nasıl olabilir ?
David Cary

UART sabit oranlı bir iletişimdir. FDX, verileri olabildiğince hızlı gönderir ve yapmayan verileri yeniden gönderir.
TheDoctor

Bu protokol hakkında daha fazla bilgi edinmek isterim. Yanıtınıza UART'tan daha hızlı bir protokol açıklayan bir bağlantı ekleyebilir misiniz? ACK-NAK kullanarak genel otomatik tekrar isteği fikrinden mi bahsediyorsunuz , yoksa aklınızda belirli bir protokol var mı? "FDX" veya "tam çift yönlü seri" için yaptığım Google aramalarının hiçbiri açıklamanızla eşleşmiyor.
David Cary

1

Bir yapı göndermek oldukça basittir.

Yapıyı normalde yaptığınız gibi bildirebilir ve daha sonra verileri yeni bir konuma kopyalamak için memcpy (@ myStruct, @ myArray) kullanabilir ve ardından verileri veri akışı olarak yazmak için aşağıdaki koda benzer bir şey kullanabilirsiniz.

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 

Ardından, diğer aygıttaki pime aşağıdakileri yapan bir kesme rutini ekleyebilirsiniz:

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

attachInterrupt(0,readSerial,Rising);  

// mcu'ya pinhigh olduğunda fxn çağırmasını söyleyin. Bu hemen hemen her an gerçekleşir. bu istenmezse, kesmeyi kaldırın ve ana yürütme döngünüzdeki yeni karakterleri izleyin (aka, UART yoklama).

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

Sözdizimi ve işaretçilerin kullanımı biraz gözden geçirilmelidir. Yukarıdaki kodun derlenmeyeceğinden emin olduğum için bir all-nighter çektim, ama fikir orada. Yapınızı doldurun, kopyalayın, çerçeveleme hatalarını önlemek için bant dışı sinyalleri kullanın, verileri yazın. Diğer tarafta, verileri alın, bir yapıya kopyalayın ve ardından verilere normal üye erişim yöntemleri ile erişilebilir olun.

Bit alanlarının kullanımı da işe yarayacak, sadece nibble'ların geriye doğru görüneceğini unutmayın. Örneğin, 0011 1101 yazmaya çalışmak, makineler bayt sırasına göre farklılık gösterirse 1101 0011'in diğer ucunda görünmesine neden olabilir.

Veri bütünlüğü önemliyse, yanlış hizalanmış çöp verilerini kopyalamadığınızdan emin olmak için bir sağlama toplamı da ekleyebilirsiniz. Bu tavsiye ettiğim hızlı ve etkili bir kontroldür.


1

Eğer veri hacmini tahammül ederse, sisteme iletişim ayıklama olduğunu böylece ikili gönderirken daha dizeleri gönderirken çok daha kolay; sprintf () / sscanf () ve onların varyantları burada arkadaşlarınız. İletişimi kendi modüllerinde (.cpp dosyası) özel işlevlere dahil edin; kanalı daha sonra optimize etmeniz gerekirse ( çalışan bir sisteminiz olduktan sonra) , dize tabanlı modülü daha küçük iletiler için kodlanmış bir modülle değiştirebilirsiniz.

Alan genişlikleri, sınırlayıcılar, çizgi sonları, önemsiz sıfırlar, +işaretlerin varlığı vb.İle ilgili olarak iletim üzerindeki protokol özelliklerine sıkı sıkıya bağlı kalır ve bunları alımda daha gevşek yorumlarsanız hayatınızı kolaylaştıracaksınız .


Başlangıçta kod, verileri bir Quadcopter'ın dengeleyici döngüsünde geri göndermek için yazılmıştır, bu yüzden oldukça hızlı olması gerekiyordu.
Steven10172

0

Burada resmi bir kimlik bilgim yok, ancak bir değişkenin durumunu içermek için belirli bir karakter pozisyonu / pozisyonları seçtiğimde, deneyimlerime göre işler oldukça verimli bir şekilde gitti, böylece ilk üç karakteri sıcaklık ve bir sonraki olarak karakter olarak atayabilirsiniz. bir servo açısı olarak üç, vb. Gönderme sonunda değişkenleri ayrı ayrı kaydedip seri olarak göndermek için bir dizede birleştiririm. Alıcı ucunda, ilk üç karakteri alıp bunları istediğim değişken tipine dönüştürerek, sonra bir sonraki değişken değerini almak için tekrar yaparak dizeyi ayırırdım. Bu sistem, her değişkenin alacağı karakter miktarını bildiğinizde en iyi şekilde çalışır ve seri verilerin her geçişinde her zaman aynı değişkenleri (umarım verilir) ararsınız.

Belirsiz uzunluğun sonunu koymak için bir değişken seçebilir ve ardından bu değişkeni ilk karakterinden dizenin sonuna kadar alabilirsiniz. Verilmiş, seri veri dizesi değişken tiplerine ve bunların miktarına bağlı olarak gerçekten uzun sürebilir, ancak bu kullandığım sistem ve şimdiye kadar vurduğum tek aksaklık seri uzunluğu, bu yüzden tek dezavantaj bilmek.


Bir int / float / char içine x miktarda karakter kaydetmek için ne tür fonksiyonlar kullanıyorsunuz?
Steven10172

1
Bunu fark etmeyebilirsiniz, ancak açıkladığınız şey tam olarakstruct a'nın bellekte nasıl düzenlendiğidir (doldurmayı göz ardı ederek) ve kullandığınız veri aktarım işlevlerinin Steven'ın cevabında tartışılanlara benzer olacağını düşünüyorum .
asheeshr

@ AsheeshR Aslında böyle bir duygu yapıları vardı, ama şahsen yapıları yeniden biçimlendirmeye ve sonra diğer tarafta tekrar okumaya çalışırken bir duvara çarpma eğilimindeyim. Bu yüzden sadece bu dize şeyi yapacağımı düşündüm, böylece işler yanlış anlaşılırsa kolayca hata ayıklayabilirim ve böylece "MOTORa023 MOTORb563" gibi atarsam seri verileri kendim bile okuyabilirim. boşluklar.
Newbie97

@ Steven10172 iyi itiraf ediyorum ben belirli fonksiyonları takip etmiyorum, daha ziyade her zaman belirli fonksiyonu google. İnt String, string yüzer, ve dize char . Bu yöntemleri normal c ++ 'da kullandığımı ve bunları Arduino IDE'de kendim denemediğimi unutmayın.
Newbie97

0

Seriyi yapı verileri gönderme

Hiçbir şey fantezi. Bir yapı gönderir. Verileri sınırlamak için bir kaçış karakteri '^' kullanır.

Arduino kodu

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 Kodu:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.