发送大量的串行数据


13

因此,在机器人技术领域,有时您需要将多个板和/或计算机链接在一起以共享信息或保存统计数据。目前,我需要通过串行连接发送一些不同的变量,并且想知道这样做的最佳方法是什么?

到目前为止,我已经确定发送结构可能是发送数据的更简单方法。有谁知道其他更有效的方式吗?

请记住,从本质上讲,我将不得不发送4个电机,压缩机,不同温度,随机物体和3个臂部分的数据。

Answers:


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()您需要知道数据是“良好”,“未找到”还是“不良”。

=成功

未找到 =在指定的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 2014年

@TheDoctor,我更新了代码。现在应该有更多评论
Steven10172

1

如果您真的想快速发送它,我建议使用全双工串行(FDX)。USB和以太网使用相同的协议,并且比UART快很多。缺点是它通常需要外部硬件来促进高数据速率。我听说新的软件 Sreial支持FDX,但这可能比硬件UART还要慢。有关通信协议的更多信息,请参见如何连接两个没有屏蔽的Arduino?


听起来很有趣。我将不得不进一步研究它。
Steven10172

实际上,标准的UART通信如何使“ 全双工串行 ”“比UART快得多”?
大卫·卡里

UART是固定速率通信。FDX会尽快发送数据,然后重新发送未发送的数据。
TheDoctor 2014年

我很想了解有关此协议的更多信息。您能否在答案中添加一个描述比UART更快的协议的链接?您是在谈论使用ACK-NAK 自动重复请求的一般想法,还是在考虑一些特定的协议?我的Google搜索“ FDX”或“全双工串行”似乎都不符合您的描述。
David Cary

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

//告诉MCU当引脚为高电平时调用fxn。这几乎会在任何时候发生。如果不希望这样做,请删除中断,然后仅在主执行循环中(即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 2014年

0

我在这里没有官方证书,但是根据我的经验,当我选择某个字符位置来包含变量的状态时,事情进行的相当有效,因此您可以将前三个字符指定为温度,然后将下一个指定为温度三个作为伺服器的角度,依此类推。在发送端,我将分别保存变量,然后将它们组合为字符串以串行发送。在接收端,我将字符串分开,将前三个字符转换为所需的任何变量类型,然后再次进行操作以获取下一个变量值。当您确定每个变量占用的字符数一定并且每次串行数据循环通过时总是寻找相同的变量(希望是给定的)时,此系统效果最佳。

您可以选择一个变量以不确定的长度放在最后,然后从该变量的第一个字符到字符串末尾获取该变量。当然,取决于变量类型和变量的数量,串行数据字符串可能会变长,但这是我使用的系统,到目前为止,我遇到的唯一挫折是串行长度,所以这是我唯一的缺点了解。


您使用哪种函数将x个字符保存到int / float / char中?
Steven10172

1
您可能没有意识到这一点,但是您所描述的正是 a struct在内存中的组织方式(不考虑填充),并且我想您所使用的数据传递函数将类似于Steven的答案中讨论的那些函数。
asheeshr 2014年

@AsheeshR我实际上有一种感觉,也许结构就是这种方式,但是当我尝试重新格式化结构然后在另一侧再次阅读它们时,我个人倾向于碰壁。这就是为什么我认为我只是做这个字符串事情,以便我可以轻松调试是否被误读,并且如果我将其指定为“ MOTORa023 MOTORb563”之类的话,甚至可以自己读取串行数据,等等,而无需空间。
Newbie97

@ Steven10172好吧,我承认我没有跟踪特定的功能,而是每次都在Google上搜索特定的功能。字符串INT, 串浮动,字符串为char。请记住,我在常规c ++中使用了这些方法,而我自己还没有在Arduino IDE中尝试过这些方法。
Newbie97

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.