답변:
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에 접두사 문자가 없습니다.
Bad = 어떻게 든 접두사 문자가 발견되었지만 데이터는 손상되지 않았습니다. 일반적으로 접미사 문자가 없거나 데이터의 크기가 잘못되었음을 의미합니다.
데이터의 유효성 검사 :
// 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
정말로 빨리 보내려면 FDX (Full Duplex Serial)를 권장합니다. USB 및 이더넷과 동일한 프로토콜이며 UART보다 훨씬 빠릅니다. 단점은 일반적으로 높은 데이터 속도를 위해 외부 하드웨어가 필요하다는 것입니다. 새 softwareSreial 이 FDX를 지원 한다고 들었지만 하드웨어 UART보다 속도가 느릴 수 있습니다. 통신 프로토콜에 대한 자세한 내용은 차폐없이 두 개의 Arduino를 연결하는 방법을 참조하십시오 .
구조를 보내는 것은 매우 간단합니다.
평소와 같이 구조를 선언 한 다음 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);
// pinhigh 일 때 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이 나타날 수 있습니다.
데이터 무결성이 중요한 경우 체크섬을 추가하여 잘못 정렬 된 가비지 데이터를 복사하지 않도록 할 수도 있습니다. 이것은 내가 추천하는 빠르고 효과적인 검사입니다.
당신은 데이터 볼륨을 허용 할 수있는 경우, communicatons를 디버깅하는 것은 너무 진을 보낼 때보다 문자열을 보낼 때 훨씬 쉽게; sprintf () / sscanf ()와 그 변형은 여기에 당신의 친구입니다. 전용 기능으로 통신을 자체 모듈 (.cpp 파일)로 묶습니다. 나중에 시스템을 작동 시킨 후 채널을 최적화해야하는 경우 작은 메시지를 위해 코드화 된 모듈로 문자열 기반 모듈을 교체 할 수 있습니다.
필드 너비, 구분 기호, 줄 끝, 중요하지 않은 제로, +
부호의 존재 등과 관련하여 전송시 프로토콜 사양을 준수하고 수신시 느슨하게 해석하면 인생을 훨씬 쉽게 만들 수 있습니다 .
여기에는 공식 자격 증명이 없지만 경험상 변수의 상태를 포함 할 특정 문자 위치를 선택하면 첫 세 문자를 온도로 지정하고 다음 문자를 온도로 지정할 수 있습니다. 서보 각도로 3 등입니다. 보내는 쪽에서 변수를 개별적으로 저장 한 다음 문자열로 결합하여 직렬로 보냅니다. 수신 측에서 문자열을 분리하여 처음 세 문자를 가져 와서 필요한 변수 유형으로 변환 한 다음 다시 다음 변수 값을 가져옵니다. 이 시스템은 각 변수가 차지할 문자의 양을 알고있을 때 가장 잘 작동하며 직렬 데이터가 반복 될 때마다 항상 동일한 변수 (원하는 경우)를 찾습니다.
하나의 변수를 선택하여 마지막 길이를 결정할 수 없으며 첫 번째 문자에서 문자열 끝까지 변수를 가져올 수 있습니다. 물론, 직렬 데이터 문자열은 변수 유형과 그 양에 따라 실제로 길어질 수 있지만 이것이 내가 사용하는 시스템이며 지금까지 내가 겪었던 유일한 단점은 직렬 길이이므로 유일한 단점입니다. 에 대해 알다.
struct
메모리에서 패딩을 무시 하는 방식 이며, 사용하는 데이터 전송 기능이 Steven의 답변 에서 논의 된 기능과 유사하다고 생각합니다 .
멋진 것은 없습니다. 구조체를 보냅니다. 이스케이프 문자 '^'을 사용하여 데이터를 구분합니다.
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);
}
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