大量のシリアルデータを送信する


13

そのため、ロボット工学の分野では、情報を共有したり統計データを保存したりするために、複数のボードやコンピューターをリンクする必要があります。現在、シリアル接続を介していくつかの異なる変数を送信する必要がありますが、そのようなことを行うための最善の方法は何であるか疑問に思っていましたか?

これまでのところ、構造を送信する方がおそらくデータを送信するためのより簡単な方法であると判断しました。誰もがより効率的な他の方法を知っていますか?

基本的に、4つのモーター、コンプレッサー、異なる温度、ランダムなもの、およびアームの3つのセクションのデータを送信する必要があることに注意してください。

回答:


9

多くの異なる変数を送信する最も効率的な方法であるStructsに対する個人的な考えから、構造体と変数をシリアルで簡単に送信できるようにライブラリを構築しました。 ソースコード

このライブラリでは、シリアルで簡単に送信できます。ハードウェアとソフトウェアのシリアルで使用しました。通常、これはxbeeと組み合わせて使用​​されるため、ロボットとの間でデータをワイヤレスで送信できます。

データを送信するときは、変数または構造体のいずれかを送信できるため、簡単になります(気にしません)。

シリアルを介して単純な文字を送信する例を次に示します。

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

シリアル経由で単純なintを送信する例:

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

シリアルで構造体を送信する例:

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

受信例:

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

StreamSendを介して送信されたintを受信します。

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を介して送信されたStructの受信:

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

を使用してデータを読み取った後StreamSend::receiveObject()、データがGOOD、Not Found、またはBADであるかどうかを知る必要があります。

良好 =成功

見つかりません=指定されたostreamでプレフィックス文字が見つかりませんでした

悪い =何らかの理由でプレフィックス文字が見つかりましたが、データはそのままです。通常、接尾辞文字が見つからなかったか、データが正しいサイズではなかったことを意味します。

データの有効性のテスト:

// 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クラス:

#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
全リンク回答のような全コード回答は推奨されません。あなたのコードが持っていない限りトンのコメントを、私は何が起こっているのいくつかの説明を置くことをお勧めします
TheDoctor

@TheDoctor、コードを更新しました。今より多くのコメントがあるはず
Steven10172

1

本当に高速で送信したい場合は、全二重シリアル(FDX)をお勧めします。これは、USBおよびイーサネットが使用するものと同じプロトコルであり、UARTよりもはるかに高速です。欠点は、通常、高いデータレートを実現するために外部ハードウェアが必要になることです。新しいsoftwareSreialはFDXをサポートしていると聞きましたが、これはハードウェアUARTよりも遅いかもしれません。通信プロトコルの詳細については、「シールドなしで2つのArduinoを接続する方法」を参照してください


この音は面白い。さらに検討する必要があります。
Steven10172 14

実際に標準のUART通信である場合、「全二重シリアル」を「UARTよりもはるかに高速」にする方法を教えてください。
デビッドケーリー14

UARTは固定レート通信です。FDXは、可能な限り高速にデータを送信し、作成できなかったデータを再送信します。
TheDoctor 14

このプロトコルについてもっと知りたいです。UARTよりも高速なプロトコルを説明するリンクを回答に追加できますか?ACK-NAKを使用した自動リピートリクエストの一般的なアイデアについて話しているのですか、それとも念頭に置いている特定のプロトコルがありますか?Googleで「FDX」または「全二重シリアル」を検索しても、説明と一致しないようです。
デビッドケーリー14年

1

構造の送信は非常に簡単です。

通常どおりに構造を宣言し、memcpy(@ myStruct、@ myArray)を使用してデータを新しい場所にコピーし、次のコードのようなものを使用してデータをデータストリームとして書き込みます。

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 

その後、次のことを行う他のデバイスのピンに割り込みルーチンを接続できます。

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

attachInterrupt(0,readSerial,Rising);  

//ピンハイのときにfxnを呼び出すようにmcuに伝えます。これは事実上いつでも起こります。それが望ましくない場合は、割り込みを削除し、メインエグゼクティブループ(別名、UARTポーリング)で新しい文字を監視するだけです。

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

ポインタの構文と使用には、いくつかのレビューが必要です。徹夜したので、上記のコードはコンパイルできませんが、アイデアはそこにあります。構造を埋め、コピーし、帯域外シグナリングを使用してフレーミングエラーを回避し、データを書き込みます。一方、データを受信して​​構造体にコピーすると、通常のメンバーアクセスメソッドを介してデータにアクセスできるようになります。

ビットフィールドの使用も機能しますが、ニブルは逆向きに見えることに注意してください。たとえば、0011 1101を書き込もうとすると、マシンのバイト順が異なる場合、もう一方の端に1101 0011が表示されます。

データの整合性が重要な場合は、チェックサムを追加して、不整合なガベージデータをコピーしていないことを確認することもできます。これは、私が推奨する迅速で効果的なチェックです。


1

あなたはデータ量を許容できる場合は、communicatonsをデバッグすることはあるので、バイナリを送信する場合に比べて、文字列を送信するときに、はるかに簡単。sprintf()/ sscanf()とそのバリアントは、ここでの友達です。独自のモジュール(.cppファイル)の専用機能で通信を囲みます。後からチャネルを最適化する必要がある場合- 稼働中のシステムを使用した後- 文字列ベースのモジュールを、より小さなメッセージ用にコーディングされたモジュールに置き換えることができます。

送信時のプロトコル仕様を厳守し、受信時のフィールドの幅、区切り文字、行末、無意味なゼロ、+記号の存在などに関してそれらをより緩やかに解釈すると、人生が楽になります。


元々、コードはQuadcopterの安定化ループでデータを送り返すように書かれていたため、かなり高速でなければなりませんでした。
Steven10172 14

0

ここには公式の資格情報はありませんが、私の経験では、変数の状態を含めるために特定のキャラクターの位置を選択すると、物事がかなり効率的になりました。最初の3つのキャラクターを温度として指定し、次のサーボの角度として3など。送信側では、変数を個別に保存し、それらを文字列に結合してシリアル送信します。受信側では、文字列を分解して、最初の3文字を取得し、必要な変数型に変換してから、次の変数値を取得します。このシステムは、各変数が使用する特定の文字数を知っていて、シリアルデータがループするたびに常に同じ変数(これが与えられることを願っています)を探すときに最適に機能します。

1つの変数を選択して不定の長さの最後に配置し、その変数を最初の文字から文字列の最後まで取得できます。確かに、シリアルデータ文字列は変数の種類とそれらの量によっては非常に長くなる可能性がありますが、これは私が使用するシステムであり、これまでのところ私がヒットした唯一の後退はシリアルの長さなので、それが唯一の欠点です知ってる。


x個の文字をint / float / charに保存するには、どのような関数を使用しますか?
Steven10172 14

1
あなたはこれに気付かないかもしれませんが、あなたが説明するのは正確にstruct(パディングを無視して)メモリ内でどのように編成されるかであり、使用するデータ転送関数はスティーブンの答えで議論されたものと似ていると思います。
asheeshr

@AsheeshR実際に構造体がそのように感じるかもしれないが、個人的に構造体を再フォーマットし、反対側でそれらを再度読み込もうとすると、私は個人的に壁にぶつかる傾向がある。だから、この文字列をやるだけだと思ったので、読み間違えたら簡単にデバッグでき、「MOTORa023 MOTORb563」などのように指定すれば自分でシリアルデータを読むこともできます。スペース。
Newbie97 14

@ Steven10172よく私は特定の機能を追跡するのではなく、毎回特定の機能をグーグルで確認します。Stringをintに、 StringをfloatにStringをcharに。私はこれらのメソッドを通常のC ++で使用しており、自分でArduino IDEで試したことがないことに注意してください。
Newbie97 14

0

シリアルを介して構造体データを送信する

派手なものは何もありません。構造体を送信します。データを区切るためにエスケープ文字「^」を使用します。

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

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.